Add support for ports that can switch between the host and device roles at runtime (USB OTG-style role switching): negotiate VIRTIO_USB_F_SWITCH_ROLE support during probe, extend otg_get_role() to also report each port's supported roles (is_otg), and register a USB Role Switch class device so userspace can observe and control the role and be notified of role changes via VIRTIO_USB_EVT_OTG_CHANGE_ROLE.
This is a proof-of-concept for internal demo purposes; the two directions currently work quite differently and neither is fully guest-initiated: - Device-to-host: the guest writes the new role to its own USB Role Switch sysfs entry. The driver sends a switch command to the host device; the host device performs the switch and sends back a VIRTIO_USB_EVT_OTG_CHANGE_ROLE event, which is what actually updates the guest's role. - Host-to-device: cannot be initiated from the guest at all. The host kernel driver switches role on its own, either from an OTG interrupt or a manual sysfs role write on the host side. The host virtio device notices the USB port disappearing, checks the role via sysfs, and if it changed, sends the guest a VIRTIO_USB_EVT_OTG_CHANGE_ROLE event; the guest driver just reacts to it. Real USB OTG separates this into two signals: bus_req, asserted by a B-device (peripheral) to request becoming host, and bus_drop, asserted by the current host to grant permission for the role swap - without bus_drop, the peripheral's request cannot succeed. VIRTIO_USB_CMD_OTG_SWITCH_ROLE only implements the bus_req side; there is no bus_drop equivalent, so the current host's permission is implicitly always granted. Fine for a first version; can be revisited if a real use case needs the host to refuse a switch. Signed-off-by: Igor Skalkin <[email protected]> --- drivers/usb/virtio_usb/controller.c | 94 ++++++-- drivers/usb/virtio_usb/controller.h | 21 + drivers/usb/virtio_usb/device.c | 8 drivers/usb/virtio_usb/host.c | 45 +++- drivers/usb/virtio_usb/otg.c | 401 ++++++++++++++++++++++++++++-------- drivers/usb/virtio_usb/otg.h | 16 + include/uapi/linux/virtio_usb.h | 1 7 files changed, 474 insertions(+), 112 deletions(-) diff --git a/drivers/usb/virtio_usb/controller.c b/drivers/usb/virtio_usb/controller.c index 59af5cc..c018725 100644 --- a/drivers/usb/virtio_usb/controller.c +++ b/drivers/usb/virtio_usb/controller.c @@ -10,8 +10,8 @@ #include <uapi/linux/virtio_ids.h> #include "controller.h" -#include "host.h" #include "device.h" +#include "host.h" #include "otg.h" #include "vq_common.h" @@ -86,6 +86,14 @@ static int virtio_usb_validate(struct virtio_device *vdev) return -EINVAL; } + if ((!(virtio_has_feature(vdev, VIRTIO_USB_F_HOST)) || + !(virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE))) && + virtio_has_feature(vdev, VIRTIO_USB_F_SWITCH_ROLE)) { + dev_err(&vdev->dev, + "OTG requires both (host and device) roles support\n"); + return -EINVAL; + } + if (!virtio_usb_cmd_timeout_ms) { dev_err(&vdev->dev, "msg_timeout_ms value cannot be zero\n"); return -EINVAL; @@ -129,6 +137,12 @@ static int virtio_usb_probe(struct virtio_device *vdev) if (virtio_has_feature(vdev, VIRTIO_USB_F_DEVICE)) vusb->device_role = 1; + if (virtio_has_feature(vdev, VIRTIO_USB_F_SWITCH_ROLE)) + vusb->switch_role = 1; + + dev_info(&vdev->dev, "%s nports %d (h_role %d dev_role %d)\n", __func__, + vusb->nports, vusb->host_role, vusb->device_role); + /* 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, OTG_* only when both @@ -219,20 +233,44 @@ static int virtio_usb_probe(struct virtio_device *vdev) } } + if (vusb->device_role) { + INIT_WORK(&vusb->vq_dev_event_work, virtio_usb_dc_event_work); + INIT_WORK(&vusb->vq_dev_data_rx_work, virtio_usb_dc_data_work); + + /* Populate the shared device event queue before telling the + * backend we are ready, so a DEV_CONNECTED event can never + * race ahead of the driver having posted receive buffers. + */ + rc = virtio_usb_dc_event_populate(vusb); + if (rc) { + dev_err(&vdev->dev, + "%s virtio_usb_dc_event_populate() error(%d)\n", + __func__, rc); + goto on_error; + } + } + + virtio_device_ready(vdev); + /* 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). + * each port's actual role - and, if switch_role is negotiated, + * whether the port supports switching - individually via + * otg_get_role(), since a port's own role is otherwise ambiguous + * (host|device). */ for (i = 0; i < vusb->nports; i++) { + vusb->vports[i].vusb = vusb; + spin_lock_init(&vusb->vports[i].vhc_lock); + 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; + u32 status, role, supported_role; - status = otg_get_role(vusb, i, &role); + status = otg_get_role(vusb, i, &role, &supported_role); if (status != VIRTIO_USB_S_OK) { dev_err(&vdev->dev, "%s status(%d)\n", __func__, status); @@ -247,9 +285,25 @@ static int virtio_usb_probe(struct virtio_device *vdev) goto on_error; } vusb->vports[i].role = role; + vusb->vports[i].is_otg = + supported_role & + (1UL << VIRTIO_USB_F_SWITCH_ROLE); + + if (vusb->vports[i].is_otg && !vusb->switch_role) { + dev_err(&vdev->dev, + "%s port%d switch_role not supported\n", + __func__, i); + rc = -EIO; + goto on_error; + } } } + for (i = 0; i < vusb->nports; i++) + dev_info(&vdev->dev, "%s port[%d] VIRTIO_USB_F_%s is_otg %s\n", + __func__, i, vusb->vports[i].role ? "DEVICE" : "HOST", + vusb->vports[i].is_otg ? "TRUE" : "FALSE"); + 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); @@ -278,13 +332,9 @@ static int virtio_usb_probe(struct virtio_device *vdev) } } - if (vusb->device_role) { - INIT_WORK(&vusb->vq_dev_data_rx_work, virtio_usb_dc_data_work); - INIT_WORK(&vusb->vq_dev_event_work, virtio_usb_dc_event_work); - - for (i = 0; i < vusb->nports; i++) { - if (vusb->vports[i].role != VIRTIO_USB_ROLE_DEVICE) - continue; + for (i = 0; i < vusb->nports; i++) { + if (vusb->vports[i].is_otg || + vusb->vports[i].role == VIRTIO_USB_ROLE_DEVICE) { rc = virtio_usb_dc_init(vusb, i); if (rc) { dev_err(&vdev->dev, @@ -293,17 +343,17 @@ static int virtio_usb_probe(struct virtio_device *vdev) goto on_error; } } - rc = virtio_usb_dc_event_populate(vusb); - if (rc) { - dev_err(&vdev->dev, - "%s virtio_usb_dc_event_populate() error(%d)\n", - __func__, rc); - goto on_error; - } } - virtio_device_ready(vdev); + if (vusb->host_role && vusb->device_role) + if (otg_sysfs_init(vusb)) + goto on_error; + + WRITE_ONCE(vusb->started, true); + if (vusb->device_role) + schedule_work(&vusb->vq_dev_event_work); + dev_info(&vdev->dev, "%s returns %d\n", __func__, rc); return rc; on_error: @@ -323,6 +373,9 @@ static void virtio_usb_remove(struct virtio_device *vdev) struct virtio_usb *vusb = vdev->priv; int i; + /* Stop queue-driven workers from scheduling new activity. */ + WRITE_ONCE(vusb->started, false); + for (i = 0; i < vusb->nvqs; i++) vusb->vqueues[i].stop(vusb, &vusb->vqueues[i]); @@ -344,6 +397,7 @@ static void virtio_usb_remove(struct virtio_device *vdev) static const unsigned int virtio_usb_features[] = { VIRTIO_USB_F_HOST, VIRTIO_USB_F_DEVICE, + VIRTIO_USB_F_SWITCH_ROLE, }; static const struct virtio_device_id id_table[] = { diff --git a/drivers/usb/virtio_usb/controller.h b/drivers/usb/virtio_usb/controller.h index 4d9e0e2..deee053 100644 --- a/drivers/usb/virtio_usb/controller.h +++ b/drivers/usb/virtio_usb/controller.h @@ -20,6 +20,7 @@ struct virtio_usb_hc_vp; struct virtio_usb_dc; /* Forward declaration - full definition in otg.h */ struct virtio_usb_otg; +struct virtio_usb_port_otg; #define VIRTIO_USB_VQ_COMMAND_IDX 0 #define VIRTIO_USB_VQ_EVENT_IDX 1 @@ -31,14 +32,27 @@ struct virtio_usb_otg; /** * struct virtio_usb_port - Per-virtual-port state. + * @vusb: VirtIO usb device this port belongs to. + * @is_otg: True if this port supports dynamic role switching. * @role: Role of this port (VIRTIO_USB_ROLE_HOST or _DEVICE). * @vhc: Host controller - non-NULL when role is HOST. * @vudc: Device controller - non-NULL when role is DEVICE. + * @otg: Per-port OTG role-switching state - non-NULL when is_otg. */ struct virtio_usb_port { + struct virtio_usb *vusb; + bool is_otg; unsigned int role; + /*! Protects vhc against concurrent OTG-triggered init/deinit while + * the host event-processing worker is reading it. Only needs to + * guard the pointer itself: virtio_usb_hc_vp_init()/_deinit()'s + * sleeping work (devm_kzalloc(), usb_add_hcd(), usb_remove_hcd()) + * happens outside this lock. + */ + spinlock_t vhc_lock; struct virtio_usb_hc_vp *vhc; struct virtio_usb_dc *vudc; + struct virtio_usb_port_otg *otg; }; /** @@ -63,6 +77,9 @@ struct virtio_usb_port { * otg_get_role() below, since with both host_role and * device_role negotiated a port's own role is otherwise * ambiguous. + * @switch_role: flag indicating support for dynamically switching roles + * @started: True once probe() has finished bringing up every VP - guards + * notify callbacks against running before setup is complete. * @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 @@ -76,13 +93,15 @@ struct virtio_usb { struct virtio_device *vdev; struct virtio_usb_queue *vqueues; struct virtio_usb_port *vports; - unsigned int nports; + u32 nports; u32 nvqs; bool host_role; bool device_role; int host_vq_base; int dev_vq_base; int otg_vq_base; + bool switch_role; + bool started; struct work_struct vq_host_data_rx_work; struct work_struct vq_host_evt_work; struct work_struct vq_dev_data_rx_work; diff --git a/drivers/usb/virtio_usb/device.c b/drivers/usb/virtio_usb/device.c index 798c265..5ae0fc8 100644 --- a/drivers/usb/virtio_usb/device.c +++ b/drivers/usb/virtio_usb/device.c @@ -1056,6 +1056,8 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event) break; } case VIRTIO_USB_EVT_DEV_DISCONNECTED: { + pr_info("%s port_id %d VIRTIO_USB_EVT_DEV_DISCONNECTED\n", + __func__, port_id); if (vudc->driver && vudc->driver->disconnect) vudc->driver->disconnect(&vudc->gadget); if (vudc->registered) @@ -1080,16 +1082,19 @@ static void virtio_usb_dc_event_process(struct virtio_usb_event *event) break; } case VIRTIO_USB_EVT_DEV_RESET: { + pr_info("%s VIRTIO_USB_EVT_DEV_RESET\n", __func__); if (driver) usb_gadget_udc_reset(&vudc->gadget, driver); break; } case VIRTIO_USB_EVT_DEV_SUSPEND: { + pr_info("%s VIRTIO_USB_EVT_DEV_SUSPEND\n", __func__); if (driver && driver->suspend) driver->suspend(&vudc->gadget); break; } case VIRTIO_USB_EVT_DEV_RESUME: { + pr_info("%s VIRTIO_USB_EVT_DEV_RESUME\n", __func__); if (driver && driver->resume) driver->resume(&vudc->gadget); break; @@ -1308,6 +1313,9 @@ static void virtio_usb_dc_evt_notify_cb(struct virtqueue *vqueue) { struct virtio_usb *vusb = vqueue->vdev->priv; + if (!READ_ONCE(vusb->started)) + return; + schedule_work(&vusb->vq_dev_event_work); } diff --git a/drivers/usb/virtio_usb/host.c b/drivers/usb/virtio_usb/host.c index 9926e66..5dc47f3 100644 --- a/drivers/usb/virtio_usb/host.c +++ b/drivers/usb/virtio_usb/host.c @@ -1013,12 +1013,16 @@ int virtio_usb_hc_vp_init(struct virtio_usb *vusb, unsigned int vp_idx) vhcd_vp->hcqs[i] = &vusb->vqueues[vusb->host_vq_base + i]; /* Install into the port before add_hcd so vhcd_vp->vusb is set */ + spin_lock(&vusb->vports[vp_idx].vhc_lock); vusb->vports[vp_idx].vhc = vhcd_vp; + spin_unlock(&vusb->vports[vp_idx].vhc_lock); /* Add HCDs first so hs/ss are valid before any PORT_CONNECTED event */ rc = virtio_usb_add_hcd(vusb, vhcd_vp); if (rc) { + spin_lock(&vusb->vports[vp_idx].vhc_lock); vusb->vports[vp_idx].vhc = NULL; + spin_unlock(&vusb->vports[vp_idx].vhc_lock); return rc; } @@ -1032,7 +1036,11 @@ int virtio_usb_hc_vp_init(struct virtio_usb *vusb, unsigned int vp_idx) */ int virtio_usb_hc_vp_deinit(struct virtio_usb *vusb, unsigned int vp_idx) { - struct virtio_usb_hc_vp *vhcd_vp = vusb->vports[vp_idx].vhc; + struct virtio_usb_hc_vp *vhcd_vp; + + spin_lock(&vusb->vports[vp_idx].vhc_lock); + vhcd_vp = vusb->vports[vp_idx].vhc; + spin_unlock(&vusb->vports[vp_idx].vhc_lock); if (!vhcd_vp) return 0; @@ -1045,7 +1053,9 @@ int virtio_usb_hc_vp_deinit(struct virtio_usb *vusb, unsigned int vp_idx) vhcd_vp->ss = NULL; vhcd_vp->hs = NULL; + spin_lock(&vusb->vports[vp_idx].vhc_lock); vusb->vports[vp_idx].vhc = NULL; + spin_unlock(&vusb->vports[vp_idx].vhc_lock); return 0; } @@ -1085,7 +1095,18 @@ static void virtio_usb_hc_evt_process_one(struct virtio_usb_event *uevent) return; } + /* vhc can be concurrently init/deinit'd by an OTG role switch + * (virtio_usb_otg_event_process() runs on its own workqueue) - + * vhc_lock protects only the pointer read itself; the vhcd_vp it + * points to remains valid for as long as we hold a reference to + * it here, since virtio_usb_hc_vp_deinit() only clears the + * vports[vp_idx].vhc pointer under the same lock, it does not + * free vhcd_vp itself (devm-managed, freed at device teardown). + */ + spin_lock(&vusb->vports[vp_idx].vhc_lock); vhcd_vp = vusb->vports[vp_idx].vhc; + spin_unlock(&vusb->vports[vp_idx].vhc_lock); + if (!vhcd_vp) { dev_err_ratelimited( &vusb->vdev->dev, @@ -1174,10 +1195,12 @@ static void virtio_usb_hc_evt_process_one(struct virtio_usb_event *uevent) * @work: kernel work item embedded in struct virtio_usb. * * The host event queue is shared across all host-role VPs and its VP - * may not even exist yet at probe time (e.g. a dual-role instance - * where every port currently reports device role), so events are - * drained and processed here, in process context, rather than - * directly inside the interrupt-context notify callback. + * may not even exist yet (or may be concurrently torn down by an OTG + * role switch), so events are drained and processed here, in process + * context, instead of directly inside the interrupt-context notify + * callback - this lets virtio_usb_hc_evt_process_one() safely take + * vhc_lock without needing an atomic-context-safe primitive on the + * writer side (virtio_usb_hc_vp_init()/_deinit() sleep). * * Context: Process context. */ @@ -1215,7 +1238,11 @@ static void virtio_usb_hc_dataq_stop_cb(struct virtio_usb *vusb, cancel_work_sync(&vusb->vq_host_data_rx_work); for (vp_idx = 0; vp_idx < vusb->nports; vp_idx++) { - struct virtio_usb_hc_vp *vhcd_vp = vusb->vports[vp_idx].vhc; + struct virtio_usb_hc_vp *vhcd_vp; + + spin_lock(&vusb->vports[vp_idx].vhc_lock); + vhcd_vp = vusb->vports[vp_idx].vhc; + spin_unlock(&vusb->vports[vp_idx].vhc_lock); if (!vhcd_vp) continue; @@ -1284,6 +1311,9 @@ static void virtio_usb_hc_evt_notify_cb(struct virtqueue *vqueue) { struct virtio_usb *vusb = vqueue->vdev->priv; + if (!READ_ONCE(vusb->started)) + return; + schedule_work(&vusb->vq_host_evt_work); } @@ -1310,6 +1340,9 @@ static void virtio_usb_host_data_notify_cb(struct virtqueue *vqueue) { struct virtio_usb *vusb = vqueue->vdev->priv; + if (!READ_ONCE(vusb->started)) + return; + schedule_work(&vusb->vq_host_data_rx_work); } diff --git a/include/uapi/linux/virtio_usb.h b/include/uapi/linux/virtio_usb.h index 4cbfb3f..29cec5d 100644 --- a/include/uapi/linux/virtio_usb.h +++ b/include/uapi/linux/virtio_usb.h @@ -318,6 +318,7 @@ enum { struct virtio_usb_otg_cmd_role { struct virtio_usb_cmd_status status; __le32 role; /* VIRTIO_USB_ROLE_XXX */ + __le32 supported_role; /* VIRTIO_USB_F_[HOST|DEVICE|SWITCH_ROLE] */ }; /***************************************************************************** diff --git a/drivers/usb/virtio_usb/otg.c b/drivers/usb/virtio_usb/otg.c index 557dfae..f0f390f 100644 --- a/drivers/usb/virtio_usb/otg.c +++ b/drivers/usb/virtio_usb/otg.c @@ -7,11 +7,238 @@ #include <linux/mutex.h> #include "controller.h" +#include "host.h" #include "otg.h" #include "vq_common.h" +// for usb_role enum from include/linux/usb/role.h +const char *role_switch_role_names[] = { "USB_ROLE_NONE", "USB_ROLE_HOST", + "USB_ROLE_DEVICE" }; + +static int vusb_set_role_cb(struct usb_role_switch *sw, enum usb_role role) +{ + struct virtio_usb_port *vport = usb_role_switch_get_drvdata(sw); + + pr_info("%s set_role to %s\n", __func__, role_switch_role_names[role]); + mutex_lock(&vport->otg->lock); + + if (role == USB_ROLE_HOST && vport->role == VIRTIO_USB_ROLE_DEVICE) { + pr_warn("%s we are in device role, switch to host\n", __func__); + schedule_work(&vport->otg->set_role_work); + } + mutex_unlock(&vport->otg->lock); + return 0; +} + +static enum usb_role vusb_get_role_cb(struct usb_role_switch *sw) +{ + struct virtio_usb_port *vport = usb_role_switch_get_drvdata(sw); + + switch (vport->role) { + case VIRTIO_USB_ROLE_HOST: + return USB_ROLE_HOST; + case VIRTIO_USB_ROLE_DEVICE: + return USB_ROLE_DEVICE; + default: + return USB_ROLE_NONE; + } +} + +static void virtio_usb_otg_event_process(struct virtio_usb_event *event) +{ + struct virtio_usb *vusb = event->vusb; + struct virtio_usb_otg_event *evt = virtio_usb_event_buf(event); + int port_id = le32_to_cpu(evt->port); + struct virtio_usb_port *vport = &vusb->vports[port_id]; + u32 status, role, supported_role, old_role; + int rc; + + if (le32_to_cpu(evt->code) != VIRTIO_USB_EVT_OTG_CHANGE_ROLE) { + pr_err("%s wrong event code %d\n", __func__, + le32_to_cpu(evt->code)); + return; + } + + old_role = vport->role; + pr_info("%s VIRTIO_USB_EVT_OTG_CHANGE_ROLE curr_role %s\n", __func__, + old_role == VIRTIO_USB_ROLE_DEVICE ? "VIRTIO_USB_ROLE_DEVICE" : + "VIRTIO_USB_ROLE_HOST"); + + status = otg_get_role(vusb, port_id, &role, &supported_role); + if (status != VIRTIO_USB_S_OK) { + pr_err("%s otg_get_role status %d\n", __func__, status); + return; + } + + /* A port's usb_hcd/root hub is only supposed to exist while that + * port is actually in host role - it's immediately visible to the + * rest of the kernel (lsusb, udev, ...) the moment usb_add_hcd() + * runs, unlike the device-role side's vudc, which stays dormant + * and kernel-invisible until a later BIND event. So, unlike vudc + * (already unconditionally pre-allocated for every OTG-capable + * port at probe time, regardless of its current role - see + * virtio_usb_probe()), vhc must be created/destroyed dynamically, + * exactly at the moment a port's role actually changes. + */ + if (role != old_role && role == VIRTIO_USB_ROLE_HOST) { + rc = virtio_usb_hc_vp_init(vusb, port_id); + if (rc) { + pr_err("%s virtio_usb_hc_vp_init() port=%d error(%d)\n", + __func__, port_id, rc); + /* Leave vport->role at its old value: reporting a + * host role with no working HCD behind it would be + * worse than not switching at all. + */ + return; + } + } else if (role != old_role && old_role == VIRTIO_USB_ROLE_HOST) { + virtio_usb_hc_vp_deinit(vusb, port_id); + } + + vport->role = role; + vport->is_otg = supported_role & (1UL << VIRTIO_USB_F_SWITCH_ROLE); + pr_info("%s The new role (%s) is set\n", __func__, + role == VIRTIO_USB_ROLE_DEVICE ? "VIRTIO_USB_ROLE_DEVICE" : + "VIRTIO_USB_ROLE_HOST"); +} + +/** + * virtio_usb_otg_event_work() - OTG event queue receive worker. + * @work: Kernel work to handle event completion. + * + * Context: Process context. + */ +static void virtio_usb_otg_event_work(struct work_struct *work) +{ + struct virtio_usb_otg *otg = + container_of(work, struct virtio_usb_otg, event_process_work); + struct virtio_usb_queue *evtq = otg->oqs[VIRTIO_USB_VQ_EVENT_IDX]; + + virtio_usb_evt_work(evtq, virtio_usb_otg_event_process); +} + +/** + * virtio_usb_otg_set_role_work() - OTG send command to device worker. + * + * Context: Process context. + */ +static void virtio_usb_otg_set_role_work(struct work_struct *work) +{ + struct virtio_usb_port_otg *vport_otg = + container_of(work, struct virtio_usb_port_otg, set_role_work); + struct virtio_usb_otg *otg = vport_otg->vusb->otg; + struct virtio_usb_otg_cmd_hdr *hdr; + struct virtio_usb_cmd *cmd; + int rc; + + cmd = virtio_usb_cmd_alloc( + sizeof(*hdr), sizeof(struct virtio_usb_cmd_status), GFP_KERNEL); + if (!cmd) + return; + + hdr = virtio_usb_cmd_request(cmd); + hdr->code = cpu_to_le32(VIRTIO_USB_CMD_OTG_SWITCH_ROLE); + hdr->port = cpu_to_le32(vport_otg->port_id); + cmd->msg.queue = otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX]; + + mutex_lock(&otg->lock); + rc = virtio_usb_cmd_send_sync(vport_otg->vusb, NULL, NULL, cmd); + mutex_unlock(&otg->lock); + + if (rc) + pr_err("%s virtio_usb_cmd_send_sync() error %d\n", __func__, + rc); + else + pr_info("%s success\n", __func__); +} + +/** + * virtio_usb_otg_event_populate() - Add events to the otg event queue. + * @vusb: VirtIO USB device. + * + * Context: Any context. + * Return: 0 on success -errno on failure. + */ +static int virtio_usb_otg_event_populate(struct virtio_usb *vusb) +{ + struct virtio_usb_queue *otg_evt_queue = + vusb->otg->oqs[VIRTIO_USB_VQ_EVENT_IDX]; + struct virtio_usb_event *events; + + events = virtio_usb_events_alloc(vusb, otg_evt_queue, + sizeof(struct virtio_usb_otg_event)); + if (!events) + return -ENOMEM; + + return virtio_usb_events_populate(events); +} + +static void otg_vports_deinit(struct virtio_usb *vusb) +{ + if (!vusb->vports) + return; + + for (int i = 0; i < vusb->nports; ++i) { + struct virtio_usb_port *vport = &vusb->vports[i]; + /* is_otg is set before otg_sysfs_init() runs (which is + * where vport->otg actually gets allocated) - a probe + * failure in between the two would otherwise leave + * is_otg true but otg still NULL here. + */ + if (!vport->is_otg || !vport->otg) + continue; + + cancel_work_sync(&vport->otg->set_role_work); + usb_role_switch_unregister(vport->otg->sw); + vport->otg->sw = NULL; + kfree(vport->otg->sysfs_name); + vport->otg->sysfs_name = NULL; + } +} + +static int otg_vport_init(struct virtio_usb *vusb, int port_id) +{ + struct device *dev = &vusb->vdev->dev; + struct virtio_usb_port *vport = &vusb->vports[port_id]; + struct virtio_usb_port_otg *vport_otg; + struct usb_role_switch_desc desc = { 0 }; + + vport_otg = devm_kzalloc(dev, sizeof(*vport_otg), GFP_KERNEL); + if (!vport_otg) + return -ENOMEM; + + vport_otg->vusb = vusb; + vport_otg->port_id = port_id; + vport_otg->sysfs_name = kasprintf(GFP_KERNEL, "port%u", port_id); + + mutex_init(&vport_otg->lock); + INIT_WORK(&vport_otg->set_role_work, virtio_usb_otg_set_role_work); + + desc.name = vport_otg->sysfs_name; + desc.set = vusb_set_role_cb; + desc.get = vusb_get_role_cb; + desc.driver_data = vport; + desc.fwnode = NULL; + desc.allow_userspace_control = true; + + vport_otg->sw = usb_role_switch_register(dev, &desc); + if (IS_ERR(vport_otg->sw)) { + int rc = PTR_ERR(vport_otg->sw); + dev_err(dev, "port%u: usb_role_switch_register() failed: %d\n", + port_id, rc); + vport_otg->sw = NULL; + kfree(vport_otg->sysfs_name); + vport_otg->sysfs_name = NULL; + return rc; + } + + vport->otg = vport_otg; + return 0; +} + int otg_init(struct virtio_usb *vusb) { + int rc; struct virtio_usb_otg *otg = devm_kzalloc(&vusb->vdev->dev, sizeof(*otg), GFP_KERNEL); unsigned int i; @@ -24,12 +251,39 @@ int otg_init(struct virtio_usb *vusb) for (i = 0; i < VIRTIO_USB_VQ_OTG_MAX; i++) otg->oqs[i] = &vusb->vqueues[vusb->otg_vq_base + i]; + INIT_WORK((struct work_struct *)&otg->event_process_work, + virtio_usb_otg_event_work); + mutex_init(&otg->lock); - init_completion(&otg->completion); + + rc = virtio_usb_otg_event_populate(vusb); + if (rc) + return rc; return 0; } +int otg_sysfs_init(struct virtio_usb *vusb) +{ + int i, rc; + + for (i = 0; i < vusb->nports; i++) + if (vusb->vports[i].is_otg) { + /* Skip if already registered - probe may be retried + * after a Virtio reset and the role switch device + * persists across retries (devm allocation). + * Calling usb_role_switch_register() again would + * return -EEXIST from device_register(). + */ + if (vusb->vports[i].otg) + continue; + rc = otg_vport_init(vusb, i); + if (rc) + return rc; + } + return 0; +} + void otg_deinit(struct virtio_usb *vusb) { struct virtio_usb_otg *otg = vusb->otg; @@ -37,55 +291,75 @@ void otg_deinit(struct virtio_usb *vusb) if (!otg) return; - /* Wake potential OTG command waiters before releasing OTG objects. */ - complete_all(&otg->completion); - + /* + * event_process_work is already cancelled by + * virtio_usb_otg_evtq_stop_cb(), called earlier in + * virtio_usb_remove()'s vqueues[i].stop() loop. + */ + otg_vports_deinit(vusb); vusb->otg = NULL; } +/** + * virtio_usb_otg_evt_notify_cb() - OTG Event virtqueue notification callback + * @vqueue: Underlying event virtqueue. + * + * This callback function is called upon a vring interrupt request from the + * device. + * + * Context: Interrupt context. + */ +static void virtio_usb_otg_evt_notify_cb(struct virtqueue *vqueue) +{ + struct virtio_usb *vusb = vqueue->vdev->priv; + + if (!READ_ONCE(vusb->started) || !vusb->otg) + return; + + schedule_work(&vusb->otg->event_process_work); +} + /* 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) +u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role, + u32 *supported_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; + struct virtio_usb_otg_cmd_hdr *hdr; + struct virtio_usb_otg_cmd_role *resp; + struct virtio_usb_cmd *cmd; u32 status = VIRTIO_USB_S_ERR_INTERNAL; + int rc; - 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) + cmd = virtio_usb_cmd_alloc(sizeof(*hdr), sizeof(*resp), GFP_KERNEL); + if (!cmd) 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)); + hdr = virtio_usb_cmd_request(cmd); + hdr->code = cpu_to_le32(VIRTIO_USB_CMD_OTG_GET_ROLE); + hdr->port = cpu_to_le32(port_id); + cmd->msg.queue = vusb->otg->oqs[VIRTIO_USB_VQ_COMMAND_IDX]; - mutex_lock(&vusb->otg->lock); + virtio_usb_cmd_ref(cmd); - if (virtqueue_add_sgs(vq, sgs, 1u, 1u, msg, GFP_ATOMIC)) { - pr_err("%s virtqueue_add_sgs error\n", __func__); - goto exit; - } + mutex_lock(&vusb->otg->lock); + rc = virtio_usb_cmd_send_sync(vusb, NULL, NULL, cmd); + mutex_unlock(&vusb->otg->lock); - if (!virtqueue_kick(vq)) { - pr_err("%s virtqueue_kick error\n", __func__); - goto exit; + if (rc) { + pr_err("%s virtio_usb_cmd_send_sync() error %d\n", __func__, + rc); + virtio_usb_cmd_unref(cmd); + return status; } - 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); + resp = virtio_usb_cmd_response(cmd); + status = le32_to_cpu(resp->status.code); + *role = le32_to_cpu(resp->role); + *supported_role = le32_to_cpu(resp->supported_role); + virtio_usb_cmd_unref(cmd); if (*role != VIRTIO_USB_ROLE_HOST && *role != VIRTIO_USB_ROLE_DEVICE) pr_err("%s - wrong role (%d)\n", __func__, *role); @@ -96,71 +370,32 @@ u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role) "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. + /* + * Now that otg_get_role()/virtio_usb_otg_set_role_work() go + * through the common, timeout-protected virtio_usb_cmd_send_sync() + * instead of blocking indefinitely on virtqueue_is_broken(), it's + * safe to cancel event_process_work directly here, same as every + * other role's own evtq stop callback. */ - 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); + virtio_usb_evt_drain_stop_cb( + vq, vusb->otg ? &vusb->otg->event_process_work : NULL); } 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, + .callback = virtio_usb_cmd_notify_cb, .name = "virtusb-otg-cmd", - .process = NULL, - .stop = virtio_usb_otg_cmdq_stop_cb, + .process = virtio_usb_cmd_process_cb, + .stop = virtio_usb_cmdq_stop_cb, }, [VIRTIO_USB_VQ_EVENT_IDX] = { - .callback = NULL, + .callback = virtio_usb_otg_evt_notify_cb, .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 index a34317c..85e9b9e 100644 --- a/drivers/usb/virtio_usb/otg.h +++ b/drivers/usb/virtio_usb/otg.h @@ -8,17 +8,29 @@ #ifndef VIRTIO_USB_OTG_H #define VIRTIO_USB_OTG_H +#include <linux/usb/role.h> #include "controller.h" extern int otg_init(struct virtio_usb *vusb); +extern int otg_sysfs_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); +extern u32 otg_get_role(struct virtio_usb *vusb, int port_id, u32 *role, + u32 *supported_role); + +struct virtio_usb_port_otg { + struct virtio_usb *vusb; + struct mutex lock; + struct usb_role_switch *sw; + char *sysfs_name; + struct work_struct set_role_work; + int port_id; +}; struct virtio_usb_otg { struct virtio_usb *vusb; struct mutex lock; - struct completion completion; struct virtio_usb_queue *oqs[VIRTIO_USB_VQ_OTG_MAX]; + struct work_struct event_process_work; }; extern const struct virtio_usb_vq_desc otg_vqueues[VIRTIO_USB_VQ_OTG_MAX];
