HelenOS sources
This source file includes following definitions.
- uhci_rh_init
- uhci_rh_schedule
- uhci_port_reset_enable
- req_get_port_state
- req_get_port_status
- req_clear_port_feature
- req_set_port_feature
- req_status_change_handler
#include <assert.h>
#include <async.h>
#include <ddi.h>
#include <errno.h>
#include <macros.h>
#include <mem.h>
#include <time.h>
#include <usb/debug.h>
#include <usb/descriptor.h>
#include <usb/classes/hub.h>
#include <usb/request.h>
#include <usb/host/endpoint.h>
#include <usb/usb.h>
#include "uhci_rh.h"
enum {
UHCI_RH_PORT_COUNT = 2,
UHCI_PORT_BYTES = STATUS_BYTES(UHCI_RH_PORT_COUNT),
};
static const struct {
usb_hub_descriptor_header_t header;
uint8_t removable[UHCI_PORT_BYTES];
uint8_t powered[UHCI_PORT_BYTES];
} __attribute__((packed)) hub_descriptor = {
.header = {
.length = sizeof(hub_descriptor),
.descriptor_type = USB_DESCTYPE_HUB,
.port_count = UHCI_RH_PORT_COUNT,
.characteristics =
HUB_CHAR_NO_POWER_SWITCH_FLAG | HUB_CHAR_NO_OC_FLAG,
.power_good_time = 50,
.max_current = 0,
},
.removable = { 0 },
.powered = { 0xFF },
};
static usbvirt_device_ops_t ops;
errno_t uhci_rh_init(uhci_rh_t *instance, ioport16_t *ports, const char *name)
{
assert(instance);
instance->ports[0] = ports;
instance->ports[1] = ports + 1;
instance->reset_changed[0] = false;
instance->reset_changed[1] = false;
return virthub_base_init(&instance->base, name, &ops, instance,
NULL, &hub_descriptor.header, HUB_STATUS_CHANGE_PIPE);
}
errno_t uhci_rh_schedule(uhci_rh_t *instance, usb_transfer_batch_t *batch)
{
assert(instance);
assert(batch);
do {
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)
fibril_usleep(instance->base.endpoint_descriptor.poll_interval * 1000);
} while (batch->error == ENAK);
usb_transfer_batch_finish(batch);
return EOK;
}
enum {
STATUS_CONNECTED = (1 << 0),
STATUS_CONNECTED_CHANGED = (1 << 1),
STATUS_ENABLED = (1 << 2),
STATUS_ENABLED_CHANGED = (1 << 3),
STATUS_LINE_D_PLUS = (1 << 4),
STATUS_LINE_D_MINUS = (1 << 5),
STATUS_RESUME = (1 << 6),
STATUS_ALWAYS_ONE = (1 << 7),
STATUS_LOW_SPEED = (1 << 8),
STATUS_IN_RESET = (1 << 9),
STATUS_SUSPEND = (1 << 12),
STATUS_CHANGE_BITS = STATUS_CONNECTED_CHANGED | STATUS_ENABLED_CHANGED,
STATUS_WC_BITS = STATUS_CHANGE_BITS,
};
static void uhci_port_reset_enable(ioport16_t *port)
{
assert(port);
uint16_t port_status = pio_read_16(port);
port_status &= ~STATUS_WC_BITS;
port_status |= STATUS_IN_RESET;
pio_write_16(port, port_status);
fibril_usleep(50000);
port_status = pio_read_16(port);
port_status &= ~STATUS_IN_RESET;
pio_write_16(port, port_status);
while ((port_status = pio_read_16(port)) & STATUS_IN_RESET)
;
udelay(10);
port_status &= ~STATUS_WC_BITS;
pio_write_16(port, port_status | STATUS_ENABLED | STATUS_CONNECTED_CHANGED);
}
#define TEST_SIZE_INIT(size, port, hub) \
do { \
if (uint16_usb2host(setup_packet->length) != size) \
return ESTALL; \
port = uint16_usb2host(setup_packet->index) - 1; \
if (port != 0 && port != 1) \
return EINVAL; \
hub = virthub_get_data(device); \
assert(hub);\
} while (0)
#define RH_DEBUG(hub, port, msg, ...) \
do { \
if ((int)port >= 0) \
usb_log_debug("RH(%p-%d): " msg, hub, port, ##__VA_ARGS__); \
else \
usb_log_debug("RH(%p):" msg, hub, ##__VA_ARGS__); \
} while (0)
static errno_t req_get_port_state(usbvirt_device_t *device,
const usb_device_request_setup_packet_t *setup_packet,
uint8_t *data, size_t *act_size)
{
uhci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(1, port, hub);
if (setup_packet->value != 0)
return EINVAL;
const uint16_t value = pio_read_16(hub->ports[port]);
data[0] = ((value & STATUS_LINE_D_MINUS) ? 1 : 0) |
((value & STATUS_LINE_D_PLUS) ? 2 : 0);
RH_DEBUG(hub, port, "Bus state %" PRIx8 "(source %" PRIx16 ")",
data[0], value);
*act_size = 1;
return EOK;
}
#define BIT_VAL(val, bit) \
((val & bit) ? 1 : 0)
#define UHCI2USB(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)
{
uhci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(4, port, hub);
if (setup_packet->value != 0)
return EINVAL;
const uint16_t val = pio_read_16(hub->ports[port]);
const uint32_t status = uint32_host2usb(
UHCI2USB(val, STATUS_CONNECTED, USB_HUB_PORT_STATUS_CONNECTION) |
UHCI2USB(val, STATUS_ENABLED, USB_HUB_PORT_STATUS_ENABLE) |
UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_SUSPEND) |
UHCI2USB(val, STATUS_IN_RESET, USB_HUB_PORT_STATUS_RESET) |
UHCI2USB(val, STATUS_ALWAYS_ONE, USB2_HUB_PORT_STATUS_POWER) |
UHCI2USB(val, STATUS_LOW_SPEED, USB2_HUB_PORT_STATUS_LOW_SPEED) |
UHCI2USB(val, STATUS_CONNECTED_CHANGED, USB_HUB_PORT_STATUS_C_CONNECTION) |
UHCI2USB(val, STATUS_ENABLED_CHANGED, USB2_HUB_PORT_STATUS_C_ENABLE) |
#if 0
UHCI2USB(val, STATUS_SUSPEND, USB2_HUB_PORT_STATUS_C_SUSPEND) |
#endif
(hub->reset_changed[port] ? USB_HUB_PORT_STATUS_C_RESET : 0));
RH_DEBUG(hub, port, "Port status %" PRIx32 " (source %" PRIx16
"%s)", uint32_usb2host(status), val,
hub->reset_changed[port] ? "-reset" : "");
memcpy(data, &status, sizeof(status));
*act_size = sizeof(status);
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)
{
uhci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(0, port, hub);
const unsigned feature = uint16_usb2host(setup_packet->value);
const uint16_t status = pio_read_16(hub->ports[port]);
const uint16_t val = status & (~STATUS_WC_BITS);
switch (feature) {
case USB2_HUB_FEATURE_PORT_ENABLE:
RH_DEBUG(hub, port, "Clear port enable (status %"
PRIx16 ")", status);
pio_write_16(hub->ports[port], val & ~STATUS_ENABLED);
break;
case USB2_HUB_FEATURE_PORT_SUSPEND:
RH_DEBUG(hub, port, "Clear port suspend (status %"
PRIx16 ")", status);
pio_write_16(hub->ports[port], val & ~STATUS_SUSPEND);
usb_log_warning("Resume is not implemented on port %u", port);
break;
case USB_HUB_FEATURE_PORT_POWER:
RH_DEBUG(hub, port, "Clear port power (status %" PRIx16 ")",
status);
usb_log_warning("Tried to power off port %u", port);
break;
case USB_HUB_FEATURE_C_PORT_CONNECTION:
RH_DEBUG(hub, port, "Clear port conn change (status %"
PRIx16 ")", status);
pio_write_16(hub->ports[port], val | STATUS_CONNECTED_CHANGED);
break;
case USB_HUB_FEATURE_C_PORT_RESET:
RH_DEBUG(hub, port, "Clear port reset change (status %"
PRIx16 ")", status);
hub->reset_changed[port] = false;
break;
case USB2_HUB_FEATURE_C_PORT_ENABLE:
RH_DEBUG(hub, port, "Clear port enable change (status %"
PRIx16 ")", status);
pio_write_16(hub->ports[port], status | STATUS_ENABLED_CHANGED);
break;
case USB2_HUB_FEATURE_C_PORT_SUSPEND:
RH_DEBUG(hub, port, "Clear port suspend change (status %"
PRIx16 ")", status);
return ENOTSUP;
case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
RH_DEBUG(hub, port, "Clear port OC change (status %"
PRIx16 ")", status);
break;
default:
RH_DEBUG(hub, port, "Clear unknown feature %d (status %"
PRIx16 ")", feature, status);
usb_log_warning("Clearing feature %d is unsupported",
feature);
return ESTALL;
}
return EOK;
}
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)
{
uhci_rh_t *hub;
unsigned port;
TEST_SIZE_INIT(0, port, hub);
const unsigned feature = uint16_usb2host(setup_packet->value);
const uint16_t status = pio_read_16(hub->ports[port]);
switch (feature) {
case USB_HUB_FEATURE_PORT_RESET:
RH_DEBUG(hub, port, "Set port reset before (status %" PRIx16
")", status);
uhci_port_reset_enable(hub->ports[port]);
hub->reset_changed[port] = true;
RH_DEBUG(hub, port, "Set port reset after (status %" PRIx16
")", pio_read_16(hub->ports[port]));
break;
case USB2_HUB_FEATURE_PORT_SUSPEND:
RH_DEBUG(hub, port, "Set port suspend (status %" PRIx16
")", status);
pio_write_16(hub->ports[port],
(status & ~STATUS_WC_BITS) | STATUS_SUSPEND);
usb_log_warning("Suspend is not implemented on port %u", port);
break;
case USB_HUB_FEATURE_PORT_POWER:
RH_DEBUG(hub, port, "Set port power (status %" PRIx16
")", status);
usb_log_warning("Tried to power port %u", port);
break;
case USB_HUB_FEATURE_C_PORT_CONNECTION:
case USB2_HUB_FEATURE_C_PORT_ENABLE:
case USB2_HUB_FEATURE_C_PORT_SUSPEND:
case USB_HUB_FEATURE_C_PORT_OVER_CURRENT:
RH_DEBUG(hub, port, "Set port change flag (status %" PRIx16
")", status);
break;
default:
RH_DEBUG(hub, port, "Set unknown feature %d (status %" PRIx16
")", feature, status);
usb_log_warning("Setting feature %d is unsupported",
feature);
return ESTALL;
}
return EOK;
}
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)
{
uhci_rh_t *hub = virthub_get_data(device);
assert(hub);
if (buffer_size < 1)
return ESTALL;
const uint16_t status_a = pio_read_16(hub->ports[0]);
const uint16_t status_b = pio_read_16(hub->ports[1]);
const uint8_t status =
((((status_a & STATUS_CHANGE_BITS) != 0) || hub->reset_changed[0]) ?
0x2 : 0) |
((((status_b & STATUS_CHANGE_BITS) != 0) || hub->reset_changed[1]) ?
0x4 : 0);
if (status)
RH_DEBUG(hub, -1, "Event mask %" PRIx8
" (status_a %" PRIx16 "%s),"
" (status_b %" PRIx16 "%s)", status,
status_a, hub->reset_changed[0] ? "-reset" : "",
status_b, hub->reset_changed[1] ? "-reset" : "");
((uint8_t *)buffer)[0] = status;
*actual_size = 1;
return (status != 0 ? EOK : ENAK);
}
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_STATE),
.name = "GetBusState",
.callback = req_get_port_state,
},
{
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_nop,
},
{
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 = virthub_base_get_null_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