HelenOS sources
This source file includes following definitions.
- XHCI_REG_MASK
- xhci_rh_init
- xhci_rh_fini
- get_rh_port
- rh_enumerate_device
- rh_remove_device
- handle_port_change
- xhci_rh_set_ports_protocol
- xhci_rh_start
- xhci_rh_stop
- rh_worker
#include <errno.h>
#include <str_error.h>
#include <usb/request.h>
#include <usb/debug.h>
#include <usb/host/bus.h>
#include <usb/host/ddf_helpers.h>
#include <usb/dma_buffer.h>
#include <usb/host/hcd.h>
#include <usb/port.h>
#include "debug.h"
#include "commands.h"
#include "endpoint.h"
#include "hc.h"
#include "hw_struct/trb.h"
#include "rh.h"
#include "transfers.h"
static const uint32_t port_events_mask =
XHCI_REG_MASK(XHCI_PORT_CSC) |
XHCI_REG_MASK(XHCI_PORT_PEC) |
XHCI_REG_MASK(XHCI_PORT_WRC) |
XHCI_REG_MASK(XHCI_PORT_OCC) |
XHCI_REG_MASK(XHCI_PORT_PRC) |
XHCI_REG_MASK(XHCI_PORT_PLC) |
XHCI_REG_MASK(XHCI_PORT_CEC);
static const uint32_t port_reset_mask =
XHCI_REG_MASK(XHCI_PORT_WRC) |
XHCI_REG_MASK(XHCI_PORT_PRC);
typedef struct rh_port {
usb_port_t base;
xhci_rh_t *rh;
uint8_t major;
xhci_port_regs_t *regs;
xhci_device_t *device;
} rh_port_t;
static int rh_worker(void *);
errno_t xhci_rh_init(xhci_rh_t *rh, xhci_hc_t *hc)
{
assert(rh);
assert(hc);
rh->hc = hc;
rh->max_ports = XHCI_REG_RD(hc->cap_regs, XHCI_CAP_MAX_PORTS);
rh->ports = calloc(rh->max_ports, sizeof(rh_port_t));
if (!rh->ports)
return ENOMEM;
const errno_t err = bus_device_init(&rh->device.base, &rh->hc->bus.base);
if (err) {
free(rh->ports);
return err;
}
rh->event_worker = joinable_fibril_create(&rh_worker, rh);
if (!rh->event_worker) {
free(rh->ports);
return err;
}
for (unsigned i = 0; i < rh->max_ports; i++) {
usb_port_init(&rh->ports[i].base);
rh->ports[i].rh = rh;
rh->ports[i].regs = &rh->hc->op_regs->portrs[i];
}
rh->device.route_str = 0;
xhci_sw_ring_init(&rh->event_ring, rh->max_ports);
return EOK;
}
errno_t xhci_rh_fini(xhci_rh_t *rh)
{
assert(rh);
xhci_rh_stop(rh);
joinable_fibril_destroy(rh->event_worker);
xhci_sw_ring_fini(&rh->event_ring);
return EOK;
}
static rh_port_t *get_rh_port(usb_port_t *port)
{
assert(port);
return (rh_port_t *) port;
}
static errno_t rh_enumerate_device(usb_port_t *usb_port)
{
errno_t err;
rh_port_t *port = get_rh_port(usb_port);
if (port->major <= 2) {
XHCI_REG_SET(port->regs, XHCI_PORT_PR, 1);
if ((err = usb_port_wait_for_enabled(&port->base)))
return err;
} else {
XHCI_REG_SET(port->regs, XHCI_PORT_WPR, 1);
if ((err = usb_port_wait_for_enabled(&port->base)))
return err;
}
uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
if (!enabled)
return ENOENT;
unsigned psiv = (status & XHCI_REG_MASK(XHCI_PORT_PS)) >>
XHCI_REG_SHIFT(XHCI_PORT_PS);
const usb_speed_t speed = port->rh->hc->speeds[psiv].usb_speed;
device_t *dev = hcd_ddf_fun_create(&port->rh->hc->base, speed);
if (!dev) {
usb_log_error("Failed to create USB device function.");
return ENOMEM;
}
dev->hub = &port->rh->device.base;
dev->tier = 1;
dev->port = port - port->rh->ports + 1;
port->device = xhci_device_get(dev);
port->device->rh_port = dev->port;
usb_log_debug("Enumerating new %s-speed device on port %u.",
usb_str_speed(dev->speed), dev->port);
if ((err = bus_device_enumerate(dev))) {
usb_log_error("Failed to enumerate USB device: %s", str_error(err));
return err;
}
if (!ddf_fun_get_name(dev->fun)) {
bus_device_set_default_name(dev);
}
if ((err = ddf_fun_bind(dev->fun))) {
usb_log_error("Failed to register device " XHCI_DEV_FMT " DDF function: %s.",
XHCI_DEV_ARGS(*port->device), str_error(err));
goto err_usb_dev;
}
return EOK;
err_usb_dev:
hcd_ddf_fun_destroy(dev);
port->device = NULL;
return err;
}
static void rh_remove_device(usb_port_t *usb_port)
{
rh_port_t *port = get_rh_port(usb_port);
assert(port->device);
usb_log_info("Device " XHCI_DEV_FMT " at port %zu has been disconnected.",
XHCI_DEV_ARGS(*port->device), port - port->rh->ports + 1);
bus_device_gone(&port->device->base);
port->device = NULL;
}
static void handle_port_change(xhci_rh_t *rh, uint8_t port_id)
{
rh_port_t *const port = &rh->ports[port_id - 1];
uint32_t status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
while (status & port_events_mask) {
XHCI_REG_WR_FIELD(&port->regs->portsc,
status & ~XHCI_REG_MASK(XHCI_PORT_PED), 32);
const bool connected = !!(status & XHCI_REG_MASK(XHCI_PORT_CCS));
const bool enabled = !!(status & XHCI_REG_MASK(XHCI_PORT_PED));
if (status & XHCI_REG_MASK(XHCI_PORT_CSC)) {
usb_log_info("Connected state changed on port %u.", port_id);
status &= ~XHCI_REG_MASK(XHCI_PORT_CSC);
if (connected) {
usb_port_connected(&port->base, &rh_enumerate_device);
} else {
usb_port_disabled(&port->base, &rh_remove_device);
}
}
if (status & port_reset_mask) {
status &= ~port_reset_mask;
if (enabled) {
usb_port_enabled(&port->base);
} else {
usb_port_disabled(&port->base, &rh_remove_device);
}
}
status &= port_events_mask;
if (status != 0)
usb_log_debug("RH port %u change not handled: 0x%x", port_id, status);
status = XHCI_REG_RD_FIELD(&port->regs->portsc, 32);
}
}
void xhci_rh_set_ports_protocol(xhci_rh_t *rh,
unsigned offset, unsigned count, unsigned major)
{
for (unsigned i = offset; i < offset + count; i++)
rh->ports[i - 1].major = major;
}
void xhci_rh_start(xhci_rh_t *rh)
{
xhci_sw_ring_restart(&rh->event_ring);
joinable_fibril_start(rh->event_worker);
for (uint8_t i = 0; i < rh->max_ports; ++i) {
handle_port_change(rh, i + 1);
rh_port_t *const port = &rh->ports[i];
if (XHCI_REG_RD(port->regs, XHCI_PORT_CCS) &&
port->base.state == PORT_DISABLED)
usb_port_connected(&port->base, &rh_enumerate_device);
}
}
void xhci_rh_stop(xhci_rh_t *rh)
{
xhci_sw_ring_stop(&rh->event_ring);
joinable_fibril_join(rh->event_worker);
for (uint8_t i = 0; i < rh->max_ports; ++i) {
rh_port_t *const port = &rh->ports[i];
usb_port_disabled(&port->base, &rh_remove_device);
usb_port_fini(&port->base);
}
}
static int rh_worker(void *arg)
{
xhci_rh_t *const rh = arg;
xhci_trb_t trb;
while (xhci_sw_ring_dequeue(&rh->event_ring, &trb) == EOK) {
uint8_t port_id = XHCI_QWORD_EXTRACT(trb.parameter, 31, 24);
usb_log_debug("Port status change event detected for port %u.", port_id);
handle_port_change(rh, port_id);
}
return 0;
}
HelenOS homepage, sources at GitHub