756 lines · 19.1 KB · C
RawDownload
// SPDX-License-Identifier: GPL-2.0/* * Intel USBIO Bridge driver * * Copyright (c) 2025 Intel Corporation. * Copyright (c) 2025 Red Hat, Inc. */ #include <linux/acpi.h>#include <linux/auxiliary_bus.h>#include <linux/byteorder/generic.h>#include <linux/cleanup.h>#include <linux/completion.h>#include <linux/dev_printk.h>#include <linux/device.h>#include <linux/lockdep.h>#include <linux/mutex.h>#include <linux/string.h>#include <linux/types.h>#include <linux/usb.h>#include <linux/usb/usbio.h> /************************************* * USBIO Bridge Protocol Definitions * *************************************/ /* USBIO Control Commands */#define USBIO_CTRLCMD_PROTVER	0#define USBIO_CTRLCMD_FWVER	1#define USBIO_CTRLCMD_HS	2#define USBIO_CTRLCMD_ENUMGPIO	16#define USBIO_CTRLCMD_ENUMI2C	17 /* USBIO Packet Flags */#define USBIO_PKTFLAG_ACK	BIT(0)#define USBIO_PKTFLAG_RSP	BIT(1)#define USBIO_PKTFLAG_CMP	BIT(2)#define USBIO_PKTFLAG_ERR	BIT(3) #define USBIO_PKTFLAGS_REQRESP	(USBIO_PKTFLAG_CMP | USBIO_PKTFLAG_ACK) #define USBIO_CTRLXFER_TIMEOUT 0#define USBIO_BULKXFER_TIMEOUT 100 struct usbio_protver {	u8 ver;} __packed; struct usbio_fwver {	u8 major;	u8 minor;	__le16 patch;	__le16 build;} __packed; /*********************************** * USBIO Bridge Device Definitions * ***********************************/ /** * struct usbio_device - the usb device exposing IOs * * @dev: the device in the usb interface * @udev: the detected usb device * @intf: the usb interface * @quirks: quirks * @ctrl_mutex: protects ctrl_buf * @ctrl_pipe: the control transfer pipe * @ctrlbuf_len: the size of the control transfer pipe * @ctrlbuf: the buffer used for control transfers * @bulk_mutex: protects tx_buf, rx_buf and split bulk-transfers getting interrupted * @tx_pipe: the bulk out pipe * @txbuf_len: the size of the bulk out pipe * @txbuf: the buffer used for bulk out transfers * @rx_pipe: the bulk in pipe * @rxbuf_len: the size of the bulk in pipe * @rxdat_len: the data length at rx buffer * @rxbuf: the buffer used for bulk in transfers * @urb: the urb to read bulk pipe * @done: completion object as request is done * @cli_list: device's client list * @nr_gpio_banks: Number of GPIO banks * @gpios: GPIO bank descriptors * @nr_gpio_banks: Number of I2C busses * @gpios: I2C bank descriptors */struct usbio_device {	struct device *dev;	struct usb_device *udev;	struct usb_interface *intf;	unsigned long quirks; 	struct mutex ctrl_mutex;	unsigned int ctrl_pipe;	u16 ctrlbuf_len;	void *ctrlbuf; 	struct mutex bulk_mutex;	unsigned int tx_pipe;	u16 txbuf_len;	void *txbuf; 	unsigned int rx_pipe;	u16 rxbuf_len;	u16 rxdat_len;	void *rxbuf;	struct urb *urb; 	struct completion done; 	struct list_head cli_list; 	unsigned int nr_gpio_banks;	struct usbio_gpio_bank_desc gpios[USBIO_MAX_GPIOBANKS]; 	unsigned int nr_i2c_buses;	struct usbio_i2c_bus_desc i2cs[USBIO_MAX_I2CBUSES];}; /** * struct usbio_client - represents a usbio client * * @auxdev: auxiliary device object * @mutex: protects @bridge * @bridge: usbio bridge who service the client * @link: usbio bridge clients list member */struct usbio_client {	struct auxiliary_device auxdev;	struct mutex mutex;	struct usbio_device *bridge;	struct list_head link;}; #define adev_to_client(adev) container_of_const(adev, struct usbio_client, auxdev) static int usbio_ctrl_msg(struct usbio_device *usbio, u8 type, u8 cmd,			  const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len){	u8 request = USB_TYPE_VENDOR | USB_RECIP_DEVICE;	struct usbio_ctrl_packet *cpkt;	unsigned int pipe;	u16 cpkt_len;	int ret; 	lockdep_assert_held(&usbio->ctrl_mutex); 	if ((obuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))) ||	    (ibuf_len > (usbio->ctrlbuf_len - sizeof(*cpkt))))		return -EMSGSIZE; 	/* Prepare Control Packet Header */	cpkt = usbio->ctrlbuf;	cpkt->header.type = type;	cpkt->header.cmd = cmd;	if (type == USBIO_PKTTYPE_CTRL || ibuf_len)		cpkt->header.flags = USBIO_PKTFLAGS_REQRESP;	else		cpkt->header.flags = USBIO_PKTFLAG_CMP;	cpkt->len = obuf_len; 	/* Copy the data */	memcpy(cpkt->data, obuf, obuf_len); 	pipe = usb_sndctrlpipe(usbio->udev, usbio->ctrl_pipe);	cpkt_len = sizeof(*cpkt) + obuf_len;	ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_OUT, 0, 0,			      cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT);	dev_dbg(usbio->dev, "control out %d hdr %*phN data %*phN\n", ret,		(int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); 	if (ret != cpkt_len) {		dev_err(usbio->dev, "USB control out failed: %d\n", ret);		return (ret < 0) ? ret : -EPROTO;	} 	if (!(cpkt->header.flags & USBIO_PKTFLAG_ACK))		return 0; 	pipe = usb_rcvctrlpipe(usbio->udev, usbio->ctrl_pipe);	cpkt_len = sizeof(*cpkt) + ibuf_len;	ret = usb_control_msg(usbio->udev, pipe, 0, request | USB_DIR_IN, 0, 0,			      cpkt, cpkt_len, USBIO_CTRLXFER_TIMEOUT);	dev_dbg(usbio->dev, "control in %d hdr %*phN data %*phN\n", ret,		(int)sizeof(*cpkt), cpkt, (int)cpkt->len, cpkt->data); 	if (ret < sizeof(*cpkt)) {		dev_err(usbio->dev, "USB control in failed: %d\n", ret);		return (ret < 0) ? ret : -EPROTO;	} 	if (cpkt->header.type != type || cpkt->header.cmd != cmd ||	    !(cpkt->header.flags & USBIO_PKTFLAG_RSP)) {		dev_err(usbio->dev, "Unexpected reply type: %u, cmd: %u, flags: %u\n",			cpkt->header.type, cpkt->header.cmd, cpkt->header.flags);		return -EPROTO;	} 	if (cpkt->header.flags & USBIO_PKTFLAG_ERR)		return -EREMOTEIO; 	if (ibuf_len < cpkt->len)		return -ENOSPC; 	memcpy(ibuf, cpkt->data, cpkt->len); 	return cpkt->len;} int usbio_control_msg(struct auxiliary_device *adev, u8 type, u8 cmd,		      const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len){	struct usbio_client *client = adev_to_client(adev);	struct usbio_device *usbio;	int ret; 	guard(mutex)(&client->mutex); 	usbio = client->bridge;	if (!usbio)		return -ENODEV; /* Disconnected */ 	ret = usb_autopm_get_interface(usbio->intf);	if (ret)		return ret; 	mutex_lock(&usbio->ctrl_mutex); 	ret = usbio_ctrl_msg(client->bridge, type, cmd, obuf, obuf_len, ibuf, ibuf_len); 	mutex_unlock(&usbio->ctrl_mutex);	usb_autopm_put_interface(usbio->intf); 	return ret;}EXPORT_SYMBOL_NS_GPL(usbio_control_msg, "USBIO"); static void usbio_bulk_recv(struct urb *urb){	struct usbio_bulk_packet *bpkt = urb->transfer_buffer;	struct usbio_device *usbio = urb->context; 	if (!urb->status) {		if (bpkt->header.flags & USBIO_PKTFLAG_RSP) {			usbio->rxdat_len = urb->actual_length;			complete(&usbio->done);		}	} else if (urb->status != -ENOENT) {		dev_err(usbio->dev, "Bulk in error %d\n", urb->status);	} 	usb_submit_urb(usbio->urb, GFP_ATOMIC);} int usbio_bulk_msg(struct auxiliary_device *adev, u8 type, u8 cmd, bool last,		   const void *obuf, u16 obuf_len, void *ibuf, u16 ibuf_len){	struct usbio_client *client = adev_to_client(adev);	struct usbio_device *usbio = client->bridge;	struct usbio_bulk_packet *bpkt;	int ret, act = 0;	u16 bpkt_len; 	lockdep_assert_held(&client->mutex);	lockdep_assert_held(&usbio->bulk_mutex); 	if ((obuf_len > (usbio->txbuf_len - sizeof(*bpkt))) ||	    (ibuf_len > (usbio->rxbuf_len - sizeof(*bpkt))))		return -EMSGSIZE; 	if (ibuf_len)		reinit_completion(&usbio->done); 	/* If no data to send, skip to read */	if (!obuf_len)		goto read; 	/* Prepare Bulk Packet Header */	bpkt = usbio->txbuf;	bpkt->header.type = type;	bpkt->header.cmd = cmd;	if (!last)		bpkt->header.flags = 0;	else if (ibuf_len)		bpkt->header.flags = USBIO_PKTFLAGS_REQRESP;	else		bpkt->header.flags = USBIO_PKTFLAG_CMP;	bpkt->len = cpu_to_le16(obuf_len); 	/* Copy the data */	memcpy(bpkt->data, obuf, obuf_len); 	bpkt_len = sizeof(*bpkt) + obuf_len;	ret = usb_bulk_msg(usbio->udev, usbio->tx_pipe, bpkt, bpkt_len, &act,			   USBIO_BULKXFER_TIMEOUT);	dev_dbg(usbio->dev, "bulk out %d hdr %*phN data %*phN\n", act,		(int)sizeof(*bpkt), bpkt, obuf_len, bpkt->data); 	if (ret || act != bpkt_len) {		dev_err(usbio->dev, "Bulk out failed: %d\n", ret);		return ret ?: -EPROTO;	} 	if (!(bpkt->header.flags & USBIO_PKTFLAG_ACK))		return obuf_len; read:	ret = wait_for_completion_timeout(&usbio->done, USBIO_BULKXFER_TIMEOUT);	if (ret <= 0) {		dev_err(usbio->dev, "Bulk in wait failed: %d\n", ret);		return ret ?: -ETIMEDOUT;	} 	act = usbio->rxdat_len;	bpkt = usbio->rxbuf;	bpkt_len = le16_to_cpu(bpkt->len);	dev_dbg(usbio->dev, "bulk in %d hdr %*phN data %*phN\n", act,		(int)sizeof(*bpkt), bpkt, bpkt_len, bpkt->data); 	/*	 * Unsupported bulk commands get only an usbio_packet_header with	 * the error flag set as reply. Return -EPIPE for this case.	 */	if (act == sizeof(struct usbio_packet_header) &&	    (bpkt->header.flags & USBIO_PKTFLAG_ERR))		return -EPIPE; 	if (act < sizeof(*bpkt)) {		dev_err(usbio->dev, "Bulk in short read: %d\n", act);		return -EPROTO;	} 	if (bpkt->header.type != type || bpkt->header.cmd != cmd ||	    !(bpkt->header.flags & USBIO_PKTFLAG_RSP)) {		dev_err(usbio->dev,			"Unexpected bulk in type 0x%02x cmd 0x%02x flags 0x%02x\n",			bpkt->header.type, bpkt->header.cmd, bpkt->header.flags);		return -EPROTO;	} 	if (bpkt->header.flags & USBIO_PKTFLAG_ERR)		return -EREMOTEIO; 	if (ibuf_len < bpkt_len)		return -ENOSPC; 	/* The device must not claim more payload than it actually sent. */	if (bpkt_len > act - sizeof(*bpkt))		return -EPROTO; 	memcpy(ibuf, bpkt->data, bpkt_len); 	return bpkt_len;}EXPORT_SYMBOL_NS_GPL(usbio_bulk_msg, "USBIO"); int usbio_acquire(struct auxiliary_device *adev){	struct usbio_client *client = adev_to_client(adev);	struct usbio_device *usbio;	int ret; 	mutex_lock(&client->mutex); 	usbio = client->bridge;	if (!usbio) {		ret = -ENODEV; /* Disconnected */		goto err_unlock;	} 	ret = usb_autopm_get_interface(usbio->intf);	if (ret)		goto err_unlock; 	mutex_lock(&usbio->bulk_mutex); 	/* Leave client locked until release to avoid abba deadlock issues */	return 0; err_unlock:	mutex_unlock(&client->mutex); 	return ret;}EXPORT_SYMBOL_NS_GPL(usbio_acquire, "USBIO"); void usbio_release(struct auxiliary_device *adev){	struct usbio_client *client = adev_to_client(adev);	struct usbio_device *usbio = client->bridge; 	lockdep_assert_held(&client->mutex); 	mutex_unlock(&usbio->bulk_mutex);	usb_autopm_put_interface(usbio->intf);	mutex_unlock(&client->mutex);}EXPORT_SYMBOL_NS_GPL(usbio_release, "USBIO"); void usbio_get_txrxbuf_len(struct auxiliary_device *adev, u16 *txbuf_len, u16 *rxbuf_len){	struct usbio_client *client = adev_to_client(adev);	struct usbio_device *usbio; 	guard(mutex)(&client->mutex); 	usbio = client->bridge;	if (!usbio)		return; /* Disconnected */ 	*txbuf_len = usbio->txbuf_len;	*rxbuf_len = usbio->rxbuf_len;}EXPORT_SYMBOL_NS_GPL(usbio_get_txrxbuf_len, "USBIO"); unsigned long usbio_get_quirks(struct auxiliary_device *adev){	struct usbio_client *client = adev_to_client(adev);	struct usbio_device *usbio; 	guard(mutex)(&client->mutex); 	usbio = client->bridge;	if (!usbio)		return 0; /* Disconnected */ 	return usbio->quirks;}EXPORT_SYMBOL_NS_GPL(usbio_get_quirks, "USBIO"); static void usbio_auxdev_release(struct device *dev){	struct auxiliary_device *adev = to_auxiliary_dev(dev);	struct usbio_client *client = adev_to_client(adev); 	mutex_destroy(&client->mutex);	kfree(client);} static int usbio_add_client(struct usbio_device *usbio, char *name, u8 id, void *data){	struct usbio_client *client;	struct auxiliary_device *adev;	int ret; 	client = kzalloc_obj(*client);	if (!client)		return -ENOMEM; 	mutex_init(&client->mutex);	client->bridge = usbio;	adev = &client->auxdev;	adev->name = name;	adev->id = id; 	adev->dev.parent = usbio->dev;	adev->dev.platform_data = data;	adev->dev.release = usbio_auxdev_release; 	ret = auxiliary_device_init(adev);	if (ret) {		usbio_auxdev_release(&adev->dev);		return ret;	} 	ret = auxiliary_device_add(adev);	if (ret) {		auxiliary_device_uninit(adev);		return ret;	} 	list_add_tail(&client->link, &usbio->cli_list); 	return 0;} static int usbio_enum_gpios(struct usbio_device *usbio){	struct usbio_gpio_bank_desc *gpio = usbio->gpios; 	dev_dbg(usbio->dev, "GPIO Banks: %d\n", usbio->nr_gpio_banks); 	for (unsigned int i = 0; i < usbio->nr_gpio_banks; i++)		dev_dbg(usbio->dev, "\tBank%d[%d] map: %#08x\n",			gpio[i].id, gpio[i].pins, gpio[i].bmap); 	usbio_add_client(usbio, USBIO_GPIO_CLIENT, 0, gpio); 	return 0;} static int usbio_enum_i2cs(struct usbio_device *usbio){	struct usbio_i2c_bus_desc *i2c = usbio->i2cs; 	dev_dbg(usbio->dev, "I2C Busses: %d\n", usbio->nr_i2c_buses); 	for (unsigned int i = 0; i < usbio->nr_i2c_buses; i++) {		dev_dbg(usbio->dev, "\tBus%d caps: %#02x\n", i2c[i].id, i2c[i].caps);		usbio_add_client(usbio, USBIO_I2C_CLIENT, i, &i2c[i]);

Lines 1–500 of 756.