With both host_role and device_role negotiated, a port's own role is ambiguous (host|device) - determining it requires more than reading the two feature bits. Introduce a minimal OTG command/event queue pair (otg_vq_base, otg_vqueues[], otg_init()/otg_deinit()) and otg_get_role(), a synchronous VIRTIO_USB_CMD_OTG_GET_ROLE request/ response exchange, and query every port's role individually during probe instead of the alternative of overloading the config space ports field with role bits.
This replaces the previous commit's DEVICE-only placeholder for the both-roles-negotiated case with the real per-port answer. Signed-off-by: Igor Skalkin <[email protected]> --- drivers/usb/virtio_usb/Makefile | 3 drivers/usb/virtio_usb/controller.c | 87 +++++++++++++++--- drivers/usb/virtio_usb/controller.h | 11 ++ drivers/usb/virtio_usb/otg.c | 168 ++++++++++++++++++++++++++++++++++++ drivers/usb/virtio_usb/otg.h | 26 +++++ 5 files changed, 277 insertions(+), 18 deletions(-) create mode 100644 drivers/usb/virtio_usb/otg.c create mode 100644 drivers/usb/virtio_usb/otg.h diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c index 0646807..59af5cc 100644 --- a/drivers/usb/virtio_usb/controller.c +++ b/drivers/usb/virtio_usb/controller.c @@ -12,6 +12,7 @@ #include "controller.h" #include "host.h" #include "device.h" +#include "otg.h" #include "vq_common.h" u32 virtio_usb_cmd_timeout_ms = MSEC_PER_SEC; @@ -130,12 +131,12 @@ static int virtio_usb_probe(struct virtio_device *vdev) /* Only allocate/negotiate the virtqueue triplets this instance * actually needs: HOST_* only exists when host_role is negotiated, - * DEV_* only when device_role is negotiated. A pure single-role - * instance therefore has exactly VIRTIO_USB_VQ_HOST_MAX (3) or - * VIRTIO_USB_VQ_DEV_MAX (3) virtqueues, not a fixed layout - queues - * that don't exist on the wire must not be created, since a peer - * with no host-role VP has no host command/event/data queues to - * negotiate at all. + * DEV_* only when device_role is negotiated, OTG_* only when both + * roles are negotiated. A pure single-role instance therefore has + * exactly VIRTIO_USB_VQ_HOST_MAX (3) or VIRTIO_USB_VQ_DEV_MAX (3) + * virtqueues, not a fixed layout - queues that don't exist on the + * wire must not be created, since a peer with no host-role VP has + * no host command/event/data queues to negotiate at all. */ vusb->host_vq_base = -1; vusb->dev_vq_base = -1; @@ -148,17 +149,15 @@ static int virtio_usb_probe(struct virtio_device *vdev) vusb->dev_vq_base = nvqs; nvqs += VIRTIO_USB_VQ_DEV_MAX; } - - /* Resolve every port's role. With only one role negotiated, every - * port unambiguously has that role. With both negotiated, a port's - * own role is ambiguous until a later commit adds an OTG-based - * per-port query - default to DEVICE for now as a placeholder. - */ - for (i = 0; i < vusb->nports; i++) { - if (vusb->host_role && !vusb->device_role) - vusb->vports[i].role = VIRTIO_USB_ROLE_HOST; - else if (vusb->device_role) - vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE; + if (vusb->host_role && vusb->device_role) { + /* The OTG command/event queue pair is needed whenever both + * roles are negotiated - it is how the driver asks each + * port for its actual role via otg_get_role() below, since + * a port's own role is otherwise ambiguous (host|device) + * until then. + */ + vusb->otg_vq_base = nvqs; + nvqs += VIRTIO_USB_VQ_OTG_MAX; } vusb->vqueues = devm_kcalloc(&vdev->dev, nvqs, sizeof(*vusb->vqueues), @@ -192,6 +191,18 @@ static int virtio_usb_probe(struct virtio_device *vdev) dev_vqueues[i].stop; } + if (vusb->host_role && vusb->device_role) + for (i = 0; i < VIRTIO_USB_VQ_OTG_MAX; i++) { + vusb->vqueues[vusb->otg_vq_base + i].name = + otg_vqueues[i].name; + vusb->vqueues[vusb->otg_vq_base + i].callback = + otg_vqueues[i].callback; + vusb->vqueues[vusb->otg_vq_base + i].process = + otg_vqueues[i].process; + vusb->vqueues[vusb->otg_vq_base + i].stop = + otg_vqueues[i].stop; + } + rc = virtio_usb_find_vqs(vusb); if (rc) { dev_err(&vdev->dev, "%s virtio_usb_find_vqs() error(%d)\n", @@ -199,6 +210,46 @@ static int virtio_usb_probe(struct virtio_device *vdev) goto on_error; } + if (vusb->host_role && vusb->device_role) { + rc = otg_init(vusb); + if (rc) { + dev_err(&vdev->dev, "%s otg_init() error(%d)\n", + __func__, rc); + goto on_error; + } + } + + /* Resolve every port's role. With only one role negotiated, every + * port unambiguously has that role. With both negotiated, query + * each port's actual role individually via otg_get_role(), since + * it is otherwise ambiguous (host|device). + */ + for (i = 0; i < vusb->nports; i++) { + if (vusb->host_role && !vusb->device_role) { + vusb->vports[i].role = VIRTIO_USB_ROLE_HOST; + } else if (vusb->device_role && !vusb->host_role) { + vusb->vports[i].role = VIRTIO_USB_ROLE_DEVICE; + } else { + u32 status, role; + + status = otg_get_role(vusb, i, &role); + if (status != VIRTIO_USB_S_OK) { + dev_err(&vdev->dev, "%s status(%d)\n", __func__, + status); + rc = -EIO; + goto on_error; + } + if (role != VIRTIO_USB_ROLE_HOST && + role != VIRTIO_USB_ROLE_DEVICE) { + dev_err(&vdev->dev, "%s port%d wrong role %d\n", + __func__, i, role); + rc = -EIO; + goto on_error; + } + vusb->vports[i].role = role; + } + } + if (vusb->host_role) { INIT_WORK(&vusb->vq_host_data_rx_work, virtio_usb_hc_rx_work); INIT_WORK(&vusb->vq_host_evt_work, virtio_usb_hc_evt_work); @@ -286,6 +337,8 @@ static void virtio_usb_remove(struct virtio_device *vdev) virtio_reset_device(vdev); vdev->config->del_vqs(vdev); + + otg_deinit(vusb); } static const unsigned int virtio_usb_features[] = { diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h index ec59922..4d9e0e2 100644 --- a/drivers/usb/virtio_usb/controller.h +++ b/drivers/usb/virtio_usb/controller.h @@ -18,6 +18,8 @@ struct virtio_usb_hc_vp; /* Forward declaration - full definition in device.h */ struct virtio_usb_dc; +/* Forward declaration - full definition in otg.h */ +struct virtio_usb_otg; #define VIRTIO_USB_VQ_COMMAND_IDX 0 #define VIRTIO_USB_VQ_EVENT_IDX 1 @@ -25,6 +27,7 @@ struct virtio_usb_dc; #define VIRTIO_USB_VQ_HOST_MAX 3 #define VIRTIO_USB_VQ_DEV_MAX 3 +#define VIRTIO_USB_VQ_OTG_MAX 2 /** * struct virtio_usb_port - Per-virtual-port state. @@ -54,6 +57,12 @@ struct virtio_usb_port { * @dev_vq_base: index into vqueues[] where the DEV_COMMAND/EVENT/DATA * triplet starts, or -1 if this instance has no * device-role VP. + * @otg_vq_base: index into vqueues[] where the OTG_COMMAND/EVENT pair + * starts, or -1 if this instance has neither a host-role + * nor a device-role VP. Used to query each port's role via + * otg_get_role() below, since with both host_role and + * device_role negotiated a port's own role is otherwise + * ambiguous. * @vq_host_data_rx_work: Kernel work draining the host data queue, shared * across every host-role VP. * @vq_host_evt_work: Kernel work draining the host event queue, shared @@ -73,10 +82,12 @@ struct virtio_usb { bool device_role; int host_vq_base; int dev_vq_base; + int otg_vq_base; struct work_struct vq_host_data_rx_work; struct work_struct vq_host_evt_work; struct work_struct vq_dev_data_rx_work; struct work_struct vq_dev_event_work; + struct virtio_usb_otg *otg; }; /** diff --git a/drivers/usb/virtio_usb/Makefile b/drivers/usb/virtio_usb/Makefile index 2222222..b7ee9e8 100644 --- a/drivers/usb/virtio_usb/Makefile +++ b/drivers/usb/virtio_usb/Makefile @@ -1,8 +1,9 @@ # SPDX-License-Identifier: GPL-2.0-or-later virtio-usb-y := controller.o \ vq_common.o \ host.o \ - device.o + device.o \ + otg.o obj-$(CONFIG_USB_VIRTIO) += virtio-usb.o diff --git a/drivers/usb/virtio_usb/otg.c b/drivers/usb/virtio_usb/otg.c new file mode 100644 index 0000000..557dfae --- /dev/null +++ b/drivers/usb/virtio_usb/otg.c @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * virtio_usb: VirtIO USB device + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#include <linux/mutex.h> +#include "controller.h" +#include "otg.h" +#include "vq_common.h" + +int otg_init(struct virtio_usb *vusb) +{ + struct virtio_usb_otg *otg = + devm_kzalloc(&vusb->vdev->dev, sizeof(*otg), GFP_KERNEL); + unsigned int i; + + if (!otg) + return -ENOMEM; + + otg->vusb = vusb; + vusb->otg = otg; + for (i = 0; i < VIRTIO_USB_VQ_OTG_MAX; i++) + otg->oqs[i] = &vusb->vqueues[vusb->otg_vq_base + i]; + + mutex_init(&otg->lock); + init_completion(&otg->completion); + + return 0; +} + +void otg_deinit(struct virtio_usb *vusb) +{ + struct virtio_usb_otg *otg = vusb->otg; + + if (!otg) + return; + + /* Wake potential OTG command waiters before releasing OTG objects. */ + complete_all(&otg->completion); + + vusb->otg = NULL; +} + +/* Send an OTG command and get a response. + * + * The function is implemented as synchronous. Design pattern is + * virtio_can.c/virtio_can_send_ctrl_msg() + */ +u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role) +{ + struct scatterlist sg_out, sg_in, *sgs[2] = { &sg_out, &sg_in }; + struct virtqueue *vq = + vusb->otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX]->vqueue; + unsigned int len; + u32 status = VIRTIO_USB_S_ERR_INTERNAL; + + struct otg_get_role { + struct virtio_usb_otg_cmd_hdr cmd_hdr; + struct virtio_usb_otg_cmd_role cmd_role; + } *msg = kzalloc(sizeof(struct otg_get_role), GFP_KERNEL); + + if (!msg) + return status; + + msg->cmd_hdr.code = cpu_to_le32(VIRTIO_USB_CMD_OTG_GET_ROLE); + msg->cmd_hdr.port = cpu_to_le32(port_id); + sg_init_one(&sg_out, &msg->cmd_hdr, sizeof(msg->cmd_hdr)); + sg_init_one(&sg_in, &msg->cmd_role, sizeof(msg->cmd_role)); + + mutex_lock(&vusb->otg->lock); + + if (virtqueue_add_sgs(vq, sgs, 1u, 1u, msg, GFP_ATOMIC)) { + pr_err("%s virtqueue_add_sgs error\n", __func__); + goto exit; + } + + if (!virtqueue_kick(vq)) { + pr_err("%s virtqueue_kick error\n", __func__); + goto exit; + } + + while (!virtqueue_get_buf(vq, &len) && !virtqueue_is_broken(vq)) + wait_for_completion(&vusb->otg->completion); + + status = le32_to_cpu(msg->cmd_role.status.code); + *role = le32_to_cpu(msg->cmd_role.role); + + if (*role != VIRTIO_USB_ROLE_HOST && *role != VIRTIO_USB_ROLE_DEVICE) + pr_err("%s - wrong role (%d)\n", __func__, *role); + else { + pr_info("%s otg_role %s\n", __func__, + *role == VIRTIO_USB_ROLE_HOST ? + "VIRTIO_USB_ROLE_HOST" : + "VIRTIO_USB_ROLE_DEVICE"); + } + +exit: + kfree(msg); + mutex_unlock(&vusb->otg->lock); + return status; +} + +static void virtio_usb_otg_cmd_notify_cb(struct virtqueue *vqueue) +{ + struct virtio_usb *vusb = vqueue->vdev->priv; + + if (!vusb->otg) + return; + + complete(&vusb->otg->completion); +} + +static void virtio_usb_otg_cmdq_stop_cb(struct virtio_usb *vusb, + struct virtio_usb_queue *vq) +{ + unsigned long flags; + + if (!vusb->otg || !vq->vqueue) + return; + + /* + * Wake sleepers in OTG synchronous command paths so they can + * observe started=false and exit. + */ + complete_all(&vusb->otg->completion); + + spin_lock_irqsave(&vq->lock, flags); + virtqueue_disable_cb(vq->vqueue); + spin_unlock_irqrestore(&vq->lock, flags); +} + +static void virtio_usb_otg_evtq_stop_cb(struct virtio_usb *vusb, + struct virtio_usb_queue *vq) +{ + unsigned long flags; + u32 length; + void *buf; + + if (!vq->vqueue) + return; + + /* The OTG event queue is not populated yet at this stage (no + * VIRTIO_USB_F_SWITCH_ROLE negotiation, no CHANGE_ROLE events), + * so this only has to make sure del_vqs() finds the ring empty. + */ + spin_lock_irqsave(&vq->lock, flags); + virtqueue_disable_cb(vq->vqueue); + while ((buf = virtqueue_get_buf(vq->vqueue, &length))) + ; + spin_unlock_irqrestore(&vq->lock, flags); +} + +const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX] = { + [VIRTIO_USB_VQ_COMMAND_IDX] = { + .callback = virtio_usb_otg_cmd_notify_cb, + .name = "virtusb-otg-cmd", + .process = NULL, + .stop = virtio_usb_otg_cmdq_stop_cb, + }, + [VIRTIO_USB_VQ_EVENT_IDX] = { + .callback = NULL, + .name = "virtusb-otg-evt", + .process = NULL, + .stop = virtio_usb_otg_evtq_stop_cb, + }, +}; diff --git a/drivers/usb/virtio_usb/otg.h b/drivers/usb/virtio_usb/otg.h new file mode 100644 index 0000000..a34317c --- /dev/null +++ b/drivers/usb/virtio_usb/otg.h @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * virtio_usb: VirtIO USB device + * + * Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries. + */ + +#ifndef VIRTIO_USB_OTG_H +#define VIRTIO_USB_OTG_H + +#include "controller.h" + +extern int otg_init(struct virtio_usb *vusb); +extern void otg_deinit(struct virtio_usb *vusb); +extern u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role); + +struct virtio_usb_otg { + struct virtio_usb *vusb; + struct mutex lock; + struct completion completion; + struct virtio_usb_queue *oqs[VIRTIO_USB_VQ_OTG_MAX]; +}; + +extern const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX]; + +#endif /* VIRTIO_USB_OTG_H */
