* [PATCH v4 0/3] Introduce Protection Domain Restart (PDR) Helpers @ 2020-02-26 16:59 Sibi Sankar 2020-02-26 16:59 ` [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers Sibi Sankar ` (2 more replies) 0 siblings, 3 replies; 10+ messages in thread From: Sibi Sankar @ 2020-02-26 16:59 UTC (permalink / raw) To: bjorn.andersson, srinivas.kandagatla, robh+dt Cc: agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta, Sibi Sankar Qualcomm SoCs (starting with MSM8998) allow for multiple protection domains (PDs) to run on the same Q6 sub-system. This allows for services like AVS AUDIO to have their own separate address space and crash/recover without disrupting the other PDs running on the same Q6 ADSP. This patch series introduces pdr helper library and adds PD tracking functionality for "avs/audio" allowing apr services to register themselves asynchronously once the dependent PDs are up. V4: * Fixup dt bindings and examples. [Rob] * Dropping r-b from Bjorn/Srini for the dt bindings. * Privatize pdr_service/pdr_handle. [Srini/Bjorn] * Introduce notifier_init_complete to deal with cases where qmi_handle_net_reset is not enough to reset the port. This is to deal with cases where qrtr-ns starts after the adsp. * Introduce per addr per pds to deal with multiple service_paths per pdr_handle. * Uniformly rename servreg -> notifier, servloc -> locator. [Narendra] * Drop pdr_servreg_link_create tracking the service_path tracks all pds associated with it. [Bjorn] * Remove safe traversal for all cases where list is left unmodified. [Bjorn] * Address review comments in the apr driver and add comments. [Bjorn] * Other misc fixes. [Narendra/Bjorn] V3: * patches 2 and 3 remain unchanged. * reset servloc_addr/servreg_addr. * fixup the helpers to handle servloc_work/servreg_work asynchronously. * fixup useage of list_lock across traversals, insertions and deletions. * fixup the helpers to use a single lookup list. * skip waiting for response on ind_ack send. * introduce pdr_servreg_link_create to re-use existing qmi connection to servreg instances. This helps tracking PDs running on the same remote processor. * have a per node curr_state in pdr_list_node to preserve all state updates during indack_cb. * introduce additional servreg_service_state values to help the client distinguish between a fatal and non-fatal pdr_lookup errors. * re-order pdr_handle_release sequence. * fixup "!ind_msg->service_path returns true always" warning. * fixup comments. V2: * fixup pd_status callback to return void. * return 0 from pdr_get_domain_list on success. * introduce status_lock to linearize the pd_status reported back to the clients. * use the correct service name length across various string operations performed. * service locator will now schedule the pending lookups registered when it comes up. * other minor cleanups that Bjorn suggested. * use pr_warn to indicate that the wait for service locator timed out. * add Bjorn/Srini's "Reviewed-by" for the dt-bindings. Sibi Sankar (3): soc: qcom: Introduce Protection Domain Restart helpers dt-bindings: soc: qcom: apr: Add protection domain bindings soc: qcom: apr: Add avs/audio tracking functionality .../devicetree/bindings/soc/qcom/qcom,apr.txt | 50 ++ drivers/soc/qcom/Kconfig | 6 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/apr.c | 123 ++- drivers/soc/qcom/pdr_interface.c | 768 ++++++++++++++++++ drivers/soc/qcom/pdr_internal.h | 379 +++++++++ include/linux/soc/qcom/apr.h | 1 + include/linux/soc/qcom/pdr.h | 30 + include/linux/soc/qcom/qmi.h | 1 + 9 files changed, 1350 insertions(+), 9 deletions(-) create mode 100644 drivers/soc/qcom/pdr_interface.c create mode 100644 drivers/soc/qcom/pdr_internal.h create mode 100644 include/linux/soc/qcom/pdr.h -- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers 2020-02-26 16:59 [PATCH v4 0/3] Introduce Protection Domain Restart (PDR) Helpers Sibi Sankar @ 2020-02-26 16:59 ` Sibi Sankar 2020-02-28 5:34 ` Bjorn Andersson 2020-02-26 17:00 ` [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings Sibi Sankar 2020-02-26 17:00 ` [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality Sibi Sankar 2 siblings, 1 reply; 10+ messages in thread From: Sibi Sankar @ 2020-02-26 16:59 UTC (permalink / raw) To: bjorn.andersson, srinivas.kandagatla, robh+dt Cc: agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta, Sibi Sankar Qualcomm SoCs (starting with MSM8998) allow for multiple protection domains to run on the same Q6 sub-system. This allows for services like ATH10K WLAN FW to have their own separate address space and crash/recover without disrupting the modem and other PDs running on the same sub-system. The PDR helpers introduces an abstraction that allows for tracking/controlling the life cycle of protection domains running on various Q6 sub-systems. Signed-off-by: Sibi Sankar <sibis@codeaurora.org> --- drivers/soc/qcom/Kconfig | 5 + drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/pdr_interface.c | 768 +++++++++++++++++++++++++++++++ drivers/soc/qcom/pdr_internal.h | 379 +++++++++++++++ include/linux/soc/qcom/pdr.h | 30 ++ include/linux/soc/qcom/qmi.h | 1 + 6 files changed, 1184 insertions(+) create mode 100644 drivers/soc/qcom/pdr_interface.c create mode 100644 drivers/soc/qcom/pdr_internal.h create mode 100644 include/linux/soc/qcom/pdr.h diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index d0a73e76d5638..cca6a43e771d9 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -76,6 +76,11 @@ config QCOM_OCMEM requirements. This is typically used by the GPU, camera/video, and audio components on some Snapdragon SoCs. +config QCOM_PDR_HELPERS + tristate + depends on ARCH_QCOM || COMPILE_TEST + select QCOM_QMI_HELPERS + config QCOM_PM bool "Qualcomm Power Management" depends on ARCH_QCOM && !ARM64 diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index 9fb35c8a495e1..5d6b83dc58e82 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_QCOM_GLINK_SSR) += glink_ssr.o obj-$(CONFIG_QCOM_GSBI) += qcom_gsbi.o obj-$(CONFIG_QCOM_MDT_LOADER) += mdt_loader.o obj-$(CONFIG_QCOM_OCMEM) += ocmem.o +obj-$(CONFIG_QCOM_PDR_HELPERS) += pdr_interface.o obj-$(CONFIG_QCOM_PM) += spm.o obj-$(CONFIG_QCOM_QMI_HELPERS) += qmi_helpers.o qmi_helpers-y += qmi_encdec.o qmi_interface.o diff --git a/drivers/soc/qcom/pdr_interface.c b/drivers/soc/qcom/pdr_interface.c new file mode 100644 index 0000000000000..97afff230b121 --- /dev/null +++ b/drivers/soc/qcom/pdr_interface.c @@ -0,0 +1,768 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Copyright (C) 2020 The Linux Foundation. All rights reserved. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/string.h> +#include <linux/workqueue.h> + +#include "pdr_internal.h" + +struct pdr_service { + char service_name[SERVREG_NAME_LENGTH + 1]; + char service_path[SERVREG_NAME_LENGTH + 1]; + + struct sockaddr_qrtr addr; + + unsigned int instance; + unsigned int service; + u8 service_data_valid; + u32 service_data; + int state; + + bool need_notifier_register; + bool need_notifier_remove; + bool need_locator_lookup; + bool service_connected; + + struct list_head node; +}; + +struct pdr_handle { + struct qmi_handle locator_hdl; + struct qmi_handle notifier_hdl; + + struct sockaddr_qrtr locator_addr; + + struct list_head lookups; + struct list_head indack_list; + + /* control access to pdr lookup/indack lists */ + struct mutex list_lock; + + /* serialize pd status invocation */ + struct mutex status_lock; + + /* control access to locator/notifier init state */ + struct mutex lock; + + bool notifier_init_complete; + bool locator_init_complete; + + struct work_struct locator_work; + struct work_struct notifier_work; + struct work_struct indack_work; + + struct workqueue_struct *notifier_wq; + struct workqueue_struct *indack_wq; + + void (*status)(int state, char *service_path, void *priv); + void *priv; +}; + +struct pdr_list_node { + enum servreg_service_state curr_state; + u16 transaction_id; + struct pdr_service *pds; + struct list_head node; +}; + +static int pdr_locator_new_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, + locator_hdl); + struct pdr_service *pds; + + /* Create a local client port for QMI communication */ + pdr->locator_addr.sq_family = AF_QIPCRTR; + pdr->locator_addr.sq_node = svc->node; + pdr->locator_addr.sq_port = svc->port; + + mutex_lock(&pdr->lock); + pdr->locator_init_complete = true; + mutex_unlock(&pdr->lock); + + /* Service pending lookup requests */ + mutex_lock(&pdr->list_lock); + list_for_each_entry(pds, &pdr->lookups, node) { + if (pds->need_locator_lookup) + schedule_work(&pdr->locator_work); + } + mutex_unlock(&pdr->list_lock); + + return 0; +} + +static void pdr_locator_del_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, + locator_hdl); + + mutex_lock(&pdr->lock); + pdr->locator_init_complete = false; + mutex_unlock(&pdr->lock); + + pdr->locator_addr.sq_node = 0; + pdr->locator_addr.sq_port = 0; +} + +static struct qmi_ops pdr_locator_ops = { + .new_server = pdr_locator_new_server, + .del_server = pdr_locator_del_server, +}; + +static int pdr_register_listener(struct pdr_handle *pdr, + struct pdr_service *pds, + bool enable) +{ + struct servreg_register_listener_resp resp; + struct servreg_register_listener_req req; + struct qmi_txn txn; + int ret; + + ret = qmi_txn_init(&pdr->notifier_hdl, &txn, + servreg_register_listener_resp_ei, + &resp); + if (ret < 0) + return ret; + + req.enable = enable; + strcpy(req.service_path, pds->service_path); + + ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr, + &txn, SERVREG_REGISTER_LISTENER_REQ, + SERVREG_REGISTER_LISTENER_REQ_LEN, + servreg_register_listener_req_ei, + &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + return ret; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) { + pr_err("PDR: %s register listener txn wait failed: %d\n", + pds->service_path, ret); + return ret; + } + + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + pr_err("PDR: %s register listener failed: 0x%x\n", + pds->service_path, resp.resp.error); + return ret; + } + + if ((int)resp.curr_state < INT_MIN || (int)resp.curr_state > INT_MAX) + pr_err("PDR: %s notification state invalid: 0x%x\n", + pds->service_path, resp.curr_state); + + pds->state = resp.curr_state; + + return 0; +} + +static void pdr_notifier_work(struct work_struct *work) +{ + struct pdr_handle *pdr = container_of(work, struct pdr_handle, + notifier_work); + struct pdr_service *pds; + + mutex_lock(&pdr->list_lock); + list_for_each_entry(pds, &pdr->lookups, node) { + if (pds->service_connected) { + if (!pds->need_notifier_register) + continue; + + pds->need_notifier_register = false; + mutex_unlock(&pdr->list_lock); + pdr_register_listener(pdr, pds, true); + } else { + if (!pds->need_notifier_remove) + continue; + + pds->need_notifier_remove = false; + mutex_unlock(&pdr->list_lock); + pds->state = SERVREG_SERVICE_STATE_DOWN; + } + + mutex_lock(&pdr->status_lock); + pdr->status(pds->state, pds->service_path, pdr->priv); + mutex_unlock(&pdr->status_lock); + + return; + } + mutex_unlock(&pdr->list_lock); +} + +static int pdr_notifier_new_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, + notifier_hdl); + struct pdr_service *pds; + + mutex_lock(&pdr->list_lock); + list_for_each_entry(pds, &pdr->lookups, node) { + if (pds->service == svc->service && + pds->instance == svc->instance) { + pds->service_connected = true; + pds->need_notifier_register = true; + pds->addr.sq_family = AF_QIPCRTR; + pds->addr.sq_node = svc->node; + pds->addr.sq_port = svc->port; + queue_work(pdr->notifier_wq, &pdr->notifier_work); + } + } + mutex_unlock(&pdr->list_lock); + + return 0; +} + +static void pdr_notifier_del_server(struct qmi_handle *qmi, + struct qmi_service *svc) +{ + struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, + notifier_hdl); + struct pdr_service *pds; + + mutex_lock(&pdr->list_lock); + list_for_each_entry(pds, &pdr->lookups, node) { + if (pds->service == svc->service && + pds->instance == svc->instance) { + pds->service_connected = false; + pds->need_notifier_remove = true; + pds->addr.sq_node = 0; + pds->addr.sq_port = 0; + queue_work(pdr->notifier_wq, &pdr->notifier_work); + } + } + mutex_unlock(&pdr->list_lock); +} + +static struct qmi_ops pdr_notifier_ops = { + .new_server = pdr_notifier_new_server, + .del_server = pdr_notifier_del_server, +}; + +static int pdr_send_indack_msg(struct pdr_handle *pdr, struct pdr_service *pds, + u16 tid) +{ + struct servreg_set_ack_resp resp; + struct servreg_set_ack_req req; + struct qmi_txn txn; + int ret; + + ret = qmi_txn_init(&pdr->notifier_hdl, &txn, servreg_set_ack_resp_ei, + &resp); + if (ret < 0) + return ret; + + req.transaction_id = tid; + strcpy(req.service_path, pds->service_path); + + ret = qmi_send_request(&pdr->notifier_hdl, &pds->addr, + &txn, SERVREG_SET_ACK_REQ, + SERVREG_SET_ACK_REQ_LEN, + servreg_set_ack_req_ei, + &req); + + /* Skip waiting for response */ + qmi_txn_cancel(&txn); + return ret; +} + +static void pdr_indack_work(struct work_struct *work) +{ + struct pdr_handle *pdr = container_of(work, struct pdr_handle, + indack_work); + struct pdr_list_node *ind, *tmp; + struct pdr_service *pds; + + list_for_each_entry_safe(ind, tmp, &pdr->indack_list, node) { + pds = ind->pds; + pdr_send_indack_msg(pdr, pds, ind->transaction_id); + + mutex_lock(&pdr->status_lock); + pds->state = ind->curr_state; + pdr->status(pds->state, pds->service_path, pdr->priv); + mutex_unlock(&pdr->status_lock); + + mutex_lock(&pdr->list_lock); + list_del(&ind->node); + mutex_unlock(&pdr->list_lock); + + kfree(ind); + } +} + +static void pdr_indication_cb(struct qmi_handle *qmi, + struct sockaddr_qrtr *sq, + struct qmi_txn *txn, const void *data) +{ + struct pdr_handle *pdr = container_of(qmi, struct pdr_handle, + notifier_hdl); + const struct servreg_state_updated_ind *ind_msg = data; + struct pdr_list_node *ind; + struct pdr_service *pds; + bool found; + + if (!ind_msg || !ind_msg->service_path[0] || + strlen(ind_msg->service_path) > SERVREG_NAME_LENGTH) + return; + + mutex_lock(&pdr->list_lock); + list_for_each_entry(pds, &pdr->lookups, node) { + if (strcmp(pds->service_path, ind_msg->service_path)) + continue; + + found = true; + break; + } + mutex_unlock(&pdr->list_lock); + + if (!found) + return; + + pr_info("PDR: Indication received from %s, state: 0x%x, trans-id: %d\n", + ind_msg->service_path, ind_msg->curr_state, + ind_msg->transaction_id); + + ind = kzalloc(sizeof(*ind), GFP_KERNEL); + if (!ind) + return; + + ind->transaction_id = ind_msg->transaction_id; + ind->curr_state = ind_msg->curr_state; + ind->pds = pds; + + mutex_lock(&pdr->list_lock); + list_add_tail(&ind->node, &pdr->indack_list); + mutex_unlock(&pdr->list_lock); + + queue_work(pdr->indack_wq, &pdr->indack_work); +} + +static struct qmi_msg_handler qmi_indication_handler[] = { + { + .type = QMI_INDICATION, + .msg_id = SERVREG_STATE_UPDATED_IND_ID, + .ei = servreg_state_updated_ind_ei, + .decoded_size = sizeof(struct servreg_state_updated_ind), + .fn = pdr_indication_cb, + }, + {} +}; + +static int pdr_get_domain_list(struct servreg_get_domain_list_req *req, + struct servreg_get_domain_list_resp *resp, + struct pdr_handle *pdr) +{ + struct qmi_txn txn; + int ret; + + ret = qmi_txn_init(&pdr->locator_hdl, &txn, + servreg_get_domain_list_resp_ei, resp); + if (ret < 0) + return ret; + + ret = qmi_send_request(&pdr->locator_hdl, + &pdr->locator_addr, + &txn, SERVREG_GET_DOMAIN_LIST_REQ, + SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN, + servreg_get_domain_list_req_ei, + req); + if (ret < 0) { + qmi_txn_cancel(&txn); + return ret; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) { + pr_err("PDR: %s get domain list txn wait failed: %d\n", + req->service_name, ret); + return ret; + } + + if (resp->resp.result != QMI_RESULT_SUCCESS_V01) { + pr_err("PDR: %s get domain list failed: 0x%x\n", + req->service_name, resp->resp.error); + return -EREMOTEIO; + } + + return 0; +} + +static int pdr_locate_service(struct pdr_handle *pdr, struct pdr_service *pds) +{ + struct servreg_get_domain_list_resp *resp; + struct servreg_get_domain_list_req req; + struct servreg_location_entry *entry; + int domains_read = 0; + int ret, i; + + resp = kzalloc(sizeof(*resp), GFP_KERNEL); + if (!resp) + return -ENOMEM; + + /* Prepare req message */ + strcpy(req.service_name, pds->service_name); + req.domain_offset_valid = true; + req.domain_offset = 0; + + do { + req.domain_offset = domains_read; + ret = pdr_get_domain_list(&req, resp, pdr); + if (ret < 0) + goto out; + + for (i = domains_read; i < resp->domain_list_len; i++) { + entry = &resp->domain_list[i]; + + if (strnlen(entry->name, sizeof(entry->name)) == sizeof(entry->name)) + continue; + + if (!strcmp(entry->name, pds->service_path)) { + pds->service_data_valid = entry->service_data_valid; + pds->service_data = entry->service_data; + pds->instance = entry->instance; + goto out; + } + } + + /* Update ret to indicate that the service is not yet found */ + ret = -ENXIO; + + /* Always read total_domains from the response msg */ + if (resp->domain_list_len > resp->total_domains) + resp->domain_list_len = resp->total_domains; + + domains_read += resp->domain_list_len; + } while (domains_read < resp->total_domains); +out: + kfree(resp); + return ret; +} + +static void pdr_locator_work(struct work_struct *work) +{ + struct pdr_handle *pdr = container_of(work, struct pdr_handle, + locator_work); + struct pdr_service *pds; + int ret = 0; + + /* Bail out early if the SERVREG LOCATOR QMI service is not up */ + mutex_lock(&pdr->lock); + if (!pdr->locator_init_complete) { + mutex_unlock(&pdr->lock); + pr_debug("PDR: SERVICE LOCATOR service not available\n"); + return; + } + mutex_unlock(&pdr->lock); + + mutex_lock(&pdr->list_lock); + list_for_each_entry(pds, &pdr->lookups, node) { + if (!pds->need_locator_lookup) + continue; + + pds->need_locator_lookup = false; + mutex_unlock(&pdr->list_lock); + + ret = pdr_locate_service(pdr, pds); + if (ret < 0) + goto exit; + + /* Initialize notifier QMI handle */ + mutex_lock(&pdr->lock); + if (!pdr->notifier_init_complete) { + ret = qmi_handle_init(&pdr->notifier_hdl, + SERVREG_STATE_UPDATED_IND_MAX_LEN, + &pdr_notifier_ops, + qmi_indication_handler); + if (ret < 0) { + mutex_unlock(&pdr->lock); + goto exit; + } + pdr->notifier_init_complete = true; + } + mutex_unlock(&pdr->lock); + + ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1, + pds->instance); + if (ret < 0) + goto exit; + + return; + } + mutex_unlock(&pdr->list_lock); +exit: + if (ret < 0) { + /* Notify lookup failed */ + mutex_lock(&pdr->list_lock); + list_del(&pds->node); + mutex_unlock(&pdr->list_lock); + + if (ret == -ENXIO) + pds->state = SERVREG_LOCATOR_UNKNOWN_SERVICE; + else + pds->state = SERVREG_LOCATOR_ERR; + + pr_err("PDR: service lookup for %s failed: %d\n", + pds->service_name, ret); + + mutex_lock(&pdr->status_lock); + pdr->status(pds->state, pds->service_path, pdr->priv); + mutex_unlock(&pdr->status_lock); + kfree(pds); + } +} + +/** + * pdr_add_lookup() - register a tracking request for a PD + * @pdr: PDR client handle + * @service_name: service name of the tracking request + * @service_path: service path of the tracking request + * + * Registering a pdr lookup allows for tracking the life cycle of the PD. + * + * Return: pdr_service object on success, ERR_PTR on failure. -EALREADY is + * returned if a lookup is already in progress for the given service path. + */ +struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr, + const char *service_name, + const char *service_path) +{ + struct pdr_service *pds, *tmp; + int ret; + + if (IS_ERR_OR_NULL(pdr)) + return ERR_PTR(-EINVAL); + + if (!service_name || strlen(service_name) > SERVREG_NAME_LENGTH || + !service_path || strlen(service_path) > SERVREG_NAME_LENGTH) + return ERR_PTR(-EINVAL); + + pds = kzalloc(sizeof(*pds), GFP_KERNEL); + if (!pds) + return ERR_PTR(-ENOMEM); + + pds->service = SERVREG_NOTIFIER_SERVICE; + strcpy(pds->service_name, service_name); + strcpy(pds->service_path, service_path); + pds->need_locator_lookup = true; + + mutex_lock(&pdr->list_lock); + list_for_each_entry(tmp, &pdr->lookups, node) { + if (strcmp(tmp->service_path, service_path)) + continue; + + mutex_unlock(&pdr->list_lock); + ret = -EALREADY; + goto err; + } + + list_add(&pds->node, &pdr->lookups); + mutex_unlock(&pdr->list_lock); + + schedule_work(&pdr->locator_work); + + return pds; +err: + kfree(pds); + return ERR_PTR(ret); +} +EXPORT_SYMBOL(pdr_add_lookup); + +/** + * pdr_restart_pd() - restart PD + * @pdr: PDR client handle + * @pds: PD service handle + * + * Restarts the PD tracked by the PDR client handle for a given service path. + * + * Return: 0 on success, negative errno on failure. + */ +int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds) +{ + struct servreg_restart_pd_resp resp; + struct servreg_restart_pd_req req; + struct sockaddr_qrtr addr; + struct pdr_service *tmp; + struct qmi_txn txn; + int ret; + + if (IS_ERR_OR_NULL(pdr) || IS_ERR_OR_NULL(pds)) + return -EINVAL; + + mutex_lock(&pdr->list_lock); + list_for_each_entry(tmp, &pdr->lookups, node) { + if (tmp != pds) + continue; + + if (!pds->service_connected) + break; + + /* Prepare req message */ + strcpy(req.service_path, pds->service_path); + addr = pds->addr; + break; + } + mutex_unlock(&pdr->list_lock); + + if (!req.service_path[0]) + return -EINVAL; + + ret = qmi_txn_init(&pdr->notifier_hdl, &txn, + servreg_restart_pd_resp_ei, + &resp); + if (ret < 0) + return ret; + + ret = qmi_send_request(&pdr->notifier_hdl, &addr, + &txn, SERVREG_RESTART_PD_REQ, + SERVREG_RESTART_PD_REQ_MAX_LEN, + servreg_restart_pd_req_ei, &req); + if (ret < 0) { + qmi_txn_cancel(&txn); + return ret; + } + + ret = qmi_txn_wait(&txn, 5 * HZ); + if (ret < 0) { + pr_err("PDR: %s PD restart txn wait failed: %d\n", + req.service_path, ret); + return ret; + } + + /* Check response if PDR is disabled */ + if (resp.resp.result == QMI_RESULT_FAILURE_V01 && + resp.resp.error == QMI_ERR_DISABLED_V01) { + pr_err("PDR: %s PD restart is disabled: 0x%x\n", + req.service_path, resp.resp.error); + return -EOPNOTSUPP; + } + + /* Check the response for other error case*/ + if (resp.resp.result != QMI_RESULT_SUCCESS_V01) { + pr_err("PDR: %s request for PD restart failed: 0x%x\n", + req.service_path, resp.resp.error); + return -EREMOTEIO; + } + + return 0; +} +EXPORT_SYMBOL(pdr_restart_pd); + +/** + * pdr_handle_alloc() - initialize the PDR client handle + * @status: function to be called on PD state change + * @priv: handle for client's use + * + * Initializes the PDR client handle to allow for tracking/restart of PDs. + * + * Return: pdr_handle object on success, ERR_PTR on failure. + */ +struct pdr_handle *pdr_handle_alloc(void (*status)(int state, + char *service_path, + void *priv), void *priv) +{ + struct pdr_handle *pdr; + int ret; + + if (!status) + return ERR_PTR(-EINVAL); + + pdr = kzalloc(sizeof(*pdr), GFP_KERNEL); + if (!pdr) + return ERR_PTR(-ENOMEM); + + pdr->status = *status; + pdr->priv = priv; + + mutex_init(&pdr->status_lock); + mutex_init(&pdr->list_lock); + mutex_init(&pdr->lock); + + INIT_LIST_HEAD(&pdr->lookups); + INIT_LIST_HEAD(&pdr->indack_list); + + INIT_WORK(&pdr->locator_work, pdr_locator_work); + INIT_WORK(&pdr->notifier_work, pdr_notifier_work); + INIT_WORK(&pdr->indack_work, pdr_indack_work); + + pdr->notifier_wq = create_singlethread_workqueue("pdr_notifier_wq"); + if (!pdr->notifier_wq) { + ret = -ENOMEM; + goto free_pdr_handle; + } + + pdr->indack_wq = alloc_ordered_workqueue("pdr_indack_wq", WQ_HIGHPRI); + if (!pdr->indack_wq) { + ret = -ENOMEM; + goto destroy_notifier; + } + + ret = qmi_handle_init(&pdr->locator_hdl, + SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN, + &pdr_locator_ops, NULL); + if (ret < 0) + goto destroy_indack; + + ret = qmi_add_lookup(&pdr->locator_hdl, SERVREG_LOCATOR_SERVICE, 1, 1); + if (ret < 0) + goto release_qmi_handle; + + return pdr; + +release_qmi_handle: + qmi_handle_release(&pdr->locator_hdl); +destroy_indack: + destroy_workqueue(pdr->indack_wq); +destroy_notifier: + destroy_workqueue(pdr->notifier_wq); +free_pdr_handle: + kfree(pdr); + + return ERR_PTR(ret); +} +EXPORT_SYMBOL(pdr_handle_alloc); + +/** + * pdr_handle_release() - release the PDR client handle + * @pdr: PDR client handle + * + * Cleans up pending tracking requests and releases the underlying qmi handles. + */ +void pdr_handle_release(struct pdr_handle *pdr) +{ + struct pdr_service *pds, *tmp; + + if (IS_ERR_OR_NULL(pdr)) + return; + + mutex_lock(&pdr->list_lock); + list_for_each_entry_safe(pds, tmp, &pdr->lookups, node) { + list_del(&pds->node); + kfree(pds); + } + mutex_unlock(&pdr->list_lock); + + cancel_work_sync(&pdr->locator_work); + cancel_work_sync(&pdr->notifier_work); + cancel_work_sync(&pdr->indack_work); + + destroy_workqueue(pdr->notifier_wq); + destroy_workqueue(pdr->indack_wq); + + qmi_handle_release(&pdr->locator_hdl); + mutex_lock(&pdr->lock); + if (pdr->notifier_init_complete) + qmi_handle_release(&pdr->notifier_hdl); + mutex_unlock(&pdr->lock); + + kfree(pdr); +} +EXPORT_SYMBOL(pdr_handle_release); diff --git a/drivers/soc/qcom/pdr_internal.h b/drivers/soc/qcom/pdr_internal.h new file mode 100644 index 0000000000000..15b5002e4127b --- /dev/null +++ b/drivers/soc/qcom/pdr_internal.h @@ -0,0 +1,379 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __QCOM_PDR_HELPER_INTERNAL__ +#define __QCOM_PDR_HELPER_INTERNAL__ + +#include <linux/soc/qcom/pdr.h> + +#define SERVREG_LOCATOR_SERVICE 0x40 +#define SERVREG_NOTIFIER_SERVICE 0x42 + +#define SERVREG_REGISTER_LISTENER_REQ 0x20 +#define SERVREG_GET_DOMAIN_LIST_REQ 0x21 +#define SERVREG_STATE_UPDATED_IND_ID 0x22 +#define SERVREG_SET_ACK_REQ 0x23 +#define SERVREG_RESTART_PD_REQ 0x24 + +#define SERVREG_DOMAIN_LIST_LENGTH 32 +#define SERVREG_RESTART_PD_REQ_MAX_LEN 67 +#define SERVREG_REGISTER_LISTENER_REQ_LEN 71 +#define SERVREG_SET_ACK_REQ_LEN 72 +#define SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN 74 +#define SERVREG_STATE_UPDATED_IND_MAX_LEN 79 +#define SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN 2389 + +struct servreg_location_entry { + char name[SERVREG_NAME_LENGTH + 1]; + u8 service_data_valid; + u32 service_data; + u32 instance; +}; + +struct qmi_elem_info servreg_location_entry_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = SERVREG_NAME_LENGTH + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_location_entry, + name), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_location_entry, + instance), + }, + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_location_entry, + service_data_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0, + .offset = offsetof(struct servreg_location_entry, + service_data), + }, + {} +}; + +struct servreg_get_domain_list_req { + char service_name[SERVREG_NAME_LENGTH + 1]; + u8 domain_offset_valid; + u32 domain_offset; +}; + +struct qmi_elem_info servreg_get_domain_list_req_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = SERVREG_NAME_LENGTH + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct servreg_get_domain_list_req, + service_name), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct servreg_get_domain_list_req, + domain_offset_valid), + }, + { + .data_type = QMI_UNSIGNED_4_BYTE, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct servreg_get_domain_list_req, + domain_offset), + }, + {} +}; + +struct servreg_get_domain_list_resp { + struct qmi_response_type_v01 resp; + u8 total_domains_valid; + u16 total_domains; + u8 db_rev_count_valid; + u16 db_rev_count; + u8 domain_list_valid; + u32 domain_list_len; + struct servreg_location_entry domain_list[SERVREG_DOMAIN_LIST_LENGTH]; +}; + +struct qmi_elem_info servreg_get_domain_list_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_get_domain_list_resp, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct servreg_get_domain_list_resp, + total_domains_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct servreg_get_domain_list_resp, + total_domains), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct servreg_get_domain_list_resp, + db_rev_count_valid), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x11, + .offset = offsetof(struct servreg_get_domain_list_resp, + db_rev_count), + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct servreg_get_domain_list_resp, + domain_list_valid), + }, + { + .data_type = QMI_DATA_LEN, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct servreg_get_domain_list_resp, + domain_list_len), + }, + { + .data_type = QMI_STRUCT, + .elem_len = SERVREG_DOMAIN_LIST_LENGTH, + .elem_size = sizeof(struct servreg_location_entry), + .array_type = NO_ARRAY, + .tlv_type = 0x12, + .offset = offsetof(struct servreg_get_domain_list_resp, + domain_list), + .ei_array = servreg_location_entry_ei, + }, + {} +}; + +struct servreg_register_listener_req { + u8 enable; + char service_path[SERVREG_NAME_LENGTH + 1]; +}; + +struct qmi_elem_info servreg_register_listener_req_ei[] = { + { + .data_type = QMI_UNSIGNED_1_BYTE, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct servreg_register_listener_req, + enable), + }, + { + .data_type = QMI_STRING, + .elem_len = SERVREG_NAME_LENGTH + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_register_listener_req, + service_path), + }, + {} +}; + +struct servreg_register_listener_resp { + struct qmi_response_type_v01 resp; + u8 curr_state_valid; + enum servreg_service_state curr_state; +}; + +struct qmi_elem_info servreg_register_listener_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_register_listener_resp, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + { + .data_type = QMI_OPT_FLAG, + .elem_len = 1, + .elem_size = sizeof(u8), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct servreg_register_listener_resp, + curr_state_valid), + }, + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(enum servreg_service_state), + .array_type = NO_ARRAY, + .tlv_type = 0x10, + .offset = offsetof(struct servreg_register_listener_resp, + curr_state), + }, + {} +}; + +struct servreg_restart_pd_req { + char service_path[SERVREG_NAME_LENGTH + 1]; +}; + +struct qmi_elem_info servreg_restart_pd_req_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = SERVREG_NAME_LENGTH + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct servreg_restart_pd_req, + service_path), + }, + {} +}; + +struct servreg_restart_pd_resp { + struct qmi_response_type_v01 resp; +}; + +struct qmi_elem_info servreg_restart_pd_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_restart_pd_resp, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + {} +}; + +struct servreg_state_updated_ind { + enum servreg_service_state curr_state; + char service_path[SERVREG_NAME_LENGTH + 1]; + u16 transaction_id; +}; + +struct qmi_elem_info servreg_state_updated_ind_ei[] = { + { + .data_type = QMI_SIGNED_4_BYTE_ENUM, + .elem_len = 1, + .elem_size = sizeof(u32), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct servreg_state_updated_ind, + curr_state), + }, + { + .data_type = QMI_STRING, + .elem_len = SERVREG_NAME_LENGTH + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_state_updated_ind, + service_path), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x03, + .offset = offsetof(struct servreg_state_updated_ind, + transaction_id), + }, + {} +}; + +struct servreg_set_ack_req { + char service_path[SERVREG_NAME_LENGTH + 1]; + u16 transaction_id; +}; + +struct qmi_elem_info servreg_set_ack_req_ei[] = { + { + .data_type = QMI_STRING, + .elem_len = SERVREG_NAME_LENGTH + 1, + .elem_size = sizeof(char), + .array_type = NO_ARRAY, + .tlv_type = 0x01, + .offset = offsetof(struct servreg_set_ack_req, + service_path), + }, + { + .data_type = QMI_UNSIGNED_2_BYTE, + .elem_len = 1, + .elem_size = sizeof(u16), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_set_ack_req, + transaction_id), + }, + {} +}; + +struct servreg_set_ack_resp { + struct qmi_response_type_v01 resp; +}; + +struct qmi_elem_info servreg_set_ack_resp_ei[] = { + { + .data_type = QMI_STRUCT, + .elem_len = 1, + .elem_size = sizeof(struct qmi_response_type_v01), + .array_type = NO_ARRAY, + .tlv_type = 0x02, + .offset = offsetof(struct servreg_set_ack_resp, + resp), + .ei_array = qmi_response_type_v01_ei, + }, + {} +}; + +#endif diff --git a/include/linux/soc/qcom/pdr.h b/include/linux/soc/qcom/pdr.h new file mode 100644 index 0000000000000..b725a9cb0df08 --- /dev/null +++ b/include/linux/soc/qcom/pdr.h @@ -0,0 +1,30 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __QCOM_PDR_HELPER__ +#define __QCOM_PDR_HELPER__ + +#include <linux/soc/qcom/qmi.h> + +#define SERVREG_NAME_LENGTH 64 + +struct pdr_service; +struct pdr_handle; + +enum servreg_service_state { + SERVREG_LOCATOR_ERR = 0x1, + SERVREG_LOCATOR_UNKNOWN_SERVICE = 0x2, + SERVREG_SERVICE_STATE_DOWN = 0x0FFFFFFF, + SERVREG_SERVICE_STATE_UP = 0x1FFFFFFF, + SERVREG_SERVICE_STATE_EARLY_DOWN = 0x2FFFFFFF, + SERVREG_SERVICE_STATE_UNINIT = 0x7FFFFFFF, +}; + +struct pdr_handle *pdr_handle_alloc(void (*status)(int state, + char *service_path, + void *priv), void *priv); +struct pdr_service *pdr_add_lookup(struct pdr_handle *pdr, + const char *service_name, + const char *service_path); +int pdr_restart_pd(struct pdr_handle *pdr, struct pdr_service *pds); +void pdr_handle_release(struct pdr_handle *pdr); + +#endif diff --git a/include/linux/soc/qcom/qmi.h b/include/linux/soc/qcom/qmi.h index 5efa2b67fa557..e712f94b89fcc 100644 --- a/include/linux/soc/qcom/qmi.h +++ b/include/linux/soc/qcom/qmi.h @@ -88,6 +88,7 @@ struct qmi_elem_info { #define QMI_ERR_CLIENT_IDS_EXHAUSTED_V01 5 #define QMI_ERR_INVALID_ID_V01 41 #define QMI_ERR_ENCODING_V01 58 +#define QMI_ERR_DISABLED_V01 69 #define QMI_ERR_INCOMPATIBLE_STATE_V01 90 #define QMI_ERR_NOT_SUPPORTED_V01 94 -- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers 2020-02-26 16:59 ` [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers Sibi Sankar @ 2020-02-28 5:34 ` Bjorn Andersson 0 siblings, 0 replies; 10+ messages in thread From: Bjorn Andersson @ 2020-02-28 5:34 UTC (permalink / raw) To: Sibi Sankar Cc: srinivas.kandagatla, robh+dt, agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta On Wed 26 Feb 08:59 PST 2020, Sibi Sankar wrote: > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig [..] > +config QCOM_PDR_HELPERS > + tristate > + depends on ARCH_QCOM || COMPILE_TEST As discussed on one of you other patches, please omit the depends on for Kconfig entries that are not user selectable. Presumably anyone selecting this option will have ARCH_QCOM met already. > + select QCOM_QMI_HELPERS [..] > diff --git a/drivers/soc/qcom/pdr_interface.c b/drivers/soc/qcom/pdr_interface.c [..] > +static void pdr_locator_work(struct work_struct *work) > +{ > + struct pdr_handle *pdr = container_of(work, struct pdr_handle, > + locator_work); > + struct pdr_service *pds; > + int ret = 0; > + > + /* Bail out early if the SERVREG LOCATOR QMI service is not up */ > + mutex_lock(&pdr->lock); > + if (!pdr->locator_init_complete) { > + mutex_unlock(&pdr->lock); > + pr_debug("PDR: SERVICE LOCATOR service not available\n"); > + return; > + } > + mutex_unlock(&pdr->lock); > + > + mutex_lock(&pdr->list_lock); > + list_for_each_entry(pds, &pdr->lookups, node) { > + if (!pds->need_locator_lookup) > + continue; > + > + pds->need_locator_lookup = false; > + mutex_unlock(&pdr->list_lock); > + > + ret = pdr_locate_service(pdr, pds); > + if (ret < 0) > + goto exit; > + > + /* Initialize notifier QMI handle */ > + mutex_lock(&pdr->lock); > + if (!pdr->notifier_init_complete) { > + ret = qmi_handle_init(&pdr->notifier_hdl, > + SERVREG_STATE_UPDATED_IND_MAX_LEN, > + &pdr_notifier_ops, > + qmi_indication_handler); > + if (ret < 0) { > + mutex_unlock(&pdr->lock); > + goto exit; > + } > + pdr->notifier_init_complete = true; > + } > + mutex_unlock(&pdr->lock); > + > + ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1, > + pds->instance); > + if (ret < 0) > + goto exit; > + > + return; If the caller calls pdr_add_lookup() multiple times in quick succession wouldn't it be possile to get the worker scheduled with multiple entries in &pdr->lookups with need_locator_lookup set? If so I think it makes sense to break the content of this loop, and the error handling under exit out into a separate function. And even if this would not be the case, breaking this out in a separate function would allow you to change the loop to: list_for_each_entry() { if (pdr->need_locator_lookup) { do_the_lookup(); break; } } Which I think is easier to reason about than the loop with a return at the end. > + } > + mutex_unlock(&pdr->list_lock); > +exit: > + if (ret < 0) { > + /* Notify lookup failed */ > + mutex_lock(&pdr->list_lock); > + list_del(&pds->node); > + mutex_unlock(&pdr->list_lock); > + > + if (ret == -ENXIO) > + pds->state = SERVREG_LOCATOR_UNKNOWN_SERVICE; > + else > + pds->state = SERVREG_LOCATOR_ERR; > + > + pr_err("PDR: service lookup for %s failed: %d\n", > + pds->service_name, ret); > + > + mutex_lock(&pdr->status_lock); > + pdr->status(pds->state, pds->service_path, pdr->priv); > + mutex_unlock(&pdr->status_lock); > + kfree(pds); > + } > +} [..] > +struct pdr_handle *pdr_handle_alloc(void (*status)(int state, > + char *service_path, > + void *priv), void *priv) > +{ > + struct pdr_handle *pdr; > + int ret; > + > + if (!status) > + return ERR_PTR(-EINVAL); > + > + pdr = kzalloc(sizeof(*pdr), GFP_KERNEL); > + if (!pdr) > + return ERR_PTR(-ENOMEM); > + > + pdr->status = *status; Please omit the * here. Regards, Bjorn ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers @ 2020-02-28 5:34 ` Bjorn Andersson 0 siblings, 0 replies; 10+ messages in thread From: Bjorn Andersson @ 2020-02-28 5:34 UTC (permalink / raw) To: Sibi Sankar Cc: srinivas.kandagatla, robh+dt, agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta On Wed 26 Feb 08:59 PST 2020, Sibi Sankar wrote: > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig [..] > +config QCOM_PDR_HELPERS > + tristate > + depends on ARCH_QCOM || COMPILE_TEST As discussed on one of you other patches, please omit the depends on for Kconfig entries that are not user selectable. Presumably anyone selecting this option will have ARCH_QCOM met already. > + select QCOM_QMI_HELPERS [..] > diff --git a/drivers/soc/qcom/pdr_interface.c b/drivers/soc/qcom/pdr_interface.c [..] > +static void pdr_locator_work(struct work_struct *work) > +{ > + struct pdr_handle *pdr = container_of(work, struct pdr_handle, > + locator_work); > + struct pdr_service *pds; > + int ret = 0; > + > + /* Bail out early if the SERVREG LOCATOR QMI service is not up */ > + mutex_lock(&pdr->lock); > + if (!pdr->locator_init_complete) { > + mutex_unlock(&pdr->lock); > + pr_debug("PDR: SERVICE LOCATOR service not available\n"); > + return; > + } > + mutex_unlock(&pdr->lock); > + > + mutex_lock(&pdr->list_lock); > + list_for_each_entry(pds, &pdr->lookups, node) { > + if (!pds->need_locator_lookup) > + continue; > + > + pds->need_locator_lookup = false; > + mutex_unlock(&pdr->list_lock); > + > + ret = pdr_locate_service(pdr, pds); > + if (ret < 0) > + goto exit; > + > + /* Initialize notifier QMI handle */ > + mutex_lock(&pdr->lock); > + if (!pdr->notifier_init_complete) { > + ret = qmi_handle_init(&pdr->notifier_hdl, > + SERVREG_STATE_UPDATED_IND_MAX_LEN, > + &pdr_notifier_ops, > + qmi_indication_handler); > + if (ret < 0) { > + mutex_unlock(&pdr->lock); > + goto exit; > + } > + pdr->notifier_init_complete = true; > + } > + mutex_unlock(&pdr->lock); > + > + ret = qmi_add_lookup(&pdr->notifier_hdl, pds->service, 1, > + pds->instance); > + if (ret < 0) > + goto exit; > + > + return; If the caller calls pdr_add_lookup() multiple times in quick succession wouldn't it be possile to get the worker scheduled with multiple entries in &pdr->lookups with need_locator_lookup set? If so I think it makes sense to break the content of this loop, and the error handling under exit out into a separate function. And even if this would not be the case, breaking this out in a separate function would allow you to change the loop to: list_for_each_entry() { if (pdr->need_locator_lookup) { do_the_lookup(); break; } } Which I think is easier to reason about than the loop with a return at the end. > + } > + mutex_unlock(&pdr->list_lock); > +exit: > + if (ret < 0) { > + /* Notify lookup failed */ > + mutex_lock(&pdr->list_lock); > + list_del(&pds->node); > + mutex_unlock(&pdr->list_lock); > + > + if (ret == -ENXIO) > + pds->state = SERVREG_LOCATOR_UNKNOWN_SERVICE; > + else > + pds->state = SERVREG_LOCATOR_ERR; > + > + pr_err("PDR: service lookup for %s failed: %d\n", > + pds->service_name, ret); > + > + mutex_lock(&pdr->status_lock); > + pdr->status(pds->state, pds->service_path, pdr->priv); > + mutex_unlock(&pdr->status_lock); > + kfree(pds); > + } > +} [..] > +struct pdr_handle *pdr_handle_alloc(void (*status)(int state, > + char *service_path, > + void *priv), void *priv) > +{ > + struct pdr_handle *pdr; > + int ret; > + > + if (!status) > + return ERR_PTR(-EINVAL); > + > + pdr = kzalloc(sizeof(*pdr), GFP_KERNEL); > + if (!pdr) > + return ERR_PTR(-ENOMEM); > + > + pdr->status = *status; Please omit the * here. Regards, Bjorn ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings 2020-02-26 16:59 [PATCH v4 0/3] Introduce Protection Domain Restart (PDR) Helpers Sibi Sankar 2020-02-26 16:59 ` [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers Sibi Sankar @ 2020-02-26 17:00 ` Sibi Sankar 2020-02-28 5:35 ` Bjorn Andersson 2020-02-26 17:00 ` [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality Sibi Sankar 2 siblings, 1 reply; 10+ messages in thread From: Sibi Sankar @ 2020-02-26 17:00 UTC (permalink / raw) To: bjorn.andersson, srinivas.kandagatla, robh+dt Cc: agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta, Sibi Sankar Qualcomm SoCs (starting with MSM8998) allow for multiple protection domains (PDs) to run on the same Q6 sub-system. This allows for services like AVS AUDIO to have their own separate address space and crash/recover without disrupting the other PDs running on the same Q6 ADSP. Add "qcom,protection-domain" bindings to capture the dependencies between the APR service and the PD on which the apr service runs. Signed-off-by: Sibi Sankar <sibis@codeaurora.org> --- .../devicetree/bindings/soc/qcom/qcom,apr.txt | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt index db501269f47b8..f8fa71f5d84ba 100644 --- a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt @@ -45,6 +45,18 @@ by the individual bindings for the specific service 12 - Ultrasound stream manager. 13 - Listen stream manager. +- qcom,protection-domain + Usage: optional + Value type: <stringlist> + Definition: Must list the protection domain service name and path + that the particular apr service has a dependency on. + Possible values are : + "avs/audio", "msm/adsp/audio_pd". + "kernel/elf_loader", "msm/modem/wlan_pd". + "tms/servreg", "msm/adsp/audio_pd". + "tms/servreg", "msm/modem/wlan_pd". + "tms/servreg", "msm/slpi/sensor_pd". + = EXAMPLE The following example represents a QDSP based sound card on a MSM8996 device which uses apr as communication between Apps and QDSP. @@ -82,3 +94,41 @@ which uses apr as communication between Apps and QDSP. ... }; }; + += EXAMPLE 2 +The following example represents a QDSP based sound card with protection domain +dependencies specified. Here some of the apr services are dependent on services +running on protection domain hosted on ADSP/SLPI remote processors while others +have no such dependency. + + apr { + compatible = "qcom,apr-v2"; + qcom,glink-channels = "apr_audio_svc"; + qcom,apr-domain = <APR_DOMAIN_ADSP>; + + q6core { + compatible = "qcom,q6core"; + reg = <APR_SVC_ADSP_CORE>; + }; + + q6afe: q6afe { + compatible = "qcom,q6afe"; + reg = <APR_SVC_AFE>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + ... + }; + + q6asm: q6asm { + compatible = "qcom,q6asm"; + reg = <APR_SVC_ASM>; + qcom,protection-domain = "tms/servreg", "msm/slpi/sensor_pd"; + ... + }; + + q6adm: q6adm { + compatible = "qcom,q6adm"; + reg = <APR_SVC_ADM>; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + ... + }; + }; -- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings 2020-02-26 17:00 ` [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings Sibi Sankar @ 2020-02-28 5:35 ` Bjorn Andersson 0 siblings, 0 replies; 10+ messages in thread From: Bjorn Andersson @ 2020-02-28 5:35 UTC (permalink / raw) To: Sibi Sankar Cc: srinivas.kandagatla, robh+dt, agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta On Wed 26 Feb 09:00 PST 2020, Sibi Sankar wrote: > Qualcomm SoCs (starting with MSM8998) allow for multiple protection > domains (PDs) to run on the same Q6 sub-system. This allows for > services like AVS AUDIO to have their own separate address space and > crash/recover without disrupting the other PDs running on the same Q6 > ADSP. Add "qcom,protection-domain" bindings to capture the dependencies > between the APR service and the PD on which the apr service runs. > Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org> > Signed-off-by: Sibi Sankar <sibis@codeaurora.org> > --- > .../devicetree/bindings/soc/qcom/qcom,apr.txt | 50 +++++++++++++++++++ > 1 file changed, 50 insertions(+) > > diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt > index db501269f47b8..f8fa71f5d84ba 100644 > --- a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt > +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt > @@ -45,6 +45,18 @@ by the individual bindings for the specific service > 12 - Ultrasound stream manager. > 13 - Listen stream manager. > > +- qcom,protection-domain > + Usage: optional > + Value type: <stringlist> > + Definition: Must list the protection domain service name and path > + that the particular apr service has a dependency on. > + Possible values are : > + "avs/audio", "msm/adsp/audio_pd". > + "kernel/elf_loader", "msm/modem/wlan_pd". > + "tms/servreg", "msm/adsp/audio_pd". > + "tms/servreg", "msm/modem/wlan_pd". > + "tms/servreg", "msm/slpi/sensor_pd". > + > = EXAMPLE > The following example represents a QDSP based sound card on a MSM8996 device > which uses apr as communication between Apps and QDSP. > @@ -82,3 +94,41 @@ which uses apr as communication between Apps and QDSP. > ... > }; > }; > + > += EXAMPLE 2 > +The following example represents a QDSP based sound card with protection domain > +dependencies specified. Here some of the apr services are dependent on services > +running on protection domain hosted on ADSP/SLPI remote processors while others > +have no such dependency. > + > + apr { > + compatible = "qcom,apr-v2"; > + qcom,glink-channels = "apr_audio_svc"; > + qcom,apr-domain = <APR_DOMAIN_ADSP>; > + > + q6core { > + compatible = "qcom,q6core"; > + reg = <APR_SVC_ADSP_CORE>; > + }; > + > + q6afe: q6afe { > + compatible = "qcom,q6afe"; > + reg = <APR_SVC_AFE>; > + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; > + ... > + }; > + > + q6asm: q6asm { > + compatible = "qcom,q6asm"; > + reg = <APR_SVC_ASM>; > + qcom,protection-domain = "tms/servreg", "msm/slpi/sensor_pd"; > + ... > + }; > + > + q6adm: q6adm { > + compatible = "qcom,q6adm"; > + reg = <APR_SVC_ADM>; > + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; > + ... > + }; > + }; > -- > The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, > a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings @ 2020-02-28 5:35 ` Bjorn Andersson 0 siblings, 0 replies; 10+ messages in thread From: Bjorn Andersson @ 2020-02-28 5:35 UTC (permalink / raw) To: Sibi Sankar Cc: srinivas.kandagatla, robh+dt, agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta On Wed 26 Feb 09:00 PST 2020, Sibi Sankar wrote: > Qualcomm SoCs (starting with MSM8998) allow for multiple protection > domains (PDs) to run on the same Q6 sub-system. This allows for > services like AVS AUDIO to have their own separate address space and > crash/recover without disrupting the other PDs running on the same Q6 > ADSP. Add "qcom,protection-domain" bindings to capture the dependencies > between the APR service and the PD on which the apr service runs. > Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org> > Signed-off-by: Sibi Sankar <sibis@codeaurora.org> > --- > .../devicetree/bindings/soc/qcom/qcom,apr.txt | 50 +++++++++++++++++++ > 1 file changed, 50 insertions(+) > > diff --git a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt > index db501269f47b8..f8fa71f5d84ba 100644 > --- a/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt > +++ b/Documentation/devicetree/bindings/soc/qcom/qcom,apr.txt > @@ -45,6 +45,18 @@ by the individual bindings for the specific service > 12 - Ultrasound stream manager. > 13 - Listen stream manager. > > +- qcom,protection-domain > + Usage: optional > + Value type: <stringlist> > + Definition: Must list the protection domain service name and path > + that the particular apr service has a dependency on. > + Possible values are : > + "avs/audio", "msm/adsp/audio_pd". > + "kernel/elf_loader", "msm/modem/wlan_pd". > + "tms/servreg", "msm/adsp/audio_pd". > + "tms/servreg", "msm/modem/wlan_pd". > + "tms/servreg", "msm/slpi/sensor_pd". > + > = EXAMPLE > The following example represents a QDSP based sound card on a MSM8996 device > which uses apr as communication between Apps and QDSP. > @@ -82,3 +94,41 @@ which uses apr as communication between Apps and QDSP. > ... > }; > }; > + > += EXAMPLE 2 > +The following example represents a QDSP based sound card with protection domain > +dependencies specified. Here some of the apr services are dependent on services > +running on protection domain hosted on ADSP/SLPI remote processors while others > +have no such dependency. > + > + apr { > + compatible = "qcom,apr-v2"; > + qcom,glink-channels = "apr_audio_svc"; > + qcom,apr-domain = <APR_DOMAIN_ADSP>; > + > + q6core { > + compatible = "qcom,q6core"; > + reg = <APR_SVC_ADSP_CORE>; > + }; > + > + q6afe: q6afe { > + compatible = "qcom,q6afe"; > + reg = <APR_SVC_AFE>; > + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; > + ... > + }; > + > + q6asm: q6asm { > + compatible = "qcom,q6asm"; > + reg = <APR_SVC_ASM>; > + qcom,protection-domain = "tms/servreg", "msm/slpi/sensor_pd"; > + ... > + }; > + > + q6adm: q6adm { > + compatible = "qcom,q6adm"; > + reg = <APR_SVC_ADM>; > + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; > + ... > + }; > + }; > -- > The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, > a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 10+ messages in thread
* [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality 2020-02-26 16:59 [PATCH v4 0/3] Introduce Protection Domain Restart (PDR) Helpers Sibi Sankar 2020-02-26 16:59 ` [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers Sibi Sankar 2020-02-26 17:00 ` [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings Sibi Sankar @ 2020-02-26 17:00 ` Sibi Sankar 2020-02-28 5:38 ` Bjorn Andersson 2 siblings, 1 reply; 10+ messages in thread From: Sibi Sankar @ 2020-02-26 17:00 UTC (permalink / raw) To: bjorn.andersson, srinivas.kandagatla, robh+dt Cc: agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta, Sibi Sankar Use PDR helper functions to track the protection domains that the apr services are dependent upon on SDM845 SoC, specifically the "avs/audio" service running on ADSP Q6. Signed-off-by: Sibi Sankar <sibis@codeaurora.org> --- drivers/soc/qcom/Kconfig | 1 + drivers/soc/qcom/apr.c | 123 ++++++++++++++++++++++++++++++++--- include/linux/soc/qcom/apr.h | 1 + 3 files changed, 116 insertions(+), 9 deletions(-) diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index cca6a43e771d9..57000f1615ada 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -202,6 +202,7 @@ config QCOM_APR tristate "Qualcomm APR Bus (Asynchronous Packet Router)" depends on ARCH_QCOM || COMPILE_TEST depends on RPMSG + select QCOM_PDR_HELPERS help Enable APR IPC protocol support between application processor and QDSP6. APR is diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c index 4fcc32420c474..1f35b097c6356 100644 --- a/drivers/soc/qcom/apr.c +++ b/drivers/soc/qcom/apr.c @@ -11,6 +11,7 @@ #include <linux/workqueue.h> #include <linux/of_device.h> #include <linux/soc/qcom/apr.h> +#include <linux/soc/qcom/pdr.h> #include <linux/rpmsg.h> #include <linux/of.h> @@ -21,6 +22,7 @@ struct apr { spinlock_t rx_lock; struct idr svcs_idr; int dest_domain_id; + struct pdr_handle *pdr; struct workqueue_struct *rxwq; struct work_struct rx_work; struct list_head rx_list; @@ -289,6 +291,9 @@ static int apr_add_device(struct device *dev, struct device_node *np, id->svc_id + 1, GFP_ATOMIC); spin_unlock(&apr->svcs_lock); + of_property_read_string_index(np, "qcom,protection-domain", + 1, &adev->service_path); + dev_info(dev, "Adding APR dev: %s\n", dev_name(&adev->dev)); ret = device_register(&adev->dev); @@ -300,14 +305,75 @@ static int apr_add_device(struct device *dev, struct device_node *np, return ret; } -static void of_register_apr_devices(struct device *dev) +static int of_apr_add_pd_lookups(struct device *dev) +{ + const char *service_name, *service_path; + struct apr *apr = dev_get_drvdata(dev); + struct device_node *node; + struct pdr_service *pds; + int ret; + + for_each_child_of_node(dev->of_node, node) { + ret = of_property_read_string_index(node, "qcom,protection-domain", + 0, &service_name); + if (ret < 0) + continue; + + ret = of_property_read_string_index(node, "qcom,protection-domain", + 1, &service_path); + if (ret < 0) { + dev_err(dev, "pdr service path missing: %d\n", ret); + return ret; + } + + pds = pdr_add_lookup(apr->pdr, service_name, service_path); + if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { + dev_err(dev, "pdr add lookup failed: %d\n", ret); + return PTR_ERR(pds); + } + } + + return 0; +} + +static void of_register_apr_devices(struct device *dev, const char *svc_path) { struct apr *apr = dev_get_drvdata(dev); struct device_node *node; + const char *service_path; + int ret; for_each_child_of_node(dev->of_node, node) { struct apr_device_id id = { {0} }; + /* + * This function is called with svc_path NULL during + * apr_probe(), in which case we register any apr devices + * without a qcom,protection-domain specified. + * + * Then as the protection domains becomes available + * (if applicable) this function is again called, but with + * svc_path representing the service becoming available. In + * this case we register any apr devices with a matching + * qcom,protection-domain. + */ + + ret = of_property_read_string_index(node, "qcom,protection-domain", + 1, &service_path); + if (svc_path) { + /* skip APR services that are PD independent */ + if (ret) + continue; + + /* skip APR services whose PD paths don't match */ + if (strcmp(service_path, svc_path)) + continue; + } else { + /* skip APR services whose PD lookups are registered */ + if (ret == 0) + continue; + } + if (of_property_read_u32(node, "reg", &id.svc_id)) continue; @@ -318,6 +384,34 @@ static void of_register_apr_devices(struct device *dev) } } +static int apr_remove_device(struct device *dev, void *svc_path) +{ + struct apr_device *adev = to_apr_device(dev); + + if (svc_path && adev->service_path) { + if (!strcmp(adev->service_path, (char *)svc_path)) + device_unregister(&adev->dev); + } else { + device_unregister(&adev->dev); + } + + return 0; +} + +static void apr_pd_status(int state, char *svc_path, void *priv) +{ + struct apr *apr = (struct apr *)priv; + + switch (state) { + case SERVREG_SERVICE_STATE_UP: + of_register_apr_devices(apr->dev, svc_path); + break; + case SERVREG_SERVICE_STATE_DOWN: + device_for_each_child(apr->dev, svc_path, apr_remove_device); + break; + } +} + static int apr_probe(struct rpmsg_device *rpdev) { struct device *dev = &rpdev->dev; @@ -343,28 +437,39 @@ static int apr_probe(struct rpmsg_device *rpdev) return -ENOMEM; } INIT_WORK(&apr->rx_work, apr_rxwq); + + apr->pdr = pdr_handle_alloc(apr_pd_status, apr); + if (IS_ERR(apr->pdr)) { + dev_err(dev, "Failed to init PDR handle\n"); + ret = PTR_ERR(apr->pdr); + goto destroy_wq; + } + INIT_LIST_HEAD(&apr->rx_list); spin_lock_init(&apr->rx_lock); spin_lock_init(&apr->svcs_lock); idr_init(&apr->svcs_idr); - of_register_apr_devices(dev); - - return 0; -} -static int apr_remove_device(struct device *dev, void *null) -{ - struct apr_device *adev = to_apr_device(dev); + ret = of_apr_add_pd_lookups(dev); + if (ret) + goto handle_release; - device_unregister(&adev->dev); + of_register_apr_devices(dev, NULL); return 0; + +handle_release: + pdr_handle_release(apr->pdr); +destroy_wq: + destroy_workqueue(apr->rxwq); + return ret; } static void apr_remove(struct rpmsg_device *rpdev) { struct apr *apr = dev_get_drvdata(&rpdev->dev); + pdr_handle_release(apr->pdr); device_for_each_child(&rpdev->dev, NULL, apr_remove_device); flush_workqueue(apr->rxwq); destroy_workqueue(apr->rxwq); diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h index c5d52e2cb275f..7f0bc3cf4d610 100644 --- a/include/linux/soc/qcom/apr.h +++ b/include/linux/soc/qcom/apr.h @@ -85,6 +85,7 @@ struct apr_device { uint16_t domain_id; uint32_t version; char name[APR_NAME_SIZE]; + const char *service_path; spinlock_t lock; struct list_head node; }; -- The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, a Linux Foundation Collaborative Project ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality 2020-02-26 17:00 ` [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality Sibi Sankar @ 2020-02-28 5:38 ` Bjorn Andersson 0 siblings, 0 replies; 10+ messages in thread From: Bjorn Andersson @ 2020-02-28 5:38 UTC (permalink / raw) To: Sibi Sankar Cc: srinivas.kandagatla, robh+dt, agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta On Wed 26 Feb 09:00 PST 2020, Sibi Sankar wrote: > Use PDR helper functions to track the protection domains that the apr > services are dependent upon on SDM845 SoC, specifically the "avs/audio" > service running on ADSP Q6. > > Signed-off-by: Sibi Sankar <sibis@codeaurora.org> Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org> > --- Please do include a changelog as you respin your patches. Regards, Bjorn > drivers/soc/qcom/Kconfig | 1 + > drivers/soc/qcom/apr.c | 123 ++++++++++++++++++++++++++++++++--- > include/linux/soc/qcom/apr.h | 1 + > 3 files changed, 116 insertions(+), 9 deletions(-) > > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig > index cca6a43e771d9..57000f1615ada 100644 > --- a/drivers/soc/qcom/Kconfig > +++ b/drivers/soc/qcom/Kconfig > @@ -202,6 +202,7 @@ config QCOM_APR > tristate "Qualcomm APR Bus (Asynchronous Packet Router)" > depends on ARCH_QCOM || COMPILE_TEST > depends on RPMSG > + select QCOM_PDR_HELPERS > help > Enable APR IPC protocol support between > application processor and QDSP6. APR is > diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c > index 4fcc32420c474..1f35b097c6356 100644 > --- a/drivers/soc/qcom/apr.c > +++ b/drivers/soc/qcom/apr.c > @@ -11,6 +11,7 @@ > #include <linux/workqueue.h> > #include <linux/of_device.h> > #include <linux/soc/qcom/apr.h> > +#include <linux/soc/qcom/pdr.h> > #include <linux/rpmsg.h> > #include <linux/of.h> > > @@ -21,6 +22,7 @@ struct apr { > spinlock_t rx_lock; > struct idr svcs_idr; > int dest_domain_id; > + struct pdr_handle *pdr; > struct workqueue_struct *rxwq; > struct work_struct rx_work; > struct list_head rx_list; > @@ -289,6 +291,9 @@ static int apr_add_device(struct device *dev, struct device_node *np, > id->svc_id + 1, GFP_ATOMIC); > spin_unlock(&apr->svcs_lock); > > + of_property_read_string_index(np, "qcom,protection-domain", > + 1, &adev->service_path); > + > dev_info(dev, "Adding APR dev: %s\n", dev_name(&adev->dev)); > > ret = device_register(&adev->dev); > @@ -300,14 +305,75 @@ static int apr_add_device(struct device *dev, struct device_node *np, > return ret; > } > > -static void of_register_apr_devices(struct device *dev) > +static int of_apr_add_pd_lookups(struct device *dev) > +{ > + const char *service_name, *service_path; > + struct apr *apr = dev_get_drvdata(dev); > + struct device_node *node; > + struct pdr_service *pds; > + int ret; > + > + for_each_child_of_node(dev->of_node, node) { > + ret = of_property_read_string_index(node, "qcom,protection-domain", > + 0, &service_name); > + if (ret < 0) > + continue; > + > + ret = of_property_read_string_index(node, "qcom,protection-domain", > + 1, &service_path); > + if (ret < 0) { > + dev_err(dev, "pdr service path missing: %d\n", ret); > + return ret; > + } > + > + pds = pdr_add_lookup(apr->pdr, service_name, service_path); > + if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { > + dev_err(dev, "pdr add lookup failed: %d\n", ret); > + return PTR_ERR(pds); > + } > + } > + > + return 0; > +} > + > +static void of_register_apr_devices(struct device *dev, const char *svc_path) > { > struct apr *apr = dev_get_drvdata(dev); > struct device_node *node; > + const char *service_path; > + int ret; > > for_each_child_of_node(dev->of_node, node) { > struct apr_device_id id = { {0} }; > > + /* > + * This function is called with svc_path NULL during > + * apr_probe(), in which case we register any apr devices > + * without a qcom,protection-domain specified. > + * > + * Then as the protection domains becomes available > + * (if applicable) this function is again called, but with > + * svc_path representing the service becoming available. In > + * this case we register any apr devices with a matching > + * qcom,protection-domain. > + */ > + > + ret = of_property_read_string_index(node, "qcom,protection-domain", > + 1, &service_path); > + if (svc_path) { > + /* skip APR services that are PD independent */ > + if (ret) > + continue; > + > + /* skip APR services whose PD paths don't match */ > + if (strcmp(service_path, svc_path)) > + continue; > + } else { > + /* skip APR services whose PD lookups are registered */ > + if (ret == 0) > + continue; > + } > + > if (of_property_read_u32(node, "reg", &id.svc_id)) > continue; > > @@ -318,6 +384,34 @@ static void of_register_apr_devices(struct device *dev) > } > } > > +static int apr_remove_device(struct device *dev, void *svc_path) > +{ > + struct apr_device *adev = to_apr_device(dev); > + > + if (svc_path && adev->service_path) { > + if (!strcmp(adev->service_path, (char *)svc_path)) > + device_unregister(&adev->dev); > + } else { > + device_unregister(&adev->dev); > + } > + > + return 0; > +} > + > +static void apr_pd_status(int state, char *svc_path, void *priv) > +{ > + struct apr *apr = (struct apr *)priv; > + > + switch (state) { > + case SERVREG_SERVICE_STATE_UP: > + of_register_apr_devices(apr->dev, svc_path); > + break; > + case SERVREG_SERVICE_STATE_DOWN: > + device_for_each_child(apr->dev, svc_path, apr_remove_device); > + break; > + } > +} > + > static int apr_probe(struct rpmsg_device *rpdev) > { > struct device *dev = &rpdev->dev; > @@ -343,28 +437,39 @@ static int apr_probe(struct rpmsg_device *rpdev) > return -ENOMEM; > } > INIT_WORK(&apr->rx_work, apr_rxwq); > + > + apr->pdr = pdr_handle_alloc(apr_pd_status, apr); > + if (IS_ERR(apr->pdr)) { > + dev_err(dev, "Failed to init PDR handle\n"); > + ret = PTR_ERR(apr->pdr); > + goto destroy_wq; > + } > + > INIT_LIST_HEAD(&apr->rx_list); > spin_lock_init(&apr->rx_lock); > spin_lock_init(&apr->svcs_lock); > idr_init(&apr->svcs_idr); > - of_register_apr_devices(dev); > - > - return 0; > -} > > -static int apr_remove_device(struct device *dev, void *null) > -{ > - struct apr_device *adev = to_apr_device(dev); > + ret = of_apr_add_pd_lookups(dev); > + if (ret) > + goto handle_release; > > - device_unregister(&adev->dev); > + of_register_apr_devices(dev, NULL); > > return 0; > + > +handle_release: > + pdr_handle_release(apr->pdr); > +destroy_wq: > + destroy_workqueue(apr->rxwq); > + return ret; > } > > static void apr_remove(struct rpmsg_device *rpdev) > { > struct apr *apr = dev_get_drvdata(&rpdev->dev); > > + pdr_handle_release(apr->pdr); > device_for_each_child(&rpdev->dev, NULL, apr_remove_device); > flush_workqueue(apr->rxwq); > destroy_workqueue(apr->rxwq); > diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h > index c5d52e2cb275f..7f0bc3cf4d610 100644 > --- a/include/linux/soc/qcom/apr.h > +++ b/include/linux/soc/qcom/apr.h > @@ -85,6 +85,7 @@ struct apr_device { > uint16_t domain_id; > uint32_t version; > char name[APR_NAME_SIZE]; > + const char *service_path; > spinlock_t lock; > struct list_head node; > }; > -- > The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, > a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality @ 2020-02-28 5:38 ` Bjorn Andersson 0 siblings, 0 replies; 10+ messages in thread From: Bjorn Andersson @ 2020-02-28 5:38 UTC (permalink / raw) To: Sibi Sankar Cc: srinivas.kandagatla, robh+dt, agross, mark.rutland, linux-arm-msm, linux-remoteproc, linux-kernel, devicetree, tsoni, vnkgutta On Wed 26 Feb 09:00 PST 2020, Sibi Sankar wrote: > Use PDR helper functions to track the protection domains that the apr > services are dependent upon on SDM845 SoC, specifically the "avs/audio" > service running on ADSP Q6. > > Signed-off-by: Sibi Sankar <sibis@codeaurora.org> Reviewed-by: Bjorn Andersson <bjorn.andersson@linaro.org> > --- Please do include a changelog as you respin your patches. Regards, Bjorn > drivers/soc/qcom/Kconfig | 1 + > drivers/soc/qcom/apr.c | 123 ++++++++++++++++++++++++++++++++--- > include/linux/soc/qcom/apr.h | 1 + > 3 files changed, 116 insertions(+), 9 deletions(-) > > diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig > index cca6a43e771d9..57000f1615ada 100644 > --- a/drivers/soc/qcom/Kconfig > +++ b/drivers/soc/qcom/Kconfig > @@ -202,6 +202,7 @@ config QCOM_APR > tristate "Qualcomm APR Bus (Asynchronous Packet Router)" > depends on ARCH_QCOM || COMPILE_TEST > depends on RPMSG > + select QCOM_PDR_HELPERS > help > Enable APR IPC protocol support between > application processor and QDSP6. APR is > diff --git a/drivers/soc/qcom/apr.c b/drivers/soc/qcom/apr.c > index 4fcc32420c474..1f35b097c6356 100644 > --- a/drivers/soc/qcom/apr.c > +++ b/drivers/soc/qcom/apr.c > @@ -11,6 +11,7 @@ > #include <linux/workqueue.h> > #include <linux/of_device.h> > #include <linux/soc/qcom/apr.h> > +#include <linux/soc/qcom/pdr.h> > #include <linux/rpmsg.h> > #include <linux/of.h> > > @@ -21,6 +22,7 @@ struct apr { > spinlock_t rx_lock; > struct idr svcs_idr; > int dest_domain_id; > + struct pdr_handle *pdr; > struct workqueue_struct *rxwq; > struct work_struct rx_work; > struct list_head rx_list; > @@ -289,6 +291,9 @@ static int apr_add_device(struct device *dev, struct device_node *np, > id->svc_id + 1, GFP_ATOMIC); > spin_unlock(&apr->svcs_lock); > > + of_property_read_string_index(np, "qcom,protection-domain", > + 1, &adev->service_path); > + > dev_info(dev, "Adding APR dev: %s\n", dev_name(&adev->dev)); > > ret = device_register(&adev->dev); > @@ -300,14 +305,75 @@ static int apr_add_device(struct device *dev, struct device_node *np, > return ret; > } > > -static void of_register_apr_devices(struct device *dev) > +static int of_apr_add_pd_lookups(struct device *dev) > +{ > + const char *service_name, *service_path; > + struct apr *apr = dev_get_drvdata(dev); > + struct device_node *node; > + struct pdr_service *pds; > + int ret; > + > + for_each_child_of_node(dev->of_node, node) { > + ret = of_property_read_string_index(node, "qcom,protection-domain", > + 0, &service_name); > + if (ret < 0) > + continue; > + > + ret = of_property_read_string_index(node, "qcom,protection-domain", > + 1, &service_path); > + if (ret < 0) { > + dev_err(dev, "pdr service path missing: %d\n", ret); > + return ret; > + } > + > + pds = pdr_add_lookup(apr->pdr, service_name, service_path); > + if (IS_ERR(pds) && PTR_ERR(pds) != -EALREADY) { > + dev_err(dev, "pdr add lookup failed: %d\n", ret); > + return PTR_ERR(pds); > + } > + } > + > + return 0; > +} > + > +static void of_register_apr_devices(struct device *dev, const char *svc_path) > { > struct apr *apr = dev_get_drvdata(dev); > struct device_node *node; > + const char *service_path; > + int ret; > > for_each_child_of_node(dev->of_node, node) { > struct apr_device_id id = { {0} }; > > + /* > + * This function is called with svc_path NULL during > + * apr_probe(), in which case we register any apr devices > + * without a qcom,protection-domain specified. > + * > + * Then as the protection domains becomes available > + * (if applicable) this function is again called, but with > + * svc_path representing the service becoming available. In > + * this case we register any apr devices with a matching > + * qcom,protection-domain. > + */ > + > + ret = of_property_read_string_index(node, "qcom,protection-domain", > + 1, &service_path); > + if (svc_path) { > + /* skip APR services that are PD independent */ > + if (ret) > + continue; > + > + /* skip APR services whose PD paths don't match */ > + if (strcmp(service_path, svc_path)) > + continue; > + } else { > + /* skip APR services whose PD lookups are registered */ > + if (ret == 0) > + continue; > + } > + > if (of_property_read_u32(node, "reg", &id.svc_id)) > continue; > > @@ -318,6 +384,34 @@ static void of_register_apr_devices(struct device *dev) > } > } > > +static int apr_remove_device(struct device *dev, void *svc_path) > +{ > + struct apr_device *adev = to_apr_device(dev); > + > + if (svc_path && adev->service_path) { > + if (!strcmp(adev->service_path, (char *)svc_path)) > + device_unregister(&adev->dev); > + } else { > + device_unregister(&adev->dev); > + } > + > + return 0; > +} > + > +static void apr_pd_status(int state, char *svc_path, void *priv) > +{ > + struct apr *apr = (struct apr *)priv; > + > + switch (state) { > + case SERVREG_SERVICE_STATE_UP: > + of_register_apr_devices(apr->dev, svc_path); > + break; > + case SERVREG_SERVICE_STATE_DOWN: > + device_for_each_child(apr->dev, svc_path, apr_remove_device); > + break; > + } > +} > + > static int apr_probe(struct rpmsg_device *rpdev) > { > struct device *dev = &rpdev->dev; > @@ -343,28 +437,39 @@ static int apr_probe(struct rpmsg_device *rpdev) > return -ENOMEM; > } > INIT_WORK(&apr->rx_work, apr_rxwq); > + > + apr->pdr = pdr_handle_alloc(apr_pd_status, apr); > + if (IS_ERR(apr->pdr)) { > + dev_err(dev, "Failed to init PDR handle\n"); > + ret = PTR_ERR(apr->pdr); > + goto destroy_wq; > + } > + > INIT_LIST_HEAD(&apr->rx_list); > spin_lock_init(&apr->rx_lock); > spin_lock_init(&apr->svcs_lock); > idr_init(&apr->svcs_idr); > - of_register_apr_devices(dev); > - > - return 0; > -} > > -static int apr_remove_device(struct device *dev, void *null) > -{ > - struct apr_device *adev = to_apr_device(dev); > + ret = of_apr_add_pd_lookups(dev); > + if (ret) > + goto handle_release; > > - device_unregister(&adev->dev); > + of_register_apr_devices(dev, NULL); > > return 0; > + > +handle_release: > + pdr_handle_release(apr->pdr); > +destroy_wq: > + destroy_workqueue(apr->rxwq); > + return ret; > } > > static void apr_remove(struct rpmsg_device *rpdev) > { > struct apr *apr = dev_get_drvdata(&rpdev->dev); > > + pdr_handle_release(apr->pdr); > device_for_each_child(&rpdev->dev, NULL, apr_remove_device); > flush_workqueue(apr->rxwq); > destroy_workqueue(apr->rxwq); > diff --git a/include/linux/soc/qcom/apr.h b/include/linux/soc/qcom/apr.h > index c5d52e2cb275f..7f0bc3cf4d610 100644 > --- a/include/linux/soc/qcom/apr.h > +++ b/include/linux/soc/qcom/apr.h > @@ -85,6 +85,7 @@ struct apr_device { > uint16_t domain_id; > uint32_t version; > char name[APR_NAME_SIZE]; > + const char *service_path; > spinlock_t lock; > struct list_head node; > }; > -- > The Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, > a Linux Foundation Collaborative Project ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2020-02-28 5:38 UTC | newest] Thread overview: 10+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2020-02-26 16:59 [PATCH v4 0/3] Introduce Protection Domain Restart (PDR) Helpers Sibi Sankar 2020-02-26 16:59 ` [PATCH v4 1/3] soc: qcom: Introduce Protection Domain Restart helpers Sibi Sankar 2020-02-28 5:34 ` Bjorn Andersson 2020-02-28 5:34 ` Bjorn Andersson 2020-02-26 17:00 ` [PATCH v4 2/3] dt-bindings: soc: qcom: apr: Add protection domain bindings Sibi Sankar 2020-02-28 5:35 ` Bjorn Andersson 2020-02-28 5:35 ` Bjorn Andersson 2020-02-26 17:00 ` [PATCH v4 3/3] soc: qcom: apr: Add avs/audio tracking functionality Sibi Sankar 2020-02-28 5:38 ` Bjorn Andersson 2020-02-28 5:38 ` Bjorn Andersson
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.