HelenOS sources
This source file includes following definitions.
- ehci_rh_hub_desc_init
- ehci_rh_init
- ehci_rh_schedule
- ehci_rh_interrupt
- req_get_status
- req_clear_hub_feature
- req_get_port_status
- stop_reset
- stop_resume
- delayed_job
- req_clear_port_feature
- req_set_port_feature
- req_status_change_handler
#include <assert.h>
#include <errno.h>
#include <mem.h>
#include <str_error.h>
#include <stdint.h>
#include <stddef.h>
#include <usb/classes/hub.h>
#include <usb/debug.h>
#include <usb/descriptor.h>
#include <usb/request.h>
#include <usb/usb.h>
#include <usb/host/endpoint.h>
#include <usbvirt/device.h>
#include "ehci_rh.h"
enum {
HUB_STATUS_CHANGE_PIPE = 1,
};
static usbvirt_device_ops_t ops;
static void ehci_rh_hub_desc_init(ehci_rh_t *instance, unsigned hcs)
{
assert(instance);
const unsigned dsize = sizeof(usb_hub_descriptor_header_t) +
STATUS_BYTES(instance->port_count) * 2;
assert(dsize <= sizeof(instance->hub_descriptor));
instance->hub_descriptor.header.length = dsize;
instance->hub_descriptor.header.descriptor_type = USB_DESCTYPE_HUB;
instance->hub_descriptor.header.port_count = instance->port_count;
instance->hub_descriptor.header.characteristics = 0 |
((hcs & EHCI_CAPS_HCS_PPC_FLAG) ? 0x09 : 0x12) |
((hcs & EHCI_CAPS_HCS_INDICATORS_FLAG) ? 0x80 : 0) |
(0x3 << 5);
instance->hub_descriptor.header.characteristics_reserved = 0;
instance->hub_descriptor.header.power_good_time = 50;
instance->hub_descriptor.header.max_current = 0;
instance->hub_descriptor.rempow[0] = 0xff;
instance->hub_descriptor.rempow[1] = 0xff;
instance->hub_descriptor.rempow[2] = 0xff;
instance->hub_descriptor.rempow[3] = 0xff;
}
errno_t ehci_rh_init(ehci_rh_t *instance, ehci_caps_regs_t *caps, ehci_regs_t *regs,
fibril_mutex_t *guard, const char *name)
{
assert(instance);
instance->registers = regs;
instance->port_count =
(EHCI_RD(caps->hcsparams) >> EHCI_CAPS_HCS_N_PORTS_SHIFT) &
EHCI_CAPS_HCS_N_PORTS_MASK;
usb_log_debug2("RH(%p): hcsparams: %x.", instance,
EHCI_RD(caps->hcsparams));
usb_log_info("RH(%p): Found %u ports.", instance,
instance->port_count);
if (EHCI_RD(caps->hcsparams) & EHCI_CAPS_HCS_PPC_FLAG) {
usb_log_info("RH(%p): Per-port power switching.", instance);
} else {
usb_log_info("RH(%p): No power switching.", instance);
}
for (unsigned i = 0; i < instance->port_count; ++i)
usb_log_debug2("RH(%p-%u): status: %" PRIx32, instance, i,
EHCI_RD(regs->portsc[i]));
for (unsigned i = 0; i < EHCI_MAX_PORTS; ++i) {
instance->reset_flag[i] = false;
instance->resume_flag[i] = false;
}
ehci_rh_hub_desc_init(instance, EHCI_RD(caps->hcsparams));
instance->guard = guard;
instance->status_change_endpoint = NULL;
return virthub_base_init(&instance->base, name, &ops, instance,
NULL, &instance->hub_descriptor.header, HUB_STATUS_CHANGE_PIPE);
}
errno_t ehci_rh_schedule(ehci_rh_t *instance, usb_transfer_batch_t *batch)
{
assert(instance);
assert(batch);
batch->error = virthub_base_request(&instance->base, batch->target,
batch->dir, (void *) batch->setup.buffer,
batch->dma_buffer.virt, batch->size,
&batch->transferred_size);
if (batch->error == ENAK) {
usb_log_debug("RH(%p): BATCH(%p) adding as unfinished",
instance, batch);
fibril_mutex_lock(instance->guard);
const int err = endpoint_activate_locked(batch->ep, batch);
if (err) {
fibril_mutex_unlock(batch->ep->guard);
return err;
}
assert(!instance->status_change_endpoint);
endpoint_add_ref(batch->ep);
instance->status_change_endpoint = batch->ep;
fibril_mutex_unlock(instance->guard);
} else {
usb_log_debug("RH(%p): BATCH(%p) virtual request complete: %s",
instance, batch, str_error(batch->error));
usb_transfer_batch_finish(batch);
}
return EOK;
}
errno_t ehci_rh_interrupt(ehci_rh_t *instance)
{
fibril_mutex_lock(instance->guard);
endpoint_t *ep = instance->status_change_endpoint;
if (!ep) {
fibril_mutex_unlock(instance->guard);
return EOK;
}
usb_transfer_batch_t *const batch = ep->active_batch;
endpoint_deactivate_locked(ep);
instance->status_change_endpoint = NULL;
fibril_mutex_unlock(instance->guard);
endpoint_del_ref(ep);
if (batch) {
usb_log_debug2("RH(%p): Interrupt. Processing batch: %p",
instance, batch);
batch->error = virthub_base_request(&instance->base, batch->target,
batch->dir, (void *) batch->setup.buffer,
batch->dma_buffer.virt, batch->size,
&batch->transferred_size);
usb_transfer_batch_finish(batch);
}
return EOK;
}
#define TEST_SIZE_INIT(size, port, hub) \
do { \
hub = virthub_get_data(device); \
assert(hub);\
if (uint16_usb2host(setup_packet->length) != size) \
return ESTALL; \
port = uint16_usb2host(setup_packet->index) - 1; \
if (port > hub->port_count) \
return EINVAL; \
} while (0)
static errno_t req_get_status(usbvirt_device_t *device,
const usb_device_request_setup_packet_t *setup_packet,
uint8_t *data, size_t *act_size)
{
ehci_rh_t *hub = virthub_get_data(device);
assert(hub);
if (uint16_usb2host(setup_packet->length) != 4)
return ESTALL;
const uint32_t val = 0;
memcpy(data, &val, sizeof(val));
*act_size = sizeof(val);
return EOK;
}
static errno_t req_clear_hub_feature(usbvirt_device_t *device,
const usb_device_request_setup_packet_t *setup_packet,
uint8_t *data, size_t *act_size)
{
ehci_rh_t *hub = virthub_get_data(device);
assert(hub);
return ESTALL;
}
#define BIT_VAL(val, bit) ((val & bit) ? 1 : 0)
#define EHCI2USB(val, bit, mask) (BIT_VAL(val, bit) ? mask : 0)
static errno_t req_get_port_status(usbvirt_device_t *device,
const usb_device_request_setup_packet_t *setup_packet,
uint8_t *data, size_t *act_size)
{
ehci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(4, port, hub);
if (setup_packet->value != 0)
return EINVAL;
const uint32_t reg = EHCI_RD(hub->registers->portsc[port]);
const uint32_t status = uint32_host2usb(
EHCI2USB(reg, USB_PORTSC_CONNECT_FLAG, USB_HUB_PORT_STATUS_CONNECTION) |
EHCI2USB(reg, USB_PORTSC_ENABLED_FLAG, USB_HUB_PORT_STATUS_ENABLE) |
EHCI2USB(reg, USB_PORTSC_SUSPEND_FLAG, USB2_HUB_PORT_STATUS_SUSPEND) |
EHCI2USB(reg, USB_PORTSC_OC_ACTIVE_FLAG, USB_HUB_PORT_STATUS_OC) |
EHCI2USB(reg, USB_PORTSC_PORT_RESET_FLAG, USB_HUB_PORT_STATUS_RESET) |
EHCI2USB(reg, USB_PORTSC_PORT_POWER_FLAG, USB2_HUB_PORT_STATUS_POWER) |
(((reg & USB_PORTSC_LINE_STATUS_MASK) == USB_PORTSC_LINE_STATUS_K) ?
(USB2_HUB_PORT_STATUS_LOW_SPEED) : 0) |
((reg & USB_PORTSC_PORT_OWNER_FLAG) ? 0 : USB2_HUB_PORT_STATUS_HIGH_SPEED) |
EHCI2USB(reg, USB_PORTSC_PORT_TEST_MASK, USB2_HUB_PORT_STATUS_TEST) |
EHCI2USB(reg, USB_PORTSC_INDICATOR_MASK, USB2_HUB_PORT_STATUS_INDICATOR) |
EHCI2USB(reg, USB_PORTSC_CONNECT_CH_FLAG, USB_HUB_PORT_STATUS_C_CONNECTION) |
EHCI2USB(reg, USB_PORTSC_EN_CHANGE_FLAG, USB2_HUB_PORT_STATUS_C_ENABLE) |
(hub->resume_flag[port] ? USB2_HUB_PORT_STATUS_C_SUSPEND : 0) |
EHCI2USB(reg, USB_PORTSC_OC_CHANGE_FLAG, USB_HUB_PORT_STATUS_C_OC) |
(hub->reset_flag[port] ? USB_HUB_PORT_STATUS_C_RESET : 0));
usb_log_debug2("RH(%p-%u) port status: %" PRIx32 "(%" PRIx32 ")", hub, port,
status, reg);
memcpy(data, &status, sizeof(status));
*act_size = sizeof(status);
return EOK;
}
typedef struct {
ehci_rh_t *hub;
unsigned port;
} ehci_rh_job_t;
static errno_t stop_reset(void *arg)
{
ehci_rh_job_t *job = arg;
fibril_usleep(50000);
usb_log_debug("RH(%p-%u): Clearing reset", job->hub, job->port);
EHCI_CLR(job->hub->registers->portsc[job->port],
USB_PORTSC_PORT_RESET_FLAG);
while (EHCI_RD(job->hub->registers->portsc[job->port]) &
USB_PORTSC_PORT_RESET_FLAG) {
fibril_usleep(1);
}
usb_log_debug("RH(%p-%u): Reset complete", job->hub, job->port);
if (!(EHCI_RD(job->hub->registers->portsc[job->port]) &
USB_PORTSC_ENABLED_FLAG)) {
usb_log_info("RH(%p-%u): Port not enabled after reset (%" PRIX32
"), giving up ownership", job->hub, job->port,
EHCI_RD(job->hub->registers->portsc[job->port]));
EHCI_SET(job->hub->registers->portsc[job->port],
USB_PORTSC_PORT_OWNER_FLAG);
}
job->hub->reset_flag[job->port] = true;
ehci_rh_interrupt(job->hub);
free(job);
return 0;
}
static errno_t stop_resume(void *arg)
{
ehci_rh_job_t *job = arg;
fibril_usleep(20000);
usb_log_debug("RH(%p-%u): Stopping resume", job->hub, job->port);
EHCI_CLR(job->hub->registers->portsc[job->port],
USB_PORTSC_RESUME_FLAG);
job->hub->resume_flag[job->port] = true;
ehci_rh_interrupt(job->hub);
free(job);
return 0;
}
static errno_t delayed_job(errno_t (*func)(void *), ehci_rh_t *rh, unsigned port)
{
ehci_rh_job_t *job = malloc(sizeof(*job));
if (!job)
return ENOMEM;
job->hub = rh;
job->port = port;
fid_t fib = fibril_create(func, job);
if (!fib) {
free(job);
return ENOMEM;
}
fibril_add_ready(fib);
usb_log_debug2("RH(%p-%u): Scheduled delayed stop job.", rh, port);
return EOK;
}
static errno_t req_clear_port_feature(usbvirt_device_t *device,
const usb_device_request_setup_packet_t *setup_packet,
uint8_t *data, size_t *act_size)
{
ehci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(0, port, hub);
const unsigned feature = uint16_usb2host(setup_packet->value);
switch (feature) {
case USB_HUB_FEATURE_PORT_POWER:
usb_log_debug2("RH(%p-%u): Clear port power.", hub, port);
EHCI_CLR(hub->registers->portsc[port],
USB_PORTSC_PORT_POWER_FLAG);
return EOK;
case USB2_HUB_FEATURE_PORT_ENABLE:
usb_log_debug2("RH(%p-%u): Clear port enable.", hub, port);
EHCI_CLR(hub->registers->portsc[port],
USB_PORTSC_ENABLED_FLAG);
return EOK;
case USB2_HUB_FEATURE_PORT_SUSPEND:
usb_log_debug2("RH(%p-%u): Clear port suspend.", hub, port);
if ((EHCI_RD(hub->registers->portsc[port]) &
USB_PORTSC_SUSPEND_FLAG) == 0)
return EOK;
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_RESUME_FLAG);
return delayed_job(stop_resume, hub, port);
case USB_HUB_FEATURE_C_PORT_CONNECTION:
usb_log_debug2("RH(%p-%u): Clear port connection change.",
hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_CONNECT_CH_FLAG);
return EOK;
case USB2_HUB_FEATURE_C_PORT_ENABLE:
usb_log_debug2("RH(%p-%u): Clear port enable change.",
hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_CONNECT_CH_FLAG);
return EOK;
case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
usb_log_debug2("RH(%p-%u): Clear port OC change.",
hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_OC_CHANGE_FLAG);
return EOK;
case USB2_HUB_FEATURE_C_PORT_SUSPEND:
usb_log_debug2("RH(%p-%u): Clear port suspend change.",
hub, port);
hub->resume_flag[port] = false;
return EOK;
case USB_HUB_FEATURE_C_PORT_RESET:
usb_log_debug2("RH(%p-%u): Clear port reset change.",
hub, port);
hub->reset_flag[port] = false;
return EOK;
default:
usb_log_warning("RH(%p-%u): Clear unknown feature: %u",
hub, port, feature);
return ENOTSUP;
}
}
static errno_t req_set_port_feature(usbvirt_device_t *device,
const usb_device_request_setup_packet_t *setup_packet,
uint8_t *data, size_t *act_size)
{
ehci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(0, port, hub);
const unsigned feature = uint16_usb2host(setup_packet->value);
switch (feature) {
case USB2_HUB_FEATURE_PORT_ENABLE:
usb_log_debug2("RH(%p-%u): Set port enable.", hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_ENABLED_FLAG);
return EOK;
case USB2_HUB_FEATURE_PORT_SUSPEND:
usb_log_debug2("RH(%p-%u): Set port suspend.", hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_SUSPEND_FLAG);
return EOK;
case USB_HUB_FEATURE_PORT_RESET:
usb_log_debug2("RH(%p-%u): Set port reset.", hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_PORT_RESET_FLAG);
return delayed_job(stop_reset, hub, port);
case USB_HUB_FEATURE_PORT_POWER:
usb_log_debug2("RH(%p-%u): Set port power.", hub, port);
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_PORT_POWER_FLAG);
return EOK;
default:
usb_log_warning("RH(%p-%u): Set unknown feature: %u",
hub, port, feature);
return ENOTSUP;
}
}
static errno_t req_status_change_handler(usbvirt_device_t *device,
usb_endpoint_t endpoint, usb_transfer_type_t tr_type,
void *buffer, size_t buffer_size, size_t *actual_size)
{
ehci_rh_t *hub = virthub_get_data(device);
assert(hub);
if (buffer_size < STATUS_BYTES(hub->port_count))
return ESTALL;
uint16_t mask = 0;
for (unsigned port = 0; port < hub->port_count; ++port) {
uint32_t status = EHCI_RD(hub->registers->portsc[port]);
if ((status & USB_PORTSC_WC_MASK) || hub->reset_flag[port]) {
if ((status & USB_PORTSC_CONNECT_CH_FLAG) &&
(status & USB_PORTSC_LINE_STATUS_MASK) ==
USB_PORTSC_LINE_STATUS_K)
EHCI_SET(hub->registers->portsc[port],
USB_PORTSC_PORT_OWNER_FLAG);
else
mask |= (2 << port);
}
}
usb_log_debug2("RH(%p): root hub interrupt mask: %" PRIx16, hub, mask);
if (mask == 0)
return ENAK;
mask = uint16_host2usb(mask);
memcpy(buffer, &mask, STATUS_BYTES(hub->port_count));
*actual_size = STATUS_BYTES(hub->port_count);
return EOK;
}
static const usbvirt_control_request_handler_t control_transfer_handlers[] = {
{
STD_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
.name = "GetDescriptor",
.callback = virthub_base_get_hub_descriptor,
},
{
CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_DEVREQ_GET_DESCRIPTOR),
.name = "GetDescriptor",
.callback = virthub_base_get_hub_descriptor,
},
{
CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_DESCRIPTOR),
.name = "GetHubDescriptor",
.callback = virthub_base_get_hub_descriptor,
},
{
CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
.name = "GetPortStatus",
.callback = req_get_port_status,
},
{
CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_CLEAR_FEATURE),
.name = "ClearHubFeature",
.callback = req_clear_hub_feature,
},
{
CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_CLEAR_FEATURE),
.name = "ClearPortFeature",
.callback = req_clear_port_feature,
},
{
CLASS_REQ_IN(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_GET_STATUS),
.name = "GetHubStatus",
.callback = req_get_status,
},
{
CLASS_REQ_IN(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_GET_STATUS),
.name = "GetPortStatus",
.callback = req_get_port_status,
},
{
CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_DEVICE, USB_HUB_REQUEST_SET_FEATURE),
.name = "SetHubFeature",
.callback = req_nop,
},
{
CLASS_REQ_OUT(USB_REQUEST_RECIPIENT_OTHER, USB_HUB_REQUEST_SET_FEATURE),
.name = "SetPortFeature",
.callback = req_set_port_feature,
},
{
.callback = NULL
}
};
static usbvirt_device_ops_t ops = {
.control = control_transfer_handlers,
.data_in[HUB_STATUS_CHANGE_PIPE] = req_status_change_handler,
};
HelenOS homepage, sources at GitHub