// SPDX-License-Identifier: GPL-2.0-or-later
/*
* xen-hcd.c
*
* Xen USB Virtual Host Controller driver
*
* Copyright (C) 2009, FUJITSU LABORATORIES LTD.
* Author: Noboru Iwamatsu <n_iwamatsu@jp.fujitsu.com>
*/
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/list.h>
#include <linux/usb/hcd.h>
#include <linux/io.h>
#include <xen/xen.h>
#include <xen/xenbus.h>
#include <xen/grant_table.h>
#include <xen/events.h>
#include <xen/page.h>
#include <xen/interface/io/usbif.h>
/* Private per-URB data */
struct urb_priv {
struct list_head list;
struct urb *urb;
int req_id; /* RING_REQUEST id for submitting */
int unlink_req_id; /* RING_REQUEST id for unlinking */
int status;
bool unlinked; /* dequeued marker */
};
/* virtual roothub port status */
struct rhport_status {
__u32 status;
bool resuming; /* in resuming */
bool c_connection; /* connection changed */
unsigned long timeout;
};
/* status of attached device */
struct vdevice_status {
int devnum;
enum usb_device_state status;
enum usb_device_speed speed;
};
/* RING request shadow */
struct usb_shadow {
struct xenusb_urb_request req;
struct urb *urb;
bool in_flight;
};
struct xenhcd_info {
/* Virtual Host Controller has 4 urb queues */
struct list_head pending_submit_list;
struct list_head pending_unlink_list;
struct list_head in_progress_list;
struct list_head giveback_waiting_list;
spinlock_t lock;
/* timer that kick pending and giveback waiting urbs */
struct timer_list watchdog;
unsigned long actions;
/* virtual root hub */
int rh_numports;
struct rhport_status ports[XENUSB_MAX_PORTNR];
struct vdevice_status devices[XENUSB_MAX_PORTNR];
/* Xen related staff */
struct xenbus_device *xbdev;
int urb_ring_ref;
int conn_ring_ref;
struct xenusb_urb_front_ring urb_ring;
struct xenusb_conn_front_ring conn_ring;
unsigned int evtchn;
unsigned int irq;
struct usb_shadow shadow[XENUSB_URB_RING_SIZE];
unsigned int shadow_free;
bool error;
};
#define XENHCD_RING_JIFFIES (HZ/200)
#define XENHCD_SCAN_JIFFIES 1
enum xenhcd_timer_action {
TIMER_RING_WATCHDOG,
TIMER_SCAN_PENDING_URBS,
};
static struct kmem_cache *xenhcd_urbp_cachep;
static inline struct xenhcd_info *xenhcd_hcd_to_info(struct usb_hcd *hcd)
{
return (struct xenhcd_info *)hcd->hcd_priv;
}
static inline struct usb_hcd *xenhcd_info_to_hcd(struct xenhcd_info *info)
{
return container_of((void *)info, struct usb_hcd, hcd_priv);
}
static void xenhcd_set_error(struct xenhcd_info *info, const char *msg)
{
info->error = true;
pr_alert("xen-hcd: protocol error: %s!\n", msg);
}
static inline void xenhcd_timer_action_done(struct xenhcd_info *info,
enum xenhcd_timer_action action)
{
clear_bit(action, &info->actions);
}
static void xenhcd_timer_action(struct xenhcd_info *info,
enum xenhcd_timer_action action)
{
if (timer_pending(&info->watchdog) &&
test_bit(TIMER_SCAN_PENDING_URBS, &info->actions))
return;
if (!test_and_set_bit(action, &info->actions)) {
unsigned long t;
switch (action) {
case TIMER_RING_WATCHDOG:
t = XENHCD_RING_JIFFIES;
break;
default:
t = XENHCD_SCAN_JIFFIES;
break;
}
mod_timer(&info->watchdog, t + jiffies);
}
}
/*
* set virtual port connection status
*/
static void xenhcd_set_connect_state(struct xenhcd_info *info, int portnum)
{
int port;
port = portnum - 1;
if (info->ports[port].status & USB_PORT_STAT_POWER) {
switch (info->devices[port].speed) {
case XENUSB_SPEED_NONE:
info->ports[port].status &=
~(USB_PORT_STAT_CONNECTION |
USB_PORT_STAT_ENABLE |
USB_PORT_STAT_LOW_SPEED |
USB_PORT_STAT_HIGH_SPEED |
USB_PORT_STAT_SUSPEND);
break;
case XENUSB_SPEED_LOW:
info->ports[port].status |= USB_PORT_STAT_CONNECTION;
info->ports[port].status |= USB_PORT_STAT_LOW_SPEED;
break;
case XENUSB_SPEED_FULL:
info->ports[port].status |= USB_PORT_STAT_CONNECTION;
break;
case XENUSB_SPEED_HIGH:
info->ports[port].status |= USB_PORT_STAT_CONNECTION;
info->ports[port].status |= USB_PORT_STAT_HIGH_SPEED;
break;
default: /* error */
return;
}
info->ports[port].status |= (USB_PORT_STAT_C_CONNECTION << 16);
}
}
/*
* set virtual device connection status
*/
static int xenhcd_rhport_connect(struct xenhcd_info *info, __u8 portnum,
__u8 speed)
{
int port;
if (portnum < 1 || portnum > info->rh_numports)
return -EINVAL; /* invalid port number */
port = portnum - 1;
if (info->devices[port].speed != speed) {
switch (speed) {
case XENUSB_SPEED_NONE: /* disconnect */
info->devices[port].status = USB_STATE_NOTATTACHED;
break;
case XENUSB_SPEED_LOW:
case XENUSB_SPEED_FULL:
case XENUSB_SPEED_HIGH:
info->devices[port].status = USB_STATE_ATTACHED;
break;
|