Use the DRM RAS netlink infrastructure to perform device telemetry. During PCI probe, create a DRM RAS node with DRM_RAS_NODE_TYPE_TELEMETRY type and add callbacks to telemetry_request for read and write operations.
Co-developed-by: Jeff Hugo <[email protected]> Signed-off-by: Jeff Hugo <[email protected]> Signed-off-by: Zack McKevitt <[email protected]> --- drivers/accel/qaic/Makefile | 1 + drivers/accel/qaic/qaic.h | 19 ++ drivers/accel/qaic/qaic_drv.c | 19 ++ drivers/accel/qaic/qaic_telemetry.c | 463 ++++++++++++++++++++++++++++ drivers/accel/qaic/qaic_telemetry.h | 18 ++ include/uapi/drm/qaic_accel.h | 28 ++ 6 files changed, 548 insertions(+) create mode 100644 drivers/accel/qaic/qaic_telemetry.c create mode 100644 drivers/accel/qaic/qaic_telemetry.h diff --git a/drivers/accel/qaic/Makefile b/drivers/accel/qaic/Makefile index 71f727b74da3..b2bbe9b3e077 100644 --- a/drivers/accel/qaic/Makefile +++ b/drivers/accel/qaic/Makefile @@ -13,6 +13,7 @@ qaic-y := \ qaic_ras.o \ qaic_ssr.o \ qaic_sysfs.o \ + qaic_telemetry.o \ qaic_timesync.o \ sahara.o diff --git a/drivers/accel/qaic/qaic.h b/drivers/accel/qaic/qaic.h index 02ca99000e2a..f5dbeadba658 100644 --- a/drivers/accel/qaic/qaic.h +++ b/drivers/accel/qaic/qaic.h @@ -17,6 +17,7 @@ #include <linux/workqueue.h> #include <drm/drm_device.h> #include <drm/drm_gem.h> +#include <drm/drm_ras.h> #define QAIC_DBC_BASE SZ_128K #define QAIC_DBC_SIZE SZ_4K @@ -203,6 +204,24 @@ struct qaic_device { void *ssr_mhi_buf; /* DBC which is under SSR. Sentinel U32_MAX would mean that no SSR in progress */ u32 ssr_dbc; + /* MHI telemetry channel device */ + struct mhi_device *tele_ch; + /* Head in list of requests queued in MHI telemetry device */ + struct list_head tele_xfer_list; + /* Req. ID of request that will be queued next in MHI telemetry device */ + u32 tele_next_seq_num; + /* Synchronizes MHI telemetry device transactions and its xfer list */ + struct mutex tele_mutex; + /* + * TRUE: A tx MHI transaction has failed and a rx buffer is still queued + * in telemetry device. Such a buffer is considered lost rx buffer + * FALSE: No rx buffer is lost in telemetry device + */ + bool tele_lost_buf; + /* Work queue for tasks related to MHI telemetry device */ + struct workqueue_struct *tele_wq; + /* Netlink telemetry node */ + struct drm_ras_node *ras_node; /* Array of DBC struct of this device */ struct dma_bridge_chan dbc[] __counted_by(num_dbc); }; diff --git a/drivers/accel/qaic/qaic_drv.c b/drivers/accel/qaic/qaic_drv.c index c1bb6db78d12..d2aed51052a6 100644 --- a/drivers/accel/qaic/qaic_drv.c +++ b/drivers/accel/qaic/qaic_drv.c @@ -32,6 +32,7 @@ #include "qaic_debugfs.h" #include "qaic_ras.h" #include "qaic_ssr.h" +#include "qaic_telemetry.h" #include "qaic_timesync.h" #include "sahara.h" @@ -381,6 +382,7 @@ static void qaic_notify_reset(struct qaic_device *qdev) qdev->dev_state = QAIC_OFFLINE; /* wake up any waiters to avoid waiting for timeouts at sync */ wake_all_cntl(qdev); + wake_all_telemetry(qdev); for (i = 0; i < qdev->num_dbc; ++i) wakeup_dbc(qdev, i); synchronize_srcu(&qdev->dev_lock); @@ -431,6 +433,9 @@ static struct qaic_device *create_qdev(struct pci_dev *pdev, if (ret) return NULL; ret = drmm_mutex_init(drm, &qdev->bootlog_mutex); + if (ret) + return NULL; + ret = drmm_mutex_init(drm, &qdev->tele_mutex); if (ret) return NULL; @@ -443,6 +448,9 @@ static struct qaic_device *create_qdev(struct pci_dev *pdev, qdev->ssr_wq = qaicm_wq_init(drm, "qaic_ssr"); if (IS_ERR(qdev->ssr_wq)) return NULL; + qdev->tele_wq = qaicm_wq_init(drm, "qaic_tele"); + if (IS_ERR(qdev->tele_wq)) + return NULL; ret = qaicm_srcu_init(drm, &qdev->dev_lock); if (ret) @@ -458,6 +466,7 @@ static struct qaic_device *create_qdev(struct pci_dev *pdev, INIT_LIST_HEAD(&qdev->cntl_xfer_list); INIT_LIST_HEAD(&qdev->bootlog); + INIT_LIST_HEAD(&qdev->tele_xfer_list); INIT_LIST_HEAD(&qddev->users); for (i = 0; i < qdev->num_dbc; ++i) { @@ -595,6 +604,10 @@ static int qaic_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) if (ret) return ret; + ret = qaic_drm_ras_register_node(qdev); + 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)) { @@ -615,6 +628,7 @@ static void qaic_pci_remove(struct pci_dev *pdev) qaic_dev_reset_clean_local_state(qdev); qaic_mhi_free_controller(qdev->mhi_cntrl, link_up); + qaic_drm_ras_unregister_node(qdev); qaic_destroy_drm_device(qdev, QAIC_NO_PARTITION); } @@ -813,6 +827,10 @@ static int __init qaic_init(void) goto free_bootlog; } + ret = qaic_telemetry_register(); + if (ret) + pr_debug("qaic: qaic_telemetry_register failed %d\n", ret); + return 0; free_bootlog: @@ -842,6 +860,7 @@ static void __exit qaic_exit(void) * reinitializing the link_up state after the cleanup is done. */ link_up = true; + qaic_telemetry_unregister(); qaic_ssr_unregister(); qaic_ras_unregister(); qaic_bootlog_unregister(); diff --git a/drivers/accel/qaic/qaic_telemetry.c b/drivers/accel/qaic/qaic_telemetry.c new file mode 100644 index 000000000000..dbfb36cf319c --- /dev/null +++ b/drivers/accel/qaic/qaic_telemetry.c @@ -0,0 +1,463 @@ +// 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 <drm/drm_file.h> +#include <drm/drm_ras.h> +#include <asm/byteorder.h> +#include <linux/completion.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/kref.h> +#include <linux/list.h> +#include <linux/mhi.h> +#include <linux/mutex.h> +#include <linux/namei.h> +#include <linux/path.h> +#include <linux/sprintf.h> +#include <linux/srcu.h> +#include <linux/types.h> +#include <linux/workqueue.h> +#include <uapi/drm/qaic_accel.h> + +#include "qaic.h" +#include "qaic_telemetry.h" + +#define MAGIC 0x55AA +#define VERSION 0x1 + +enum fw_errno { + ERR_READ_ONLY = -1, + ERR_NOT_SUPPORTED = -2, + ERR_WHILE_PROCESSING = -3, + ERR_INVALID_VALUE = -4, + ERR_INVALID_HEADER = -5, +}; + +enum cmd_type { + TYPE_READ, /* read value from device */ + TYPE_WRITE, /* write value to device */ +}; + +enum msg_type { + MSG_PUSH, /* async push from device */ + MSG_REQ, /* sync request to device */ + MSG_RESP, /* sync response from device */ +}; + +struct telemetry_data { + u8 cmd; + u8 cmd_type; + u8 status; + __le64 val; /* signed */ +} __packed; + +struct telemetry_header { + __le16 magic; + __le16 ver; + __le32 seq_num; + u8 type; + u8 id; + __le16 len; +} __packed; + +struct telemetry_msg { /* little endian encoded */ + struct telemetry_header hdr; + struct telemetry_data data; +} __packed; + +struct wrapper_msg { + struct kref ref_count; + struct telemetry_msg msg; +}; + +struct xfer_queue_elem { + /* + * Node in list of ongoing transfer request on telemetry channel. + * Maintained by root device struct + */ + struct list_head list; + /* Sequence number of this transfer request */ + u32 seq_num; + /* This is used to wait on until completion of transfer request */ + struct completion xfer_done; + /* Received data from device */ + void *buf; +}; + +struct resp_work { + /* Work struct to schedule work coming on QAIC_TELEMETRY 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 */ + void *buf; +}; + +static void free_wrapper(struct kref *ref) +{ + struct wrapper_msg *wrapper = container_of(ref, struct wrapper_msg, ref_count); + + kfree(wrapper); +} + +int telemetry_request(struct qaic_device *qdev, u8 cmd, u8 cmd_type, s64 *val) +{ + struct wrapper_msg *wrapper; + struct xfer_queue_elem elem; + struct telemetry_msg *resp; + struct telemetry_msg *req; + long ret = 0; + + wrapper = kzalloc_obj(*wrapper); + if (!wrapper) + return -ENOMEM; + + kref_init(&wrapper->ref_count); + req = &wrapper->msg; + + ret = mutex_lock_interruptible(&qdev->tele_mutex); + if (ret) + goto free_req; + + req->hdr.magic = cpu_to_le16(MAGIC); + req->hdr.ver = cpu_to_le16(VERSION); + req->hdr.seq_num = cpu_to_le32(qdev->tele_next_seq_num++); + req->hdr.type = MSG_REQ; + req->hdr.id = 0; + req->hdr.len = cpu_to_le16(sizeof(req->data)); + + req->data.cmd = cmd; + req->data.cmd_type = cmd_type; + req->data.status = 0; + if (cmd_type == TYPE_READ) + req->data.val = cpu_to_le64(0); + else + req->data.val = cpu_to_le64(*val); + + elem.seq_num = qdev->tele_next_seq_num - 1; + elem.buf = NULL; + init_completion(&elem.xfer_done); + if (likely(!qdev->tele_lost_buf)) { + resp = kzalloc_obj(*resp); + if (!resp) { + mutex_unlock(&qdev->tele_mutex); + ret = -ENOMEM; + goto free_req; + } + + ret = mhi_queue_buf(qdev->tele_ch, DMA_FROM_DEVICE, resp, sizeof(*resp), MHI_EOT); + if (ret) { + mutex_unlock(&qdev->tele_mutex); + goto free_resp; + } + } else { + /* + * we lost a buffer because we queued a recv buf, but then + * queuing the corresponding tx buf failed. To try to avoid + * a memory leak, lets reclaim it and use it for this + * transaction. + */ + qdev->tele_lost_buf = false; + } + + kref_get(&wrapper->ref_count); + ret = mhi_queue_buf(qdev->tele_ch, DMA_TO_DEVICE, req, sizeof(*req), MHI_EOT); + if (ret) { + qdev->tele_lost_buf = true; + kref_put(&wrapper->ref_count, free_wrapper); + mutex_unlock(&qdev->tele_mutex); + goto free_req; + } + + list_add_tail(&elem.list, &qdev->tele_xfer_list); + mutex_unlock(&qdev->tele_mutex); + + ret = wait_for_completion_interruptible_timeout(&elem.xfer_done, HZ); + /* + * not using _interruptable because we have to cleanup or we'll + * likely cause memory corruption + */ + mutex_lock(&qdev->tele_mutex); + if (!list_empty(&elem.list)) + list_del(&elem.list); + if (!ret && !elem.buf) + ret = -ETIMEDOUT; + else if (ret > 0 && !elem.buf) + ret = -EIO; + mutex_unlock(&qdev->tele_mutex); + + resp = elem.buf; + + if (ret < 0) + goto free_resp; + + if (le16_to_cpu(resp->hdr.magic) != MAGIC || le16_to_cpu(resp->hdr.ver) != VERSION || + resp->hdr.type != MSG_RESP || resp->hdr.id != 0 || + le16_to_cpu(resp->hdr.len) != sizeof(resp->data) || resp->data.cmd != cmd || + resp->data.cmd_type != cmd_type) { + ret = -EINVAL; + goto free_resp; + } + + if (resp->data.status) { + switch ((int8_t) resp->data.status) { + case ERR_READ_ONLY: + ret = -EROFS; + break; + case ERR_NOT_SUPPORTED: + ret = -EOPNOTSUPP; + break; + case ERR_WHILE_PROCESSING: + ret = -EREMOTEIO; + break; + case ERR_INVALID_VALUE: + ret = -EINVAL; + break; + case ERR_INVALID_HEADER: + ret = -EBADR; + break; + default: + ret = -EPROTO; + } + goto free_resp; + } + + if (cmd_type == TYPE_READ) + *val = le64_to_cpu(resp->data.val); + + ret = 0; + +free_resp: + kfree(resp); +free_req: + kref_put(&wrapper->ref_count, free_wrapper); + + return ret; +} + +static int qaic_telemetry_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)); + int ret; + + ret = mhi_prepare_for_transfer(mhi_dev); + if (ret) + return ret; + + dev_set_drvdata(&mhi_dev->dev, qdev); + qdev->tele_ch = mhi_dev; + qdev->tele_lost_buf = false; + + return 0; +} + +static void qaic_telemetry_mhi_remove(struct mhi_device *mhi_dev) +{ + struct qaic_device *qdev = dev_get_drvdata(&mhi_dev->dev); + + mhi_unprepare_from_transfer(qdev->tele_ch); + qdev->tele_ch = NULL; +} + +static void resp_worker(struct work_struct *work) +{ + struct resp_work *resp = container_of(work, struct resp_work, work); + struct qaic_device *qdev = resp->qdev; + struct telemetry_msg *msg = resp->buf; + struct xfer_queue_elem *elem; + struct xfer_queue_elem *i; + bool found = false; + + if (le16_to_cpu(msg->hdr.magic) != MAGIC) { + kfree(msg); + kfree(resp); + return; + } + + mutex_lock(&qdev->tele_mutex); + list_for_each_entry_safe(elem, i, &qdev->tele_xfer_list, list) { + if (elem->seq_num == le32_to_cpu(msg->hdr.seq_num)) { + found = true; + list_del_init(&elem->list); + elem->buf = msg; + complete_all(&elem->xfer_done); + break; + } + } + mutex_unlock(&qdev->tele_mutex); + + if (!found) + /* request must have timed out, drop packet */ + kfree(msg); + + kfree(resp); +} + +static void qaic_telemetry_mhi_ul_xfer_cb(struct mhi_device *mhi_dev, + struct mhi_result *mhi_result) +{ + struct telemetry_msg *msg = mhi_result->buf_addr; + struct wrapper_msg *wrapper; + + wrapper = container_of(msg, struct wrapper_msg, msg); + kref_put(&wrapper->ref_count, free_wrapper); +} + +static void qaic_telemetry_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 telemetry_msg *msg = mhi_result->buf_addr; + struct resp_work *resp; + + if (mhi_result->transaction_status) { + kfree(msg); + return; + } + + resp = kmalloc(sizeof(*resp), GFP_ATOMIC); + if (!resp) { + pci_err(qdev->pdev, "dl_xfer_cb alloc fail, dropping message\n"); + kfree(msg); + return; + } + + INIT_WORK(&resp->work, resp_worker); + resp->qdev = qdev; + resp->buf = msg; + queue_work(qdev->tele_wq, &resp->work); +} + +static const struct mhi_device_id qaic_telemetry_mhi_match_table[] = { + { .chan = "QAIC_TELEMETRY", }, + {}, +}; + +static struct mhi_driver qaic_telemetry_mhi_driver = { + .id_table = qaic_telemetry_mhi_match_table, + .remove = qaic_telemetry_mhi_remove, + .probe = qaic_telemetry_mhi_probe, + .ul_xfer_cb = qaic_telemetry_mhi_ul_xfer_cb, + .dl_xfer_cb = qaic_telemetry_mhi_dl_xfer_cb, + .driver = { + .name = "qaic_telemetry", + }, +}; + +int qaic_telemetry_register(void) +{ + return mhi_driver_register(&qaic_telemetry_mhi_driver); +} + +void qaic_telemetry_unregister(void) +{ + mhi_driver_unregister(&qaic_telemetry_mhi_driver); +} + +void wake_all_telemetry(struct qaic_device *qdev) +{ + struct xfer_queue_elem *elem; + struct xfer_queue_elem *i; + + mutex_lock(&qdev->tele_mutex); + list_for_each_entry_safe(elem, i, &qdev->tele_xfer_list, list) { + list_del_init(&elem->list); + complete_all(&elem->xfer_done); + } + qdev->tele_lost_buf = false; + mutex_unlock(&qdev->tele_mutex); +} + +static int qaic_telemetry_pre(struct drm_ras_node *ep) +{ + struct qaic_device *qdev = ep->priv; + struct inode *dev_inode; + struct path path; + char devpath[24]; + int ret; + + snprintf(devpath, sizeof(devpath), "/dev/accel/%s", to_accel_kdev(qdev->qddev)->kobj.name); + ret = kern_path(devpath, LOOKUP_FOLLOW, &path); + if (ret) + return -EINVAL; + + dev_inode = d_inode(path.dentry); + ret = inode_permission(&nop_mnt_idmap, dev_inode, MAY_READ | MAY_WRITE); + path_put(&path); + + return ret; +} + +static int qaic_telemetry_read(struct drm_ras_node *ep, u32 *field, u64 *value, s32 *status) +{ + struct qaic_device *qdev = ep->priv; + + *status = (s32)telemetry_request(qdev, *field, TYPE_READ, value); + + return *status; +} + +static int qaic_telemetry_write(struct drm_ras_node *ep, u32 *field, u64 *value, s32 *status) +{ + struct qaic_device *qdev = ep->priv; + + *status = (s32)telemetry_request(qdev, *field, TYPE_WRITE, value); + + return *status; +} + +int qaic_drm_ras_register_node(struct qaic_device *qdev) +{ + struct drm_ras_node *node; + struct pci_dev *pdev = qdev->pdev; + const char *device_name, *node_name; + int ret; + + node = kzalloc_obj(struct drm_ras_node); + if (!node) + return -ENOMEM; + + device_name = kasprintf(GFP_KERNEL, "%04x:%02x:%02x.%d", + pci_domain_nr(pdev->bus), pdev->bus->number, + PCI_SLOT(pdev->devfn), PCI_FUNC(pdev->devfn)); + if (!device_name) { + kfree(node); + return -ENOMEM; + } + + node_name = kasprintf(GFP_KERNEL, "qaic_telemetry"); + if (!node_name) { + kfree(device_name); + kfree(node); + return -ENOMEM; + } + + node->device_name = device_name; + node->node_name = node_name; + node->type = DRM_RAS_NODE_TYPE_TELEMETRY; + node->priv = qdev; + node->telemetry_read = qaic_telemetry_read; + node->telemetry_write = qaic_telemetry_write; + node->telemetry_pre = qaic_telemetry_pre; + + ret = drm_ras_node_register(node); + if (ret) { + kfree(device_name); + kfree(node_name); + kfree(qdev->ras_node); + return ret; + } + + qdev->ras_node = node; + return 0; +} + +void qaic_drm_ras_unregister_node(struct qaic_device *qdev) +{ + drm_ras_node_unregister(qdev->ras_node); + kfree(qdev->ras_node->node_name); + kfree(qdev->ras_node->device_name); + kfree(qdev->ras_node); +} diff --git a/drivers/accel/qaic/qaic_telemetry.h b/drivers/accel/qaic/qaic_telemetry.h new file mode 100644 index 000000000000..a80922591e37 --- /dev/null +++ b/drivers/accel/qaic/qaic_telemetry.h @@ -0,0 +1,18 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */ + +#ifndef __QAIC_TELEMETRY_H__ +#define __QAIC_TELEMETRY_H__ + +#include "qaic.h" + +int telemetry_request(struct qaic_device *qdev, u8 cmd, u8 cmd_type, s64 *val); +int qaic_drm_ras_register_node(struct qaic_device *qdev); +void qaic_drm_ras_unregister_node(struct qaic_device *qdev); + +int qaic_telemetry_register(void); +void qaic_telemetry_unregister(void); +void wake_all_telemetry(struct qaic_device *qdev); + +#endif /* __QAIC_TELEMETRY_H__ */ diff --git a/include/uapi/drm/qaic_accel.h b/include/uapi/drm/qaic_accel.h index c92d0309d583..5aac05101426 100644 --- a/include/uapi/drm/qaic_accel.h +++ b/include/uapi/drm/qaic_accel.h @@ -51,6 +51,34 @@ extern "C" { #define QAIC_TRANS_VALIDATE_PARTITION_FROM_DEV 19 #define QAIC_TRANS_VALIDATE_PARTITION_TO_DEV 20 +/* Telemetry fields */ +#define QAIC_DEVINFO_THERMAL_SOC_MAX_TEMP 1 +#define QAIC_DEVINFO_THERMAL_BOARD_TEMP 2 +#define QAIC_DEVINFO_THERMAL_BOARD_MAX_TEMP 3 +#define QAIC_DEVINFO_THERMAL_DDR_TEMP 4 +#define QAIC_DEVINFO_THERMAL_WARNING_TEMP 5 +#define QAIC_DEVINFO_THERMAL_SHUTDOWN_TEMP 6 +#define QAIC_DEVINFO_BOARD_TDP 7 +#define QAIC_DEVINFO_BOARD_POWER 8 +#define QAIC_DEVINFO_POWER_STATE 9 +#define QAIC_DEVINFO_POWER_MAX 10 +#define QAIC_DEVINFO_THROTTLE_PERCENT 11 +#define QAIC_DEVINFO_THROTTLE_TIME 12 +#define QAIC_DEVINFO_UPTIME 13 +#define QAIC_DEVINFO_THERMAL_SOC_FLOOR_TEMP 14 +#define QAIC_DEVINFO_THERMAL_SOC_CEILING_TEMP 15 +#define QAIC_DEVINFO_VOLTAGE_CX 16 +#define QAIC_DEVINFO_VOLTAGE_MX 17 +#define QAIC_DEVINFO_VOLTAGE_NSP_CX 18 +#define QAIC_DEVINFO_VOLTAGE_NSP_MX 19 +#define QAIC_DEVINFO_CFG_TOPS 20 +#define QAIC_DEVINFO_THERMAL_PMIC_TEMP_A 21 +#define QAIC_DEVINFO_THERMAL_PMIC_TEMP_C 22 +#define QAIC_DEVINFO_THERMAL_PMIC_TEMP_E 23 +#define QAIC_DEVINFO_POWER_ACTION 24 +#define QAIC_DEVINFO_SOC_TDP 25 +#define QAIC_DEVINFO_SOC_POWER 26 + /** * struct qaic_manage_trans_hdr - Header for a transaction in a manage message. * @type: In. Identifies this transaction. See QAIC_TRANS_* defines. -- 2.43.0
