#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/device.h>
#include <machine/bus.h>
#include <dev/usb/usb.h>
#include <dev/usb/usbdi.h>
#include <dev/usb/usbdi_util.h>
#include <dev/usb/usbdivar.h>
#define UHUB_INTR_INTERVAL 255
#ifdef UHUB_DEBUG
#define DPRINTF(x...) do { printf(x); } while (0)
#else
#define DPRINTF(x...)
#endif
#define DEVNAME(sc) ((sc)->sc_dev.dv_xname)
struct uhub_softc {
struct device sc_dev;
struct usbd_device *sc_hub;
struct usbd_pipe *sc_ipipe;
uint32_t sc_status;
uint8_t *sc_statusbuf;
size_t sc_statuslen;
u_char sc_running;
};
#define UHUB_PROTO(sc) ((sc)->sc_hub->ddesc.bDeviceProtocol)
#define UHUB_IS_HIGH_SPEED(sc) (UHUB_PROTO(sc) != UDPROTO_FSHUB)
#define UHUB_IS_SINGLE_TT(sc) (UHUB_PROTO(sc) == UDPROTO_HSHUBSTT)
int uhub_explore(struct usbd_device *hub);
void uhub_intr(struct usbd_xfer *, void *, usbd_status);
int uhub_port_connect(struct uhub_softc *, int, int);
int uhub_match(struct device *, void *, void *);
void uhub_attach(struct device *, struct device *, void *);
int uhub_detach(struct device *, int);
struct cfdriver uhub_cd = {
NULL, "uhub", DV_DULL
};
const struct cfattach uhub_ca = {
sizeof(struct uhub_softc), uhub_match, uhub_attach, uhub_detach
};
const struct cfattach uhub_uhub_ca = {
sizeof(struct uhub_softc), uhub_match, uhub_attach, uhub_detach
};
int
uhub_match(struct device *parent, void *match, void *aux)
{
struct usb_attach_arg *uaa = aux;
usb_device_descriptor_t *dd = usbd_get_device_descriptor(uaa->device);
if (uaa->iface == NULL)
return (UMATCH_NONE);
if (dd->bDeviceClass == UDCLASS_HUB)
return (UMATCH_DEVCLASS_DEVSUBCLASS);
return (UMATCH_NONE);
}
void
uhub_attach(struct device *parent, struct device *self, void *aux)
{
struct uhub_softc *sc = (struct uhub_softc *)self;
struct usb_attach_arg *uaa = aux;
struct usbd_device *dev = uaa->device;
struct usbd_hub *hub = NULL;
union {
usb_hub_descriptor_t hs;
usb_hub_ss_descriptor_t ss;
} hd;
int p, port, nports, powerdelay;
struct usbd_interface *iface = uaa->iface;
usb_endpoint_descriptor_t *ed;
struct usbd_tt *tts = NULL;
uint8_t ttthink = 0;
usbd_status err;
#ifdef UHUB_DEBUG
int nremov;
#endif
sc->sc_hub = dev;
if (dev->depth > USB_HUB_MAX_DEPTH) {
printf("%s: hub depth (%d) exceeded, hub ignored\n",
sc->sc_dev.dv_xname, USB_HUB_MAX_DEPTH);
return;
}
if (dev->depth != 0 && dev->speed == USB_SPEED_SUPER) {
if (usbd_set_hub_depth(dev, dev->depth - 1)) {
printf("%s: unable to set HUB depth\n",
sc->sc_dev.dv_xname);
return;
}
}
if (dev->speed == USB_SPEED_SUPER) {
err = usbd_get_hub_ss_descriptor(dev, &hd.ss, 1);
nports = hd.ss.bNbrPorts;
powerdelay = (hd.ss.bPwrOn2PwrGood * UHD_PWRON_FACTOR);
if (!err && nports > 7)
usbd_get_hub_ss_descriptor(dev, &hd.ss, nports);
} else {
err = usbd_get_hub_descriptor(dev, &hd.hs, 1);
nports = hd.hs.bNbrPorts;
powerdelay = (hd.hs.bPwrOn2PwrGood * UHD_PWRON_FACTOR);
ttthink = UGETW(hd.hs.wHubCharacteristics) & UHD_TT_THINK;
if (!err && nports > 7)
usbd_get_hub_descriptor(dev, &hd.hs, nports);
}
if (err) {
DPRINTF("%s: getting hub descriptor failed, error=%s\n",
sc->sc_dev.dv_xname, usbd_errstr(err));
return;
}
#ifdef UHUB_DEBUG
for (nremov = 0, port = 1; port <= nports; port++) {
if (dev->speed == USB_SPEED_SUPER) {
if (!UHD_NOT_REMOV(&hd.ss, port))
nremov++;
} else {
if (!UHD_NOT_REMOV(&hd.hs, port))
nremov++;
}
}
printf("%s: %d port%s with %d removable, %s powered",
sc->sc_dev.dv_xname, nports, nports != 1 ? "s" : "",
nremov, dev->self_powered ? "self" : "bus");
if (dev->depth > 0 && UHUB_IS_HIGH_SPEED(sc)) {
printf(", %s transaction translator%s",
UHUB_IS_SINGLE_TT(sc) ? "single" : "multiple",
UHUB_IS_SINGLE_TT(sc) ? "" : "s");
}
printf("\n");
#endif
if (nports == 0) {
printf("%s: no ports, hub ignored\n", sc->sc_dev.dv_xname);
goto bad;
}
hub = malloc(sizeof(*hub), M_USBDEV, M_NOWAIT);
if (hub == NULL)
return;
hub->ports = mallocarray(nports, sizeof(struct usbd_port),
M_USBDEV, M_NOWAIT);
if (hub->ports == NULL) {
free(hub, M_USBDEV, sizeof *hub);
return;
}
dev->hub = hub;
dev->hub->hubsoftc = sc;
hub->explore = uhub_explore;
hub->nports = nports;
hub->powerdelay = powerdelay;
hub->ttthink = ttthink >> 5;
hub->multi = UHUB_IS_SINGLE_TT(sc) ? 0 : 1;
if (!dev->self_powered && dev->powersrc->parent != NULL &&
!dev->powersrc->parent->self_powered) {
printf("%s: bus powered hub connected to bus powered hub, "
"ignored\n", sc->sc_dev.dv_xname);
goto bad;
}
ed = usbd_interface2endpoint_descriptor(iface, 0);
if (ed == NULL) {
printf("%s: no endpoint descriptor\n", sc->sc_dev.dv_xname);
goto bad;
}
if (UE_GET_XFERTYPE(ed->bmAttributes) != UE_INTERRUPT) {
printf("%s: bad interrupt endpoint\n", sc->sc_dev.dv_xname);
goto bad;
}
sc->sc_statuslen = (nports + 1 + 7) / 8;
sc->sc_statusbuf = malloc(sc->sc_statuslen, M_USBDEV, M_NOWAIT);
if (!sc->sc_statusbuf)
goto bad;
err = usbd_open_pipe_intr(iface, ed->bEndpointAddress,
USBD_SHORT_XFER_OK, &sc->sc_ipipe, sc, sc->sc_statusbuf,
sc->sc_statuslen, uhub_intr, UHUB_INTR_INTERVAL);
if (err) {
printf("%s: cannot open interrupt pipe\n",
sc->sc_dev.dv_xname);
goto bad;
}
usbd_delay_ms(dev, USB_POWER_DOWN_TIME);
if (UHUB_IS_HIGH_SPEED(sc)) {
tts = mallocarray((UHUB_IS_SINGLE_TT(sc) ? 1 : nports),
sizeof(struct usbd_tt), M_USBDEV, M_NOWAIT);
if (!tts)
goto bad;
}
for (p = 0; p < nports; p++) {
struct usbd_port *up = &hub->ports[p];
up->device = NULL;
up->parent = dev;
up->portno = p + 1;
if (dev->self_powered)
up->power = USB_MAX_POWER;
else
up->power = USB_MIN_POWER;
up->restartcnt = 0;
up->reattach = 0;
if (UHUB_IS_HIGH_SPEED(sc)) {
up->tt = &tts[UHUB_IS_SINGLE_TT(sc) ? 0 : p];
up->tt->hub = hub;
up->tt->hcpriv = NULL;
} else {
up->tt = NULL;
}
}
for (port = 1; port <= nports; port++) {
err = usbd_set_port_feature(dev, port, UHF_PORT_POWER);
if (err)
printf("%s: port %d power on failed, %s\n",
sc->sc_dev.dv_xname, port,
usbd_errstr(err));
sc->sc_status |= (1 << port);
}
if (dev->powersrc->parent != NULL)
usbd_delay_ms(dev, powerdelay + USB_EXTRA_POWER_UP_TIME);
sc->sc_running = 1;
return;
bad:
free(sc->sc_statusbuf, M_USBDEV, sc->sc_statuslen);
if (hub) {
free(hub->ports, M_USBDEV, hub->nports * sizeof(*hub->ports));
free(hub, M_USBDEV, sizeof(*hub));
}
dev->hub = NULL;
}
int
uhub_explore(struct usbd_device *dev)
{
struct uhub_softc *sc = dev->hub->hubsoftc;
struct usbd_port *up;
int status, change;
int port;
if (usbd_is_dying(sc->sc_hub))
return (EIO);
if (!sc->sc_running)
return (ENXIO);
if (sc->sc_hub->depth > USB_HUB_MAX_DEPTH)
return (EOPNOTSUPP);
for (port = 1; port <= sc->sc_hub->hub->nports; port++) {
up = &sc->sc_hub->hub->ports[port-1];
change = 0;
status = 0;
if ((sc->sc_status & (1 << port)) || up->reattach) {
sc->sc_status &= ~(1 << port);
if (usbd_get_port_status(dev, port, &up->status))
continue;
status = UGETW(up->status.wPortStatus);
change = UGETW(up->status.wPortChange);
DPRINTF("%s: port %d status=0x%04x change=0x%04x\n",
sc->sc_dev.dv_xname, port, status, change);
}
if (up->reattach) {
change |= UPS_C_CONNECT_STATUS;
up->reattach = 0;
}
if (change & UPS_C_PORT_ENABLED) {
usbd_clear_port_feature(sc->sc_hub, port,
UHF_C_PORT_ENABLE);
if (change & UPS_C_CONNECT_STATUS) {
} else if (status & UPS_PORT_ENABLED) {
printf("%s: illegal enable change, port %d\n",
sc->sc_dev.dv_xname, port);
} else {
if (up->restartcnt)
printf("%s: port error, restarting "
"port %d\n",
sc->sc_dev.dv_xname, port);
if (up->restartcnt++ < USBD_RESTART_MAX)
change |= UPS_C_CONNECT_STATUS;
else
printf("%s: port error, giving up "
"port %d\n",
sc->sc_dev.dv_xname, port);
}
}
if (change & UPS_C_PORT_RESET) {
usbd_clear_port_feature(sc->sc_hub, port,
UHF_C_PORT_RESET);
change |= UPS_C_CONNECT_STATUS;
}
if (change & UPS_C_BH_PORT_RESET &&
sc->sc_hub->speed == USB_SPEED_SUPER) {
usbd_clear_port_feature(sc->sc_hub, port,
UHF_C_BH_PORT_RESET);
}
if (change & UPS_C_CONNECT_STATUS) {
if (uhub_port_connect(sc, port, status))
continue;
up->restartcnt = 0;
}
if (change & UPS_C_PORT_LINK_STATE) {
usbd_clear_port_feature(sc->sc_hub, port,
UHF_C_PORT_LINK_STATE);
}
if (up->device != NULL && up->device->hub != NULL)
up->device->hub->explore(up->device);
}
return (0);
}
int
uhub_detach(struct device *self, int flags)
{
struct uhub_softc *sc = (struct uhub_softc *)self;
struct usbd_hub *hub = sc->sc_hub->hub;
struct usbd_port *rup;
int port;
if (hub == NULL)
return (0);
usbd_close_pipe(sc->sc_ipipe);
for (port = 0; port < hub->nports; port++) {
rup = &hub->ports[port];
if (rup->device != NULL) {
usbd_detach(rup->device, self);
rup->device = NULL;
}
}
free(hub->ports[0].tt, M_USBDEV,
(UHUB_IS_SINGLE_TT(sc) ? 1 : hub->nports) * sizeof(struct usbd_tt));
free(sc->sc_statusbuf, M_USBDEV, sc->sc_statuslen);
free(hub->ports, M_USBDEV, hub->nports * sizeof(*hub->ports));
free(hub, M_USBDEV, sizeof(*hub));
sc->sc_hub->hub = NULL;
return (0);
}
void
uhub_intr(struct usbd_xfer *xfer, void *addr, usbd_status status)
{
struct uhub_softc *sc = addr;
uint32_t stats = 0;
int i;
if (usbd_is_dying(sc->sc_hub))
return;
DPRINTF("%s: intr status=%d\n", sc->sc_dev.dv_xname, status);
if (status == USBD_STALLED)
usbd_clear_endpoint_stall_async(sc->sc_ipipe);
else if (status == USBD_NORMAL_COMPLETION) {
for (i = 0; i < xfer->actlen; i++)
stats |= (uint32_t)(xfer->buffer[i]) << (i * 8);
sc->sc_status |= stats;
usb_needs_explore(sc->sc_hub, 0);
}
}
int
uhub_port_connect(struct uhub_softc *sc, int port, int status)
{
struct usbd_port *up = &sc->sc_hub->hub->ports[port-1];
int speed, change;
usbd_clear_port_feature(sc->sc_hub, port, UHF_C_PORT_CONNECTION);
if (up->device != NULL) {
usbd_detach(up->device, &sc->sc_dev);
up->device = NULL;
}
if ((status & UPS_CURRENT_CONNECT_STATUS) == 0)
return (0);
if ((status & (UPS_PORT_POWER|UPS_PORT_POWER_SS)) == 0) {
printf("%s: connected port %d has no power\n", DEVNAME(sc),
port);
return (-1);
}
usbd_delay_ms(sc->sc_hub, USB_PORT_POWERUP_DELAY);
if (usbd_reset_port(sc->sc_hub, port)) {
printf("%s: port %d reset failed\n", DEVNAME(sc), port);
return (-1);
}
if (usbd_get_port_status(sc->sc_hub, port, &up->status))
return (-1);
status = UGETW(up->status.wPortStatus);
change = UGETW(up->status.wPortChange);
DPRINTF("%s: port %d status=0x%04x change=0x%04x\n", DEVNAME(sc),
port, status, change);
if ((status & UPS_CURRENT_CONNECT_STATUS) == 0) {
DPRINTF("%s: port %d, device disappeared after reset\n",
DEVNAME(sc), port);
return (-1);
}
if ((status & UPS_PORT_POWER) == 0)
status &= ~UPS_PORT_POWER_SS;
if (status & UPS_HIGH_SPEED)
speed = USB_SPEED_HIGH;
else if (status & UPS_LOW_SPEED)
speed = USB_SPEED_LOW;
else {
if (status & UPS_PORT_POWER)
speed = USB_SPEED_FULL;
else
speed = sc->sc_hub->speed;
}
if (speed > sc->sc_hub->speed)
speed = sc->sc_hub->speed;
if (usbd_new_device(&sc->sc_dev, sc->sc_hub->bus, sc->sc_hub->depth + 1,
speed, port, up)) {
printf("%s: device problem, disabling port %d\n", DEVNAME(sc),
port);
usbd_clear_port_feature(sc->sc_hub, port, UHF_PORT_ENABLE);
return (-1);
}
return (0);
}