xref: /linux/drivers/fwctl/pds/main.c (revision e2516abf1c88212d98af889070123469c28ca2fe)
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