14d09dd11SShannon Nelson // SPDX-License-Identifier: GPL-2.0
24d09dd11SShannon Nelson /* Copyright(c) Advanced Micro Devices, Inc */
34d09dd11SShannon Nelson
44d09dd11SShannon Nelson #include <linux/module.h>
54d09dd11SShannon Nelson #include <linux/auxiliary_bus.h>
64d09dd11SShannon Nelson #include <linux/pci.h>
74d09dd11SShannon Nelson #include <linux/vmalloc.h>
892c66ee8SBrett Creeley #include <linux/bitfield.h>
94d09dd11SShannon Nelson
104d09dd11SShannon Nelson #include <uapi/fwctl/fwctl.h>
114d09dd11SShannon Nelson #include <uapi/fwctl/pds.h>
124d09dd11SShannon Nelson #include <linux/fwctl.h>
134d09dd11SShannon Nelson
144d09dd11SShannon Nelson #include <linux/pds/pds_common.h>
154d09dd11SShannon Nelson #include <linux/pds/pds_core_if.h>
164d09dd11SShannon Nelson #include <linux/pds/pds_adminq.h>
174d09dd11SShannon Nelson #include <linux/pds/pds_auxbus.h>
184d09dd11SShannon Nelson
194d09dd11SShannon Nelson struct pdsfc_uctx {
204d09dd11SShannon Nelson struct fwctl_uctx uctx;
214d09dd11SShannon Nelson u32 uctx_caps;
224d09dd11SShannon Nelson };
234d09dd11SShannon Nelson
2492c66ee8SBrett Creeley struct pdsfc_rpc_endpoint_info {
2592c66ee8SBrett Creeley u32 endpoint;
2692c66ee8SBrett Creeley dma_addr_t operations_pa;
2792c66ee8SBrett Creeley struct pds_fwctl_query_data *operations;
2892c66ee8SBrett Creeley struct mutex lock; /* lock for endpoint info management */
2992c66ee8SBrett Creeley };
3092c66ee8SBrett Creeley
314d09dd11SShannon Nelson struct pdsfc_dev {
324d09dd11SShannon Nelson struct fwctl_device fwctl;
334d09dd11SShannon Nelson struct pds_auxiliary_dev *padev;
344d09dd11SShannon Nelson u32 caps;
354d09dd11SShannon Nelson struct pds_fwctl_ident ident;
3692c66ee8SBrett Creeley dma_addr_t endpoints_pa;
3792c66ee8SBrett Creeley struct pds_fwctl_query_data *endpoints;
3892c66ee8SBrett Creeley struct pdsfc_rpc_endpoint_info *endpoint_info;
394d09dd11SShannon Nelson };
404d09dd11SShannon Nelson
pdsfc_open_uctx(struct fwctl_uctx * uctx)414d09dd11SShannon Nelson static int pdsfc_open_uctx(struct fwctl_uctx *uctx)
424d09dd11SShannon Nelson {
434d09dd11SShannon Nelson struct pdsfc_dev *pdsfc = container_of(uctx->fwctl, struct pdsfc_dev, fwctl);
444d09dd11SShannon Nelson struct pdsfc_uctx *pdsfc_uctx = container_of(uctx, struct pdsfc_uctx, uctx);
454d09dd11SShannon Nelson
464d09dd11SShannon Nelson pdsfc_uctx->uctx_caps = pdsfc->caps;
474d09dd11SShannon Nelson
484d09dd11SShannon Nelson return 0;
494d09dd11SShannon Nelson }
504d09dd11SShannon Nelson
pdsfc_close_uctx(struct fwctl_uctx * uctx)514d09dd11SShannon Nelson static void pdsfc_close_uctx(struct fwctl_uctx *uctx)
524d09dd11SShannon Nelson {
534d09dd11SShannon Nelson }
544d09dd11SShannon Nelson
pdsfc_info(struct fwctl_uctx * uctx,size_t * length)554d09dd11SShannon Nelson static void *pdsfc_info(struct fwctl_uctx *uctx, size_t *length)
564d09dd11SShannon Nelson {
574d09dd11SShannon Nelson struct pdsfc_uctx *pdsfc_uctx = container_of(uctx, struct pdsfc_uctx, uctx);
584d09dd11SShannon Nelson struct fwctl_info_pds *info;
594d09dd11SShannon Nelson
604d09dd11SShannon Nelson info = kzalloc(sizeof(*info), GFP_KERNEL);
614d09dd11SShannon Nelson if (!info)
624d09dd11SShannon Nelson return ERR_PTR(-ENOMEM);
634d09dd11SShannon Nelson
644d09dd11SShannon Nelson info->uctx_caps = pdsfc_uctx->uctx_caps;
654d09dd11SShannon Nelson
664d09dd11SShannon Nelson return info;
674d09dd11SShannon Nelson }
684d09dd11SShannon Nelson
pdsfc_identify(struct pdsfc_dev * pdsfc)694d09dd11SShannon Nelson static int pdsfc_identify(struct pdsfc_dev *pdsfc)
704d09dd11SShannon Nelson {
714d09dd11SShannon Nelson struct device *dev = &pdsfc->fwctl.dev;
724d09dd11SShannon Nelson union pds_core_adminq_comp comp = {0};
734d09dd11SShannon Nelson union pds_core_adminq_cmd cmd;
744d09dd11SShannon Nelson struct pds_fwctl_ident *ident;
754d09dd11SShannon Nelson dma_addr_t ident_pa;
764d09dd11SShannon Nelson int err;
774d09dd11SShannon Nelson
784d09dd11SShannon Nelson ident = dma_alloc_coherent(dev->parent, sizeof(*ident), &ident_pa, GFP_KERNEL);
794d09dd11SShannon Nelson if (!ident) {
804d09dd11SShannon Nelson dev_err(dev, "Failed to map ident buffer\n");
814d09dd11SShannon Nelson return -ENOMEM;
824d09dd11SShannon Nelson }
834d09dd11SShannon Nelson
844d09dd11SShannon Nelson cmd = (union pds_core_adminq_cmd) {
854d09dd11SShannon Nelson .fwctl_ident = {
864d09dd11SShannon Nelson .opcode = PDS_FWCTL_CMD_IDENT,
874d09dd11SShannon Nelson .version = 0,
884d09dd11SShannon Nelson .len = cpu_to_le32(sizeof(*ident)),
894d09dd11SShannon Nelson .ident_pa = cpu_to_le64(ident_pa),
904d09dd11SShannon Nelson }
914d09dd11SShannon Nelson };
924d09dd11SShannon Nelson
934d09dd11SShannon Nelson err = pds_client_adminq_cmd(pdsfc->padev, &cmd, sizeof(cmd), &comp, 0);
944d09dd11SShannon Nelson if (err)
954d09dd11SShannon Nelson dev_err(dev, "Failed to send adminq cmd opcode: %u err: %d\n",
964d09dd11SShannon Nelson cmd.fwctl_ident.opcode, err);
974d09dd11SShannon Nelson else
984d09dd11SShannon Nelson pdsfc->ident = *ident;
994d09dd11SShannon Nelson
1004d09dd11SShannon Nelson dma_free_coherent(dev->parent, sizeof(*ident), ident, ident_pa);
1014d09dd11SShannon Nelson
1024d09dd11SShannon Nelson return err;
1034d09dd11SShannon Nelson }
1044d09dd11SShannon Nelson
pdsfc_free_endpoints(struct pdsfc_dev * pdsfc)10592c66ee8SBrett Creeley static void pdsfc_free_endpoints(struct pdsfc_dev *pdsfc)
10692c66ee8SBrett Creeley {
10792c66ee8SBrett Creeley struct device *dev = &pdsfc->fwctl.dev;
108*fd292c1fSShannon Nelson u32 num_endpoints;
10992c66ee8SBrett Creeley int i;
11092c66ee8SBrett Creeley
11192c66ee8SBrett Creeley if (!pdsfc->endpoints)
11292c66ee8SBrett Creeley return;
11392c66ee8SBrett Creeley
114*fd292c1fSShannon Nelson num_endpoints = le32_to_cpu(pdsfc->endpoints->num_entries);
115*fd292c1fSShannon Nelson for (i = 0; pdsfc->endpoint_info && i < num_endpoints; i++)
11692c66ee8SBrett Creeley mutex_destroy(&pdsfc->endpoint_info[i].lock);
11792c66ee8SBrett Creeley vfree(pdsfc->endpoint_info);
11892c66ee8SBrett Creeley pdsfc->endpoint_info = NULL;
11992c66ee8SBrett Creeley dma_free_coherent(dev->parent, PAGE_SIZE,
12092c66ee8SBrett Creeley pdsfc->endpoints, pdsfc->endpoints_pa);
12192c66ee8SBrett Creeley pdsfc->endpoints = NULL;
12292c66ee8SBrett Creeley pdsfc->endpoints_pa = DMA_MAPPING_ERROR;
12392c66ee8SBrett Creeley }
12492c66ee8SBrett Creeley
pdsfc_free_operations(struct pdsfc_dev * pdsfc)12592c66ee8SBrett Creeley static void pdsfc_free_operations(struct pdsfc_dev *pdsfc)
12692c66ee8SBrett Creeley {
12792c66ee8SBrett Creeley struct device *dev = &pdsfc->fwctl.dev;
12892c66ee8SBrett Creeley u32 num_endpoints;
12992c66ee8SBrett Creeley int i;
13092c66ee8SBrett Creeley
13192c66ee8SBrett Creeley num_endpoints = le32_to_cpu(pdsfc->endpoints->num_entries);
13292c66ee8SBrett Creeley for (i = 0; i < num_endpoints; i++) {
13392c66ee8SBrett Creeley struct pdsfc_rpc_endpoint_info *ei = &pdsfc->endpoint_info[i];
13492c66ee8SBrett Creeley
13592c66ee8SBrett Creeley if (ei->operations) {
13692c66ee8SBrett Creeley dma_free_coherent(dev->parent, PAGE_SIZE,
13792c66ee8SBrett Creeley ei->operations, ei->operations_pa);
13892c66ee8SBrett Creeley ei->operations = NULL;
13992c66ee8SBrett Creeley ei->operations_pa = DMA_MAPPING_ERROR;
14092c66ee8SBrett Creeley }
14192c66ee8SBrett Creeley }
14292c66ee8SBrett Creeley }
14392c66ee8SBrett Creeley
pdsfc_get_endpoints(struct pdsfc_dev * pdsfc,dma_addr_t * pa)14492c66ee8SBrett Creeley static struct pds_fwctl_query_data *pdsfc_get_endpoints(struct pdsfc_dev *pdsfc,
14592c66ee8SBrett Creeley dma_addr_t *pa)
14692c66ee8SBrett Creeley {
14792c66ee8SBrett Creeley struct device *dev = &pdsfc->fwctl.dev;
14892c66ee8SBrett Creeley union pds_core_adminq_comp comp = {0};
14992c66ee8SBrett Creeley struct pds_fwctl_query_data *data;
15092c66ee8SBrett Creeley union pds_core_adminq_cmd cmd;
15192c66ee8SBrett Creeley dma_addr_t data_pa;
15292c66ee8SBrett Creeley int err;
15392c66ee8SBrett Creeley
15492c66ee8SBrett Creeley data = dma_alloc_coherent(dev->parent, PAGE_SIZE, &data_pa, GFP_KERNEL);
15592c66ee8SBrett Creeley if (!data) {
15692c66ee8SBrett Creeley dev_err(dev, "Failed to map endpoint list\n");
15792c66ee8SBrett Creeley return ERR_PTR(-ENOMEM);
15892c66ee8SBrett Creeley }
15992c66ee8SBrett Creeley
16092c66ee8SBrett Creeley cmd = (union pds_core_adminq_cmd) {
16192c66ee8SBrett Creeley .fwctl_query = {
16292c66ee8SBrett Creeley .opcode = PDS_FWCTL_CMD_QUERY,
16392c66ee8SBrett Creeley .entity = PDS_FWCTL_RPC_ROOT,
16492c66ee8SBrett Creeley .version = 0,
16592c66ee8SBrett Creeley .query_data_buf_len = cpu_to_le32(PAGE_SIZE),
16692c66ee8SBrett Creeley .query_data_buf_pa = cpu_to_le64(data_pa),
16792c66ee8SBrett Creeley }
16892c66ee8SBrett Creeley };
16992c66ee8SBrett Creeley
17092c66ee8SBrett Creeley err = pds_client_adminq_cmd(pdsfc->padev, &cmd, sizeof(cmd), &comp, 0);
17192c66ee8SBrett Creeley if (err) {
17292c66ee8SBrett Creeley dev_err(dev, "Failed to send adminq cmd opcode: %u entity: %u err: %d\n",
17392c66ee8SBrett Creeley cmd.fwctl_query.opcode, cmd.fwctl_query.entity, err);
17492c66ee8SBrett Creeley dma_free_coherent(dev->parent, PAGE_SIZE, data, data_pa);
17592c66ee8SBrett Creeley return ERR_PTR(err);
17692c66ee8SBrett Creeley }
17792c66ee8SBrett Creeley
17892c66ee8SBrett Creeley *pa = data_pa;
17992c66ee8SBrett Creeley
18092c66ee8SBrett Creeley return data;
18192c66ee8SBrett Creeley }
18292c66ee8SBrett Creeley
pdsfc_init_endpoints(struct pdsfc_dev * pdsfc)18392c66ee8SBrett Creeley static int pdsfc_init_endpoints(struct pdsfc_dev *pdsfc)
18492c66ee8SBrett Creeley {
18592c66ee8SBrett Creeley struct pds_fwctl_query_data_endpoint *ep_entry;
18692c66ee8SBrett Creeley u32 num_endpoints;
18792c66ee8SBrett Creeley int i;
18892c66ee8SBrett Creeley
18992c66ee8SBrett Creeley pdsfc->endpoints = pdsfc_get_endpoints(pdsfc, &pdsfc->endpoints_pa);
19092c66ee8SBrett Creeley if (IS_ERR(pdsfc->endpoints))
19192c66ee8SBrett Creeley return PTR_ERR(pdsfc->endpoints);
19292c66ee8SBrett Creeley
19392c66ee8SBrett Creeley num_endpoints = le32_to_cpu(pdsfc->endpoints->num_entries);
19492c66ee8SBrett Creeley pdsfc->endpoint_info = vcalloc(num_endpoints,
19592c66ee8SBrett Creeley sizeof(*pdsfc->endpoint_info));
19692c66ee8SBrett Creeley if (!pdsfc->endpoint_info) {
19792c66ee8SBrett Creeley pdsfc_free_endpoints(pdsfc);
19892c66ee8SBrett Creeley return -ENOMEM;
19992c66ee8SBrett Creeley }
20092c66ee8SBrett Creeley
20192c66ee8SBrett Creeley ep_entry = (struct pds_fwctl_query_data_endpoint *)pdsfc->endpoints->entries;
20292c66ee8SBrett Creeley for (i = 0; i < num_endpoints; i++) {
20392c66ee8SBrett Creeley mutex_init(&pdsfc->endpoint_info[i].lock);
204*fd292c1fSShannon Nelson pdsfc->endpoint_info[i].endpoint = le32_to_cpu(ep_entry[i].id);
20592c66ee8SBrett Creeley }
20692c66ee8SBrett Creeley
20792c66ee8SBrett Creeley return 0;
20892c66ee8SBrett Creeley }
20992c66ee8SBrett Creeley
pdsfc_get_operations(struct pdsfc_dev * pdsfc,dma_addr_t * pa,u32 ep)21092c66ee8SBrett Creeley static struct pds_fwctl_query_data *pdsfc_get_operations(struct pdsfc_dev *pdsfc,
21192c66ee8SBrett Creeley dma_addr_t *pa, u32 ep)
21292c66ee8SBrett Creeley {
21392c66ee8SBrett Creeley struct pds_fwctl_query_data_operation *entries;
21492c66ee8SBrett Creeley struct device *dev = &pdsfc->fwctl.dev;
21592c66ee8SBrett Creeley union pds_core_adminq_comp comp = {0};
21692c66ee8SBrett Creeley struct pds_fwctl_query_data *data;
21792c66ee8SBrett Creeley union pds_core_adminq_cmd cmd;
21892c66ee8SBrett Creeley dma_addr_t data_pa;
219*fd292c1fSShannon Nelson u32 num_entries;
22092c66ee8SBrett Creeley int err;
22192c66ee8SBrett Creeley int i;
22292c66ee8SBrett Creeley
22392c66ee8SBrett Creeley /* Query the operations list for the given endpoint */
22492c66ee8SBrett Creeley data = dma_alloc_coherent(dev->parent, PAGE_SIZE, &data_pa, GFP_KERNEL);
22592c66ee8SBrett Creeley if (!data) {
22692c66ee8SBrett Creeley dev_err(dev, "Failed to map operations list\n");
22792c66ee8SBrett Creeley return ERR_PTR(-ENOMEM);
22892c66ee8SBrett Creeley }
22992c66ee8SBrett Creeley
23092c66ee8SBrett Creeley cmd = (union pds_core_adminq_cmd) {
23192c66ee8SBrett Creeley .fwctl_query = {
23292c66ee8SBrett Creeley .opcode = PDS_FWCTL_CMD_QUERY,
23392c66ee8SBrett Creeley .entity = PDS_FWCTL_RPC_ENDPOINT,
23492c66ee8SBrett Creeley .version = 0,
23592c66ee8SBrett Creeley .query_data_buf_len = cpu_to_le32(PAGE_SIZE),
23692c66ee8SBrett Creeley .query_data_buf_pa = cpu_to_le64(data_pa),
23792c66ee8SBrett Creeley .ep = cpu_to_le32(ep),
23892c66ee8SBrett Creeley }
23992c66ee8SBrett Creeley };
24092c66ee8SBrett Creeley
24192c66ee8SBrett Creeley err = pds_client_adminq_cmd(pdsfc->padev, &cmd, sizeof(cmd), &comp, 0);
24292c66ee8SBrett Creeley if (err) {
24392c66ee8SBrett Creeley dev_err(dev, "Failed to send adminq cmd opcode: %u entity: %u err: %d\n",
24492c66ee8SBrett Creeley cmd.fwctl_query.opcode, cmd.fwctl_query.entity, err);
24592c66ee8SBrett Creeley dma_free_coherent(dev->parent, PAGE_SIZE, data, data_pa);
24692c66ee8SBrett Creeley return ERR_PTR(err);
24792c66ee8SBrett Creeley }
24892c66ee8SBrett Creeley
24992c66ee8SBrett Creeley *pa = data_pa;
25092c66ee8SBrett Creeley
25192c66ee8SBrett Creeley entries = (struct pds_fwctl_query_data_operation *)data->entries;
252*fd292c1fSShannon Nelson num_entries = le32_to_cpu(data->num_entries);
253*fd292c1fSShannon Nelson dev_dbg(dev, "num_entries %d\n", num_entries);
254*fd292c1fSShannon Nelson for (i = 0; i < num_entries; i++) {
25592c66ee8SBrett Creeley
25692c66ee8SBrett Creeley /* Translate FW command attribute to fwctl scope */
25792c66ee8SBrett Creeley switch (entries[i].scope) {
25892c66ee8SBrett Creeley case PDSFC_FW_CMD_ATTR_READ:
25992c66ee8SBrett Creeley case PDSFC_FW_CMD_ATTR_WRITE:
26092c66ee8SBrett Creeley case PDSFC_FW_CMD_ATTR_SYNC:
26192c66ee8SBrett Creeley entries[i].scope = FWCTL_RPC_CONFIGURATION;
26292c66ee8SBrett Creeley break;
26392c66ee8SBrett Creeley case PDSFC_FW_CMD_ATTR_DEBUG_READ:
26492c66ee8SBrett Creeley entries[i].scope = FWCTL_RPC_DEBUG_READ_ONLY;
26592c66ee8SBrett Creeley break;
26692c66ee8SBrett Creeley case PDSFC_FW_CMD_ATTR_DEBUG_WRITE:
26792c66ee8SBrett Creeley entries[i].scope = FWCTL_RPC_DEBUG_WRITE;
26892c66ee8SBrett Creeley break;
26992c66ee8SBrett Creeley default:
27092c66ee8SBrett Creeley entries[i].scope = FWCTL_RPC_DEBUG_WRITE_FULL;
27192c66ee8SBrett Creeley break;
27292c66ee8SBrett Creeley }
27392c66ee8SBrett Creeley dev_dbg(dev, "endpoint %d operation: id %x scope %d\n",
274*fd292c1fSShannon Nelson ep, le32_to_cpu(entries[i].id), entries[i].scope);
27592c66ee8SBrett Creeley }
27692c66ee8SBrett Creeley
27792c66ee8SBrett Creeley return data;
27892c66ee8SBrett Creeley }
27992c66ee8SBrett Creeley
pdsfc_validate_rpc(struct pdsfc_dev * pdsfc,struct fwctl_rpc_pds * rpc,enum fwctl_rpc_scope scope)28092c66ee8SBrett Creeley static int pdsfc_validate_rpc(struct pdsfc_dev *pdsfc,
28192c66ee8SBrett Creeley struct fwctl_rpc_pds *rpc,
28292c66ee8SBrett Creeley enum fwctl_rpc_scope scope)
28392c66ee8SBrett Creeley {
28492c66ee8SBrett Creeley struct pds_fwctl_query_data_operation *op_entry;
28592c66ee8SBrett Creeley struct pdsfc_rpc_endpoint_info *ep_info = NULL;
28692c66ee8SBrett Creeley struct device *dev = &pdsfc->fwctl.dev;
287*fd292c1fSShannon Nelson u32 num_entries;
28892c66ee8SBrett Creeley int i;
28992c66ee8SBrett Creeley
29092c66ee8SBrett Creeley /* validate rpc in_len & out_len based
29192c66ee8SBrett Creeley * on ident.max_req_sz & max_resp_sz
29292c66ee8SBrett Creeley */
293*fd292c1fSShannon Nelson if (rpc->in.len > le32_to_cpu(pdsfc->ident.max_req_sz)) {
29492c66ee8SBrett Creeley dev_dbg(dev, "Invalid request size %u, max %u\n",
295*fd292c1fSShannon Nelson rpc->in.len, le32_to_cpu(pdsfc->ident.max_req_sz));
29692c66ee8SBrett Creeley return -EINVAL;
29792c66ee8SBrett Creeley }
29892c66ee8SBrett Creeley
299*fd292c1fSShannon Nelson if (rpc->out.len > le32_to_cpu(pdsfc->ident.max_resp_sz)) {
30092c66ee8SBrett Creeley dev_dbg(dev, "Invalid response size %u, max %u\n",
301*fd292c1fSShannon Nelson rpc->out.len, le32_to_cpu(pdsfc->ident.max_resp_sz));
30292c66ee8SBrett Creeley return -EINVAL;
30392c66ee8SBrett Creeley }
30492c66ee8SBrett Creeley
305*fd292c1fSShannon Nelson num_entries = le32_to_cpu(pdsfc->endpoints->num_entries);
306*fd292c1fSShannon Nelson for (i = 0; i < num_entries; i++) {
30792c66ee8SBrett Creeley if (pdsfc->endpoint_info[i].endpoint == rpc->in.ep) {
30892c66ee8SBrett Creeley ep_info = &pdsfc->endpoint_info[i];
30992c66ee8SBrett Creeley break;
31092c66ee8SBrett Creeley }
31192c66ee8SBrett Creeley }
31292c66ee8SBrett Creeley if (!ep_info) {
31392c66ee8SBrett Creeley dev_dbg(dev, "Invalid endpoint %d\n", rpc->in.ep);
31492c66ee8SBrett Creeley return -EINVAL;
31592c66ee8SBrett Creeley }
31692c66ee8SBrett Creeley
31792c66ee8SBrett Creeley /* query and cache this endpoint's operations */
31892c66ee8SBrett Creeley mutex_lock(&ep_info->lock);
31992c66ee8SBrett Creeley if (!ep_info->operations) {
32092c66ee8SBrett Creeley struct pds_fwctl_query_data *operations;
32192c66ee8SBrett Creeley
32292c66ee8SBrett Creeley operations = pdsfc_get_operations(pdsfc,
32392c66ee8SBrett Creeley &ep_info->operations_pa,
32492c66ee8SBrett Creeley rpc->in.ep);
32592c66ee8SBrett Creeley if (IS_ERR(operations)) {
32692c66ee8SBrett Creeley mutex_unlock(&ep_info->lock);
32792c66ee8SBrett Creeley return -ENOMEM;
32892c66ee8SBrett Creeley }
32992c66ee8SBrett Creeley ep_info->operations = operations;
33092c66ee8SBrett Creeley }
33192c66ee8SBrett Creeley mutex_unlock(&ep_info->lock);
33292c66ee8SBrett Creeley
33392c66ee8SBrett Creeley /* reject unsupported and/or out of scope commands */
33492c66ee8SBrett Creeley op_entry = (struct pds_fwctl_query_data_operation *)ep_info->operations->entries;
335*fd292c1fSShannon Nelson num_entries = le32_to_cpu(ep_info->operations->num_entries);
336*fd292c1fSShannon Nelson for (i = 0; i < num_entries; i++) {
337*fd292c1fSShannon Nelson if (PDS_FWCTL_RPC_OPCODE_CMP(rpc->in.op, le32_to_cpu(op_entry[i].id))) {
33892c66ee8SBrett Creeley if (scope < op_entry[i].scope)
33992c66ee8SBrett Creeley return -EPERM;
34092c66ee8SBrett Creeley return 0;
34192c66ee8SBrett Creeley }
34292c66ee8SBrett Creeley }
34392c66ee8SBrett Creeley
34492c66ee8SBrett Creeley dev_dbg(dev, "Invalid operation %d for endpoint %d\n", rpc->in.op, rpc->in.ep);
34592c66ee8SBrett Creeley
34692c66ee8SBrett Creeley return -EINVAL;
34792c66ee8SBrett Creeley }
34892c66ee8SBrett Creeley
pdsfc_fw_rpc(struct fwctl_uctx * uctx,enum fwctl_rpc_scope scope,void * in,size_t in_len,size_t * out_len)3494d09dd11SShannon Nelson static void *pdsfc_fw_rpc(struct fwctl_uctx *uctx, enum fwctl_rpc_scope scope,
3504d09dd11SShannon Nelson void *in, size_t in_len, size_t *out_len)
3514d09dd11SShannon Nelson {
35292c66ee8SBrett Creeley struct pdsfc_dev *pdsfc = container_of(uctx->fwctl, struct pdsfc_dev, fwctl);
35392c66ee8SBrett Creeley struct device *dev = &uctx->fwctl->dev;
35492c66ee8SBrett Creeley union pds_core_adminq_comp comp = {0};
35592c66ee8SBrett Creeley dma_addr_t out_payload_dma_addr = 0;
35692c66ee8SBrett Creeley dma_addr_t in_payload_dma_addr = 0;
35792c66ee8SBrett Creeley struct fwctl_rpc_pds *rpc = in;
35892c66ee8SBrett Creeley union pds_core_adminq_cmd cmd;
35992c66ee8SBrett Creeley void *out_payload = NULL;
36092c66ee8SBrett Creeley void *in_payload = NULL;
36192c66ee8SBrett Creeley void *out = NULL;
36292c66ee8SBrett Creeley int err;
36392c66ee8SBrett Creeley
36492c66ee8SBrett Creeley err = pdsfc_validate_rpc(pdsfc, rpc, scope);
36592c66ee8SBrett Creeley if (err)
36692c66ee8SBrett Creeley return ERR_PTR(err);
36792c66ee8SBrett Creeley
36892c66ee8SBrett Creeley if (rpc->in.len > 0) {
36992c66ee8SBrett Creeley in_payload = kzalloc(rpc->in.len, GFP_KERNEL);
37092c66ee8SBrett Creeley if (!in_payload) {
37192c66ee8SBrett Creeley dev_err(dev, "Failed to allocate in_payload\n");
37292c66ee8SBrett Creeley err = -ENOMEM;
37392c66ee8SBrett Creeley goto err_out;
37492c66ee8SBrett Creeley }
37592c66ee8SBrett Creeley
37692c66ee8SBrett Creeley if (copy_from_user(in_payload, u64_to_user_ptr(rpc->in.payload),
37792c66ee8SBrett Creeley rpc->in.len)) {
37892c66ee8SBrett Creeley dev_dbg(dev, "Failed to copy in_payload from user\n");
37992c66ee8SBrett Creeley err = -EFAULT;
38092c66ee8SBrett Creeley goto err_in_payload;
38192c66ee8SBrett Creeley }
38292c66ee8SBrett Creeley
38392c66ee8SBrett Creeley in_payload_dma_addr = dma_map_single(dev->parent, in_payload,
38492c66ee8SBrett Creeley rpc->in.len, DMA_TO_DEVICE);
38592c66ee8SBrett Creeley err = dma_mapping_error(dev->parent, in_payload_dma_addr);
38692c66ee8SBrett Creeley if (err) {
38792c66ee8SBrett Creeley dev_dbg(dev, "Failed to map in_payload\n");
38892c66ee8SBrett Creeley goto err_in_payload;
38992c66ee8SBrett Creeley }
39092c66ee8SBrett Creeley }
39192c66ee8SBrett Creeley
39292c66ee8SBrett Creeley if (rpc->out.len > 0) {
39392c66ee8SBrett Creeley out_payload = kzalloc(rpc->out.len, GFP_KERNEL);
39492c66ee8SBrett Creeley if (!out_payload) {
39592c66ee8SBrett Creeley dev_dbg(dev, "Failed to allocate out_payload\n");
39692c66ee8SBrett Creeley err = -ENOMEM;
39792c66ee8SBrett Creeley goto err_out_payload;
39892c66ee8SBrett Creeley }
39992c66ee8SBrett Creeley
40092c66ee8SBrett Creeley out_payload_dma_addr = dma_map_single(dev->parent, out_payload,
40192c66ee8SBrett Creeley rpc->out.len, DMA_FROM_DEVICE);
40292c66ee8SBrett Creeley err = dma_mapping_error(dev->parent, out_payload_dma_addr);
40392c66ee8SBrett Creeley if (err) {
40492c66ee8SBrett Creeley dev_dbg(dev, "Failed to map out_payload\n");
40592c66ee8SBrett Creeley goto err_out_payload;
40692c66ee8SBrett Creeley }
40792c66ee8SBrett Creeley }
40892c66ee8SBrett Creeley
40992c66ee8SBrett Creeley cmd = (union pds_core_adminq_cmd) {
41092c66ee8SBrett Creeley .fwctl_rpc = {
41192c66ee8SBrett Creeley .opcode = PDS_FWCTL_CMD_RPC,
412*fd292c1fSShannon Nelson .flags = cpu_to_le16(PDS_FWCTL_RPC_IND_REQ | PDS_FWCTL_RPC_IND_RESP),
41392c66ee8SBrett Creeley .ep = cpu_to_le32(rpc->in.ep),
41492c66ee8SBrett Creeley .op = cpu_to_le32(rpc->in.op),
41592c66ee8SBrett Creeley .req_pa = cpu_to_le64(in_payload_dma_addr),
41692c66ee8SBrett Creeley .req_sz = cpu_to_le32(rpc->in.len),
41792c66ee8SBrett Creeley .resp_pa = cpu_to_le64(out_payload_dma_addr),
41892c66ee8SBrett Creeley .resp_sz = cpu_to_le32(rpc->out.len),
41992c66ee8SBrett Creeley }
42092c66ee8SBrett Creeley };
42192c66ee8SBrett Creeley
42292c66ee8SBrett Creeley err = pds_client_adminq_cmd(pdsfc->padev, &cmd, sizeof(cmd), &comp, 0);
42392c66ee8SBrett Creeley if (err) {
42492c66ee8SBrett Creeley dev_dbg(dev, "%s: ep %d op %x req_pa %llx req_sz %d req_sg %d resp_pa %llx resp_sz %d resp_sg %d err %d\n",
42592c66ee8SBrett Creeley __func__, rpc->in.ep, rpc->in.op,
42692c66ee8SBrett Creeley cmd.fwctl_rpc.req_pa, cmd.fwctl_rpc.req_sz, cmd.fwctl_rpc.req_sg_elems,
42792c66ee8SBrett Creeley cmd.fwctl_rpc.resp_pa, cmd.fwctl_rpc.resp_sz, cmd.fwctl_rpc.resp_sg_elems,
42892c66ee8SBrett Creeley err);
42992c66ee8SBrett Creeley goto done;
43092c66ee8SBrett Creeley }
43192c66ee8SBrett Creeley
43292c66ee8SBrett Creeley dynamic_hex_dump("out ", DUMP_PREFIX_OFFSET, 16, 1, out_payload, rpc->out.len, true);
43392c66ee8SBrett Creeley
43492c66ee8SBrett Creeley if (copy_to_user(u64_to_user_ptr(rpc->out.payload), out_payload, rpc->out.len)) {
43592c66ee8SBrett Creeley dev_dbg(dev, "Failed to copy out_payload to user\n");
43692c66ee8SBrett Creeley out = ERR_PTR(-EFAULT);
43792c66ee8SBrett Creeley goto done;
43892c66ee8SBrett Creeley }
43992c66ee8SBrett Creeley
44092c66ee8SBrett Creeley rpc->out.retval = le32_to_cpu(comp.fwctl_rpc.err);
44192c66ee8SBrett Creeley *out_len = in_len;
44292c66ee8SBrett Creeley out = in;
44392c66ee8SBrett Creeley
44492c66ee8SBrett Creeley done:
44592c66ee8SBrett Creeley if (out_payload_dma_addr)
44692c66ee8SBrett Creeley dma_unmap_single(dev->parent, out_payload_dma_addr,
44792c66ee8SBrett Creeley rpc->out.len, DMA_FROM_DEVICE);
44892c66ee8SBrett Creeley err_out_payload:
44992c66ee8SBrett Creeley kfree(out_payload);
45092c66ee8SBrett Creeley
45192c66ee8SBrett Creeley if (in_payload_dma_addr)
45292c66ee8SBrett Creeley dma_unmap_single(dev->parent, in_payload_dma_addr,
45392c66ee8SBrett Creeley rpc->in.len, DMA_TO_DEVICE);
45492c66ee8SBrett Creeley err_in_payload:
45592c66ee8SBrett Creeley kfree(in_payload);
45692c66ee8SBrett Creeley err_out:
45792c66ee8SBrett Creeley if (err)
45892c66ee8SBrett Creeley return ERR_PTR(err);
45992c66ee8SBrett Creeley
46092c66ee8SBrett Creeley return out;
4614d09dd11SShannon Nelson }
4624d09dd11SShannon Nelson
4634d09dd11SShannon Nelson static const struct fwctl_ops pdsfc_ops = {
4644d09dd11SShannon Nelson .device_type = FWCTL_DEVICE_TYPE_PDS,
4654d09dd11SShannon Nelson .uctx_size = sizeof(struct pdsfc_uctx),
4664d09dd11SShannon Nelson .open_uctx = pdsfc_open_uctx,
4674d09dd11SShannon Nelson .close_uctx = pdsfc_close_uctx,
4684d09dd11SShannon Nelson .info = pdsfc_info,
4694d09dd11SShannon Nelson .fw_rpc = pdsfc_fw_rpc,
4704d09dd11SShannon Nelson };
4714d09dd11SShannon Nelson
pdsfc_probe(struct auxiliary_device * adev,const struct auxiliary_device_id * id)4724d09dd11SShannon Nelson static int pdsfc_probe(struct auxiliary_device *adev,
4734d09dd11SShannon Nelson const struct auxiliary_device_id *id)
4744d09dd11SShannon Nelson {
4754d09dd11SShannon Nelson struct pds_auxiliary_dev *padev =
4764d09dd11SShannon Nelson container_of(adev, struct pds_auxiliary_dev, aux_dev);
4774d09dd11SShannon Nelson struct device *dev = &adev->dev;
4784d09dd11SShannon Nelson struct pdsfc_dev *pdsfc;
4794d09dd11SShannon Nelson int err;
4804d09dd11SShannon Nelson
4814d09dd11SShannon Nelson pdsfc = fwctl_alloc_device(&padev->vf_pdev->dev, &pdsfc_ops,
4824d09dd11SShannon Nelson struct pdsfc_dev, fwctl);
4834d09dd11SShannon Nelson if (!pdsfc)
4844d09dd11SShannon Nelson return dev_err_probe(dev, -ENOMEM, "Failed to allocate fwctl device struct\n");
4854d09dd11SShannon Nelson pdsfc->padev = padev;
4864d09dd11SShannon Nelson
4874d09dd11SShannon Nelson err = pdsfc_identify(pdsfc);
4884d09dd11SShannon Nelson if (err) {
4894d09dd11SShannon Nelson fwctl_put(&pdsfc->fwctl);
4904d09dd11SShannon Nelson return dev_err_probe(dev, err, "Failed to identify device\n");
4914d09dd11SShannon Nelson }
4924d09dd11SShannon Nelson
49392c66ee8SBrett Creeley err = pdsfc_init_endpoints(pdsfc);
49492c66ee8SBrett Creeley if (err) {
49592c66ee8SBrett Creeley fwctl_put(&pdsfc->fwctl);
49692c66ee8SBrett Creeley return dev_err_probe(dev, err, "Failed to init endpoints\n");
49792c66ee8SBrett Creeley }
49892c66ee8SBrett Creeley
49992c66ee8SBrett Creeley pdsfc->caps = PDS_FWCTL_QUERY_CAP | PDS_FWCTL_SEND_CAP;
50092c66ee8SBrett Creeley
5014d09dd11SShannon Nelson err = fwctl_register(&pdsfc->fwctl);
5024d09dd11SShannon Nelson if (err) {
50392c66ee8SBrett Creeley pdsfc_free_endpoints(pdsfc);
5044d09dd11SShannon Nelson fwctl_put(&pdsfc->fwctl);
5054d09dd11SShannon Nelson return dev_err_probe(dev, err, "Failed to register device\n");
5064d09dd11SShannon Nelson }
5074d09dd11SShannon Nelson
5084d09dd11SShannon Nelson auxiliary_set_drvdata(adev, pdsfc);
5094d09dd11SShannon Nelson
5104d09dd11SShannon Nelson return 0;
5114d09dd11SShannon Nelson }
5124d09dd11SShannon Nelson
pdsfc_remove(struct auxiliary_device * adev)5134d09dd11SShannon Nelson static void pdsfc_remove(struct auxiliary_device *adev)
5144d09dd11SShannon Nelson {
5154d09dd11SShannon Nelson struct pdsfc_dev *pdsfc = auxiliary_get_drvdata(adev);
5164d09dd11SShannon Nelson
5174d09dd11SShannon Nelson fwctl_unregister(&pdsfc->fwctl);
51892c66ee8SBrett Creeley pdsfc_free_operations(pdsfc);
51992c66ee8SBrett Creeley pdsfc_free_endpoints(pdsfc);
52092c66ee8SBrett Creeley
5214d09dd11SShannon Nelson fwctl_put(&pdsfc->fwctl);
5224d09dd11SShannon Nelson }
5234d09dd11SShannon Nelson
5244d09dd11SShannon Nelson static const struct auxiliary_device_id pdsfc_id_table[] = {
5254d09dd11SShannon Nelson {.name = PDS_CORE_DRV_NAME "." PDS_DEV_TYPE_FWCTL_STR },
5264d09dd11SShannon Nelson {}
5274d09dd11SShannon Nelson };
5284d09dd11SShannon Nelson MODULE_DEVICE_TABLE(auxiliary, pdsfc_id_table);
5294d09dd11SShannon Nelson
5304d09dd11SShannon Nelson static struct auxiliary_driver pdsfc_driver = {
5314d09dd11SShannon Nelson .name = "pds_fwctl",
5324d09dd11SShannon Nelson .probe = pdsfc_probe,
5334d09dd11SShannon Nelson .remove = pdsfc_remove,
5344d09dd11SShannon Nelson .id_table = pdsfc_id_table,
5354d09dd11SShannon Nelson };
5364d09dd11SShannon Nelson
5374d09dd11SShannon Nelson module_auxiliary_driver(pdsfc_driver);
5384d09dd11SShannon Nelson
5394d09dd11SShannon Nelson MODULE_IMPORT_NS("FWCTL");
5404d09dd11SShannon Nelson MODULE_DESCRIPTION("pds fwctl driver");
5414d09dd11SShannon Nelson MODULE_AUTHOR("Shannon Nelson <shannon.nelson@amd.com>");
5424d09dd11SShannon Nelson MODULE_AUTHOR("Brett Creeley <brett.creeley@amd.com>");
5434d09dd11SShannon Nelson MODULE_LICENSE("GPL");
544