Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+23
View File
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Qualcomm Cloud AI accelerators driver
#
config DRM_ACCEL_QAIC
tristate "Qualcomm Cloud AI accelerators"
depends on DRM_ACCEL
depends on PCI && HAS_IOMEM
depends on MHI_BUS
select CRC32
select WANT_DEV_COREDUMP
help
Enables driver for Qualcomm's Cloud AI accelerator PCIe cards that are
designed to accelerate Deep Learning inference workloads.
The driver manages the PCIe devices and provides an IOCTL interface
for users to submit workloads to the devices.
If unsure, say N.
To compile this driver as a module, choose M here: the
module will be called qaic.
+19
View File
@@ -0,0 +1,19 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for Qualcomm Cloud AI accelerators driver
#
obj-$(CONFIG_DRM_ACCEL_QAIC) := qaic.o
qaic-y := \
mhi_controller.o \
qaic_control.o \
qaic_data.o \
qaic_drv.o \
qaic_ras.o \
qaic_ssr.o \
qaic_sysfs.o \
qaic_timesync.o \
sahara.o
qaic-$(CONFIG_DEBUG_FS) += qaic_debugfs.o
@@ -0,0 +1,932 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <linux/delay.h>
#include <linux/err.h>
#include <linux/memblock.h>
#include <linux/mhi.h>
#include <linux/moduleparam.h>
#include <linux/pci.h>
#include <linux/sizes.h>
#include "mhi_controller.h"
#include "qaic.h"
#define MAX_RESET_TIME_SEC 25
static unsigned int mhi_timeout_ms = 2000; /* 2 sec default */
module_param(mhi_timeout_ms, uint, 0600);
MODULE_PARM_DESC(mhi_timeout_ms, "MHI controller timeout value");
static const char *fw_image_paths[FAMILY_MAX] = {
[FAMILY_AIC100] = "qcom/aic100/sbl.bin",
[FAMILY_AIC200] = "qcom/aic200/sbl.bin",
};
static const struct mhi_channel_config aic100_channels[] = {
{
.name = "QAIC_LOOPBACK",
.num = 0,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_LOOPBACK",
.num = 1,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SAHARA",
.num = 2,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SAHARA",
.num = 3,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_DIAG",
.num = 4,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_DIAG",
.num = 5,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SSR",
.num = 6,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SSR",
.num = 7,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_QDSS",
.num = 8,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_QDSS",
.num = 9,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_CONTROL",
.num = 10,
.num_elements = 128,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_CONTROL",
.num = 11,
.num_elements = 128,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_LOGGING",
.num = 12,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_LOGGING",
.num = 13,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_STATUS",
.num = 14,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_STATUS",
.num = 15,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TELEMETRY",
.num = 16,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TELEMETRY",
.num = 17,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_DEBUG",
.num = 18,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_DEBUG",
.num = 19,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TIMESYNC",
.num = 20,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TIMESYNC",
.num = 21,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TIMESYNC_PERIODIC",
.num = 22,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TIMESYNC_PERIODIC",
.num = 23,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "IPCR",
.num = 24,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "IPCR",
.num = 25,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
};
static const struct mhi_channel_config aic200_channels[] = {
{
.name = "QAIC_LOOPBACK",
.num = 0,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_LOOPBACK",
.num = 1,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SAHARA",
.num = 2,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SAHARA",
.num = 3,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SSR",
.num = 6,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_SSR",
.num = 7,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_CONTROL",
.num = 10,
.num_elements = 128,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_CONTROL",
.num = 11,
.num_elements = 128,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_LOGGING",
.num = 12,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_LOGGING",
.num = 13,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_SBL,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_STATUS",
.num = 14,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_STATUS",
.num = 15,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TELEMETRY",
.num = 16,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TELEMETRY",
.num = 17,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TIMESYNC_PERIODIC",
.num = 22,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "QAIC_TIMESYNC_PERIODIC",
.num = 23,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "IPCR",
.num = 24,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_TO_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
{
.name = "IPCR",
.num = 25,
.num_elements = 32,
.local_elements = 0,
.event_ring = 0,
.dir = DMA_FROM_DEVICE,
.ee_mask = MHI_CH_EE_AMSS,
.pollcfg = 0,
.doorbell = MHI_DB_BRST_DISABLE,
.lpm_notify = false,
.offload_channel = false,
.doorbell_mode_switch = false,
.wake_capable = false,
},
};
static struct mhi_event_config aic100_events[] = {
{
.num_elements = 32,
.irq_moderation_ms = 0,
.irq = 0,
.channel = U32_MAX,
.priority = 1,
.mode = MHI_DB_BRST_DISABLE,
.data_type = MHI_ER_CTRL,
.hardware_event = false,
.client_managed = false,
.offload_channel = false,
},
};
static struct mhi_event_config aic200_events[] = {
{
.num_elements = 32,
.irq_moderation_ms = 0,
.irq = 0,
.channel = U32_MAX,
.priority = 1,
.mode = MHI_DB_BRST_DISABLE,
.data_type = MHI_ER_CTRL,
.hardware_event = false,
.client_managed = false,
.offload_channel = false,
},
};
static struct mhi_controller_config mhi_cntrl_configs[] = {
[FAMILY_AIC100] = {
.max_channels = 128,
.timeout_ms = 0, /* controlled by mhi_timeout */
.buf_len = 0,
.num_channels = ARRAY_SIZE(aic100_channels),
.ch_cfg = aic100_channels,
.num_events = ARRAY_SIZE(aic100_events),
.event_cfg = aic100_events,
.use_bounce_buf = false,
.m2_no_db = false,
},
[FAMILY_AIC200] = {
.max_channels = 128,
.timeout_ms = 0, /* controlled by mhi_timeout */
.buf_len = 0,
.num_channels = ARRAY_SIZE(aic200_channels),
.ch_cfg = aic200_channels,
.num_events = ARRAY_SIZE(aic200_events),
.event_cfg = aic200_events,
.use_bounce_buf = false,
.m2_no_db = false,
},
};
static int mhi_read_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 *out)
{
u32 tmp;
/*
* SOC_HW_VERSION quirk
* The SOC_HW_VERSION register (offset 0x224) is not reliable and
* may contain uninitialized values, including 0xFFFFFFFF. This could
* cause a false positive link down error. Instead, intercept any
* reads and provide the correct value of the register.
*/
if (addr - mhi_cntrl->regs == 0x224) {
*out = 0x60110200;
return 0;
}
tmp = readl_relaxed(addr);
if (tmp == U32_MAX)
return -EIO;
*out = tmp;
return 0;
}
static void mhi_write_reg(struct mhi_controller *mhi_cntrl, void __iomem *addr, u32 val)
{
writel_relaxed(val, addr);
}
static int mhi_runtime_get(struct mhi_controller *mhi_cntrl)
{
return 0;
}
static void mhi_runtime_put(struct mhi_controller *mhi_cntrl)
{
}
static void mhi_status_cb(struct mhi_controller *mhi_cntrl, enum mhi_callback reason)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_cntrl->cntrl_dev));
/* this event occurs in atomic context */
if (reason == MHI_CB_FATAL_ERROR)
pci_err(qdev->pdev, "Fatal error received from device. Attempting to recover\n");
/* this event occurs in non-atomic context */
if (reason == MHI_CB_SYS_ERROR)
qaic_dev_reset_clean_local_state(qdev);
}
static int mhi_reset_and_async_power_up(struct mhi_controller *mhi_cntrl)
{
u8 time_sec = 1;
int current_ee;
int ret;
/* Reset the device to bring the device in PBL EE */
mhi_soc_reset(mhi_cntrl);
/*
* Keep checking the execution environment(EE) after every 1 second
* interval.
*/
do {
msleep(1000);
current_ee = mhi_get_exec_env(mhi_cntrl);
} while (current_ee != MHI_EE_PBL && time_sec++ <= MAX_RESET_TIME_SEC);
/* If the device is in PBL EE retry power up */
if (current_ee == MHI_EE_PBL)
ret = mhi_async_power_up(mhi_cntrl);
else
ret = -EIO;
return ret;
}
struct mhi_controller *qaic_mhi_register_controller(struct pci_dev *pci_dev, void __iomem *mhi_bar,
int mhi_irq, bool shared_msi, int family)
{
struct mhi_controller_config mhi_config = mhi_cntrl_configs[family];
struct mhi_controller *mhi_cntrl;
int ret;
mhi_cntrl = devm_kzalloc(&pci_dev->dev, sizeof(*mhi_cntrl), GFP_KERNEL);
if (!mhi_cntrl)
return ERR_PTR(-ENOMEM);
mhi_cntrl->cntrl_dev = &pci_dev->dev;
/*
* Covers the entire possible physical ram region. Remote side is
* going to calculate a size of this range, so subtract 1 to prevent
* rollover.
*/
mhi_cntrl->iova_start = 0;
mhi_cntrl->iova_stop = PHYS_ADDR_MAX - 1;
mhi_cntrl->status_cb = mhi_status_cb;
mhi_cntrl->runtime_get = mhi_runtime_get;
mhi_cntrl->runtime_put = mhi_runtime_put;
mhi_cntrl->read_reg = mhi_read_reg;
mhi_cntrl->write_reg = mhi_write_reg;
mhi_cntrl->regs = mhi_bar;
mhi_cntrl->reg_len = SZ_4K;
mhi_cntrl->nr_irqs = 1;
mhi_cntrl->irq = devm_kmalloc(&pci_dev->dev, sizeof(*mhi_cntrl->irq), GFP_KERNEL);
if (!mhi_cntrl->irq)
return ERR_PTR(-ENOMEM);
mhi_cntrl->irq[0] = mhi_irq;
if (shared_msi) /* MSI shared with data path, no IRQF_NO_SUSPEND */
mhi_cntrl->irq_flags = IRQF_SHARED;
mhi_cntrl->fw_image = fw_image_paths[family];
if (family == FAMILY_AIC200) {
mhi_cntrl->name = "AIC200";
mhi_cntrl->seg_len = SZ_512K;
} else {
mhi_cntrl->name = "AIC100";
}
/* use latest configured timeout */
mhi_config.timeout_ms = mhi_timeout_ms;
ret = mhi_register_controller(mhi_cntrl, &mhi_config);
if (ret) {
pci_err(pci_dev, "mhi_register_controller failed %d\n", ret);
return ERR_PTR(ret);
}
ret = mhi_prepare_for_power_up(mhi_cntrl);
if (ret) {
pci_err(pci_dev, "mhi_prepare_for_power_up failed %d\n", ret);
goto prepare_power_up_fail;
}
ret = mhi_async_power_up(mhi_cntrl);
/*
* If EIO is returned it is possible that device is in SBL EE, which is
* undesired. SOC reset the device and try to power up again.
*/
if (ret == -EIO && MHI_EE_SBL == mhi_get_exec_env(mhi_cntrl)) {
pci_err(pci_dev, "Found device in SBL at MHI init. Attempting a reset.\n");
ret = mhi_reset_and_async_power_up(mhi_cntrl);
}
if (ret) {
pci_err(pci_dev, "mhi_async_power_up failed %d\n", ret);
goto power_up_fail;
}
return mhi_cntrl;
power_up_fail:
mhi_unprepare_after_power_down(mhi_cntrl);
prepare_power_up_fail:
mhi_unregister_controller(mhi_cntrl);
return ERR_PTR(ret);
}
void qaic_mhi_free_controller(struct mhi_controller *mhi_cntrl, bool link_up)
{
mhi_power_down(mhi_cntrl, link_up);
mhi_unprepare_after_power_down(mhi_cntrl);
mhi_unregister_controller(mhi_cntrl);
}
void qaic_mhi_start_reset(struct mhi_controller *mhi_cntrl)
{
mhi_power_down(mhi_cntrl, true);
}
void qaic_mhi_reset_done(struct mhi_controller *mhi_cntrl)
{
struct pci_dev *pci_dev = container_of(mhi_cntrl->cntrl_dev, struct pci_dev, dev);
int ret;
ret = mhi_async_power_up(mhi_cntrl);
if (ret)
pci_err(pci_dev, "mhi_async_power_up failed after reset %d\n", ret);
}
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2019-2020, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#ifndef MHICONTROLLERQAIC_H_
#define MHICONTROLLERQAIC_H_
struct mhi_controller *qaic_mhi_register_controller(struct pci_dev *pci_dev, void __iomem *mhi_bar,
int mhi_irq, bool shared_msi, int family);
void qaic_mhi_free_controller(struct mhi_controller *mhi_cntrl, bool link_up);
void qaic_mhi_start_reset(struct mhi_controller *mhi_cntrl);
void qaic_mhi_reset_done(struct mhi_controller *mhi_cntrl);
#endif /* MHICONTROLLERQAIC_H_ */
+359
View File
@@ -0,0 +1,359 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#ifndef _QAIC_H_
#define _QAIC_H_
#include <linux/interrupt.h>
#include <linux/kref.h>
#include <linux/mhi.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/srcu.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <drm/drm_device.h>
#include <drm/drm_gem.h>
#define QAIC_DBC_BASE SZ_128K
#define QAIC_DBC_SIZE SZ_4K
#define QAIC_SSR_DBC_SENTINEL U32_MAX /* No ongoing SSR sentinel */
#define QAIC_NO_PARTITION -1
#define QAIC_DBC_OFF(i) ((i) * QAIC_DBC_SIZE + QAIC_DBC_BASE)
#define to_qaic_bo(obj) container_of(obj, struct qaic_bo, base)
#define to_qaic_drm_device(dev) container_of(dev, struct qaic_drm_device, drm)
#define to_drm(qddev) (&(qddev)->drm)
#define to_accel_kdev(qddev) (to_drm(qddev)->accel->kdev) /* Return Linux device of accel node */
#define to_qaic_device(dev) (to_qaic_drm_device((dev))->qdev)
enum aic_families {
FAMILY_AIC100,
FAMILY_AIC200,
FAMILY_MAX,
};
enum __packed dev_states {
/* Device is offline or will be very soon */
QAIC_OFFLINE,
/* Device is booting, not clear if it's in a usable state */
QAIC_BOOT,
/* Device is fully operational */
QAIC_ONLINE,
};
enum dbc_states {
/* DBC is free and can be activated */
DBC_STATE_IDLE,
/* DBC is activated and a workload is running on device */
DBC_STATE_ASSIGNED,
/* Sub-system associated with this workload has crashed and it will shutdown soon */
DBC_STATE_BEFORE_SHUTDOWN,
/* Sub-system associated with this workload has crashed and it has shutdown */
DBC_STATE_AFTER_SHUTDOWN,
/* Sub-system associated with this workload is shutdown and it will be powered up soon */
DBC_STATE_BEFORE_POWER_UP,
/* Sub-system associated with this workload is now powered up */
DBC_STATE_AFTER_POWER_UP,
DBC_STATE_MAX,
};
extern bool datapath_polling;
struct qaic_user {
/* Uniquely identifies this user for the device */
int handle;
struct kref ref_count;
/* Char device opened by this user */
struct qaic_drm_device *qddev;
/* Node in list of users that opened this drm device */
struct list_head node;
/* SRCU used to synchronize this user during cleanup */
struct srcu_struct qddev_lock;
atomic_t chunk_id;
};
struct dma_bridge_chan {
/* Pointer to device strcut maintained by driver */
struct qaic_device *qdev;
/* ID of this DMA bridge channel(DBC) */
unsigned int id;
/* Synchronizes access to xfer_list */
spinlock_t xfer_lock;
/* Base address of request queue */
void *req_q_base;
/* Base address of response queue */
void *rsp_q_base;
/*
* Base bus address of request queue. Response queue bus address can be
* calculated by adding request queue size to this variable
*/
dma_addr_t dma_addr;
/* Total size of request and response queue in byte */
u32 total_size;
/* Capacity of request/response queue */
u32 nelem;
/* The user that opened this DBC */
struct qaic_user *usr;
/*
* Request ID of next memory handle that goes in request queue. One
* memory handle can enqueue more than one request elements, all
* this requests that belong to same memory handle have same request ID
*/
u16 next_req_id;
/* true: DBC is in use; false: DBC not in use */
bool in_use;
/*
* Base address of device registers. Used to read/write request and
* response queue's head and tail pointer of this DBC.
*/
void __iomem *dbc_base;
/* Synchronizes access to Request queue's head and tail pointer */
struct mutex req_lock;
/* Head of list where each node is a memory handle queued in request queue */
struct list_head xfer_list;
/* Synchronizes DBC readers during cleanup */
struct srcu_struct ch_lock;
/*
* When this DBC is released, any thread waiting on this wait queue is
* woken up
*/
wait_queue_head_t dbc_release;
/* Head of list where each node is a bo associated with this DBC */
struct list_head bo_lists;
/* The irq line for this DBC. Used for polling */
unsigned int irq;
/* Polling work item to simulate interrupts */
struct work_struct poll_work;
/* Represents various states of this DBC from enum dbc_states */
unsigned int state;
};
struct qaic_device {
/* Pointer to base PCI device struct of our physical device */
struct pci_dev *pdev;
/* Req. ID of request that will be queued next in MHI control device */
u32 next_seq_num;
/* Base address of the MHI bar */
void __iomem *bar_mhi;
/* Base address of the DBCs bar */
void __iomem *bar_dbc;
/* Controller structure for MHI devices */
struct mhi_controller *mhi_cntrl;
/* MHI control channel device */
struct mhi_device *cntl_ch;
/* List of requests queued in MHI control device */
struct list_head cntl_xfer_list;
/* Synchronizes MHI control device transactions and its xfer list */
struct mutex cntl_mutex;
/* Work queue for tasks related to MHI control device */
struct workqueue_struct *cntl_wq;
/* Synchronizes all the users of device during cleanup */
struct srcu_struct dev_lock;
/* Track the state of the device during resets */
enum dev_states dev_state;
/* true: single MSI is used to operate device */
bool single_msi;
/*
* true: A tx MHI transaction has failed and a rx buffer is still queued
* in control device. Such a buffer is considered lost rx buffer
* false: No rx buffer is lost in control device
*/
bool cntl_lost_buf;
/* Maximum number of DBC supported by this device */
u32 num_dbc;
/* Reference to the drm_device for this device when it is created */
struct qaic_drm_device *qddev;
/* Generate the CRC of a control message */
u32 (*gen_crc)(void *msg);
/* Validate the CRC of a control message */
bool (*valid_crc)(void *msg);
/* MHI "QAIC_TIMESYNC" channel device */
struct mhi_device *qts_ch;
/* Work queue for tasks related to MHI "QAIC_TIMESYNC" channel */
struct workqueue_struct *qts_wq;
/* MHI "QAIC_TIMESYNC_PERIODIC" channel device */
struct mhi_device *mqts_ch;
/* Head of list of page allocated by MHI bootlog device */
struct list_head bootlog;
/* MHI bootlog channel device */
struct mhi_device *bootlog_ch;
/* Work queue for tasks related to MHI bootlog device */
struct workqueue_struct *bootlog_wq;
/* Synchronizes access of pages in MHI bootlog device */
struct mutex bootlog_mutex;
/* MHI RAS channel device */
struct mhi_device *ras_ch;
/* Correctable error count */
unsigned int ce_count;
/* Un-correctable error count */
unsigned int ue_count;
/* Un-correctable non-fatal error count */
unsigned int ue_nf_count;
/* MHI SSR channel device */
struct mhi_device *ssr_ch;
/* Work queue for tasks related to MHI SSR device */
struct workqueue_struct *ssr_wq;
/* Buffer to collect SSR crashdump via SSR MHI channel */
void *ssr_mhi_buf;
/* DBC which is under SSR. Sentinel U32_MAX would mean that no SSR in progress */
u32 ssr_dbc;
/* Array of DBC struct of this device */
struct dma_bridge_chan dbc[] __counted_by(num_dbc);
};
struct qaic_drm_device {
/* The drm device struct of this drm device */
struct drm_device drm;
/* Pointer to the root device struct driven by this driver */
struct qaic_device *qdev;
/*
* The physical device can be partition in number of logical devices.
* And each logical device is given a partition id. This member stores
* that id. QAIC_NO_PARTITION is a sentinel used to mark that this drm
* device is the actual physical device
*/
s32 partition_id;
/* Head in list of users who have opened this drm device */
struct list_head users;
/* Synchronizes access to users list */
struct mutex users_mutex;
/* Pointer to array of DBC sysfs attributes */
void *sysfs_attrs;
};
struct qaic_bo {
struct drm_gem_object base;
/* Scatter/gather table for allocate/imported BO */
struct sg_table *sgt;
/* Head in list of slices of this BO */
struct list_head slices;
/* Total nents, for all slices of this BO */
int total_slice_nents;
/*
* Direction of transfer. It can assume only two value DMA_TO_DEVICE and
* DMA_FROM_DEVICE.
*/
int dir;
/* The pointer of the DBC which operates on this BO */
struct dma_bridge_chan *dbc;
/* Number of slice that belongs to this buffer */
u32 nr_slice;
/* Number of slice that have been transferred by DMA engine */
u32 nr_slice_xfer_done;
/*
* If true then user has attached slicing information to this BO by
* calling DRM_IOCTL_QAIC_ATTACH_SLICE_BO ioctl.
*/
bool sliced;
/* Request ID of this BO if it is queued for execution */
u16 req_id;
/* Wait on this for completion of DMA transfer of this BO */
struct completion xfer_done;
/*
* Node in linked list where head is dbc->xfer_list.
* This link list contain BO's that are queued for DMA transfer.
*/
struct list_head xfer_list;
/*
* Node in linked list where head is dbc->bo_lists.
* This link list contain BO's that are associated with the DBC it is
* linked to.
*/
struct list_head bo_list;
struct {
/*
* Latest timestamp(ns) at which kernel received a request to
* execute this BO
*/
u64 req_received_ts;
/*
* Latest timestamp(ns) at which kernel enqueued requests of
* this BO for execution in DMA queue
*/
u64 req_submit_ts;
/*
* Latest timestamp(ns) at which kernel received a completion
* interrupt for requests of this BO
*/
u64 req_processed_ts;
/*
* Number of elements already enqueued in DMA queue before
* enqueuing requests of this BO
*/
u32 queue_level_before;
} perf_stats;
/* Synchronizes BO operations */
struct mutex lock;
};
struct bo_slice {
/* Mapped pages */
struct sg_table *sgt;
/* Number of requests required to queue in DMA queue */
int nents;
/* See enum dma_data_direction */
int dir;
/* Actual requests that will be copied in DMA queue */
struct dbc_req *reqs;
struct kref ref_count;
/* true: No DMA transfer required */
bool no_xfer;
/* Pointer to the parent BO handle */
struct qaic_bo *bo;
/* Node in list of slices maintained by parent BO */
struct list_head slice;
/* Size of this slice in bytes */
u64 size;
/* Offset of this slice in buffer */
u64 offset;
};
int get_dbc_req_elem_size(void);
int get_dbc_rsp_elem_size(void);
int get_cntl_version(struct qaic_device *qdev, struct qaic_user *usr, u16 *major, u16 *minor);
int qaic_manage_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
void qaic_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result);
void qaic_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result);
int qaic_control_open(struct qaic_device *qdev);
void qaic_control_close(struct qaic_device *qdev);
void qaic_release_usr(struct qaic_device *qdev, struct qaic_user *usr);
irqreturn_t dbc_irq_threaded_fn(int irq, void *data);
irqreturn_t dbc_irq_handler(int irq, void *data);
int disable_dbc(struct qaic_device *qdev, u32 dbc_id, struct qaic_user *usr);
void enable_dbc(struct qaic_device *qdev, u32 dbc_id, struct qaic_user *usr);
void wakeup_dbc(struct qaic_device *qdev, u32 dbc_id);
void release_dbc(struct qaic_device *qdev, u32 dbc_id);
void qaic_data_get_fifo_info(struct dma_bridge_chan *dbc, u32 *head, u32 *tail);
void wake_all_cntl(struct qaic_device *qdev);
void qaic_dev_reset_clean_local_state(struct qaic_device *qdev);
struct drm_gem_object *qaic_gem_prime_import(struct drm_device *dev, struct dma_buf *dma_buf);
int qaic_create_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_mmap_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_attach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_execute_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_partial_execute_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_wait_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_perf_stats_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
int qaic_detach_slice_bo_ioctl(struct drm_device *dev, void *data, struct drm_file *file_priv);
void qaic_irq_polling_work(struct work_struct *work);
void qaic_dbc_enter_ssr(struct qaic_device *qdev, u32 dbc_id);
void qaic_dbc_exit_ssr(struct qaic_device *qdev);
/* qaic_sysfs.c */
int qaic_sysfs_init(struct qaic_drm_device *qddev);
void qaic_sysfs_remove(struct qaic_drm_device *qddev);
void set_dbc_state(struct qaic_device *qdev, u32 dbc_id, unsigned int state);
#endif /* _QAIC_H_ */
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,306 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <linux/debugfs.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/list.h>
#include <linux/mhi.h>
#include <linux/mutex.h>
#include <linux/overflow.h>
#include <linux/pci.h>
#include <linux/seq_file.h>
#include <linux/sprintf.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include "qaic.h"
#include "qaic_debugfs.h"
#define BOOTLOG_POOL_SIZE 16
#define BOOTLOG_MSG_SIZE 512
#define QAIC_DBC_DIR_NAME 9
struct bootlog_msg {
/* Buffer for bootlog messages */
char str[BOOTLOG_MSG_SIZE];
/* Length of bootlog message */
size_t len;
/* Root struct of device, used to access device resources */
struct qaic_device *qdev;
/* Work struct to schedule work coming on QAIC_LOGGING channel */
struct work_struct work;
};
struct bootlog_page {
/* Node in list of bootlog pages maintained by root device struct */
struct list_head node;
/* Total size of the buffer that holds the bootlogs. It is PAGE_SIZE */
unsigned int size;
/* Offset for the next bootlog */
unsigned int offset;
};
static int bootlog_show(struct seq_file *s, void *unused)
{
struct bootlog_page *page;
struct qaic_device *qdev;
size_t len;
void *log;
qdev = s->private;
mutex_lock(&qdev->bootlog_mutex);
list_for_each_entry(page, &qdev->bootlog, node) {
log = page + 1;
len = page->offset - sizeof(*page);
seq_write(s, log, len);
}
mutex_unlock(&qdev->bootlog_mutex);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(bootlog);
static int fifo_size_show(struct seq_file *s, void *unused)
{
struct dma_bridge_chan *dbc = s->private;
seq_printf(s, "%u\n", dbc->nelem);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(fifo_size);
static int queued_show(struct seq_file *s, void *unused)
{
struct dma_bridge_chan *dbc = s->private;
u32 tail = 0, head = 0;
qaic_data_get_fifo_info(dbc, &head, &tail);
if (head == U32_MAX || tail == U32_MAX)
seq_printf(s, "%u\n", 0);
else if (head > tail)
seq_printf(s, "%u\n", dbc->nelem - head + tail);
else
seq_printf(s, "%u\n", tail - head);
return 0;
}
DEFINE_SHOW_ATTRIBUTE(queued);
void qaic_debugfs_init(struct qaic_drm_device *qddev)
{
struct qaic_device *qdev = qddev->qdev;
struct dentry *debugfs_root;
struct dentry *debugfs_dir;
char name[QAIC_DBC_DIR_NAME];
u32 i;
debugfs_root = to_drm(qddev)->debugfs_root;
debugfs_create_file("bootlog", 0400, debugfs_root, qdev, &bootlog_fops);
/*
* 256 dbcs per device is likely the max we will ever see and lets static checking see a
* reasonable range.
*/
for (i = 0; i < qdev->num_dbc && i < 256; ++i) {
snprintf(name, QAIC_DBC_DIR_NAME, "dbc%03u", i);
debugfs_dir = debugfs_create_dir(name, debugfs_root);
debugfs_create_file("fifo_size", 0400, debugfs_dir, &qdev->dbc[i], &fifo_size_fops);
debugfs_create_file("queued", 0400, debugfs_dir, &qdev->dbc[i], &queued_fops);
}
}
static struct bootlog_page *alloc_bootlog_page(struct qaic_device *qdev)
{
struct bootlog_page *page;
page = (struct bootlog_page *)devm_get_free_pages(&qdev->pdev->dev, GFP_KERNEL, 0);
if (!page)
return page;
page->size = PAGE_SIZE;
page->offset = sizeof(*page);
list_add_tail(&page->node, &qdev->bootlog);
return page;
}
static int reset_bootlog(struct qaic_device *qdev)
{
struct bootlog_page *page;
struct bootlog_page *i;
mutex_lock(&qdev->bootlog_mutex);
list_for_each_entry_safe(page, i, &qdev->bootlog, node) {
list_del(&page->node);
devm_free_pages(&qdev->pdev->dev, (unsigned long)page);
}
page = alloc_bootlog_page(qdev);
mutex_unlock(&qdev->bootlog_mutex);
if (!page)
return -ENOMEM;
return 0;
}
static void *bootlog_get_space(struct qaic_device *qdev, unsigned int size)
{
struct bootlog_page *page;
page = list_last_entry(&qdev->bootlog, struct bootlog_page, node);
if (size_add(size, sizeof(*page)) > page->size)
return NULL;
if (page->offset + size > page->size) {
page = alloc_bootlog_page(qdev);
if (!page)
return NULL;
}
return (void *)page + page->offset;
}
static void bootlog_commit(struct qaic_device *qdev, unsigned int size)
{
struct bootlog_page *page;
page = list_last_entry(&qdev->bootlog, struct bootlog_page, node);
page->offset += size;
}
static void bootlog_log(struct work_struct *work)
{
struct bootlog_msg *msg = container_of(work, struct bootlog_msg, work);
struct qaic_device *qdev = msg->qdev;
void *log;
mutex_lock(&qdev->bootlog_mutex);
log = bootlog_get_space(qdev, msg->len);
if (log) {
memcpy(log, msg, msg->len);
bootlog_commit(qdev, msg->len);
}
mutex_unlock(&qdev->bootlog_mutex);
if (mhi_queue_buf(qdev->bootlog_ch, DMA_FROM_DEVICE, msg, BOOTLOG_MSG_SIZE, MHI_EOT))
devm_kfree(&qdev->pdev->dev, msg);
}
static int qaic_bootlog_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
struct bootlog_msg *msg;
int i, ret;
qdev->bootlog_wq = alloc_ordered_workqueue("qaic_bootlog", 0);
if (!qdev->bootlog_wq) {
ret = -ENOMEM;
goto out;
}
ret = reset_bootlog(qdev);
if (ret)
goto destroy_workqueue;
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
goto destroy_workqueue;
dev_set_drvdata(&mhi_dev->dev, qdev);
qdev->bootlog_ch = mhi_dev;
for (i = 0; i < BOOTLOG_POOL_SIZE; i++) {
msg = devm_kzalloc(&qdev->pdev->dev, sizeof(*msg), GFP_KERNEL);
if (!msg) {
ret = -ENOMEM;
goto mhi_unprepare;
}
msg->qdev = qdev;
INIT_WORK(&msg->work, bootlog_log);
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, msg, BOOTLOG_MSG_SIZE, MHI_EOT);
if (ret)
goto mhi_unprepare;
}
return 0;
mhi_unprepare:
mhi_unprepare_from_transfer(mhi_dev);
destroy_workqueue:
destroy_workqueue(qdev->bootlog_wq);
out:
return ret;
}
static void qaic_bootlog_mhi_remove(struct mhi_device *mhi_dev)
{
struct qaic_device *qdev;
qdev = dev_get_drvdata(&mhi_dev->dev);
mhi_unprepare_from_transfer(qdev->bootlog_ch);
destroy_workqueue(qdev->bootlog_wq);
qdev->bootlog_ch = NULL;
}
static void qaic_bootlog_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
}
static void qaic_bootlog_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev);
struct bootlog_msg *msg = mhi_result->buf_addr;
int status = mhi_result->transaction_status;
if (status && status != -EOVERFLOW) {
devm_kfree(&qdev->pdev->dev, msg);
return;
}
msg->len = mhi_result->bytes_xferd;
/* Exclude trailing null to normalize AIC100/AIC200 line endings */
if (msg->len && msg->str[msg->len - 1] == '\0')
msg->len--;
queue_work(qdev->bootlog_wq, &msg->work);
}
static const struct mhi_device_id qaic_bootlog_mhi_match_table[] = {
{ .chan = "QAIC_LOGGING", },
{},
};
static struct mhi_driver qaic_bootlog_mhi_driver = {
.id_table = qaic_bootlog_mhi_match_table,
.remove = qaic_bootlog_mhi_remove,
.probe = qaic_bootlog_mhi_probe,
.ul_xfer_cb = qaic_bootlog_mhi_ul_xfer_cb,
.dl_xfer_cb = qaic_bootlog_mhi_dl_xfer_cb,
.driver = {
.name = "qaic_bootlog",
},
};
int qaic_bootlog_register(void)
{
return mhi_driver_register(&qaic_bootlog_mhi_driver);
}
void qaic_bootlog_unregister(void)
{
mhi_driver_unregister(&qaic_bootlog_mhi_driver);
}
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#ifndef __QAIC_DEBUGFS_H__
#define __QAIC_DEBUGFS_H__
#include <drm/drm_file.h>
#ifdef CONFIG_DEBUG_FS
int qaic_bootlog_register(void);
void qaic_bootlog_unregister(void);
void qaic_debugfs_init(struct qaic_drm_device *qddev);
#else
static inline int qaic_bootlog_register(void) { return 0; }
static inline void qaic_bootlog_unregister(void) {}
static inline void qaic_debugfs_init(struct qaic_drm_device *qddev) {}
#endif /* CONFIG_DEBUG_FS */
#endif /* __QAIC_DEBUGFS_H__ */
+858
View File
@@ -0,0 +1,858 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2019-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <linux/delay.h>
#include <linux/dma-mapping.h>
#include <linux/idr.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/kobject.h>
#include <linux/kref.h>
#include <linux/mhi.h>
#include <linux/module.h>
#include <linux/msi.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/spinlock.h>
#include <linux/workqueue.h>
#include <linux/wait.h>
#include <drm/drm_accel.h>
#include <drm/drm_drv.h>
#include <drm/drm_file.h>
#include <drm/drm_gem.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_managed.h>
#include <uapi/drm/qaic_accel.h>
#include "mhi_controller.h"
#include "qaic.h"
#include "qaic_debugfs.h"
#include "qaic_ras.h"
#include "qaic_ssr.h"
#include "qaic_timesync.h"
#include "sahara.h"
MODULE_IMPORT_NS("DMA_BUF");
#define PCI_DEVICE_ID_QCOM_AIC080 0xa080
#define PCI_DEVICE_ID_QCOM_AIC100 0xa100
#define PCI_DEVICE_ID_QCOM_AIC200 0xa110
#define QAIC_NAME "qaic"
#define QAIC_DESC "Qualcomm Cloud AI Accelerators"
#define CNTL_MAJOR 5
#define CNTL_MINOR 0
#define DBC_NUM 16
struct qaic_device_config {
/* Indicates the AIC family the device belongs to */
int family;
/* A bitmask representing the available BARs */
int bar_mask;
/* An index value used to identify the MHI controller BAR */
unsigned int mhi_bar_idx;
/* An index value used to identify the DBCs BAR */
unsigned int dbc_bar_idx;
};
static const struct qaic_device_config aic080_config = {
.family = FAMILY_AIC100,
.bar_mask = BIT(0) | BIT(2) | BIT(4),
.mhi_bar_idx = 0,
.dbc_bar_idx = 2,
};
static const struct qaic_device_config aic100_config = {
.family = FAMILY_AIC100,
.bar_mask = BIT(0) | BIT(2) | BIT(4),
.mhi_bar_idx = 0,
.dbc_bar_idx = 2,
};
static const struct qaic_device_config aic200_config = {
.family = FAMILY_AIC200,
.bar_mask = BIT(0) | BIT(1) | BIT(2) | BIT(4),
.mhi_bar_idx = 1,
.dbc_bar_idx = 2,
};
bool datapath_polling;
module_param(datapath_polling, bool, 0400);
MODULE_PARM_DESC(datapath_polling, "Operate the datapath in polling mode");
static bool link_up;
static DEFINE_IDA(qaic_usrs);
static void qaicm_wq_release(struct drm_device *dev, void *res)
{
struct workqueue_struct *wq = res;
destroy_workqueue(wq);
}
static struct workqueue_struct *qaicm_wq_init(struct drm_device *dev, const char *name)
{
struct workqueue_struct *wq;
int ret;
wq = alloc_workqueue("%s", WQ_UNBOUND, 0, name);
if (!wq)
return ERR_PTR(-ENOMEM);
ret = drmm_add_action_or_reset(dev, qaicm_wq_release, wq);
if (ret)
return ERR_PTR(ret);
return wq;
}
static void qaicm_srcu_release(struct drm_device *dev, void *res)
{
struct srcu_struct *lock = res;
cleanup_srcu_struct(lock);
}
static int qaicm_srcu_init(struct drm_device *dev, struct srcu_struct *lock)
{
int ret;
ret = init_srcu_struct(lock);
if (ret)
return ret;
return drmm_add_action_or_reset(dev, qaicm_srcu_release, lock);
}
static void qaicm_pci_release(struct drm_device *dev, void *res)
{
struct qaic_device *qdev = to_qaic_device(dev);
pci_set_drvdata(qdev->pdev, NULL);
}
static void free_usr(struct kref *kref)
{
struct qaic_user *usr = container_of(kref, struct qaic_user, ref_count);
cleanup_srcu_struct(&usr->qddev_lock);
ida_free(&qaic_usrs, usr->handle);
kfree(usr);
}
static int qaic_open(struct drm_device *dev, struct drm_file *file)
{
struct qaic_drm_device *qddev = to_qaic_drm_device(dev);
struct qaic_device *qdev = qddev->qdev;
struct qaic_user *usr;
int rcu_id;
int ret;
rcu_id = srcu_read_lock(&qdev->dev_lock);
if (qdev->dev_state != QAIC_ONLINE) {
ret = -ENODEV;
goto dev_unlock;
}
usr = kmalloc_obj(*usr);
if (!usr) {
ret = -ENOMEM;
goto dev_unlock;
}
usr->handle = ida_alloc(&qaic_usrs, GFP_KERNEL);
if (usr->handle < 0) {
ret = usr->handle;
goto free_usr;
}
usr->qddev = qddev;
atomic_set(&usr->chunk_id, 0);
init_srcu_struct(&usr->qddev_lock);
kref_init(&usr->ref_count);
ret = mutex_lock_interruptible(&qddev->users_mutex);
if (ret)
goto cleanup_usr;
list_add(&usr->node, &qddev->users);
mutex_unlock(&qddev->users_mutex);
file->driver_priv = usr;
srcu_read_unlock(&qdev->dev_lock, rcu_id);
return 0;
cleanup_usr:
cleanup_srcu_struct(&usr->qddev_lock);
ida_free(&qaic_usrs, usr->handle);
free_usr:
kfree(usr);
dev_unlock:
srcu_read_unlock(&qdev->dev_lock, rcu_id);
return ret;
}
static void qaic_postclose(struct drm_device *dev, struct drm_file *file)
{
struct qaic_user *usr = file->driver_priv;
struct qaic_drm_device *qddev;
struct qaic_device *qdev;
int qdev_rcu_id;
int usr_rcu_id;
int i;
qddev = usr->qddev;
usr_rcu_id = srcu_read_lock(&usr->qddev_lock);
if (qddev) {
qdev = qddev->qdev;
qdev_rcu_id = srcu_read_lock(&qdev->dev_lock);
if (qdev->dev_state == QAIC_ONLINE) {
qaic_release_usr(qdev, usr);
for (i = 0; i < qdev->num_dbc; ++i)
if (qdev->dbc[i].usr && qdev->dbc[i].usr->handle == usr->handle)
release_dbc(qdev, i);
}
srcu_read_unlock(&qdev->dev_lock, qdev_rcu_id);
mutex_lock(&qddev->users_mutex);
if (!list_empty(&usr->node))
list_del_init(&usr->node);
mutex_unlock(&qddev->users_mutex);
}
srcu_read_unlock(&usr->qddev_lock, usr_rcu_id);
kref_put(&usr->ref_count, free_usr);
file->driver_priv = NULL;
}
DEFINE_DRM_ACCEL_FOPS(qaic_accel_fops);
static const struct drm_ioctl_desc qaic_drm_ioctls[] = {
DRM_IOCTL_DEF_DRV(QAIC_MANAGE, qaic_manage_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_CREATE_BO, qaic_create_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_MMAP_BO, qaic_mmap_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_ATTACH_SLICE_BO, qaic_attach_slice_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_EXECUTE_BO, qaic_execute_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_PARTIAL_EXECUTE_BO, qaic_partial_execute_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_WAIT_BO, qaic_wait_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_PERF_STATS_BO, qaic_perf_stats_bo_ioctl, 0),
DRM_IOCTL_DEF_DRV(QAIC_DETACH_SLICE_BO, qaic_detach_slice_bo_ioctl, 0),
};
static const struct drm_driver qaic_accel_driver = {
.driver_features = DRIVER_GEM | DRIVER_COMPUTE_ACCEL,
.name = QAIC_NAME,
.desc = QAIC_DESC,
.fops = &qaic_accel_fops,
.open = qaic_open,
.postclose = qaic_postclose,
.ioctls = qaic_drm_ioctls,
.num_ioctls = ARRAY_SIZE(qaic_drm_ioctls),
.gem_prime_import = qaic_gem_prime_import,
};
static int qaic_create_drm_device(struct qaic_device *qdev, s32 partition_id)
{
struct qaic_drm_device *qddev = qdev->qddev;
struct drm_device *drm = to_drm(qddev);
int ret;
/* Hold off implementing partitions until the uapi is determined */
if (partition_id != QAIC_NO_PARTITION)
return -EINVAL;
qddev->partition_id = partition_id;
ret = drm_dev_register(drm, 0);
if (ret) {
pci_dbg(qdev->pdev, "drm_dev_register failed %d\n", ret);
return ret;
}
ret = qaic_sysfs_init(qddev);
if (ret) {
drm_dev_unregister(drm);
pci_dbg(qdev->pdev, "qaic_sysfs_init failed %d\n", ret);
return ret;
}
qaic_debugfs_init(qddev);
return ret;
}
static void qaic_destroy_drm_device(struct qaic_device *qdev, s32 partition_id)
{
struct qaic_drm_device *qddev = qdev->qddev;
struct drm_device *drm = to_drm(qddev);
struct qaic_user *usr;
qaic_sysfs_remove(qddev);
drm_dev_unregister(drm);
qddev->partition_id = 0;
/*
* Existing users get unresolvable errors till they close FDs.
* Need to sync carefully with users calling close(). The
* list of users can be modified elsewhere when the lock isn't
* held here, but the sync'ing the srcu with the mutex held
* could deadlock. Grab the mutex so that the list will be
* unmodified. The user we get will exist as long as the
* lock is held. Signal that the qcdev is going away, and
* grab a reference to the user so they don't go away for
* synchronize_srcu(). Then release the mutex to avoid
* deadlock and make sure the user has observed the signal.
* With the lock released, we cannot maintain any state of the
* user list.
*/
mutex_lock(&qddev->users_mutex);
while (!list_empty(&qddev->users)) {
usr = list_first_entry(&qddev->users, struct qaic_user, node);
list_del_init(&usr->node);
kref_get(&usr->ref_count);
usr->qddev = NULL;
mutex_unlock(&qddev->users_mutex);
synchronize_srcu(&usr->qddev_lock);
kref_put(&usr->ref_count, free_usr);
mutex_lock(&qddev->users_mutex);
}
mutex_unlock(&qddev->users_mutex);
}
static int qaic_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
u16 major = -1, minor = -1;
struct qaic_device *qdev;
int ret;
/*
* Invoking this function indicates that the control channel to the
* device is available. We use that as a signal to indicate that
* the device side firmware has booted. The device side firmware
* manages the device resources, so we need to communicate with it
* via the control channel in order to utilize the device. Therefore
* we wait until this signal to create the drm dev that userspace will
* use to control the device, because without the device side firmware,
* userspace can't do anything useful.
*/
qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
dev_set_drvdata(&mhi_dev->dev, qdev);
qdev->cntl_ch = mhi_dev;
ret = qaic_control_open(qdev);
if (ret) {
pci_dbg(qdev->pdev, "%s: control_open failed %d\n", __func__, ret);
return ret;
}
qdev->dev_state = QAIC_BOOT;
ret = get_cntl_version(qdev, NULL, &major, &minor);
if (ret || major != CNTL_MAJOR || minor > CNTL_MINOR) {
pci_err(qdev->pdev, "%s: Control protocol version (%d.%d) not supported. Supported version is (%d.%d). Ret: %d\n",
__func__, major, minor, CNTL_MAJOR, CNTL_MINOR, ret);
ret = -EINVAL;
goto close_control;
}
qdev->dev_state = QAIC_ONLINE;
kobject_uevent(&(to_accel_kdev(qdev->qddev))->kobj, KOBJ_ONLINE);
return ret;
close_control:
qaic_control_close(qdev);
return ret;
}
static void qaic_mhi_remove(struct mhi_device *mhi_dev)
{
/* This is redundant since we have already observed the device crash */
}
static void qaic_notify_reset(struct qaic_device *qdev)
{
int i;
kobject_uevent(&(to_accel_kdev(qdev->qddev))->kobj, KOBJ_OFFLINE);
qdev->dev_state = QAIC_OFFLINE;
/* wake up any waiters to avoid waiting for timeouts at sync */
wake_all_cntl(qdev);
for (i = 0; i < qdev->num_dbc; ++i)
wakeup_dbc(qdev, i);
synchronize_srcu(&qdev->dev_lock);
}
void qaic_dev_reset_clean_local_state(struct qaic_device *qdev)
{
int i;
qaic_notify_reset(qdev);
/* start tearing things down */
qaic_clean_up_ssr(qdev);
for (i = 0; i < qdev->num_dbc; ++i)
release_dbc(qdev, i);
}
static struct qaic_device *create_qdev(struct pci_dev *pdev,
const struct qaic_device_config *config)
{
struct device *dev = &pdev->dev;
struct qaic_drm_device *qddev;
struct qaic_device *qdev;
struct drm_device *drm;
int i, ret;
qdev = devm_kzalloc(dev, struct_size(qdev, dbc, DBC_NUM), GFP_KERNEL);
if (!qdev)
return NULL;
qdev->num_dbc = DBC_NUM;
qdev->dev_state = QAIC_OFFLINE;
qddev = devm_drm_dev_alloc(&pdev->dev, &qaic_accel_driver, struct qaic_drm_device, drm);
if (IS_ERR(qddev))
return NULL;
drm = to_drm(qddev);
pci_set_drvdata(pdev, qdev);
ret = drmm_mutex_init(drm, &qddev->users_mutex);
if (ret)
return NULL;
ret = drmm_add_action_or_reset(drm, qaicm_pci_release, NULL);
if (ret)
return NULL;
ret = drmm_mutex_init(drm, &qdev->cntl_mutex);
if (ret)
return NULL;
ret = drmm_mutex_init(drm, &qdev->bootlog_mutex);
if (ret)
return NULL;
qdev->cntl_wq = qaicm_wq_init(drm, "qaic_cntl");
if (IS_ERR(qdev->cntl_wq))
return NULL;
qdev->qts_wq = qaicm_wq_init(drm, "qaic_ts");
if (IS_ERR(qdev->qts_wq))
return NULL;
qdev->ssr_wq = qaicm_wq_init(drm, "qaic_ssr");
if (IS_ERR(qdev->ssr_wq))
return NULL;
ret = qaicm_srcu_init(drm, &qdev->dev_lock);
if (ret)
return NULL;
ret = qaic_ssr_init(qdev, drm);
if (ret)
pci_info(pdev, "QAIC SSR crashdump collection not supported.\n");
qdev->qddev = qddev;
qdev->pdev = pdev;
qddev->qdev = qdev;
INIT_LIST_HEAD(&qdev->cntl_xfer_list);
INIT_LIST_HEAD(&qdev->bootlog);
INIT_LIST_HEAD(&qddev->users);
for (i = 0; i < qdev->num_dbc; ++i) {
spin_lock_init(&qdev->dbc[i].xfer_lock);
qdev->dbc[i].qdev = qdev;
qdev->dbc[i].id = i;
INIT_LIST_HEAD(&qdev->dbc[i].xfer_list);
ret = qaicm_srcu_init(drm, &qdev->dbc[i].ch_lock);
if (ret)
return NULL;
init_waitqueue_head(&qdev->dbc[i].dbc_release);
INIT_LIST_HEAD(&qdev->dbc[i].bo_lists);
ret = drmm_mutex_init(drm, &qdev->dbc[i].req_lock);
if (ret)
return NULL;
}
return qdev;
}
static int init_pci(struct qaic_device *qdev, struct pci_dev *pdev,
const struct qaic_device_config *config)
{
int bars;
int ret;
bars = pci_select_bars(pdev, IORESOURCE_MEM) & 0x3f;
/* make sure the device has the expected BARs */
if (bars != config->bar_mask) {
pci_dbg(pdev, "%s: expected BARs %#x not found in device. Found %#x\n",
__func__, config->bar_mask, bars);
return -EINVAL;
}
ret = pcim_enable_device(pdev);
if (ret)
return ret;
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret)
return ret;
dma_set_max_seg_size(&pdev->dev, UINT_MAX);
qdev->bar_mhi = devm_ioremap_resource(&pdev->dev, &pdev->resource[config->mhi_bar_idx]);
if (IS_ERR(qdev->bar_mhi))
return PTR_ERR(qdev->bar_mhi);
qdev->bar_dbc = devm_ioremap_resource(&pdev->dev, &pdev->resource[config->dbc_bar_idx]);
if (IS_ERR(qdev->bar_dbc))
return PTR_ERR(qdev->bar_dbc);
/* Managed release since we use pcim_enable_device above */
pci_set_master(pdev);
return 0;
}
static int init_msi(struct qaic_device *qdev, struct pci_dev *pdev)
{
int irq_count = qdev->num_dbc + 1;
int mhi_irq;
int ret;
int i;
/* Managed release since we use pcim_enable_device */
ret = pci_alloc_irq_vectors(pdev, irq_count, irq_count, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (ret == -ENOSPC) {
ret = pci_alloc_irq_vectors(pdev, 1, 1, PCI_IRQ_MSI | PCI_IRQ_MSIX);
if (ret < 0)
return ret;
/*
* Operate in one MSI mode. All interrupts will be directed to
* MSI0; every interrupt will wake up all the interrupt handlers
* (MHI and DBC[0-15]). Since the interrupt is now shared, it is
* not disabled during DBC threaded handler, but only one thread
* will be allowed to run per DBC, so while it can be
* interrupted, it shouldn't race with itself.
*/
qdev->single_msi = true;
pci_info(pdev, "Allocating %d MSIs failed, operating in 1 MSI mode. Performance may be impacted.\n",
irq_count);
} else if (ret < 0) {
return ret;
}
mhi_irq = pci_irq_vector(pdev, 0);
if (mhi_irq < 0)
return mhi_irq;
for (i = 0; i < qdev->num_dbc; ++i) {
ret = devm_request_threaded_irq(&pdev->dev,
pci_irq_vector(pdev, qdev->single_msi ? 0 : i + 1),
dbc_irq_handler, dbc_irq_threaded_fn, IRQF_SHARED,
"qaic_dbc", &qdev->dbc[i]);
if (ret)
return ret;
if (datapath_polling) {
qdev->dbc[i].irq = pci_irq_vector(pdev, qdev->single_msi ? 0 : i + 1);
if (!qdev->single_msi)
disable_irq_nosync(qdev->dbc[i].irq);
INIT_WORK(&qdev->dbc[i].poll_work, qaic_irq_polling_work);
}
}
return mhi_irq;
}
static int qaic_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct qaic_device_config *config = (struct qaic_device_config *)id->driver_data;
struct qaic_device *qdev;
int mhi_irq;
int ret;
int i;
qdev = create_qdev(pdev, config);
if (!qdev)
return -ENOMEM;
ret = init_pci(qdev, pdev, config);
if (ret)
return ret;
for (i = 0; i < qdev->num_dbc; ++i)
qdev->dbc[i].dbc_base = qdev->bar_dbc + QAIC_DBC_OFF(i);
mhi_irq = init_msi(qdev, pdev);
if (mhi_irq < 0)
return mhi_irq;
ret = qaic_create_drm_device(qdev, QAIC_NO_PARTITION);
if (ret)
return ret;
qdev->mhi_cntrl = qaic_mhi_register_controller(pdev, qdev->bar_mhi, mhi_irq,
qdev->single_msi, config->family);
if (IS_ERR(qdev->mhi_cntrl)) {
ret = PTR_ERR(qdev->mhi_cntrl);
qaic_destroy_drm_device(qdev, QAIC_NO_PARTITION);
return ret;
}
return 0;
}
static void qaic_pci_remove(struct pci_dev *pdev)
{
struct qaic_device *qdev = pci_get_drvdata(pdev);
if (!qdev)
return;
qaic_dev_reset_clean_local_state(qdev);
qaic_mhi_free_controller(qdev->mhi_cntrl, link_up);
qaic_destroy_drm_device(qdev, QAIC_NO_PARTITION);
}
static void qaic_pci_shutdown(struct pci_dev *pdev)
{
/* see qaic_exit for what link_up is doing */
link_up = true;
qaic_pci_remove(pdev);
}
static pci_ers_result_t qaic_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t error)
{
return PCI_ERS_RESULT_NEED_RESET;
}
static void qaic_pci_reset_prepare(struct pci_dev *pdev)
{
struct qaic_device *qdev = pci_get_drvdata(pdev);
qaic_notify_reset(qdev);
qaic_mhi_start_reset(qdev->mhi_cntrl);
qaic_dev_reset_clean_local_state(qdev);
}
static void qaic_pci_reset_done(struct pci_dev *pdev)
{
struct qaic_device *qdev = pci_get_drvdata(pdev);
qaic_mhi_reset_done(qdev->mhi_cntrl);
}
static const struct mhi_device_id qaic_mhi_match_table[] = {
{ .chan = "QAIC_CONTROL", },
{},
};
static struct mhi_driver qaic_mhi_driver = {
.id_table = qaic_mhi_match_table,
.remove = qaic_mhi_remove,
.probe = qaic_mhi_probe,
.ul_xfer_cb = qaic_mhi_ul_xfer_cb,
.dl_xfer_cb = qaic_mhi_dl_xfer_cb,
.driver = {
.name = "qaic_mhi",
},
};
static const struct pci_device_id qaic_ids[] = {
{ PCI_DEVICE_DATA(QCOM, AIC080, (kernel_ulong_t)&aic080_config), },
{ PCI_DEVICE_DATA(QCOM, AIC100, (kernel_ulong_t)&aic100_config), },
{ PCI_DEVICE_DATA(QCOM, AIC200, (kernel_ulong_t)&aic200_config), },
{ }
};
MODULE_DEVICE_TABLE(pci, qaic_ids);
static const struct pci_error_handlers qaic_pci_err_handler = {
.error_detected = qaic_pci_error_detected,
.reset_prepare = qaic_pci_reset_prepare,
.reset_done = qaic_pci_reset_done,
};
static bool qaic_is_under_reset(struct qaic_device *qdev)
{
int rcu_id;
bool ret;
rcu_id = srcu_read_lock(&qdev->dev_lock);
ret = qdev->dev_state != QAIC_ONLINE;
srcu_read_unlock(&qdev->dev_lock, rcu_id);
return ret;
}
static bool qaic_data_path_busy(struct qaic_device *qdev)
{
bool ret = false;
int dev_rcu_id;
int i;
dev_rcu_id = srcu_read_lock(&qdev->dev_lock);
if (qdev->dev_state != QAIC_ONLINE) {
srcu_read_unlock(&qdev->dev_lock, dev_rcu_id);
return false;
}
for (i = 0; i < qdev->num_dbc; i++) {
struct dma_bridge_chan *dbc = &qdev->dbc[i];
unsigned long flags;
int ch_rcu_id;
ch_rcu_id = srcu_read_lock(&dbc->ch_lock);
if (!dbc->usr || !dbc->in_use) {
srcu_read_unlock(&dbc->ch_lock, ch_rcu_id);
continue;
}
spin_lock_irqsave(&dbc->xfer_lock, flags);
ret = !list_empty(&dbc->xfer_list);
spin_unlock_irqrestore(&dbc->xfer_lock, flags);
srcu_read_unlock(&dbc->ch_lock, ch_rcu_id);
if (ret)
break;
}
srcu_read_unlock(&qdev->dev_lock, dev_rcu_id);
return ret;
}
static int qaic_pm_suspend(struct device *dev)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
dev_dbg(dev, "Suspending..\n");
if (qaic_data_path_busy(qdev)) {
dev_dbg(dev, "Device's datapath is busy. Aborting suspend..\n");
return -EBUSY;
}
if (qaic_is_under_reset(qdev)) {
dev_dbg(dev, "Device is under reset. Aborting suspend..\n");
return -EBUSY;
}
qaic_mqts_ch_stop_timer(qdev->mqts_ch);
qaic_pci_reset_prepare(qdev->pdev);
pci_save_state(qdev->pdev);
pci_disable_device(qdev->pdev);
pci_set_power_state(qdev->pdev, PCI_D3hot);
return 0;
}
static int qaic_pm_resume(struct device *dev)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
int ret;
dev_dbg(dev, "Resuming..\n");
pci_set_power_state(qdev->pdev, PCI_D0);
pci_restore_state(qdev->pdev);
ret = pci_enable_device(qdev->pdev);
if (ret) {
dev_err(dev, "pci_enable_device failed on resume %d\n", ret);
return ret;
}
pci_set_master(qdev->pdev);
qaic_pci_reset_done(qdev->pdev);
return 0;
}
static const struct dev_pm_ops qaic_pm_ops = {
SYSTEM_SLEEP_PM_OPS(qaic_pm_suspend, qaic_pm_resume)
};
static struct pci_driver qaic_pci_driver = {
.name = QAIC_NAME,
.id_table = qaic_ids,
.probe = qaic_pci_probe,
.remove = qaic_pci_remove,
.shutdown = qaic_pci_shutdown,
.err_handler = &qaic_pci_err_handler,
.driver = {
.pm = pm_sleep_ptr(&qaic_pm_ops),
},
};
static int __init qaic_init(void)
{
int ret;
ret = pci_register_driver(&qaic_pci_driver);
if (ret) {
pr_debug("qaic: pci_register_driver failed %d\n", ret);
return ret;
}
ret = mhi_driver_register(&qaic_mhi_driver);
if (ret) {
pr_debug("qaic: mhi_driver_register failed %d\n", ret);
goto free_pci;
}
ret = sahara_register();
if (ret) {
pr_debug("qaic: sahara_register failed %d\n", ret);
goto free_mhi;
}
ret = qaic_timesync_init();
if (ret)
pr_debug("qaic: qaic_timesync_init failed %d\n", ret);
ret = qaic_bootlog_register();
if (ret)
pr_debug("qaic: qaic_bootlog_register failed %d\n", ret);
ret = qaic_ras_register();
if (ret)
pr_debug("qaic: qaic_ras_register failed %d\n", ret);
ret = qaic_ssr_register();
if (ret) {
pr_debug("qaic: qaic_ssr_register failed %d\n", ret);
goto free_bootlog;
}
return 0;
free_bootlog:
qaic_bootlog_unregister();
free_mhi:
mhi_driver_unregister(&qaic_mhi_driver);
free_pci:
pci_unregister_driver(&qaic_pci_driver);
return ret;
}
static void __exit qaic_exit(void)
{
/*
* We assume that qaic_pci_remove() is called due to a hotplug event
* which would mean that the link is down, and thus
* qaic_mhi_free_controller() should not try to access the device during
* cleanup.
* We call pci_unregister_driver() below, which also triggers
* qaic_pci_remove(), but since this is module exit, we expect the link
* to the device to be up, in which case qaic_mhi_free_controller()
* should try to access the device during cleanup to put the device in
* a sane state.
* For that reason, we set link_up here to let qaic_mhi_free_controller
* know the expected link state. Since the module is going to be
* removed at the end of this, we don't need to worry about
* reinitializing the link_up state after the cleanup is done.
*/
link_up = true;
qaic_ssr_unregister();
qaic_ras_unregister();
qaic_bootlog_unregister();
qaic_timesync_deinit();
sahara_unregister();
mhi_driver_unregister(&qaic_mhi_driver);
pci_unregister_driver(&qaic_pci_driver);
}
module_init(qaic_init);
module_exit(qaic_exit);
MODULE_AUTHOR(QAIC_DESC " Kernel Driver Team");
MODULE_DESCRIPTION(QAIC_DESC " Accel Driver");
MODULE_LICENSE("GPL");
+641
View File
@@ -0,0 +1,641 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <asm/byteorder.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mhi.h>
#include "qaic.h"
#include "qaic_ras.h"
#define MAGIC 0x55AA
#define VERSION 0x2
#define HDR_SZ 12
#define NUM_TEMP_LVL 3
#define POWER_BREAK BIT(0)
enum msg_type {
MSG_PUSH, /* async push from device */
MSG_REQ, /* sync request to device */
MSG_RESP, /* sync response from device */
};
enum err_type {
CE, /* correctable error */
UE, /* uncorrectable error */
UE_NF, /* uncorrectable error that is non-fatal, expect a disruption */
ERR_TYPE_MAX,
};
static const char * const err_type_str[] = {
[CE] = "Correctable",
[UE] = "Uncorrectable",
[UE_NF] = "Uncorrectable Non-Fatal",
};
static const char * const err_class_str[] = {
[CE] = "Warning",
[UE] = "Fatal",
[UE_NF] = "Warning",
};
enum err_source {
SOC_MEM,
PCIE,
DDR,
SYS_BUS1,
SYS_BUS2,
NSP_MEM,
TSENS,
};
static const char * const err_src_str[TSENS + 1] = {
[SOC_MEM] = "SoC Memory",
[PCIE] = "PCIE",
[DDR] = "DDR",
[SYS_BUS1] = "System Bus source 1",
[SYS_BUS2] = "System Bus source 2",
[NSP_MEM] = "NSP Memory",
[TSENS] = "Temperature Sensors",
};
struct ras_data {
/* header start */
/* Magic number to validate the message */
u16 magic;
/* RAS version number */
u16 ver;
u32 seq_num;
/* RAS message type */
u8 type;
u8 id;
/* Size of RAS message without the header in byte */
u16 len;
/* header end */
s32 result;
/*
* Error source
* 0 : SoC Memory
* 1 : PCIE
* 2 : DDR
* 3 : System Bus source 1
* 4 : System Bus source 2
* 5 : NSP Memory
* 6 : Temperature Sensors
*/
u32 source;
/*
* Stores the error type, there are three types of error in RAS
* 0 : correctable error (CE)
* 1 : uncorrectable error (UE)
* 2 : uncorrectable error that is non-fatal (UE_NF)
*/
u32 err_type;
u32 err_threshold;
u32 ce_count;
u32 ue_count;
u32 intr_num;
/* Data specific to error source */
u8 syndrome[64];
} __packed;
struct soc_mem_syndrome {
u64 error_address[8];
} __packed;
struct nsp_mem_syndrome {
u32 error_address[8];
u8 nsp_id;
} __packed;
struct ddr_syndrome {
u32 count;
u32 irq_status;
u32 data_31_0[2];
u32 data_63_32[2];
u32 data_95_64[2];
u32 data_127_96[2];
u32 addr_lsb;
u16 addr_msb;
u16 parity_bits;
u16 instance;
u16 err_type;
} __packed;
struct tsens_syndrome {
u32 threshold_type;
s32 temp;
} __packed;
struct sysbus1_syndrome {
u32 slave;
u32 err_type;
u16 addr[8];
u8 instance;
} __packed;
struct sysbus2_syndrome {
u32 lsb3;
u32 msb3;
u32 lsb2;
u32 msb2;
u32 ext_id;
u16 path;
u16 op_type;
u16 len;
u16 redirect;
u8 valid;
u8 word_error;
u8 non_secure;
u8 opc;
u8 error_code;
u8 trans_type;
u8 addr_space;
u8 instance;
} __packed;
struct pcie_syndrome {
/* CE info */
u32 bad_tlp;
u32 bad_dllp;
u32 replay_rollover;
u32 replay_timeout;
u32 rx_err;
u32 internal_ce_count;
/* UE_NF info */
u32 fc_timeout;
u32 poison_tlp;
u32 ecrc_err;
u32 unsupported_req;
u32 completer_abort;
u32 completion_timeout;
/* UE info */
u32 addr;
u8 index;
/*
* Flag to indicate specific event of PCIe
* BIT(0): Power break (low power)
* BIT(1) to BIT(7): Reserved
*/
u8 flag;
} __packed;
static const char * const threshold_type_str[NUM_TEMP_LVL] = {
[0] = "lower",
[1] = "upper",
[2] = "critical",
};
static void ras_msg_to_cpu(struct ras_data *msg)
{
struct sysbus1_syndrome *sysbus1_syndrome = (struct sysbus1_syndrome *)&msg->syndrome[0];
struct sysbus2_syndrome *sysbus2_syndrome = (struct sysbus2_syndrome *)&msg->syndrome[0];
struct soc_mem_syndrome *soc_syndrome = (struct soc_mem_syndrome *)&msg->syndrome[0];
struct nsp_mem_syndrome *nsp_syndrome = (struct nsp_mem_syndrome *)&msg->syndrome[0];
struct tsens_syndrome *tsens_syndrome = (struct tsens_syndrome *)&msg->syndrome[0];
struct pcie_syndrome *pcie_syndrome = (struct pcie_syndrome *)&msg->syndrome[0];
struct ddr_syndrome *ddr_syndrome = (struct ddr_syndrome *)&msg->syndrome[0];
int i;
le16_to_cpus(&msg->magic);
le16_to_cpus(&msg->ver);
le32_to_cpus(&msg->seq_num);
le16_to_cpus(&msg->len);
le32_to_cpus(&msg->result);
le32_to_cpus(&msg->source);
le32_to_cpus(&msg->err_type);
le32_to_cpus(&msg->err_threshold);
le32_to_cpus(&msg->ce_count);
le32_to_cpus(&msg->ue_count);
le32_to_cpus(&msg->intr_num);
switch (msg->source) {
case SOC_MEM:
for (i = 0; i < 8; i++)
le64_to_cpus(&soc_syndrome->error_address[i]);
break;
case PCIE:
le32_to_cpus(&pcie_syndrome->bad_tlp);
le32_to_cpus(&pcie_syndrome->bad_dllp);
le32_to_cpus(&pcie_syndrome->replay_rollover);
le32_to_cpus(&pcie_syndrome->replay_timeout);
le32_to_cpus(&pcie_syndrome->rx_err);
le32_to_cpus(&pcie_syndrome->internal_ce_count);
le32_to_cpus(&pcie_syndrome->fc_timeout);
le32_to_cpus(&pcie_syndrome->poison_tlp);
le32_to_cpus(&pcie_syndrome->ecrc_err);
le32_to_cpus(&pcie_syndrome->unsupported_req);
le32_to_cpus(&pcie_syndrome->completer_abort);
le32_to_cpus(&pcie_syndrome->completion_timeout);
le32_to_cpus(&pcie_syndrome->addr);
break;
case DDR:
le16_to_cpus(&ddr_syndrome->instance);
le16_to_cpus(&ddr_syndrome->err_type);
le32_to_cpus(&ddr_syndrome->count);
le32_to_cpus(&ddr_syndrome->irq_status);
le32_to_cpus(&ddr_syndrome->data_31_0[0]);
le32_to_cpus(&ddr_syndrome->data_31_0[1]);
le32_to_cpus(&ddr_syndrome->data_63_32[0]);
le32_to_cpus(&ddr_syndrome->data_63_32[1]);
le32_to_cpus(&ddr_syndrome->data_95_64[0]);
le32_to_cpus(&ddr_syndrome->data_95_64[1]);
le32_to_cpus(&ddr_syndrome->data_127_96[0]);
le32_to_cpus(&ddr_syndrome->data_127_96[1]);
le16_to_cpus(&ddr_syndrome->parity_bits);
le16_to_cpus(&ddr_syndrome->addr_msb);
le32_to_cpus(&ddr_syndrome->addr_lsb);
break;
case SYS_BUS1:
le32_to_cpus(&sysbus1_syndrome->slave);
le32_to_cpus(&sysbus1_syndrome->err_type);
for (i = 0; i < 8; i++)
le16_to_cpus(&sysbus1_syndrome->addr[i]);
break;
case SYS_BUS2:
le16_to_cpus(&sysbus2_syndrome->op_type);
le16_to_cpus(&sysbus2_syndrome->len);
le16_to_cpus(&sysbus2_syndrome->redirect);
le16_to_cpus(&sysbus2_syndrome->path);
le32_to_cpus(&sysbus2_syndrome->ext_id);
le32_to_cpus(&sysbus2_syndrome->lsb2);
le32_to_cpus(&sysbus2_syndrome->msb2);
le32_to_cpus(&sysbus2_syndrome->lsb3);
le32_to_cpus(&sysbus2_syndrome->msb3);
break;
case NSP_MEM:
for (i = 0; i < 8; i++)
le32_to_cpus(&nsp_syndrome->error_address[i]);
break;
case TSENS:
le32_to_cpus(&tsens_syndrome->threshold_type);
le32_to_cpus(&tsens_syndrome->temp);
break;
}
}
static void decode_ras_msg(struct qaic_device *qdev, struct ras_data *msg)
{
struct sysbus1_syndrome *sysbus1_syndrome = (struct sysbus1_syndrome *)&msg->syndrome[0];
struct sysbus2_syndrome *sysbus2_syndrome = (struct sysbus2_syndrome *)&msg->syndrome[0];
struct soc_mem_syndrome *soc_syndrome = (struct soc_mem_syndrome *)&msg->syndrome[0];
struct nsp_mem_syndrome *nsp_syndrome = (struct nsp_mem_syndrome *)&msg->syndrome[0];
struct tsens_syndrome *tsens_syndrome = (struct tsens_syndrome *)&msg->syndrome[0];
struct pcie_syndrome *pcie_syndrome = (struct pcie_syndrome *)&msg->syndrome[0];
struct ddr_syndrome *ddr_syndrome = (struct ddr_syndrome *)&msg->syndrome[0];
char *class;
char *level;
if (msg->magic != MAGIC) {
pci_warn(qdev->pdev, "Dropping RAS message with invalid magic %x\n", msg->magic);
return;
}
if (!msg->ver || msg->ver > VERSION) {
pci_warn(qdev->pdev, "Dropping RAS message with invalid version %d\n", msg->ver);
return;
}
if (msg->type != MSG_PUSH) {
pci_warn(qdev->pdev, "Dropping non-PUSH RAS message\n");
return;
}
if (msg->len != sizeof(*msg) - HDR_SZ) {
pci_warn(qdev->pdev, "Dropping RAS message with invalid len %d\n", msg->len);
return;
}
if (msg->err_type >= ERR_TYPE_MAX) {
pci_warn(qdev->pdev, "Dropping RAS message with err type %d\n", msg->err_type);
return;
}
if (msg->err_type == UE)
level = KERN_ERR;
else
level = KERN_WARNING;
switch (msg->source) {
case SOC_MEM:
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\nSyndrome:\n 0x%llx\n 0x%llx\n 0x%llx\n 0x%llx\n 0x%llx\n 0x%llx\n 0x%llx\n 0x%llx\n",
err_class_str[msg->err_type],
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold,
soc_syndrome->error_address[0],
soc_syndrome->error_address[1],
soc_syndrome->error_address[2],
soc_syndrome->error_address[3],
soc_syndrome->error_address[4],
soc_syndrome->error_address[5],
soc_syndrome->error_address[6],
soc_syndrome->error_address[7]);
break;
case PCIE:
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\n",
err_class_str[msg->err_type],
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold);
switch (msg->err_type) {
case CE:
/*
* Modeled after AER prints. This continues the dev_printk() from a few
* lines up. We reduce duplication of code, but also avoid re-printing the
* PCI device info so that the end result looks uniform to the log user.
*/
printk(KERN_WARNING pr_fmt("Syndrome:\n Bad TLP count %d\n Bad DLLP count %d\n Replay Rollover count %d\n Replay Timeout count %d\n Recv Error count %d\n Internal CE count %d\n"),
pcie_syndrome->bad_tlp,
pcie_syndrome->bad_dllp,
pcie_syndrome->replay_rollover,
pcie_syndrome->replay_timeout,
pcie_syndrome->rx_err,
pcie_syndrome->internal_ce_count);
if (msg->ver > 0x1)
pr_warn(" Power break %s\n",
pcie_syndrome->flag & POWER_BREAK ? "ON" : "OFF");
break;
case UE:
printk(KERN_ERR pr_fmt("Syndrome:\n Index %d\n Address 0x%x\n"),
pcie_syndrome->index, pcie_syndrome->addr);
break;
case UE_NF:
printk(KERN_WARNING pr_fmt("Syndrome:\n FC timeout count %d\n Poisoned TLP count %d\n ECRC error count %d\n Unsupported request count %d\n Completer abort count %d\n Completion timeout count %d\n"),
pcie_syndrome->fc_timeout,
pcie_syndrome->poison_tlp,
pcie_syndrome->ecrc_err,
pcie_syndrome->unsupported_req,
pcie_syndrome->completer_abort,
pcie_syndrome->completion_timeout);
break;
default:
break;
}
break;
case DDR:
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\nSyndrome:\n Instance %d\n Count %d\n Data 31_0 0x%x 0x%x\n Data 63_32 0x%x 0x%x\n Data 95_64 0x%x 0x%x\n Data 127_96 0x%x 0x%x\n Parity bits 0x%x\n Address msb 0x%x\n Address lsb 0x%x\n",
err_class_str[msg->err_type],
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold,
ddr_syndrome->instance,
ddr_syndrome->count,
ddr_syndrome->data_31_0[1],
ddr_syndrome->data_31_0[0],
ddr_syndrome->data_63_32[1],
ddr_syndrome->data_63_32[0],
ddr_syndrome->data_95_64[1],
ddr_syndrome->data_95_64[0],
ddr_syndrome->data_127_96[1],
ddr_syndrome->data_127_96[0],
ddr_syndrome->parity_bits,
ddr_syndrome->addr_msb,
ddr_syndrome->addr_lsb);
break;
case SYS_BUS1:
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\nSyndrome:\n instance %d\n %s\n err_type %d\n address0 0x%x\n address1 0x%x\n address2 0x%x\n address3 0x%x\n address4 0x%x\n address5 0x%x\n address6 0x%x\n address7 0x%x\n",
err_class_str[msg->err_type],
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold,
sysbus1_syndrome->instance,
sysbus1_syndrome->slave ? "Slave" : "Master",
sysbus1_syndrome->err_type,
sysbus1_syndrome->addr[0],
sysbus1_syndrome->addr[1],
sysbus1_syndrome->addr[2],
sysbus1_syndrome->addr[3],
sysbus1_syndrome->addr[4],
sysbus1_syndrome->addr[5],
sysbus1_syndrome->addr[6],
sysbus1_syndrome->addr[7]);
break;
case SYS_BUS2:
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\nSyndrome:\n instance %d\n valid %d\n word error %d\n non-secure %d\n opc %d\n error code %d\n transaction type %d\n address space %d\n operation type %d\n len %d\n redirect %d\n path %d\n ext_id %d\n lsb2 %d\n msb2 %d\n lsb3 %d\n msb3 %d\n",
err_class_str[msg->err_type],
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold,
sysbus2_syndrome->instance,
sysbus2_syndrome->valid,
sysbus2_syndrome->word_error,
sysbus2_syndrome->non_secure,
sysbus2_syndrome->opc,
sysbus2_syndrome->error_code,
sysbus2_syndrome->trans_type,
sysbus2_syndrome->addr_space,
sysbus2_syndrome->op_type,
sysbus2_syndrome->len,
sysbus2_syndrome->redirect,
sysbus2_syndrome->path,
sysbus2_syndrome->ext_id,
sysbus2_syndrome->lsb2,
sysbus2_syndrome->msb2,
sysbus2_syndrome->lsb3,
sysbus2_syndrome->msb3);
break;
case NSP_MEM:
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\nSyndrome:\n NSP ID %d\n 0x%x\n 0x%x\n 0x%x\n 0x%x\n 0x%x\n 0x%x\n 0x%x\n 0x%x\n",
err_class_str[msg->err_type],
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold,
nsp_syndrome->nsp_id,
nsp_syndrome->error_address[0],
nsp_syndrome->error_address[1],
nsp_syndrome->error_address[2],
nsp_syndrome->error_address[3],
nsp_syndrome->error_address[4],
nsp_syndrome->error_address[5],
nsp_syndrome->error_address[6],
nsp_syndrome->error_address[7]);
break;
case TSENS:
if (tsens_syndrome->threshold_type >= NUM_TEMP_LVL) {
pci_warn(qdev->pdev, "Dropping RAS message with invalid temp threshold %d\n",
tsens_syndrome->threshold_type);
break;
}
if (msg->err_type)
class = "Fatal";
else if (tsens_syndrome->threshold_type)
class = "Critical";
else
class = "Warning";
dev_printk(level, &qdev->pdev->dev, "RAS event.\nClass:%s\nDescription:%s %s %s\nError Threshold for this report %d\nSyndrome:\n %s threshold\n %d deg C\n",
class,
err_type_str[msg->err_type],
"error from",
err_src_str[msg->source],
msg->err_threshold,
threshold_type_str[tsens_syndrome->threshold_type],
tsens_syndrome->temp);
break;
}
/* Uncorrectable errors are fatal */
if (msg->err_type == UE)
mhi_soc_reset(qdev->mhi_cntrl);
switch (msg->err_type) {
case CE:
if (qdev->ce_count != UINT_MAX)
qdev->ce_count++;
break;
case UE:
if (qdev->ue_count != UINT_MAX)
qdev->ue_count++;
break;
case UE_NF:
if (qdev->ue_nf_count != UINT_MAX)
qdev->ue_nf_count++;
break;
default:
/* not possible */
break;
}
}
static ssize_t ce_count_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
return sysfs_emit(buf, "%d\n", qdev->ce_count);
}
static ssize_t ue_count_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
return sysfs_emit(buf, "%d\n", qdev->ue_count);
}
static ssize_t ue_nonfatal_count_show(struct device *dev, struct device_attribute *attr, char *buf)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(dev));
return sysfs_emit(buf, "%d\n", qdev->ue_nf_count);
}
static DEVICE_ATTR_RO(ce_count);
static DEVICE_ATTR_RO(ue_count);
static DEVICE_ATTR_RO(ue_nonfatal_count);
static struct attribute *ras_attrs[] = {
&dev_attr_ce_count.attr,
&dev_attr_ue_count.attr,
&dev_attr_ue_nonfatal_count.attr,
NULL,
};
static struct attribute_group ras_group = {
.attrs = ras_attrs,
};
static int qaic_ras_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
struct ras_data *resp;
int ret;
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
return ret;
resp = kzalloc_obj(*resp);
if (!resp) {
mhi_unprepare_from_transfer(mhi_dev);
return -ENOMEM;
}
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, resp, sizeof(*resp), MHI_EOT);
if (ret) {
kfree(resp);
mhi_unprepare_from_transfer(mhi_dev);
return ret;
}
ret = device_add_group(&qdev->pdev->dev, &ras_group);
if (ret) {
mhi_unprepare_from_transfer(mhi_dev);
pci_dbg(qdev->pdev, "ras add sysfs failed %d\n", ret);
return ret;
}
dev_set_drvdata(&mhi_dev->dev, qdev);
qdev->ras_ch = mhi_dev;
return ret;
}
static void qaic_ras_mhi_remove(struct mhi_device *mhi_dev)
{
struct qaic_device *qdev;
qdev = dev_get_drvdata(&mhi_dev->dev);
qdev->ras_ch = NULL;
device_remove_group(&qdev->pdev->dev, &ras_group);
mhi_unprepare_from_transfer(mhi_dev);
}
static void qaic_ras_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result) {}
static void qaic_ras_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev);
struct ras_data *msg = mhi_result->buf_addr;
int ret;
if (mhi_result->transaction_status) {
kfree(msg);
return;
}
ras_msg_to_cpu(msg);
decode_ras_msg(qdev, msg);
ret = mhi_queue_buf(qdev->ras_ch, DMA_FROM_DEVICE, msg, sizeof(*msg), MHI_EOT);
if (ret) {
dev_err(&mhi_dev->dev, "Cannot requeue RAS recv buf %d\n", ret);
kfree(msg);
}
}
static const struct mhi_device_id qaic_ras_mhi_match_table[] = {
{ .chan = "QAIC_STATUS", },
{},
};
static struct mhi_driver qaic_ras_mhi_driver = {
.id_table = qaic_ras_mhi_match_table,
.remove = qaic_ras_mhi_remove,
.probe = qaic_ras_mhi_probe,
.ul_xfer_cb = qaic_ras_mhi_ul_xfer_cb,
.dl_xfer_cb = qaic_ras_mhi_dl_xfer_cb,
.driver = {
.name = "qaic_ras",
},
};
int qaic_ras_register(void)
{
return mhi_driver_register(&qaic_ras_mhi_driver);
}
void qaic_ras_unregister(void)
{
mhi_driver_unregister(&qaic_ras_mhi_driver);
}
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
#ifndef __QAIC_RAS_H__
#define __QAIC_RAS_H__
int qaic_ras_register(void);
void qaic_ras_unregister(void);
#endif /* __QAIC_RAS_H__ */
+815
View File
@@ -0,0 +1,815 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020-2021, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <asm/byteorder.h>
#include <drm/drm_file.h>
#include <drm/drm_managed.h>
#include <linux/devcoredump.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/mhi.h>
#include <linux/workqueue.h>
#include "qaic.h"
#include "qaic_ssr.h"
#define SSR_RESP_MSG_SZ 32
#define SSR_MHI_BUF_SIZE SZ_64K
#define SSR_MEM_READ_DATA_SIZE ((u64)SSR_MHI_BUF_SIZE - sizeof(struct ssr_crashdump))
#define SSR_MEM_READ_CHUNK_SIZE ((u64)SSR_MEM_READ_DATA_SIZE - sizeof(struct ssr_memory_read_rsp))
#define DEBUG_TRANSFER_INFO BIT(0)
#define DEBUG_TRANSFER_INFO_RSP BIT(1)
#define MEMORY_READ BIT(2)
#define MEMORY_READ_RSP BIT(3)
#define DEBUG_TRANSFER_DONE BIT(4)
#define DEBUG_TRANSFER_DONE_RSP BIT(5)
#define SSR_EVENT BIT(8)
#define SSR_EVENT_RSP BIT(9)
#define SSR_EVENT_NACK BIT(0)
#define BEFORE_SHUTDOWN BIT(1)
#define AFTER_SHUTDOWN BIT(2)
#define BEFORE_POWER_UP BIT(3)
#define AFTER_POWER_UP BIT(4)
struct debug_info_table {
/* Save preferences. Default is mandatory */
u64 save_perf;
/* Base address of the debug region */
u64 mem_base;
/* Size of debug region in bytes */
u64 len;
/* Description */
char desc[20];
/* Filename of debug region */
char filename[20];
};
struct _ssr_hdr {
__le32 cmd;
__le32 len;
__le32 dbc_id;
};
struct ssr_hdr {
u32 cmd;
u32 len;
u32 dbc_id;
};
struct ssr_debug_transfer_info {
struct ssr_hdr hdr;
u32 resv;
u64 tbl_addr;
u64 tbl_len;
} __packed;
struct ssr_debug_transfer_info_rsp {
struct _ssr_hdr hdr;
__le32 ret;
} __packed;
struct ssr_memory_read {
struct _ssr_hdr hdr;
__le32 resv;
__le64 addr;
__le64 len;
} __packed;
struct ssr_memory_read_rsp {
struct _ssr_hdr hdr;
__le32 resv;
u8 data[];
} __packed;
struct ssr_debug_transfer_done {
struct _ssr_hdr hdr;
__le32 resv;
} __packed;
struct ssr_debug_transfer_done_rsp {
struct _ssr_hdr hdr;
__le32 ret;
} __packed;
struct ssr_event {
struct ssr_hdr hdr;
u32 event;
} __packed;
struct ssr_event_rsp {
struct _ssr_hdr hdr;
__le32 event;
} __packed;
struct ssr_resp {
/* Work struct to schedule work coming on QAIC_SSR channel */
struct work_struct work;
/* Root struct of device, used to access device resources */
struct qaic_device *qdev;
/* Buffer used by MHI for transfer requests */
u8 data[] __aligned(8);
};
/* SSR crashdump book keeping structure */
struct ssr_dump_info {
/* DBC associated with this SSR crashdump */
struct dma_bridge_chan *dbc;
/*
* It will be used when we complete the crashdump download and switch
* to waiting on SSR events
*/
struct ssr_resp *resp;
/* MEMORY READ request MHI buffer.*/
struct ssr_memory_read *read_buf_req;
/* TRUE: ->read_buf_req is queued for MHI transaction. FALSE: Otherwise */
bool read_buf_req_queued;
/* Address of table in host */
void *tbl_addr;
/* Total size of table */
u64 tbl_len;
/* Offset of table(->tbl_addr) where the new chunk will be dumped */
u64 tbl_off;
/* Address of table in device/target */
u64 tbl_addr_dev;
/* Ptr to the entire dump */
void *dump_addr;
/* Entire crashdump size */
u64 dump_sz;
/* Offset of crashdump(->dump_addr) where the new chunk will be dumped */
u64 dump_off;
/* Points to the table entry we are currently downloading */
struct debug_info_table *tbl_ent;
/* Offset in the current table entry(->tbl_ent) for next chuck */
u64 tbl_ent_off;
};
struct ssr_crashdump {
/*
* Points to a book keeping struct maintained by MHI SSR device while
* downloading a SSR crashdump. It is NULL when crashdump downloading
* not in progress.
*/
struct ssr_dump_info *dump_info;
/* Work struct to schedule work coming on QAIC_SSR channel */
struct work_struct work;
/* Root struct of device, used to access device resources */
struct qaic_device *qdev;
/* Buffer used by MHI for transfer requests */
u8 data[];
};
#define QAIC_SSR_DUMP_V1_MAGIC 0x1234567890abcdef
#define QAIC_SSR_DUMP_V1_VER 1
struct dump_file_meta {
u64 magic;
u64 version;
u64 size; /* Total size of the entire dump */
u64 tbl_len; /* Length of the table in byte */
};
/*
* Layout of crashdump
* +------------------------------------------+
* | Crashdump Meta structure |
* | type: struct dump_file_meta |
* +------------------------------------------+
* | Crashdump Table |
* | type: array of struct debug_info_table |
* | |
* | |
* | |
* +------------------------------------------+
* | Crashdump |
* | |
* | |
* | |
* | |
* | |
* +------------------------------------------+
*/
static void free_ssr_dump_info(struct ssr_crashdump *ssr_crash)
{
struct ssr_dump_info *dump_info = ssr_crash->dump_info;
ssr_crash->dump_info = NULL;
if (!dump_info)
return;
if (!dump_info->read_buf_req_queued)
kfree(dump_info->read_buf_req);
vfree(dump_info->tbl_addr);
vfree(dump_info->dump_addr);
kfree(dump_info);
}
void qaic_clean_up_ssr(struct qaic_device *qdev)
{
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
if (!ssr_crash)
return;
qaic_dbc_exit_ssr(qdev);
free_ssr_dump_info(ssr_crash);
}
static int alloc_dump(struct ssr_dump_info *dump_info)
{
struct debug_info_table *tbl_ent = dump_info->tbl_addr;
struct dump_file_meta *dump_meta;
u64 tbl_sz_lp = 0;
u64 dump_size = 0;
while (tbl_sz_lp < dump_info->tbl_len) {
le64_to_cpus(&tbl_ent->save_perf);
le64_to_cpus(&tbl_ent->mem_base);
le64_to_cpus(&tbl_ent->len);
if (tbl_ent->len == 0)
return -EINVAL;
dump_size += tbl_ent->len;
tbl_ent++;
tbl_sz_lp += sizeof(*tbl_ent);
}
dump_info->dump_sz = dump_size + dump_info->tbl_len + sizeof(*dump_meta);
dump_info->dump_addr = vzalloc(dump_info->dump_sz);
if (!dump_info->dump_addr)
return -ENOMEM;
/* Copy crashdump meta and table */
dump_meta = dump_info->dump_addr;
dump_meta->magic = QAIC_SSR_DUMP_V1_MAGIC;
dump_meta->version = QAIC_SSR_DUMP_V1_VER;
dump_meta->size = dump_info->dump_sz;
dump_meta->tbl_len = dump_info->tbl_len;
memcpy(dump_info->dump_addr + sizeof(*dump_meta), dump_info->tbl_addr, dump_info->tbl_len);
/* Offset by crashdump meta and table (copied above) */
dump_info->dump_off = dump_info->tbl_len + sizeof(*dump_meta);
return 0;
}
static int send_xfer_done(struct qaic_device *qdev, void *resp, u32 dbc_id)
{
struct ssr_debug_transfer_done *xfer_done;
int ret;
xfer_done = kmalloc_obj(*xfer_done);
if (!xfer_done) {
ret = -ENOMEM;
goto out;
}
ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, resp, SSR_RESP_MSG_SZ, MHI_EOT);
if (ret)
goto free_xfer_done;
xfer_done->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_DONE);
xfer_done->hdr.len = cpu_to_le32(sizeof(*xfer_done));
xfer_done->hdr.dbc_id = cpu_to_le32(dbc_id);
ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, xfer_done, sizeof(*xfer_done), MHI_EOT);
if (ret)
goto free_xfer_done;
return 0;
free_xfer_done:
kfree(xfer_done);
out:
return ret;
}
static int mem_read_req(struct qaic_device *qdev, u64 dest_addr, u64 dest_len)
{
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
struct ssr_memory_read *read_buf_req;
struct ssr_dump_info *dump_info;
int ret;
dump_info = ssr_crash->dump_info;
ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, ssr_crash->data, SSR_MEM_READ_DATA_SIZE,
MHI_EOT);
if (ret)
goto out;
read_buf_req = dump_info->read_buf_req;
read_buf_req->hdr.cmd = cpu_to_le32(MEMORY_READ);
read_buf_req->hdr.len = cpu_to_le32(sizeof(*read_buf_req));
read_buf_req->hdr.dbc_id = cpu_to_le32(qdev->ssr_dbc);
read_buf_req->addr = cpu_to_le64(dest_addr);
read_buf_req->len = cpu_to_le64(dest_len);
ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, read_buf_req, sizeof(*read_buf_req),
MHI_EOT);
if (!ret)
dump_info->read_buf_req_queued = true;
out:
return ret;
}
static int ssr_copy_table(struct ssr_dump_info *dump_info, void *data, u64 len)
{
if (len > dump_info->tbl_len - dump_info->tbl_off)
return -EINVAL;
memcpy(dump_info->tbl_addr + dump_info->tbl_off, data, len);
dump_info->tbl_off += len;
/* Entire table has been downloaded, alloc dump memory */
if (dump_info->tbl_off == dump_info->tbl_len) {
dump_info->tbl_ent = dump_info->tbl_addr;
return alloc_dump(dump_info);
}
return 0;
}
static int ssr_copy_dump(struct ssr_dump_info *dump_info, void *data, u64 len)
{
struct debug_info_table *tbl_ent;
tbl_ent = dump_info->tbl_ent;
if (len > tbl_ent->len - dump_info->tbl_ent_off)
return -EINVAL;
memcpy(dump_info->dump_addr + dump_info->dump_off, data, len);
dump_info->dump_off += len;
dump_info->tbl_ent_off += len;
/*
* Current segment (a entry in table) of the crashdump is complete,
* move to next one
*/
if (tbl_ent->len == dump_info->tbl_ent_off) {
dump_info->tbl_ent++;
dump_info->tbl_ent_off = 0;
}
return 0;
}
static void ssr_dump_worker(struct work_struct *work)
{
struct ssr_crashdump *ssr_crash = container_of(work, struct ssr_crashdump, work);
struct qaic_device *qdev = ssr_crash->qdev;
struct ssr_memory_read_rsp *mem_rd_resp;
struct debug_info_table *tbl_ent;
struct ssr_dump_info *dump_info;
u64 dest_addr, dest_len;
struct _ssr_hdr *_hdr;
struct ssr_hdr hdr;
u64 data_len;
int ret;
mem_rd_resp = (struct ssr_memory_read_rsp *)ssr_crash->data;
_hdr = &mem_rd_resp->hdr;
hdr.cmd = le32_to_cpu(_hdr->cmd);
hdr.len = le32_to_cpu(_hdr->len);
hdr.dbc_id = le32_to_cpu(_hdr->dbc_id);
if (hdr.dbc_id != qdev->ssr_dbc)
goto reset_device;
dump_info = ssr_crash->dump_info;
if (!dump_info)
goto reset_device;
if (hdr.cmd != MEMORY_READ_RSP)
goto free_dump_info;
if (hdr.len > SSR_MEM_READ_DATA_SIZE)
goto free_dump_info;
data_len = hdr.len - sizeof(*mem_rd_resp);
if (dump_info->tbl_off < dump_info->tbl_len) /* Chunk belongs to table */
ret = ssr_copy_table(dump_info, mem_rd_resp->data, data_len);
else /* Chunk belongs to crashdump */
ret = ssr_copy_dump(dump_info, mem_rd_resp->data, data_len);
if (ret)
goto free_dump_info;
if (dump_info->tbl_off < dump_info->tbl_len) {
/* Continue downloading table */
dest_addr = dump_info->tbl_addr_dev + dump_info->tbl_off;
dest_len = min(SSR_MEM_READ_CHUNK_SIZE, dump_info->tbl_len - dump_info->tbl_off);
ret = mem_read_req(qdev, dest_addr, dest_len);
} else if (dump_info->dump_off < dump_info->dump_sz) {
/* Continue downloading crashdump */
tbl_ent = dump_info->tbl_ent;
dest_addr = tbl_ent->mem_base + dump_info->tbl_ent_off;
dest_len = min(SSR_MEM_READ_CHUNK_SIZE, tbl_ent->len - dump_info->tbl_ent_off);
ret = mem_read_req(qdev, dest_addr, dest_len);
} else {
/* Crashdump download complete */
ret = send_xfer_done(qdev, dump_info->resp->data, hdr.dbc_id);
}
/* Most likely a MHI xfer has failed */
if (ret)
goto free_dump_info;
return;
free_dump_info:
/* Free the allocated memory */
free_ssr_dump_info(ssr_crash);
reset_device:
/*
* After subsystem crashes in device crashdump collection begins but
* something went wrong while collecting crashdump, now instead of
* handling this error we just reset the device as the best effort has
* been made
*/
mhi_soc_reset(qdev->mhi_cntrl);
}
static struct ssr_dump_info *alloc_dump_info(struct qaic_device *qdev,
struct ssr_debug_transfer_info *debug_info)
{
struct ssr_dump_info *dump_info;
int ret;
le64_to_cpus(&debug_info->tbl_len);
le64_to_cpus(&debug_info->tbl_addr);
if (debug_info->tbl_len == 0 ||
debug_info->tbl_len % sizeof(struct debug_info_table) != 0) {
ret = -EINVAL;
goto out;
}
/* Allocate SSR crashdump book keeping structure */
dump_info = kzalloc_obj(*dump_info);
if (!dump_info) {
ret = -ENOMEM;
goto out;
}
/* Buffer used to send MEMORY READ request to device via MHI */
dump_info->read_buf_req = kzalloc_obj(*dump_info->read_buf_req);
if (!dump_info->read_buf_req) {
ret = -ENOMEM;
goto free_dump_info;
}
/* Crashdump meta table buffer */
dump_info->tbl_addr = vzalloc(debug_info->tbl_len);
if (!dump_info->tbl_addr) {
ret = -ENOMEM;
goto free_read_buf_req;
}
dump_info->tbl_addr_dev = debug_info->tbl_addr;
dump_info->tbl_len = debug_info->tbl_len;
return dump_info;
free_read_buf_req:
kfree(dump_info->read_buf_req);
free_dump_info:
kfree(dump_info);
out:
return ERR_PTR(ret);
}
static int dbg_xfer_info_rsp(struct qaic_device *qdev, struct dma_bridge_chan *dbc,
struct ssr_debug_transfer_info *debug_info)
{
struct ssr_debug_transfer_info_rsp *debug_rsp;
struct ssr_crashdump *ssr_crash = NULL;
int ret = 0, ret2;
debug_rsp = kmalloc_obj(*debug_rsp);
if (!debug_rsp)
return -ENOMEM;
if (!qdev->ssr_mhi_buf) {
ret = -ENOMEM;
goto send_rsp;
}
if (dbc->state != DBC_STATE_BEFORE_POWER_UP) {
ret = -EINVAL;
goto send_rsp;
}
ssr_crash = qdev->ssr_mhi_buf;
ssr_crash->dump_info = alloc_dump_info(qdev, debug_info);
if (IS_ERR(ssr_crash->dump_info)) {
ret = PTR_ERR(ssr_crash->dump_info);
ssr_crash->dump_info = NULL;
}
send_rsp:
debug_rsp->hdr.cmd = cpu_to_le32(DEBUG_TRANSFER_INFO_RSP);
debug_rsp->hdr.len = cpu_to_le32(sizeof(*debug_rsp));
debug_rsp->hdr.dbc_id = cpu_to_le32(dbc->id);
/*
* 0 = Return an ACK confirming the host is ready to download crashdump
* 1 = Return an NACK confirming the host is not ready to download crashdump
*/
debug_rsp->ret = cpu_to_le32(ret ? 1 : 0);
ret2 = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, debug_rsp, sizeof(*debug_rsp), MHI_EOT);
if (ret2) {
free_ssr_dump_info(ssr_crash);
kfree(debug_rsp);
return ret2;
}
return ret;
}
static void dbg_xfer_done_rsp(struct qaic_device *qdev, struct dma_bridge_chan *dbc,
struct ssr_debug_transfer_done_rsp *xfer_rsp)
{
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
u32 status = le32_to_cpu(xfer_rsp->ret);
struct device *dev = &qdev->pdev->dev;
struct ssr_dump_info *dump_info;
dump_info = ssr_crash->dump_info;
if (!dump_info)
return;
if (status) {
free_ssr_dump_info(ssr_crash);
return;
}
dev_coredumpv(dev, dump_info->dump_addr, dump_info->dump_sz, GFP_KERNEL);
/* dev_coredumpv will free dump_info->dump_addr */
dump_info->dump_addr = NULL;
free_ssr_dump_info(ssr_crash);
}
static void ssr_worker(struct work_struct *work)
{
struct ssr_resp *resp = container_of(work, struct ssr_resp, work);
struct ssr_hdr *hdr = (struct ssr_hdr *)resp->data;
struct ssr_dump_info *dump_info = NULL;
struct qaic_device *qdev = resp->qdev;
struct ssr_crashdump *ssr_crash;
struct ssr_event_rsp *event_rsp;
struct dma_bridge_chan *dbc;
struct ssr_event *event;
u32 ssr_event_ack;
int ret;
le32_to_cpus(&hdr->cmd);
le32_to_cpus(&hdr->len);
le32_to_cpus(&hdr->dbc_id);
if (hdr->len > SSR_RESP_MSG_SZ)
goto out;
if (hdr->dbc_id >= qdev->num_dbc)
goto out;
dbc = &qdev->dbc[hdr->dbc_id];
switch (hdr->cmd) {
case DEBUG_TRANSFER_INFO:
ret = dbg_xfer_info_rsp(qdev, dbc, (struct ssr_debug_transfer_info *)resp->data);
if (ret)
break;
ssr_crash = qdev->ssr_mhi_buf;
dump_info = ssr_crash->dump_info;
dump_info->dbc = dbc;
dump_info->resp = resp;
/* Start by downloading debug table */
ret = mem_read_req(qdev, dump_info->tbl_addr_dev,
min(dump_info->tbl_len, SSR_MEM_READ_CHUNK_SIZE));
if (ret) {
free_ssr_dump_info(ssr_crash);
break;
}
/*
* Till now everything went fine, which means that we will be
* collecting crashdump chunk by chunk. Do not queue a response
* buffer for SSR cmds till the crashdump is complete.
*/
return;
case SSR_EVENT:
event = (struct ssr_event *)hdr;
le32_to_cpus(&event->event);
ssr_event_ack = event->event;
ssr_crash = qdev->ssr_mhi_buf;
switch (event->event) {
case BEFORE_SHUTDOWN:
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_SHUTDOWN);
qaic_dbc_enter_ssr(qdev, hdr->dbc_id);
break;
case AFTER_SHUTDOWN:
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_SHUTDOWN);
break;
case BEFORE_POWER_UP:
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_BEFORE_POWER_UP);
break;
case AFTER_POWER_UP:
/*
* If dump info is a non NULL value it means that we
* have received this SSR event while downloading a
* crashdump for this DBC is still in progress. NACK
* the SSR event
*/
if (ssr_crash && ssr_crash->dump_info) {
free_ssr_dump_info(ssr_crash);
ssr_event_ack = SSR_EVENT_NACK;
break;
}
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_AFTER_POWER_UP);
break;
default:
break;
}
event_rsp = kmalloc_obj(*event_rsp);
if (!event_rsp)
break;
event_rsp->hdr.cmd = cpu_to_le32(SSR_EVENT_RSP);
event_rsp->hdr.len = cpu_to_le32(sizeof(*event_rsp));
event_rsp->hdr.dbc_id = cpu_to_le32(hdr->dbc_id);
event_rsp->event = cpu_to_le32(ssr_event_ack);
ret = mhi_queue_buf(qdev->ssr_ch, DMA_TO_DEVICE, event_rsp, sizeof(*event_rsp),
MHI_EOT);
if (ret)
kfree(event_rsp);
if (event->event == AFTER_POWER_UP && ssr_event_ack != SSR_EVENT_NACK) {
qaic_dbc_exit_ssr(qdev);
set_dbc_state(qdev, hdr->dbc_id, DBC_STATE_IDLE);
}
break;
case DEBUG_TRANSFER_DONE_RSP:
dbg_xfer_done_rsp(qdev, dbc, (struct ssr_debug_transfer_done_rsp *)hdr);
break;
default:
break;
}
out:
ret = mhi_queue_buf(qdev->ssr_ch, DMA_FROM_DEVICE, resp->data, SSR_RESP_MSG_SZ, MHI_EOT);
if (ret)
kfree(resp);
}
static int qaic_ssr_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
struct ssr_resp *resp;
int ret;
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
return ret;
resp = kzalloc(sizeof(*resp) + SSR_RESP_MSG_SZ, GFP_KERNEL);
if (!resp) {
mhi_unprepare_from_transfer(mhi_dev);
return -ENOMEM;
}
resp->qdev = qdev;
INIT_WORK(&resp->work, ssr_worker);
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, resp->data, SSR_RESP_MSG_SZ, MHI_EOT);
if (ret) {
kfree(resp);
mhi_unprepare_from_transfer(mhi_dev);
return ret;
}
dev_set_drvdata(&mhi_dev->dev, qdev);
qdev->ssr_ch = mhi_dev;
return 0;
}
static void qaic_ssr_mhi_remove(struct mhi_device *mhi_dev)
{
struct qaic_device *qdev;
qdev = dev_get_drvdata(&mhi_dev->dev);
mhi_unprepare_from_transfer(qdev->ssr_ch);
qdev->ssr_ch = NULL;
}
static void qaic_ssr_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev);
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
struct _ssr_hdr *hdr = mhi_result->buf_addr;
struct ssr_dump_info *dump_info;
if (mhi_result->transaction_status) {
kfree(mhi_result->buf_addr);
return;
}
/*
* MEMORY READ is used to download crashdump. And crashdump is
* downloaded chunk by chunk in a series of MEMORY READ SSR commands.
* Hence to avoid too many kmalloc() and kfree() of the same MEMORY READ
* request buffer, we allocate only one such buffer and free it only
* once.
*/
if (le32_to_cpu(hdr->cmd) == MEMORY_READ) {
dump_info = ssr_crash->dump_info;
if (dump_info) {
dump_info->read_buf_req_queued = false;
return;
}
}
kfree(mhi_result->buf_addr);
}
static void qaic_ssr_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct ssr_resp *resp = container_of(mhi_result->buf_addr, struct ssr_resp, data);
struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev);
struct ssr_crashdump *ssr_crash = qdev->ssr_mhi_buf;
bool memory_read_rsp = false;
if (ssr_crash && ssr_crash->data == mhi_result->buf_addr)
memory_read_rsp = true;
if (mhi_result->transaction_status) {
/* Do not free SSR crashdump buffer as it allocated via managed APIs */
if (!memory_read_rsp)
kfree(resp);
return;
}
if (memory_read_rsp)
queue_work(qdev->ssr_wq, &ssr_crash->work);
else
queue_work(qdev->ssr_wq, &resp->work);
}
static const struct mhi_device_id qaic_ssr_mhi_match_table[] = {
{ .chan = "QAIC_SSR", },
{},
};
static struct mhi_driver qaic_ssr_mhi_driver = {
.id_table = qaic_ssr_mhi_match_table,
.remove = qaic_ssr_mhi_remove,
.probe = qaic_ssr_mhi_probe,
.ul_xfer_cb = qaic_ssr_mhi_ul_xfer_cb,
.dl_xfer_cb = qaic_ssr_mhi_dl_xfer_cb,
.driver = {
.name = "qaic_ssr",
},
};
int qaic_ssr_init(struct qaic_device *qdev, struct drm_device *drm)
{
struct ssr_crashdump *ssr_crash;
qdev->ssr_dbc = QAIC_SSR_DBC_SENTINEL;
/*
* Device requests only one SSR at a time. So allocating only one
* buffer to download crashdump is good enough.
*/
ssr_crash = drmm_kzalloc(drm, SSR_MHI_BUF_SIZE, GFP_KERNEL);
if (!ssr_crash)
return -ENOMEM;
ssr_crash->qdev = qdev;
INIT_WORK(&ssr_crash->work, ssr_dump_worker);
qdev->ssr_mhi_buf = ssr_crash;
return 0;
}
int qaic_ssr_register(void)
{
return mhi_driver_register(&qaic_ssr_mhi_driver);
}
void qaic_ssr_unregister(void)
{
mhi_driver_unregister(&qaic_ssr_mhi_driver);
}
+16
View File
@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#ifndef __QAIC_SSR_H__
#define __QAIC_SSR_H__
struct drm_device;
struct qaic_device;
int qaic_ssr_register(void);
void qaic_ssr_unregister(void);
void qaic_clean_up_ssr(struct qaic_device *qdev);
int qaic_ssr_init(struct qaic_device *qdev, struct drm_device *drm);
#endif /* __QAIC_SSR_H__ */
+109
View File
@@ -0,0 +1,109 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2020-2025, The Linux Foundation. All rights reserved. */
#include <drm/drm_file.h>
#include <drm/drm_managed.h>
#include <linux/device.h>
#include <linux/kernel.h>
#include <linux/kobject.h>
#include <linux/mutex.h>
#include <linux/sysfs.h>
#include "qaic.h"
#define NAME_LEN 14
struct dbc_attribute {
struct device_attribute dev_attr;
u32 dbc_id;
char name[NAME_LEN];
};
static ssize_t dbc_state_show(struct device *dev, struct device_attribute *a, char *buf)
{
struct dbc_attribute *dbc_attr = container_of(a, struct dbc_attribute, dev_attr);
struct drm_minor *minor = dev_get_drvdata(dev);
struct qaic_device *qdev;
qdev = to_qaic_device(minor->dev);
return sysfs_emit(buf, "%d\n", qdev->dbc[dbc_attr->dbc_id].state);
}
void set_dbc_state(struct qaic_device *qdev, u32 dbc_id, unsigned int state)
{
struct device *kdev = to_accel_kdev(qdev->qddev);
char *envp[3] = {};
char state_str[16];
char id_str[12];
envp[0] = id_str;
envp[1] = state_str;
if (state >= DBC_STATE_MAX)
return;
if (dbc_id >= qdev->num_dbc)
return;
if (state == qdev->dbc[dbc_id].state)
return;
scnprintf(id_str, ARRAY_SIZE(id_str), "DBC_ID=%d", dbc_id);
scnprintf(state_str, ARRAY_SIZE(state_str), "DBC_STATE=%d", state);
qdev->dbc[dbc_id].state = state;
kobject_uevent_env(&kdev->kobj, KOBJ_CHANGE, envp);
}
int qaic_sysfs_init(struct qaic_drm_device *qddev)
{
struct device *kdev = to_accel_kdev(qddev);
struct drm_device *drm = to_drm(qddev);
u32 num_dbc = qddev->qdev->num_dbc;
struct dbc_attribute *dbc_attrs;
int i, ret;
dbc_attrs = drmm_kcalloc(drm, num_dbc, sizeof(*dbc_attrs), GFP_KERNEL);
if (!dbc_attrs)
return -ENOMEM;
for (i = 0; i < num_dbc; ++i) {
struct dbc_attribute *dbc_attr = &dbc_attrs[i];
sysfs_attr_init(&dbc_attr->dev_attr.attr);
dbc_attr->dbc_id = i;
scnprintf(dbc_attr->name, NAME_LEN, "dbc%d_state", i);
dbc_attr->dev_attr.attr.name = dbc_attr->name;
dbc_attr->dev_attr.attr.mode = 0444;
dbc_attr->dev_attr.show = dbc_state_show;
ret = sysfs_create_file(&kdev->kobj, &dbc_attr->dev_attr.attr);
if (ret) {
int j;
for (j = 0; j < i; ++j) {
dbc_attr = &dbc_attrs[j];
sysfs_remove_file(&kdev->kobj, &dbc_attr->dev_attr.attr);
}
drmm_kfree(drm, dbc_attrs);
return ret;
}
}
qddev->sysfs_attrs = dbc_attrs;
return 0;
}
void qaic_sysfs_remove(struct qaic_drm_device *qddev)
{
struct dbc_attribute *dbc_attrs = qddev->sysfs_attrs;
struct device *kdev = to_accel_kdev(qddev);
u32 num_dbc = qddev->qdev->num_dbc;
int i;
if (!dbc_attrs)
return;
qddev->sysfs_attrs = NULL;
for (i = 0; i < num_dbc; ++i)
sysfs_remove_file(&kdev->kobj, &dbc_attrs[i].dev_attr.attr);
drmm_kfree(to_drm(qddev), dbc_attrs);
}
@@ -0,0 +1,404 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/math64.h>
#include <linux/mhi.h>
#include <linux/module.h>
#include <linux/time64.h>
#include <linux/timer.h>
#include "qaic.h"
#include "qaic_timesync.h"
#define QTIMER_REG_OFFSET 0xa28
#define QAIC_TIMESYNC_SIGNATURE 0x55aa
#define QAIC_CONV_QTIMER_TO_US(qtimer) (mul_u64_u32_div(qtimer, 10, 192))
static unsigned int timesync_delay_ms = 1000; /* 1 sec default */
module_param(timesync_delay_ms, uint, 0600);
MODULE_PARM_DESC(timesync_delay_ms, "Delay in ms between two consecutive timesync operations");
enum qts_msg_type {
QAIC_TS_CMD_TO_HOST,
QAIC_TS_SYNC_REQ,
QAIC_TS_ACK_TO_HOST,
QAIC_TS_MSG_TYPE_MAX
};
/**
* struct qts_hdr - Timesync message header structure.
* @signature: Unique signature to identify the timesync message.
* @reserved_1: Reserved for future use.
* @reserved_2: Reserved for future use.
* @msg_type: sub-type of the timesync message.
* @reserved_3: Reserved for future use.
*/
struct qts_hdr {
__le16 signature;
__le16 reserved_1;
u8 reserved_2;
u8 msg_type;
__le16 reserved_3;
} __packed;
/**
* struct qts_timeval - Structure to carry time information.
* @tv_sec: Seconds part of the time.
* @tv_usec: uS (microseconds) part of the time.
*/
struct qts_timeval {
__le64 tv_sec;
__le64 tv_usec;
} __packed;
/**
* struct qts_host_time_sync_msg_data - Structure to denote the timesync message.
* @header: Header of the timesync message.
* @data: Time information.
*/
struct qts_host_time_sync_msg_data {
struct qts_hdr header;
struct qts_timeval data;
} __packed;
/**
* struct mqts_dev - MHI QAIC Timesync Control device.
* @qdev: Pointer to the root device struct driven by QAIC driver.
* @mhi_dev: Pointer to associated MHI device.
* @timer: Timer handle used for timesync.
* @qtimer_addr: Device QTimer register pointer.
* @buff_in_use: atomic variable to track if the sync_msg buffer is in use.
* @dev: Device pointer to qdev->pdev->dev stored for easy access.
* @sync_msg: Buffer used to send timesync message over MHI.
*/
struct mqts_dev {
struct qaic_device *qdev;
struct mhi_device *mhi_dev;
struct timer_list timer;
void __iomem *qtimer_addr;
atomic_t buff_in_use;
struct device *dev;
struct qts_host_time_sync_msg_data *sync_msg;
};
struct qts_resp_msg {
struct qts_hdr hdr;
} __packed;
struct qts_resp {
struct qts_resp_msg data;
struct work_struct work;
struct qaic_device *qdev;
};
#ifdef readq
static u64 read_qtimer(const volatile void __iomem *addr)
{
return readq(addr);
}
#else
static u64 read_qtimer(const volatile void __iomem *addr)
{
u64 low, high;
low = readl(addr);
high = readl(addr + sizeof(u32));
return low | (high << 32);
}
#endif
static void qaic_timesync_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
dev_dbg(mqtsdev->dev, "%s status: %d xfer_len: %zu\n", __func__,
mhi_result->transaction_status, mhi_result->bytes_xferd);
atomic_set(&mqtsdev->buff_in_use, 0);
}
static void qaic_timesync_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
dev_err(mqtsdev->dev, "%s no data expected on dl channel\n", __func__);
}
static void qaic_timesync_timer(struct timer_list *t)
{
struct mqts_dev *mqtsdev = timer_container_of(mqtsdev, t, timer);
struct qts_host_time_sync_msg_data *sync_msg;
u64 device_qtimer_us;
u64 device_qtimer;
u64 host_time_us;
u64 offset_us;
u64 host_sec;
int ret;
if (atomic_read(&mqtsdev->buff_in_use)) {
dev_dbg(mqtsdev->dev, "%s buffer not free, schedule next cycle\n", __func__);
goto mod_timer;
}
atomic_set(&mqtsdev->buff_in_use, 1);
sync_msg = mqtsdev->sync_msg;
sync_msg->header.signature = cpu_to_le16(QAIC_TIMESYNC_SIGNATURE);
sync_msg->header.msg_type = QAIC_TS_SYNC_REQ;
/* Read host UTC time and convert to uS*/
host_time_us = div_u64(ktime_get_real_ns(), NSEC_PER_USEC);
device_qtimer = read_qtimer(mqtsdev->qtimer_addr);
device_qtimer_us = QAIC_CONV_QTIMER_TO_US(device_qtimer);
/* Offset between host UTC and device time */
offset_us = host_time_us - device_qtimer_us;
host_sec = div_u64(offset_us, USEC_PER_SEC);
sync_msg->data.tv_usec = cpu_to_le64(offset_us - host_sec * USEC_PER_SEC);
sync_msg->data.tv_sec = cpu_to_le64(host_sec);
ret = mhi_queue_buf(mqtsdev->mhi_dev, DMA_TO_DEVICE, sync_msg, sizeof(*sync_msg), MHI_EOT);
if (ret && (ret != -EAGAIN)) {
dev_err(mqtsdev->dev, "%s unable to queue to mhi:%d\n", __func__, ret);
return;
} else if (ret == -EAGAIN) {
atomic_set(&mqtsdev->buff_in_use, 0);
}
mod_timer:
ret = mod_timer(t, jiffies + msecs_to_jiffies(timesync_delay_ms));
if (ret)
dev_err(mqtsdev->dev, "%s mod_timer error:%d\n", __func__, ret);
}
void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
timer_delete_sync(&mqtsdev->timer);
}
static int qaic_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
struct mqts_dev *mqtsdev;
struct timer_list *timer;
int ret;
mqtsdev = kzalloc_obj(*mqtsdev);
if (!mqtsdev) {
ret = -ENOMEM;
goto out;
}
timer = &mqtsdev->timer;
mqtsdev->mhi_dev = mhi_dev;
mqtsdev->qdev = qdev;
mqtsdev->dev = &qdev->pdev->dev;
mqtsdev->sync_msg = kzalloc_obj(*mqtsdev->sync_msg);
if (!mqtsdev->sync_msg) {
ret = -ENOMEM;
goto free_mqts_dev;
}
atomic_set(&mqtsdev->buff_in_use, 0);
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
goto free_sync_msg;
/* Qtimer register pointer */
mqtsdev->qtimer_addr = qdev->bar_mhi + QTIMER_REG_OFFSET;
timer_setup(timer, qaic_timesync_timer, 0);
timer->expires = jiffies + msecs_to_jiffies(timesync_delay_ms);
add_timer(timer);
dev_set_drvdata(&mhi_dev->dev, mqtsdev);
qdev->mqts_ch = mhi_dev;
return 0;
free_sync_msg:
kfree(mqtsdev->sync_msg);
free_mqts_dev:
kfree(mqtsdev);
out:
return ret;
};
static void qaic_timesync_remove(struct mhi_device *mhi_dev)
{
struct mqts_dev *mqtsdev = dev_get_drvdata(&mhi_dev->dev);
mqtsdev->qdev->mqts_ch = NULL;
timer_delete_sync(&mqtsdev->timer);
mhi_unprepare_from_transfer(mqtsdev->mhi_dev);
kfree(mqtsdev->sync_msg);
kfree(mqtsdev);
}
static const struct mhi_device_id qaic_timesync_match_table[] = {
{ .chan = "QAIC_TIMESYNC_PERIODIC"},
{},
};
MODULE_DEVICE_TABLE(mhi, qaic_timesync_match_table);
static struct mhi_driver qaic_timesync_driver = {
.id_table = qaic_timesync_match_table,
.remove = qaic_timesync_remove,
.probe = qaic_timesync_probe,
.ul_xfer_cb = qaic_timesync_ul_xfer_cb,
.dl_xfer_cb = qaic_timesync_dl_xfer_cb,
.driver = {
.name = "qaic_timesync_periodic",
},
};
static void qaic_boot_timesync_worker(struct work_struct *work)
{
struct qts_resp *resp = container_of(work, struct qts_resp, work);
struct qts_host_time_sync_msg_data *req;
struct qts_resp_msg data = resp->data;
struct qaic_device *qdev = resp->qdev;
struct mhi_device *mhi_dev;
struct timespec64 ts;
int ret;
mhi_dev = qdev->qts_ch;
/* Queue the response message beforehand to avoid race conditions */
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, &resp->data, sizeof(resp->data), MHI_EOT);
if (ret) {
kfree(resp);
dev_warn(&mhi_dev->dev, "Failed to re-queue response buffer %d\n", ret);
return;
}
switch (data.hdr.msg_type) {
case QAIC_TS_CMD_TO_HOST:
req = kzalloc_obj(*req);
if (!req)
break;
req->header = data.hdr;
req->header.msg_type = QAIC_TS_SYNC_REQ;
ktime_get_real_ts64(&ts);
req->data.tv_sec = cpu_to_le64(ts.tv_sec);
req->data.tv_usec = cpu_to_le64(div_u64(ts.tv_nsec, NSEC_PER_USEC));
ret = mhi_queue_buf(mhi_dev, DMA_TO_DEVICE, req, sizeof(*req), MHI_EOT);
if (ret) {
kfree(req);
dev_dbg(&mhi_dev->dev, "Failed to send request message. Error %d\n", ret);
}
break;
case QAIC_TS_ACK_TO_HOST:
dev_dbg(&mhi_dev->dev, "ACK received from device\n");
break;
default:
dev_err(&mhi_dev->dev, "Invalid message type %u.\n", data.hdr.msg_type);
}
}
static int qaic_boot_timesync_queue_resp(struct mhi_device *mhi_dev, struct qaic_device *qdev)
{
struct qts_resp *resp;
int ret;
resp = kzalloc_obj(*resp);
if (!resp)
return -ENOMEM;
resp->qdev = qdev;
INIT_WORK(&resp->work, qaic_boot_timesync_worker);
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, &resp->data, sizeof(resp->data), MHI_EOT);
if (ret) {
kfree(resp);
dev_warn(&mhi_dev->dev, "Failed to queue response buffer %d\n", ret);
return ret;
}
return 0;
}
static void qaic_boot_timesync_remove(struct mhi_device *mhi_dev)
{
struct qaic_device *qdev;
qdev = dev_get_drvdata(&mhi_dev->dev);
mhi_unprepare_from_transfer(qdev->qts_ch);
qdev->qts_ch = NULL;
}
static int qaic_boot_timesync_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct qaic_device *qdev = pci_get_drvdata(to_pci_dev(mhi_dev->mhi_cntrl->cntrl_dev));
int ret;
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
return ret;
qdev->qts_ch = mhi_dev;
dev_set_drvdata(&mhi_dev->dev, qdev);
ret = qaic_boot_timesync_queue_resp(mhi_dev, qdev);
if (ret) {
dev_set_drvdata(&mhi_dev->dev, NULL);
qdev->qts_ch = NULL;
mhi_unprepare_from_transfer(mhi_dev);
}
return ret;
}
static void qaic_boot_timesync_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
kfree(mhi_result->buf_addr);
}
static void qaic_boot_timesync_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct qts_resp *resp = container_of(mhi_result->buf_addr, struct qts_resp, data);
if (mhi_result->transaction_status || mhi_result->bytes_xferd != sizeof(resp->data)) {
kfree(resp);
return;
}
queue_work(resp->qdev->qts_wq, &resp->work);
}
static const struct mhi_device_id qaic_boot_timesync_match_table[] = {
{ .chan = "QAIC_TIMESYNC"},
{},
};
static struct mhi_driver qaic_boot_timesync_driver = {
.id_table = qaic_boot_timesync_match_table,
.remove = qaic_boot_timesync_remove,
.probe = qaic_boot_timesync_probe,
.ul_xfer_cb = qaic_boot_timesync_ul_xfer_cb,
.dl_xfer_cb = qaic_boot_timesync_dl_xfer_cb,
.driver = {
.name = "qaic_timesync",
},
};
int qaic_timesync_init(void)
{
int ret;
ret = mhi_driver_register(&qaic_timesync_driver);
if (ret)
return ret;
ret = mhi_driver_register(&qaic_boot_timesync_driver);
return ret;
}
void qaic_timesync_deinit(void)
{
mhi_driver_unregister(&qaic_boot_timesync_driver);
mhi_driver_unregister(&qaic_timesync_driver);
}
@@ -0,0 +1,13 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#ifndef __QAIC_TIMESYNC_H__
#define __QAIC_TIMESYNC_H__
#include <linux/mhi.h>
int qaic_timesync_init(void);
void qaic_timesync_deinit(void);
void qaic_mqts_ch_stop_timer(struct mhi_device *mhi_dev);
#endif /* __QAIC_TIMESYNC_H__ */
+934
View File
@@ -0,0 +1,934 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#include <linux/devcoredump.h>
#include <linux/firmware.h>
#include <linux/limits.h>
#include <linux/mhi.h>
#include <linux/minmax.h>
#include <linux/overflow.h>
#include <linux/types.h>
#include <linux/vmalloc.h>
#include <linux/workqueue.h>
#include "sahara.h"
#define SAHARA_HELLO_CMD 0x1 /* Min protocol version 1.0 */
#define SAHARA_HELLO_RESP_CMD 0x2 /* Min protocol version 1.0 */
#define SAHARA_READ_DATA_CMD 0x3 /* Min protocol version 1.0 */
#define SAHARA_END_OF_IMAGE_CMD 0x4 /* Min protocol version 1.0 */
#define SAHARA_DONE_CMD 0x5 /* Min protocol version 1.0 */
#define SAHARA_DONE_RESP_CMD 0x6 /* Min protocol version 1.0 */
#define SAHARA_RESET_CMD 0x7 /* Min protocol version 1.0 */
#define SAHARA_RESET_RESP_CMD 0x8 /* Min protocol version 1.0 */
#define SAHARA_MEM_DEBUG_CMD 0x9 /* Min protocol version 2.0 */
#define SAHARA_MEM_READ_CMD 0xa /* Min protocol version 2.0 */
#define SAHARA_CMD_READY_CMD 0xb /* Min protocol version 2.1 */
#define SAHARA_SWITCH_MODE_CMD 0xc /* Min protocol version 2.1 */
#define SAHARA_EXECUTE_CMD 0xd /* Min protocol version 2.1 */
#define SAHARA_EXECUTE_RESP_CMD 0xe /* Min protocol version 2.1 */
#define SAHARA_EXECUTE_DATA_CMD 0xf /* Min protocol version 2.1 */
#define SAHARA_MEM_DEBUG64_CMD 0x10 /* Min protocol version 2.5 */
#define SAHARA_MEM_READ64_CMD 0x11 /* Min protocol version 2.5 */
#define SAHARA_READ_DATA64_CMD 0x12 /* Min protocol version 2.8 */
#define SAHARA_RESET_STATE_CMD 0x13 /* Min protocol version 2.9 */
#define SAHARA_WRITE_DATA_CMD 0x14 /* Min protocol version 3.0 */
#define SAHARA_PACKET_MAX_SIZE 0xffffU /* MHI_MAX_MTU */
#define SAHARA_TRANSFER_MAX_SIZE 0x80000
#define SAHARA_READ_MAX_SIZE 0xfff0U /* Avoid unaligned requests */
#define SAHARA_NUM_TX_BUF DIV_ROUND_UP(SAHARA_TRANSFER_MAX_SIZE,\
SAHARA_PACKET_MAX_SIZE)
#define SAHARA_IMAGE_ID_NONE U32_MAX
#define SAHARA_VERSION 2
#define SAHARA_SUCCESS 0
#define SAHARA_TABLE_ENTRY_STR_LEN 20
#define SAHARA_MODE_IMAGE_TX_PENDING 0x0
#define SAHARA_MODE_IMAGE_TX_COMPLETE 0x1
#define SAHARA_MODE_MEMORY_DEBUG 0x2
#define SAHARA_MODE_COMMAND 0x3
#define SAHARA_HELLO_LENGTH 0x30
#define SAHARA_READ_DATA_LENGTH 0x14
#define SAHARA_END_OF_IMAGE_LENGTH 0x10
#define SAHARA_DONE_LENGTH 0x8
#define SAHARA_RESET_LENGTH 0x8
#define SAHARA_MEM_DEBUG64_LENGTH 0x18
#define SAHARA_MEM_READ64_LENGTH 0x18
struct sahara_packet {
__le32 cmd;
__le32 length;
union {
struct {
__le32 version;
__le32 version_compat;
__le32 max_length;
__le32 mode;
} hello;
struct {
__le32 version;
__le32 version_compat;
__le32 status;
__le32 mode;
} hello_resp;
struct {
__le32 image;
__le32 offset;
__le32 length;
} read_data;
struct {
__le32 image;
__le32 status;
} end_of_image;
struct {
__le64 table_address;
__le64 table_length;
} memory_debug64;
struct {
__le64 memory_address;
__le64 memory_length;
} memory_read64;
};
};
struct sahara_debug_table_entry64 {
__le64 type;
__le64 address;
__le64 length;
char description[SAHARA_TABLE_ENTRY_STR_LEN];
char filename[SAHARA_TABLE_ENTRY_STR_LEN];
};
struct sahara_dump_table_entry {
u64 type;
u64 address;
u64 length;
char description[SAHARA_TABLE_ENTRY_STR_LEN];
char filename[SAHARA_TABLE_ENTRY_STR_LEN];
};
#define SAHARA_DUMP_V1_MAGIC 0x1234567890abcdef
#define SAHARA_DUMP_V1_VER 1
struct sahara_memory_dump_meta_v1 {
u64 magic;
u64 version;
u64 dump_size;
u64 table_size;
};
/*
* Layout of crashdump provided to user via devcoredump
* +------------------------------------------+
* | Crashdump Meta structure |
* | type: struct sahara_memory_dump_meta_v1 |
* +------------------------------------------+
* | Crashdump Table |
* | type: array of struct |
* | sahara_dump_table_entry |
* | |
* | |
* +------------------------------------------+
* | Crashdump |
* | |
* | |
* | |
* | |
* | |
* +------------------------------------------+
*
* First is the metadata header. Userspace can use the magic number to verify
* the content type, and then check the version for the rest of the format.
* New versions should keep the magic number location/value, and version
* location, but increment the version value.
*
* For v1, the metadata lists the size of the entire dump (header + table +
* dump) and the size of the table. Then the dump image table, which describes
* the contents of the dump. Finally all the images are listed in order, with
* no deadspace in between. Userspace can use the sizes listed in the image
* table to reconstruct the individual images.
*/
struct sahara_context {
struct sahara_packet *tx[SAHARA_NUM_TX_BUF];
struct sahara_packet *rx;
struct work_struct fw_work;
struct work_struct dump_work;
struct work_struct read_data_work;
struct mhi_device *mhi_dev;
const char * const *image_table;
u32 table_size;
u32 active_image_id;
const struct firmware *firmware;
u64 dump_table_address;
u64 dump_table_length;
size_t rx_size;
size_t rx_size_requested;
void *mem_dump;
size_t mem_dump_sz;
struct sahara_dump_table_entry *dump_image;
u64 dump_image_offset;
void *mem_dump_freespace;
u64 dump_images_left;
u32 read_data_offset;
u32 read_data_length;
bool is_mem_dump_mode;
bool non_streaming;
};
static const char * const aic100_image_table[] = {
[1] = "qcom/aic100/fw1.bin",
[2] = "qcom/aic100/fw2.bin",
[4] = "qcom/aic100/fw4.bin",
[5] = "qcom/aic100/fw5.bin",
[6] = "qcom/aic100/fw6.bin",
[8] = "qcom/aic100/fw8.bin",
[9] = "qcom/aic100/fw9.bin",
[10] = "qcom/aic100/fw10.bin",
};
static const char * const aic200_image_table[] = {
[5] = "qcom/aic200/uefi.elf",
[12] = "qcom/aic200/aic200-nsp.bin",
[23] = "qcom/aic200/aop.mbn",
[32] = "qcom/aic200/tz.mbn",
[33] = "qcom/aic200/hypvm.mbn",
[38] = "qcom/aic200/xbl_config.elf",
[39] = "qcom/aic200/aic200_abl.elf",
[40] = "qcom/aic200/apdp.mbn",
[41] = "qcom/aic200/devcfg.mbn",
[42] = "qcom/aic200/sec.elf",
[43] = "qcom/aic200/aic200-hlos.elf",
[49] = "qcom/aic200/shrm.elf",
[50] = "qcom/aic200/cpucp.elf",
[51] = "qcom/aic200/aop_devcfg.mbn",
[54] = "qcom/aic200/qupv3fw.elf",
[57] = "qcom/aic200/cpucp_dtbs.elf",
[62] = "qcom/aic200/uefi_dtbs.elf",
[63] = "qcom/aic200/xbl_ac_config.mbn",
[64] = "qcom/aic200/tz_ac_config.mbn",
[65] = "qcom/aic200/hyp_ac_config.mbn",
[66] = "qcom/aic200/pdp.elf",
[67] = "qcom/aic200/pdp_cdb.elf",
[68] = "qcom/aic200/sdi.mbn",
[69] = "qcom/aic200/dcd.mbn",
[73] = "qcom/aic200/gearvm.mbn",
[74] = "qcom/aic200/sti.bin",
[76] = "qcom/aic200/tz_qti_config.mbn",
[78] = "qcom/aic200/pvs.bin",
};
static bool is_streaming(struct sahara_context *context)
{
return !context->non_streaming;
}
static int sahara_find_image(struct sahara_context *context, u32 image_id)
{
int ret;
if (image_id == context->active_image_id)
return 0;
if (context->active_image_id != SAHARA_IMAGE_ID_NONE) {
dev_err(&context->mhi_dev->dev, "image id %d is not valid as %d is active\n",
image_id, context->active_image_id);
return -EINVAL;
}
if (image_id >= context->table_size || !context->image_table[image_id]) {
dev_err(&context->mhi_dev->dev, "request for unknown image: %d\n", image_id);
return -EINVAL;
}
/*
* This image might be optional. The device may continue without it.
* Only the device knows. Suppress error messages that could suggest an
* a problem when we were actually able to continue.
*/
ret = firmware_request_nowarn(&context->firmware,
context->image_table[image_id],
&context->mhi_dev->dev);
if (ret) {
dev_dbg(&context->mhi_dev->dev, "request for image id %d / file %s failed %d\n",
image_id, context->image_table[image_id], ret);
return ret;
}
context->active_image_id = image_id;
return 0;
}
static void sahara_release_image(struct sahara_context *context)
{
if (context->active_image_id != SAHARA_IMAGE_ID_NONE)
release_firmware(context->firmware);
context->active_image_id = SAHARA_IMAGE_ID_NONE;
}
static void sahara_send_reset(struct sahara_context *context)
{
int ret;
context->is_mem_dump_mode = false;
context->read_data_offset = 0;
context->read_data_length = 0;
context->tx[0]->cmd = cpu_to_le32(SAHARA_RESET_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_RESET_LENGTH);
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
SAHARA_RESET_LENGTH, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev, "Unable to send reset response %d\n", ret);
}
static void sahara_hello(struct sahara_context *context)
{
int ret;
dev_dbg(&context->mhi_dev->dev,
"HELLO cmd received. length:%d version:%d version_compat:%d max_length:%d mode:%d\n",
le32_to_cpu(context->rx->length),
le32_to_cpu(context->rx->hello.version),
le32_to_cpu(context->rx->hello.version_compat),
le32_to_cpu(context->rx->hello.max_length),
le32_to_cpu(context->rx->hello.mode));
if (le32_to_cpu(context->rx->length) != SAHARA_HELLO_LENGTH) {
dev_err(&context->mhi_dev->dev, "Malformed hello packet - length %d\n",
le32_to_cpu(context->rx->length));
return;
}
if (le32_to_cpu(context->rx->hello.version) != SAHARA_VERSION) {
dev_err(&context->mhi_dev->dev, "Unsupported hello packet - version %d\n",
le32_to_cpu(context->rx->hello.version));
return;
}
if (le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_IMAGE_TX_PENDING &&
le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_IMAGE_TX_COMPLETE &&
le32_to_cpu(context->rx->hello.mode) != SAHARA_MODE_MEMORY_DEBUG) {
dev_err(&context->mhi_dev->dev, "Unsupported hello packet - mode %d\n",
le32_to_cpu(context->rx->hello.mode));
return;
}
context->tx[0]->cmd = cpu_to_le32(SAHARA_HELLO_RESP_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_HELLO_LENGTH);
context->tx[0]->hello_resp.version = cpu_to_le32(SAHARA_VERSION);
context->tx[0]->hello_resp.version_compat = cpu_to_le32(SAHARA_VERSION);
context->tx[0]->hello_resp.status = cpu_to_le32(SAHARA_SUCCESS);
context->tx[0]->hello_resp.mode = context->rx->hello_resp.mode;
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
SAHARA_HELLO_LENGTH, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev, "Unable to send hello response %d\n", ret);
}
static int read_data_helper(struct sahara_context *context, int buf_index)
{
enum mhi_flags mhi_flag;
u32 pkt_data_len;
int ret;
pkt_data_len = min(context->read_data_length, SAHARA_PACKET_MAX_SIZE);
memcpy(context->tx[buf_index],
&context->firmware->data[context->read_data_offset],
pkt_data_len);
context->read_data_offset += pkt_data_len;
context->read_data_length -= pkt_data_len;
if (is_streaming(context) || !context->read_data_length)
mhi_flag = MHI_EOT;
else
mhi_flag = MHI_CHAIN;
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE,
context->tx[buf_index], pkt_data_len, mhi_flag);
if (ret) {
dev_err(&context->mhi_dev->dev, "Unable to send read_data response %d\n", ret);
return ret;
}
return 0;
}
static void sahara_read_data(struct sahara_context *context)
{
u32 image_id, data_offset, data_len;
int ret;
int i;
dev_dbg(&context->mhi_dev->dev,
"READ_DATA cmd received. length:%d image:%d offset:%d data_length:%d\n",
le32_to_cpu(context->rx->length),
le32_to_cpu(context->rx->read_data.image),
le32_to_cpu(context->rx->read_data.offset),
le32_to_cpu(context->rx->read_data.length));
if (le32_to_cpu(context->rx->length) != SAHARA_READ_DATA_LENGTH) {
dev_err(&context->mhi_dev->dev, "Malformed read_data packet - length %d\n",
le32_to_cpu(context->rx->length));
return;
}
image_id = le32_to_cpu(context->rx->read_data.image);
data_offset = le32_to_cpu(context->rx->read_data.offset);
data_len = le32_to_cpu(context->rx->read_data.length);
ret = sahara_find_image(context, image_id);
if (ret) {
sahara_send_reset(context);
return;
}
/*
* Image is released when the device is done with it via
* SAHARA_END_OF_IMAGE_CMD. sahara_send_reset() will either cause the
* device to retry the operation with a modification, or decide to be
* done with the image and trigger SAHARA_END_OF_IMAGE_CMD.
* release_image() is called from SAHARA_END_OF_IMAGE_CMD. processing
* and is not needed here on error.
*/
if (context->non_streaming && data_len > SAHARA_TRANSFER_MAX_SIZE) {
dev_err(&context->mhi_dev->dev, "Malformed read_data packet - data len %d exceeds max xfer size %d\n",
data_len, SAHARA_TRANSFER_MAX_SIZE);
sahara_send_reset(context);
return;
}
if (data_offset >= context->firmware->size) {
dev_err(&context->mhi_dev->dev, "Malformed read_data packet - data offset %d exceeds file size %zu\n",
data_offset, context->firmware->size);
sahara_send_reset(context);
return;
}
if (size_add(data_offset, data_len) > context->firmware->size) {
dev_err(&context->mhi_dev->dev, "Malformed read_data packet - data offset %d and length %d exceeds file size %zu\n",
data_offset, data_len, context->firmware->size);
sahara_send_reset(context);
return;
}
context->read_data_offset = data_offset;
context->read_data_length = data_len;
if (is_streaming(context)) {
schedule_work(&context->read_data_work);
return;
}
for (i = 0; i < SAHARA_NUM_TX_BUF && context->read_data_length; ++i) {
ret = read_data_helper(context, i);
if (ret)
break;
}
}
static void sahara_end_of_image(struct sahara_context *context)
{
int ret;
dev_dbg(&context->mhi_dev->dev,
"END_OF_IMAGE cmd received. length:%d image:%d status:%d\n",
le32_to_cpu(context->rx->length),
le32_to_cpu(context->rx->end_of_image.image),
le32_to_cpu(context->rx->end_of_image.status));
if (le32_to_cpu(context->rx->length) != SAHARA_END_OF_IMAGE_LENGTH) {
dev_err(&context->mhi_dev->dev, "Malformed end_of_image packet - length %d\n",
le32_to_cpu(context->rx->length));
return;
}
if (context->active_image_id != SAHARA_IMAGE_ID_NONE &&
le32_to_cpu(context->rx->end_of_image.image) != context->active_image_id) {
dev_err(&context->mhi_dev->dev, "Malformed end_of_image packet - image %d is not the active image\n",
le32_to_cpu(context->rx->end_of_image.image));
return;
}
sahara_release_image(context);
if (le32_to_cpu(context->rx->end_of_image.status))
return;
context->tx[0]->cmd = cpu_to_le32(SAHARA_DONE_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_DONE_LENGTH);
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
SAHARA_DONE_LENGTH, MHI_EOT);
if (ret)
dev_dbg(&context->mhi_dev->dev, "Unable to send done response %d\n", ret);
}
static void sahara_memory_debug64(struct sahara_context *context)
{
int ret;
dev_dbg(&context->mhi_dev->dev,
"MEMORY DEBUG64 cmd received. length:%d table_address:%#llx table_length:%#llx\n",
le32_to_cpu(context->rx->length),
le64_to_cpu(context->rx->memory_debug64.table_address),
le64_to_cpu(context->rx->memory_debug64.table_length));
if (le32_to_cpu(context->rx->length) != SAHARA_MEM_DEBUG64_LENGTH) {
dev_err(&context->mhi_dev->dev, "Malformed memory debug64 packet - length %d\n",
le32_to_cpu(context->rx->length));
return;
}
context->dump_table_address = le64_to_cpu(context->rx->memory_debug64.table_address);
context->dump_table_length = le64_to_cpu(context->rx->memory_debug64.table_length);
if (context->dump_table_length % sizeof(struct sahara_debug_table_entry64) != 0 ||
!context->dump_table_length) {
dev_err(&context->mhi_dev->dev, "Malformed memory debug64 packet - table length %lld\n",
context->dump_table_length);
return;
}
/*
* From this point, the protocol flips. We make memory_read requests to
* the device, and the device responds with the raw data. If the device
* has an error, it will send an End of Image command. First we need to
* request the memory dump table so that we know where all the pieces
* of the dump are that we can consume.
*/
context->is_mem_dump_mode = true;
/*
* Assume that the table is smaller than our MTU so that we can read it
* in one shot. The spec does not put an upper limit on the table, but
* no known device will exceed this.
*/
if (context->dump_table_length > SAHARA_PACKET_MAX_SIZE) {
dev_err(&context->mhi_dev->dev, "Memory dump table length %lld exceeds supported size. Discarding dump\n",
context->dump_table_length);
sahara_send_reset(context);
return;
}
context->tx[0]->cmd = cpu_to_le32(SAHARA_MEM_READ64_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_MEM_READ64_LENGTH);
context->tx[0]->memory_read64.memory_address = cpu_to_le64(context->dump_table_address);
context->tx[0]->memory_read64.memory_length = cpu_to_le64(context->dump_table_length);
context->rx_size_requested = context->dump_table_length;
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
SAHARA_MEM_READ64_LENGTH, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev, "Unable to send read for dump table %d\n", ret);
}
static void sahara_processing(struct work_struct *work)
{
struct sahara_context *context = container_of(work, struct sahara_context, fw_work);
int ret;
switch (le32_to_cpu(context->rx->cmd)) {
case SAHARA_HELLO_CMD:
sahara_hello(context);
break;
case SAHARA_READ_DATA_CMD:
sahara_read_data(context);
break;
case SAHARA_END_OF_IMAGE_CMD:
sahara_end_of_image(context);
break;
case SAHARA_DONE_RESP_CMD:
/* Intentional do nothing as we don't need to exit an app */
break;
case SAHARA_RESET_RESP_CMD:
/* Intentional do nothing as we don't need to exit an app */
break;
case SAHARA_MEM_DEBUG64_CMD:
sahara_memory_debug64(context);
break;
default:
dev_err(&context->mhi_dev->dev, "Unknown command %d\n",
le32_to_cpu(context->rx->cmd));
break;
}
ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE, context->rx,
SAHARA_PACKET_MAX_SIZE, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev, "Unable to requeue rx buf %d\n", ret);
}
static void sahara_parse_dump_table(struct sahara_context *context)
{
struct sahara_dump_table_entry *image_out_table;
struct sahara_debug_table_entry64 *dev_table;
struct sahara_memory_dump_meta_v1 *dump_meta;
u64 table_nents;
u64 dump_length;
u64 mul_bytes;
int ret;
u64 i;
table_nents = context->dump_table_length / sizeof(*dev_table);
context->dump_images_left = table_nents;
dump_length = 0;
dev_table = (struct sahara_debug_table_entry64 *)(context->rx);
for (i = 0; i < table_nents; ++i) {
/* Do not trust the device, ensure the strings are terminated */
dev_table[i].description[SAHARA_TABLE_ENTRY_STR_LEN - 1] = 0;
dev_table[i].filename[SAHARA_TABLE_ENTRY_STR_LEN - 1] = 0;
if (check_add_overflow(dump_length,
le64_to_cpu(dev_table[i].length),
&dump_length)) {
/* Discard the dump */
sahara_send_reset(context);
return;
}
dev_dbg(&context->mhi_dev->dev,
"Memory dump table entry %lld type: %lld address: %#llx length: %#llx description: \"%s\" filename \"%s\"\n",
i,
le64_to_cpu(dev_table[i].type),
le64_to_cpu(dev_table[i].address),
le64_to_cpu(dev_table[i].length),
dev_table[i].description,
dev_table[i].filename);
}
if (check_add_overflow(dump_length, (u64)sizeof(*dump_meta), &dump_length)) {
/* Discard the dump */
sahara_send_reset(context);
return;
}
if (check_mul_overflow((u64)sizeof(*image_out_table), table_nents, &mul_bytes)) {
/* Discard the dump */
sahara_send_reset(context);
return;
}
if (check_add_overflow(dump_length, mul_bytes, &dump_length)) {
/* Discard the dump */
sahara_send_reset(context);
return;
}
context->mem_dump_sz = dump_length;
context->mem_dump = vzalloc(dump_length);
if (!context->mem_dump) {
/* Discard the dump */
sahara_send_reset(context);
return;
}
/* Populate the dump metadata and table for userspace */
dump_meta = context->mem_dump;
dump_meta->magic = SAHARA_DUMP_V1_MAGIC;
dump_meta->version = SAHARA_DUMP_V1_VER;
dump_meta->dump_size = dump_length;
dump_meta->table_size = context->dump_table_length;
image_out_table = context->mem_dump + sizeof(*dump_meta);
for (i = 0; i < table_nents; ++i) {
image_out_table[i].type = le64_to_cpu(dev_table[i].type);
image_out_table[i].address = le64_to_cpu(dev_table[i].address);
image_out_table[i].length = le64_to_cpu(dev_table[i].length);
strscpy(image_out_table[i].description, dev_table[i].description,
SAHARA_TABLE_ENTRY_STR_LEN);
strscpy(image_out_table[i].filename,
dev_table[i].filename,
SAHARA_TABLE_ENTRY_STR_LEN);
}
context->mem_dump_freespace = &image_out_table[i];
/* Done parsing the table, switch to image dump mode */
context->dump_table_length = 0;
/* Request the first chunk of the first image */
context->dump_image = &image_out_table[0];
dump_length = min_t(u64, context->dump_image->length, SAHARA_READ_MAX_SIZE);
/* Avoid requesting EOI sized data so that we can identify errors */
if (dump_length == SAHARA_END_OF_IMAGE_LENGTH)
dump_length = SAHARA_END_OF_IMAGE_LENGTH / 2;
context->dump_image_offset = dump_length;
context->tx[0]->cmd = cpu_to_le32(SAHARA_MEM_READ64_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_MEM_READ64_LENGTH);
context->tx[0]->memory_read64.memory_address = cpu_to_le64(context->dump_image->address);
context->tx[0]->memory_read64.memory_length = cpu_to_le64(dump_length);
context->rx_size_requested = dump_length;
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
SAHARA_MEM_READ64_LENGTH, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev, "Unable to send read for dump content %d\n", ret);
}
static void sahara_parse_dump_image(struct sahara_context *context)
{
u64 dump_length;
int ret;
memcpy(context->mem_dump_freespace, context->rx, context->rx_size);
context->mem_dump_freespace += context->rx_size;
if (context->dump_image_offset >= context->dump_image->length) {
/* Need to move to next image */
context->dump_image++;
context->dump_images_left--;
context->dump_image_offset = 0;
if (!context->dump_images_left) {
/* Dump done */
dev_coredumpv(context->mhi_dev->mhi_cntrl->cntrl_dev,
context->mem_dump,
context->mem_dump_sz,
GFP_KERNEL);
context->mem_dump = NULL;
sahara_send_reset(context);
return;
}
}
/* Get next image chunk */
dump_length = context->dump_image->length - context->dump_image_offset;
dump_length = min_t(u64, dump_length, SAHARA_READ_MAX_SIZE);
/* Avoid requesting EOI sized data so that we can identify errors */
if (dump_length == SAHARA_END_OF_IMAGE_LENGTH)
dump_length = SAHARA_END_OF_IMAGE_LENGTH / 2;
context->tx[0]->cmd = cpu_to_le32(SAHARA_MEM_READ64_CMD);
context->tx[0]->length = cpu_to_le32(SAHARA_MEM_READ64_LENGTH);
context->tx[0]->memory_read64.memory_address =
cpu_to_le64(context->dump_image->address + context->dump_image_offset);
context->tx[0]->memory_read64.memory_length = cpu_to_le64(dump_length);
context->dump_image_offset += dump_length;
context->rx_size_requested = dump_length;
ret = mhi_queue_buf(context->mhi_dev, DMA_TO_DEVICE, context->tx[0],
SAHARA_MEM_READ64_LENGTH, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev,
"Unable to send read for dump content %d\n", ret);
}
static void sahara_dump_processing(struct work_struct *work)
{
struct sahara_context *context = container_of(work, struct sahara_context, dump_work);
int ret;
/*
* We should get the expected raw data, but if the device has an error
* it is supposed to send EOI with an error code.
*/
if (context->rx_size != context->rx_size_requested &&
context->rx_size != SAHARA_END_OF_IMAGE_LENGTH) {
dev_err(&context->mhi_dev->dev,
"Unexpected response to read_data. Expected size: %#zx got: %#zx\n",
context->rx_size_requested,
context->rx_size);
goto error;
}
if (context->rx_size == SAHARA_END_OF_IMAGE_LENGTH &&
le32_to_cpu(context->rx->cmd) == SAHARA_END_OF_IMAGE_CMD) {
dev_err(&context->mhi_dev->dev,
"Unexpected EOI response to read_data. Status: %d\n",
le32_to_cpu(context->rx->end_of_image.status));
goto error;
}
if (context->rx_size == SAHARA_END_OF_IMAGE_LENGTH &&
le32_to_cpu(context->rx->cmd) != SAHARA_END_OF_IMAGE_CMD) {
dev_err(&context->mhi_dev->dev,
"Invalid EOI response to read_data. CMD: %d\n",
le32_to_cpu(context->rx->cmd));
goto error;
}
/*
* Need to know if we received the dump table, or part of a dump image.
* Since we get raw data, we cannot tell from the data itself. Instead,
* we use the stored dump_table_length, which we zero after we read and
* process the entire table.
*/
if (context->dump_table_length)
sahara_parse_dump_table(context);
else
sahara_parse_dump_image(context);
ret = mhi_queue_buf(context->mhi_dev, DMA_FROM_DEVICE, context->rx,
SAHARA_PACKET_MAX_SIZE, MHI_EOT);
if (ret)
dev_err(&context->mhi_dev->dev, "Unable to requeue rx buf %d\n", ret);
return;
error:
vfree(context->mem_dump);
context->mem_dump = NULL;
sahara_send_reset(context);
}
static void sahara_read_data_processing(struct work_struct *work)
{
struct sahara_context *context = container_of(work, struct sahara_context, read_data_work);
read_data_helper(context, 0);
}
static int sahara_mhi_probe(struct mhi_device *mhi_dev, const struct mhi_device_id *id)
{
struct sahara_context *context;
int ret;
int i;
context = devm_kzalloc(&mhi_dev->dev, sizeof(*context), GFP_KERNEL);
if (!context)
return -ENOMEM;
context->rx = devm_kzalloc(&mhi_dev->dev, SAHARA_PACKET_MAX_SIZE, GFP_KERNEL);
if (!context->rx)
return -ENOMEM;
if (!strcmp(mhi_dev->mhi_cntrl->name, "AIC200")) {
context->image_table = aic200_image_table;
context->table_size = ARRAY_SIZE(aic200_image_table);
} else {
context->image_table = aic100_image_table;
context->table_size = ARRAY_SIZE(aic100_image_table);
context->non_streaming = true;
}
/*
* There are two firmware implementations for READ_DATA handling.
* The older "SBL" implementation defines a Sahara transfer size, and
* expects that the response is a single transport transfer. If the
* FW wants to transfer a file that is larger than the transfer size,
* the FW will issue multiple READ_DATA commands. For this
* implementation, we need to allocate enough buffers to contain the
* entire Sahara transfer size.
*
* The newer "XBL" implementation does not define a maximum transfer
* size and instead expects the data to be streamed over using the
* transport level MTU. The FW will issue a single READ_DATA command
* of whatever size, and consume multiple transport level transfers
* until the expected amount of data is consumed. For this
* implementation we only need a single buffer of the transport MTU
* but we'll need to be able to use it multiple times for a single
* READ_DATA request.
*
* AIC100 is the SBL implementation and defines SAHARA_TRANSFER_MAX_SIZE
* and we need 9x SAHARA_PACKET_MAX_SIZE to cover that. We can use
* MHI_CHAIN to link multiple buffers into a single transfer but the
* remote side will not consume the buffers until it sees an EOT, thus
* we need to allocate enough buffers to put in the tx fifo to cover an
* entire READ_DATA request of the max size.
*
* AIC200 is the XBL implementation, and so a single buffer will work.
*/
for (i = 0; i < SAHARA_NUM_TX_BUF; ++i) {
context->tx[i] = devm_kzalloc(&mhi_dev->dev,
SAHARA_PACKET_MAX_SIZE,
GFP_KERNEL);
if (!context->tx[i])
return -ENOMEM;
if (is_streaming(context))
break;
}
context->mhi_dev = mhi_dev;
INIT_WORK(&context->fw_work, sahara_processing);
INIT_WORK(&context->dump_work, sahara_dump_processing);
INIT_WORK(&context->read_data_work, sahara_read_data_processing);
context->active_image_id = SAHARA_IMAGE_ID_NONE;
dev_set_drvdata(&mhi_dev->dev, context);
ret = mhi_prepare_for_transfer(mhi_dev);
if (ret)
return ret;
ret = mhi_queue_buf(mhi_dev, DMA_FROM_DEVICE, context->rx, SAHARA_PACKET_MAX_SIZE, MHI_EOT);
if (ret) {
mhi_unprepare_from_transfer(mhi_dev);
return ret;
}
return 0;
}
static void sahara_mhi_remove(struct mhi_device *mhi_dev)
{
struct sahara_context *context = dev_get_drvdata(&mhi_dev->dev);
cancel_work_sync(&context->fw_work);
cancel_work_sync(&context->dump_work);
vfree(context->mem_dump);
sahara_release_image(context);
mhi_unprepare_from_transfer(mhi_dev);
}
static void sahara_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct sahara_context *context = dev_get_drvdata(&mhi_dev->dev);
if (!mhi_result->transaction_status && context->read_data_length && is_streaming(context))
schedule_work(&context->read_data_work);
}
static void sahara_mhi_dl_xfer_cb(struct mhi_device *mhi_dev, struct mhi_result *mhi_result)
{
struct sahara_context *context = dev_get_drvdata(&mhi_dev->dev);
if (!mhi_result->transaction_status) {
context->rx_size = mhi_result->bytes_xferd;
if (context->is_mem_dump_mode)
schedule_work(&context->dump_work);
else
schedule_work(&context->fw_work);
}
}
static const struct mhi_device_id sahara_mhi_match_table[] = {
{ .chan = "QAIC_SAHARA", },
{},
};
static struct mhi_driver sahara_mhi_driver = {
.id_table = sahara_mhi_match_table,
.remove = sahara_mhi_remove,
.probe = sahara_mhi_probe,
.ul_xfer_cb = sahara_mhi_ul_xfer_cb,
.dl_xfer_cb = sahara_mhi_dl_xfer_cb,
.driver = {
.name = "sahara",
},
};
int sahara_register(void)
{
return mhi_driver_register(&sahara_mhi_driver);
}
void sahara_unregister(void)
{
mhi_driver_unregister(&sahara_mhi_driver);
}
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. */
#ifndef __SAHARA_H__
#define __SAHARA_H__
int sahara_register(void);
void sahara_unregister(void);
#endif /* __SAHARA_H__ */