695 lines · c
1// SPDX-License-Identifier: GPL-2.0-only2/*3 * Qualcomm Protection Domain mapper4 *5 * Copyright (c) 2023 Linaro Ltd.6 */7 8#include <linux/auxiliary_bus.h>9#include <linux/kernel.h>10#include <linux/mod_devicetable.h>11#include <linux/module.h>12#include <linux/of.h>13#include <linux/refcount.h>14#include <linux/slab.h>15#include <linux/soc/qcom/qmi.h>16 17#include "pdr_internal.h"18 19#define SERVREG_QMI_VERSION 0x10120#define SERVREG_QMI_INSTANCE 021 22#define TMS_SERVREG_SERVICE "tms/servreg"23 24struct qcom_pdm_domain_data {25 const char *domain;26 u32 instance_id;27 /* NULL-terminated array */28 const char * services[];29};30 31struct qcom_pdm_domain {32 struct list_head list;33 const char *name;34 u32 instance_id;35};36 37struct qcom_pdm_service {38 struct list_head list;39 struct list_head domains;40 const char *name;41};42 43struct qcom_pdm_data {44 refcount_t refcnt;45 struct qmi_handle handle;46 struct list_head services;47};48 49static DEFINE_MUTEX(qcom_pdm_mutex); /* protects __qcom_pdm_data */50static struct qcom_pdm_data *__qcom_pdm_data;51 52static struct qcom_pdm_service *qcom_pdm_find(struct qcom_pdm_data *data,53 const char *name)54{55 struct qcom_pdm_service *service;56 57 list_for_each_entry(service, &data->services, list) {58 if (!strcmp(service->name, name))59 return service;60 }61 62 return NULL;63}64 65static int qcom_pdm_add_service_domain(struct qcom_pdm_data *data,66 const char *service_name,67 const char *domain_name,68 u32 instance_id)69{70 struct qcom_pdm_service *service;71 struct qcom_pdm_domain *domain;72 73 service = qcom_pdm_find(data, service_name);74 if (service) {75 list_for_each_entry(domain, &service->domains, list) {76 if (!strcmp(domain->name, domain_name))77 return -EBUSY;78 }79 } else {80 service = kzalloc(sizeof(*service), GFP_KERNEL);81 if (!service)82 return -ENOMEM;83 84 INIT_LIST_HEAD(&service->domains);85 service->name = service_name;86 87 list_add_tail(&service->list, &data->services);88 }89 90 domain = kzalloc(sizeof(*domain), GFP_KERNEL);91 if (!domain) {92 if (list_empty(&service->domains)) {93 list_del(&service->list);94 kfree(service);95 }96 97 return -ENOMEM;98 }99 100 domain->name = domain_name;101 domain->instance_id = instance_id;102 list_add_tail(&domain->list, &service->domains);103 104 return 0;105}106 107static int qcom_pdm_add_domain(struct qcom_pdm_data *data,108 const struct qcom_pdm_domain_data *domain)109{110 int ret;111 int i;112 113 ret = qcom_pdm_add_service_domain(data,114 TMS_SERVREG_SERVICE,115 domain->domain,116 domain->instance_id);117 if (ret)118 return ret;119 120 for (i = 0; domain->services[i]; i++) {121 ret = qcom_pdm_add_service_domain(data,122 domain->services[i],123 domain->domain,124 domain->instance_id);125 if (ret)126 return ret;127 }128 129 return 0;130 131}132 133static void qcom_pdm_free_domains(struct qcom_pdm_data *data)134{135 struct qcom_pdm_service *service, *tservice;136 struct qcom_pdm_domain *domain, *tdomain;137 138 list_for_each_entry_safe(service, tservice, &data->services, list) {139 list_for_each_entry_safe(domain, tdomain, &service->domains, list) {140 list_del(&domain->list);141 kfree(domain);142 }143 144 list_del(&service->list);145 kfree(service);146 }147}148 149static void qcom_pdm_get_domain_list(struct qmi_handle *qmi,150 struct sockaddr_qrtr *sq,151 struct qmi_txn *txn,152 const void *decoded)153{154 struct qcom_pdm_data *data = container_of(qmi, struct qcom_pdm_data, handle);155 const struct servreg_get_domain_list_req *req = decoded;156 struct servreg_get_domain_list_resp *rsp;157 struct qcom_pdm_service *service;158 u32 offset;159 int ret;160 161 rsp = kzalloc(sizeof(*rsp), GFP_KERNEL);162 if (!rsp)163 return;164 165 offset = req->domain_offset_valid ? req->domain_offset : 0;166 167 rsp->resp.result = QMI_RESULT_SUCCESS_V01;168 rsp->resp.error = QMI_ERR_NONE_V01;169 170 rsp->db_rev_count_valid = true;171 rsp->db_rev_count = 1;172 173 rsp->total_domains_valid = true;174 rsp->total_domains = 0;175 176 mutex_lock(&qcom_pdm_mutex);177 178 service = qcom_pdm_find(data, req->service_name);179 if (service) {180 struct qcom_pdm_domain *domain;181 182 rsp->domain_list_valid = true;183 rsp->domain_list_len = 0;184 185 list_for_each_entry(domain, &service->domains, list) {186 u32 i = rsp->total_domains++;187 188 if (i >= offset && i < SERVREG_DOMAIN_LIST_LENGTH) {189 u32 j = rsp->domain_list_len++;190 191 strscpy(rsp->domain_list[j].name, domain->name,192 sizeof(rsp->domain_list[i].name));193 rsp->domain_list[j].instance = domain->instance_id;194 195 pr_debug("PDM: found %s / %d\n", domain->name,196 domain->instance_id);197 }198 }199 }200 201 pr_debug("PDM: service '%s' offset %d returning %d domains (of %d)\n", req->service_name,202 req->domain_offset_valid ? req->domain_offset : -1, rsp->domain_list_len, rsp->total_domains);203 204 ret = qmi_send_response(qmi, sq, txn, SERVREG_GET_DOMAIN_LIST_REQ,205 SERVREG_GET_DOMAIN_LIST_RESP_MAX_LEN,206 servreg_get_domain_list_resp_ei, rsp);207 if (ret)208 pr_err("Error sending servreg response: %d\n", ret);209 210 mutex_unlock(&qcom_pdm_mutex);211 212 kfree(rsp);213}214 215static void qcom_pdm_pfr(struct qmi_handle *qmi,216 struct sockaddr_qrtr *sq,217 struct qmi_txn *txn,218 const void *decoded)219{220 const struct servreg_loc_pfr_req *req = decoded;221 struct servreg_loc_pfr_resp rsp = {};222 int ret;223 224 pr_warn_ratelimited("PDM: service '%s' crash: '%s'\n", req->service, req->reason);225 226 rsp.rsp.result = QMI_RESULT_SUCCESS_V01;227 rsp.rsp.error = QMI_ERR_NONE_V01;228 229 ret = qmi_send_response(qmi, sq, txn, SERVREG_LOC_PFR_REQ,230 SERVREG_LOC_PFR_RESP_MAX_LEN,231 servreg_loc_pfr_resp_ei, &rsp);232 if (ret)233 pr_err("Error sending servreg response: %d\n", ret);234}235 236static const struct qmi_msg_handler qcom_pdm_msg_handlers[] = {237 {238 .type = QMI_REQUEST,239 .msg_id = SERVREG_GET_DOMAIN_LIST_REQ,240 .ei = servreg_get_domain_list_req_ei,241 .decoded_size = sizeof(struct servreg_get_domain_list_req),242 .fn = qcom_pdm_get_domain_list,243 },244 {245 .type = QMI_REQUEST,246 .msg_id = SERVREG_LOC_PFR_REQ,247 .ei = servreg_loc_pfr_req_ei,248 .decoded_size = sizeof(struct servreg_loc_pfr_req),249 .fn = qcom_pdm_pfr,250 },251 { },252};253 254static const struct qcom_pdm_domain_data adsp_audio_pd = {255 .domain = "msm/adsp/audio_pd",256 .instance_id = 74,257 .services = {258 "avs/audio",259 NULL,260 },261};262 263static const struct qcom_pdm_domain_data adsp_charger_pd = {264 .domain = "msm/adsp/charger_pd",265 .instance_id = 74,266 .services = { NULL },267};268 269static const struct qcom_pdm_domain_data adsp_root_pd = {270 .domain = "msm/adsp/root_pd",271 .instance_id = 74,272 .services = { NULL },273};274 275static const struct qcom_pdm_domain_data adsp_root_pd_pdr = {276 .domain = "msm/adsp/root_pd",277 .instance_id = 74,278 .services = {279 "tms/pdr_enabled",280 NULL,281 },282};283 284static const struct qcom_pdm_domain_data adsp_sensor_pd = {285 .domain = "msm/adsp/sensor_pd",286 .instance_id = 74,287 .services = { NULL },288};289 290static const struct qcom_pdm_domain_data msm8996_adsp_audio_pd = {291 .domain = "msm/adsp/audio_pd",292 .instance_id = 4,293 .services = { NULL },294};295 296static const struct qcom_pdm_domain_data msm8996_adsp_root_pd = {297 .domain = "msm/adsp/root_pd",298 .instance_id = 4,299 .services = { NULL },300};301 302static const struct qcom_pdm_domain_data cdsp_root_pd = {303 .domain = "msm/cdsp/root_pd",304 .instance_id = 76,305 .services = { NULL },306};307 308static const struct qcom_pdm_domain_data slpi_root_pd = {309 .domain = "msm/slpi/root_pd",310 .instance_id = 90,311 .services = { NULL },312};313 314static const struct qcom_pdm_domain_data slpi_sensor_pd = {315 .domain = "msm/slpi/sensor_pd",316 .instance_id = 90,317 .services = { NULL },318};319 320static const struct qcom_pdm_domain_data mpss_root_pd = {321 .domain = "msm/modem/root_pd",322 .instance_id = 180,323 .services = {324 NULL,325 },326};327 328static const struct qcom_pdm_domain_data mpss_root_pd_gps = {329 .domain = "msm/modem/root_pd",330 .instance_id = 180,331 .services = {332 "gps/gps_service",333 NULL,334 },335};336 337static const struct qcom_pdm_domain_data mpss_root_pd_gps_pdr = {338 .domain = "msm/modem/root_pd",339 .instance_id = 180,340 .services = {341 "gps/gps_service",342 "tms/pdr_enabled",343 NULL,344 },345};346 347static const struct qcom_pdm_domain_data msm8996_mpss_root_pd = {348 .domain = "msm/modem/root_pd",349 .instance_id = 100,350 .services = { NULL },351};352 353static const struct qcom_pdm_domain_data mpss_wlan_pd = {354 .domain = "msm/modem/wlan_pd",355 .instance_id = 180,356 .services = {357 "kernel/elf_loader",358 "wlan/fw",359 NULL,360 },361};362 363static const struct qcom_pdm_domain_data *msm8996_domains[] = {364 &msm8996_adsp_audio_pd,365 &msm8996_adsp_root_pd,366 &msm8996_mpss_root_pd,367 NULL,368};369 370static const struct qcom_pdm_domain_data *msm8998_domains[] = {371 &mpss_root_pd,372 &mpss_wlan_pd,373 NULL,374};375 376static const struct qcom_pdm_domain_data *qcm2290_domains[] = {377 &adsp_audio_pd,378 &adsp_root_pd,379 &adsp_sensor_pd,380 &mpss_root_pd_gps,381 &mpss_wlan_pd,382 NULL,383};384 385static const struct qcom_pdm_domain_data *qcs404_domains[] = {386 &adsp_audio_pd,387 &adsp_root_pd,388 &adsp_sensor_pd,389 &cdsp_root_pd,390 &mpss_root_pd,391 &mpss_wlan_pd,392 NULL,393};394 395static const struct qcom_pdm_domain_data *sc7180_domains[] = {396 &adsp_audio_pd,397 &adsp_root_pd_pdr,398 &adsp_sensor_pd,399 &mpss_root_pd_gps_pdr,400 &mpss_wlan_pd,401 NULL,402};403 404static const struct qcom_pdm_domain_data *sc7280_domains[] = {405 &adsp_audio_pd,406 &adsp_root_pd_pdr,407 &adsp_charger_pd,408 &adsp_sensor_pd,409 &cdsp_root_pd,410 &mpss_root_pd_gps_pdr,411 NULL,412};413 414static const struct qcom_pdm_domain_data *sc8180x_domains[] = {415 &adsp_audio_pd,416 &adsp_root_pd,417 &adsp_charger_pd,418 &cdsp_root_pd,419 &mpss_root_pd_gps,420 &mpss_wlan_pd,421 NULL,422};423 424static const struct qcom_pdm_domain_data *sc8280xp_domains[] = {425 &adsp_audio_pd,426 &adsp_root_pd_pdr,427 &adsp_charger_pd,428 &cdsp_root_pd,429 NULL,430};431 432static const struct qcom_pdm_domain_data *sdm660_domains[] = {433 &adsp_audio_pd,434 &adsp_root_pd,435 &adsp_sensor_pd,436 &cdsp_root_pd,437 &mpss_root_pd,438 &mpss_wlan_pd,439 NULL,440};441 442static const struct qcom_pdm_domain_data *sdm670_domains[] = {443 &adsp_audio_pd,444 &adsp_root_pd,445 &cdsp_root_pd,446 &mpss_root_pd,447 &mpss_wlan_pd,448 NULL,449};450 451static const struct qcom_pdm_domain_data *sdm845_domains[] = {452 &adsp_audio_pd,453 &adsp_root_pd,454 &cdsp_root_pd,455 &mpss_root_pd,456 &mpss_wlan_pd,457 &slpi_root_pd,458 &slpi_sensor_pd,459 NULL,460};461 462static const struct qcom_pdm_domain_data *sm6115_domains[] = {463 &adsp_audio_pd,464 &adsp_root_pd,465 &adsp_sensor_pd,466 &cdsp_root_pd,467 &mpss_root_pd_gps,468 &mpss_wlan_pd,469 NULL,470};471 472static const struct qcom_pdm_domain_data *sm6350_domains[] = {473 &adsp_audio_pd,474 &adsp_root_pd,475 &adsp_sensor_pd,476 &cdsp_root_pd,477 &mpss_wlan_pd,478 NULL,479};480 481static const struct qcom_pdm_domain_data *sm8150_domains[] = {482 &adsp_audio_pd,483 &adsp_root_pd,484 &cdsp_root_pd,485 &mpss_root_pd_gps,486 &mpss_wlan_pd,487 NULL,488};489 490static const struct qcom_pdm_domain_data *sm8250_domains[] = {491 &adsp_audio_pd,492 &adsp_root_pd,493 &cdsp_root_pd,494 &slpi_root_pd,495 &slpi_sensor_pd,496 NULL,497};498 499static const struct qcom_pdm_domain_data *sm8350_domains[] = {500 &adsp_audio_pd,501 &adsp_root_pd_pdr,502 &adsp_charger_pd,503 &cdsp_root_pd,504 &mpss_root_pd_gps,505 &slpi_root_pd,506 &slpi_sensor_pd,507 NULL,508};509 510static const struct qcom_pdm_domain_data *sm8550_domains[] = {511 &adsp_audio_pd,512 &adsp_root_pd,513 &adsp_charger_pd,514 &adsp_sensor_pd,515 &cdsp_root_pd,516 &mpss_root_pd_gps,517 NULL,518};519 520static const struct qcom_pdm_domain_data *x1e80100_domains[] = {521 &adsp_audio_pd,522 &adsp_root_pd,523 &adsp_charger_pd,524 &adsp_sensor_pd,525 &cdsp_root_pd,526 NULL,527};528 529static const struct of_device_id qcom_pdm_domains[] __maybe_unused = {530 { .compatible = "qcom,apq8016", .data = NULL, },531 { .compatible = "qcom,apq8064", .data = NULL, },532 { .compatible = "qcom,apq8074", .data = NULL, },533 { .compatible = "qcom,apq8084", .data = NULL, },534 { .compatible = "qcom,apq8096", .data = msm8996_domains, },535 { .compatible = "qcom,msm8226", .data = NULL, },536 { .compatible = "qcom,msm8909", .data = NULL, },537 { .compatible = "qcom,msm8916", .data = NULL, },538 { .compatible = "qcom,msm8939", .data = NULL, },539 { .compatible = "qcom,msm8974", .data = NULL, },540 { .compatible = "qcom,msm8996", .data = msm8996_domains, },541 { .compatible = "qcom,msm8998", .data = msm8998_domains, },542 { .compatible = "qcom,qcm2290", .data = qcm2290_domains, },543 { .compatible = "qcom,qcs404", .data = qcs404_domains, },544 { .compatible = "qcom,sc7180", .data = sc7180_domains, },545 { .compatible = "qcom,sc7280", .data = sc7280_domains, },546 { .compatible = "qcom,sc8180x", .data = sc8180x_domains, },547 { .compatible = "qcom,sc8280xp", .data = sc8280xp_domains, },548 { .compatible = "qcom,sda660", .data = sdm660_domains, },549 { .compatible = "qcom,sdm660", .data = sdm660_domains, },550 { .compatible = "qcom,sdm670", .data = sdm670_domains, },551 { .compatible = "qcom,sdm845", .data = sdm845_domains, },552 { .compatible = "qcom,sm4250", .data = sm6115_domains, },553 { .compatible = "qcom,sm6115", .data = sm6115_domains, },554 { .compatible = "qcom,sm6350", .data = sm6350_domains, },555 { .compatible = "qcom,sm7325", .data = sc7280_domains, },556 { .compatible = "qcom,sm8150", .data = sm8150_domains, },557 { .compatible = "qcom,sm8250", .data = sm8250_domains, },558 { .compatible = "qcom,sm8350", .data = sm8350_domains, },559 { .compatible = "qcom,sm8450", .data = sm8350_domains, },560 { .compatible = "qcom,sm8550", .data = sm8550_domains, },561 { .compatible = "qcom,sm8650", .data = sm8550_domains, },562 { .compatible = "qcom,x1e80100", .data = x1e80100_domains, },563 {},564};565 566static void qcom_pdm_stop(struct qcom_pdm_data *data)567{568 qcom_pdm_free_domains(data);569 570 /* The server is removed automatically */571 qmi_handle_release(&data->handle);572 573 kfree(data);574}575 576static struct qcom_pdm_data *qcom_pdm_start(void)577{578 const struct qcom_pdm_domain_data * const *domains;579 const struct of_device_id *match;580 struct qcom_pdm_data *data;581 struct device_node *root;582 int ret, i;583 584 root = of_find_node_by_path("/");585 if (!root)586 return ERR_PTR(-ENODEV);587 588 match = of_match_node(qcom_pdm_domains, root);589 of_node_put(root);590 if (!match) {591 pr_notice("PDM: no support for the platform, userspace daemon might be required.\n");592 return ERR_PTR(-ENODEV);593 }594 595 domains = match->data;596 if (!domains) {597 pr_debug("PDM: no domains\n");598 return ERR_PTR(-ENODEV);599 }600 601 data = kzalloc(sizeof(*data), GFP_KERNEL);602 if (!data)603 return ERR_PTR(-ENOMEM);604 605 INIT_LIST_HEAD(&data->services);606 607 ret = qmi_handle_init(&data->handle, SERVREG_GET_DOMAIN_LIST_REQ_MAX_LEN,608 NULL, qcom_pdm_msg_handlers);609 if (ret) {610 kfree(data);611 return ERR_PTR(ret);612 }613 614 refcount_set(&data->refcnt, 1);615 616 for (i = 0; domains[i]; i++) {617 ret = qcom_pdm_add_domain(data, domains[i]);618 if (ret)619 goto err_stop;620 }621 622 ret = qmi_add_server(&data->handle, SERVREG_LOCATOR_SERVICE,623 SERVREG_QMI_VERSION, SERVREG_QMI_INSTANCE);624 if (ret) {625 pr_err("PDM: error adding server %d\n", ret);626 goto err_stop;627 }628 629 return data;630 631err_stop:632 qcom_pdm_stop(data);633 634 return ERR_PTR(ret);635}636 637static int qcom_pdm_probe(struct auxiliary_device *auxdev,638 const struct auxiliary_device_id *id)639 640{641 struct qcom_pdm_data *data;642 int ret = 0;643 644 mutex_lock(&qcom_pdm_mutex);645 646 if (!__qcom_pdm_data) {647 data = qcom_pdm_start();648 649 if (IS_ERR(data))650 ret = PTR_ERR(data);651 else652 __qcom_pdm_data = data;653 } else {654 refcount_inc(&__qcom_pdm_data->refcnt);655 }656 657 auxiliary_set_drvdata(auxdev, __qcom_pdm_data);658 659 mutex_unlock(&qcom_pdm_mutex);660 661 return ret;662}663 664static void qcom_pdm_remove(struct auxiliary_device *auxdev)665{666 struct qcom_pdm_data *data;667 668 data = auxiliary_get_drvdata(auxdev);669 if (!data)670 return;671 672 if (refcount_dec_and_mutex_lock(&data->refcnt, &qcom_pdm_mutex)) {673 __qcom_pdm_data = NULL;674 qcom_pdm_stop(data);675 mutex_unlock(&qcom_pdm_mutex);676 }677}678 679static const struct auxiliary_device_id qcom_pdm_table[] = {680 { .name = "qcom_common.pd-mapper" },681 {},682};683MODULE_DEVICE_TABLE(auxiliary, qcom_pdm_table);684 685static struct auxiliary_driver qcom_pdm_drv = {686 .name = "qcom-pdm-mapper",687 .id_table = qcom_pdm_table,688 .probe = qcom_pdm_probe,689 .remove = qcom_pdm_remove,690};691module_auxiliary_driver(qcom_pdm_drv);692 693MODULE_DESCRIPTION("Qualcomm Protection Domain Mapper");694MODULE_LICENSE("GPL");695