// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2003-2008 Takahiro Hirofuchi
* Copyright (C) 2015-2016 Nobuo Iwata
*/
#include <linux/init.h>
#include <linux/file.h>
#include <linux/kernel.h>
#include <linux/kthread.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include "usbip_common.h"
#include "vhci.h"
#define DRIVER_AUTHOR "Takahiro Hirofuchi"
#define DRIVER_DESC "USB/IP 'Virtual' Host Controller (VHCI) Driver"
/*
* TODO
* - update root hub emulation
* - move the emulation code to userland ?
* porting to other operating systems
* minimize kernel code
* - add suspend/resume code
* - clean up everything
*/
/* See usb gadget dummy hcd */
static int vhci_hub_status(struct usb_hcd *hcd, char *buff);
static int vhci_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue,
u16 wIndex, char *buff, u16 wLength);
static int vhci_urb_enqueue(struct usb_hcd *hcd, struct urb *urb,
gfp_t mem_flags);
static int vhci_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status);
static int vhci_start(struct usb_hcd *vhci_hcd);
static void vhci_stop(struct usb_hcd *hcd);
static int vhci_get_frame_number(struct usb_hcd *hcd);
static const char driver_name[] = "vhci_hcd";
static const char driver_desc[] = "USB/IP Virtual Host Controller";
int vhci_num_controllers = VHCI_NR_HCS;
struct vhci *vhcis;
static const char * const bit_desc[] = {
"CONNECTION", /*0*/
"ENABLE", /*1*/
"SUSPEND", /*2*/
"OVER_CURRENT", /*3*/
"RESET", /*4*/
"L1", /*5*/
"R6", /*6*/
"R7", /*7*/
"POWER", /*8*/
"LOWSPEED", /*9*/
"HIGHSPEED", /*10*/
"PORT_TEST", /*11*/
"INDICATOR", /*12*/
"R13", /*13*/
"R14", /*14*/
"R15", /*15*/
"C_CONNECTION", /*16*/
"C_ENABLE", /*17*/
"C_SUSPEND", /*18*/
"C_OVER_CURRENT", /*19*/
"C_RESET", /*20*/
"C_L1", /*21*/
"R22", /*22*/
"R23", /*23*/
"R24", /*24*/
"R25", /*25*/
"R26", /*26*/
"R27", /*27*/
"R28", /*28*/
"R29", /*29*/
"R30", /*30*/
"R31", /*31*/
};
static const char * const bit_desc_ss[] = {
"CONNECTION", /*0*/
"ENABLE", /*1*/
"SUSPEND", /*2*/
"OVER_CURRENT", /*3*/
"RESET", /*4*/
"L1", /*5*/
"R6", /*6*/
"R7", /*7*/
"R8", /*8*/
"POWER", /*9*/
"HIGHSPEED", /*10*/
"PORT_TEST", /*11*/
"INDICATOR", /*12*/
"R13", /*13*/
"R14", /*14*/
"R15", /*15*/
"C_CONNECTION", /*16*/
"C_ENABLE", /*17*/
"C_SUSPEND", /*18*/
"C_OVER_CURRENT", /*19*/
"C_RESET", /*20*/
"C_BH_RESET", /*21*/
"C_LINK_STATE", /*22*/
"C_CONFIG_ERROR", /*23*/
"R24", /*24*/
"R25", /*25*/
"R26", /*26*/
"R27", /*27*/
"R28", /*28*/
"R29", /*29*/
"R30", /*30*/
"R31", /*31*/
};
static void dump_port_status_diff(u32 prev_status, u32 new_status, bool usb3)
{
int i = 0;
u32 bit = 1;
const char * const *desc = bit_desc;
if (usb3)
desc = bit_desc_ss;
pr_debug("status prev -> new: %08x -> %08x\n", prev_status, new_status);
while (bit) {
u32 prev = prev_status & bit;
u32 new = new_status & bit;
char change;
if (!prev && new)
change = '+';
else if (prev && !new)
change = '-';
else
change = ' ';
if (prev || new) {
pr_debug(" %c%s\n", change, desc[i]);
if (bit == 1) /* USB_PORT_STAT_CONNECTION */
pr_debug(" %c%s\n", change, "USB_PORT_STAT_SPEED_5GBPS");
}
bit <<= 1;
i++;
}
pr_debug("\n");
}
void rh_port_connect(struct vhci_device *vdev, enum usb_device_speed speed)
{
struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
struct vhci *vhci = vhci_hcd->vhci;
int rhport = vdev->rhport;
u32 status;
unsigned long flags;
usbip_dbg_vhci_rh("rh_port_connect %d\n", rhport);
spin_lock_irqsave(&vhci->lock, flags);
status = vhci_hcd->port_status[rhport];
status |= USB_PORT_STAT_CONNECTION | (1 << USB_PORT_FEAT_C_CONNECTION);
switch (speed) {
case USB_SPEED_HIGH:
status |= USB_PORT_STAT_HIGH_SPEED;
break;
case USB_SPEED_LOW:
status |= USB_PORT_STAT_LOW_SPEED;
break;
default:
break;
}
vhci_hcd->port_status[rhport] = status;
spin_unlock_irqrestore(&vhci->lock, flags);
usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd));
}
static void rh_port_disconnect(struct vhci_device *vdev)
{
struct vhci_hcd *vhci_hcd = vdev_to_vhci_hcd(vdev);
struct vhci *vhci = vhci_hcd->vhci;
int rhport = vdev->rhport;
u32 status;
unsigned long flags;
usbip_dbg_vhci_rh("rh_port_disconnect %d\n", rhport);
spin_lock_irqsave(&vhci->lock, flags);
status = vhci_hcd->port_status[rhport];
status &= ~USB_PORT_STAT_CONNECTION;
status |= (1 << USB_PORT_FEAT_C_CONNECTION);
vhci_hcd->port_status[rhport] = status;
spin_unlock_irqrestore(&vhci->lock, flags);
usb_hcd_poll_rh_status(vhci_hcd_to_hcd(vhci_hcd));
}
#define PORT_C_MASK \
((USB_PORT_STAT_C_CONNECTION \
| USB_PORT_STAT_C_ENABLE \
| USB_PORT_STAT_C_SUSPEND \
| USB_PORT_STAT_C_OVERCURRENT \
| USB_PORT_STAT_C_RESET) << 16)
/*
* Returns 0 if the status hasn't changed, or the number of bytes in buf.
* Ports are 0-indexed from the HCD point of view,
* and 1-indexed from the USB core pointer of view.
*
* @buf: a bitmap to show which port status has been changed.
* bit 0: reserved
* bit 1: the status of port 0 has been changed.
* bit 2: the status of port 1 has been changed.
* ...
*/
static int vhci_hub_status(struct usb_hcd *hcd, char *buf)
{
struct vhci_hcd *vhci_hcd = hcd_to_vhci_hcd(hcd);
struct vhci *vhci = vhci_hcd->vhci;
int retval = DIV_ROUND_UP(VHCI_HC_PORTS + 1, 8);
int rhport;
int changed = 0;
unsigned long flags;
memset(buf, 0, retval);
spin_lock_irqsave(&vhci->lock, flags);
if (!HCD_HW_ACCESSIBLE(hcd)) {
usbip_dbg_vhci_rh("hw accessible flag not on?\n");
goto done;
}
/* check pseudo status register for each port */
for (rhport = 0; rhport < VHCI_HC_PORTS; rhport++) {
if ((vhci_hcd->port_status[rhport] & PORT_C_MASK)) {
/* The status of a port has been changed, */
usbip_dbg_vhci_rh("port %d status changed\n", rhport);
buf[(rhport + 1) / 8] |= 1 << (rhport + 1) % 8;
changed = 1;
}
}
if ((hcd->state == HC_STATE_SUSPENDED) && (changed == 1))
usb_hcd_resume_root_hub(hcd);
done:
spin_unlock_irqrestore(&vhci->lock, flags);
return changed ? retval : 0;
}
/* usb 3.0 root hub device descriptor */
static struct {
struct usb_bos_descriptor bos;
struct usb_ss_cap_descriptor ss_cap;
} __packed usb3_bos_desc = {
.bos = {
.bLength = USB_DT_BOS_SIZE,
.bDescriptorType = USB_DT_BOS,
.wTotalLength = cpu_to_le16(sizeof(usb3_bos_desc)),
.bNumDeviceCaps = 1,
},
.ss_cap = {
.bLength = USB_DT_USB_SS_CAP_SIZE,
.bDescriptorType = USB_DT_DEVICE_CAPABILITY,
.bDevCapabilityType = USB_SS_CAP_TYPE,
.wSpeedSupported = cpu_to_le16(USB_5GBPS_OPERATION),
.bFunctionalitySupport = ilog2(USB_5GBPS_OPERATION),
},
};
static inline void
ss_hub_descriptor(struct usb_hub_descriptor *desc)
{
memset(desc, 0, sizeof *desc);
desc->bDescriptorType = USB_DT_SS_HUB;
desc->bDescLength = 12;
desc->wHubCharacteristics = cpu_to_le16(
HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM);
desc->bNbrPorts = VHCI_HC_PORTS;
desc->u.ss.bHubHdrDecLat = 0x04; /* Worst case: 0.4 micro sec*/
desc->u.ss.DeviceRemovable = 0xffff;
}
static inline void hub_descriptor(struct usb_hub_descriptor *desc)
{
int width;
memset(desc, 0, sizeof(*desc));
desc->bDescriptorType = USB_DT_HUB;
desc->wHubCharacteristics = cpu_to_le16(
HUB_CHAR_INDV_PORT_LPSM | HUB_CHAR_COMMON_OCPM);
desc->bNbrPorts = VHCI_HC_PORTS;
BUILD_BUG_ON(VHCI_HC_PORTS > USB_MAXCHILDREN);
width = desc->bNbrPorts / 8 + 1;
desc->bDescLength = USB_DT_HUB_NONVAR_SIZE + 2 * width;
memset(&desc->u.hs.DeviceRemovable[0], 0, width);
memset(&
|