HelenOS sources
This source file includes following definitions.
- hc_gen_irq_code
- hc_add
- hc_gone
- hc_enqueue_endpoint
- hc_dequeue_endpoint
- ehci_hc_status
- ehci_hc_schedule
- ehci_hc_interrupt
- hc_start
- hc_setup_roothub
- hc_init_memory
#include <assert.h>
#include <async.h>
#include <errno.h>
#include <macros.h>
#include <mem.h>
#include <stdlib.h>
#include <stdint.h>
#include <str_error.h>
#include <usb/debug.h>
#include <usb/usb.h>
#include <usb/host/utility.h>
#include "ehci_batch.h"
#include "hc.h"
#define EHCI_USED_INTERRUPTS \
(USB_INTR_IRQ_FLAG | USB_INTR_ERR_IRQ_FLAG | USB_INTR_PORT_CHANGE_FLAG | \
USB_INTR_ASYNC_ADVANCE_FLAG | USB_INTR_HOST_ERR_FLAG)
static const irq_pio_range_t ehci_pio_ranges[] = {
{
.base = 0,
.size = sizeof(ehci_regs_t)
}
};
static const irq_cmd_t ehci_irq_commands[] = {
{
.cmd = CMD_PIO_READ_32,
.dstarg = 1,
.addr = NULL
},
{
.cmd = CMD_AND,
.srcarg = 1,
.dstarg = 2,
.value = 0
},
{
.cmd = CMD_PREDICATE,
.srcarg = 2,
.value = 2
},
{
.cmd = CMD_PIO_WRITE_A_32,
.srcarg = 1,
.addr = NULL
},
{
.cmd = CMD_ACCEPT
}
};
static errno_t hc_init_memory(hc_t *instance);
errno_t hc_gen_irq_code(irq_code_t *code, hc_device_t *hcd, const hw_res_list_parsed_t *hw_res, int *irq)
{
assert(code);
assert(hw_res);
hc_t *instance = hcd_to_hc(hcd);
if (hw_res->irqs.count != 1 || hw_res->mem_ranges.count != 1)
return EINVAL;
addr_range_t regs = hw_res->mem_ranges.ranges[0];
if (RNGSZ(regs) < sizeof(ehci_regs_t))
return EOVERFLOW;
code->ranges = malloc(sizeof(ehci_pio_ranges));
if (code->ranges == NULL)
return ENOMEM;
code->cmds = malloc(sizeof(ehci_irq_commands));
if (code->cmds == NULL) {
free(code->ranges);
return ENOMEM;
}
code->rangecount = ARRAY_SIZE(ehci_pio_ranges);
code->cmdcount = ARRAY_SIZE(ehci_irq_commands);
memcpy(code->ranges, ehci_pio_ranges, sizeof(ehci_pio_ranges));
code->ranges[0].base = RNGABS(regs);
memcpy(code->cmds, ehci_irq_commands, sizeof(ehci_irq_commands));
ehci_regs_t *registers =
(ehci_regs_t *)(RNGABSPTR(regs) + EHCI_RD8(instance->caps->caplength));
code->cmds[0].addr = (void *) ®isters->usbsts;
code->cmds[3].addr = (void *) ®isters->usbsts;
EHCI_WR(code->cmds[1].value, EHCI_USED_INTERRUPTS);
usb_log_debug("Memory mapped regs at %p (size %zu), IRQ %d.",
RNGABSPTR(regs), RNGSZ(regs), hw_res->irqs.irqs[0]);
*irq = hw_res->irqs.irqs[0];
return EOK;
}
errno_t hc_add(hc_device_t *hcd, const hw_res_list_parsed_t *hw_res)
{
hc_t *instance = hcd_to_hc(hcd);
assert(hw_res);
if (hw_res->mem_ranges.count != 1 ||
hw_res->mem_ranges.ranges[0].size <
(sizeof(ehci_caps_regs_t) + sizeof(ehci_regs_t)))
return EINVAL;
errno_t ret = pio_enable_range(&hw_res->mem_ranges.ranges[0],
(void **)&instance->caps);
if (ret != EOK) {
usb_log_error("HC(%p): Failed to gain access to device "
"registers: %s.", instance, str_error(ret));
return ret;
}
usb_log_info("HC(%p): Device registers at %" PRIx64 " (%zuB) accessible.",
instance, hw_res->mem_ranges.ranges[0].address.absolute,
hw_res->mem_ranges.ranges[0].size);
instance->registers =
(void *)instance->caps + EHCI_RD8(instance->caps->caplength);
usb_log_info("HC(%p): Device control registers at %" PRIx64, instance,
hw_res->mem_ranges.ranges[0].address.absolute +
EHCI_RD8(instance->caps->caplength));
list_initialize(&instance->pending_endpoints);
fibril_mutex_initialize(&instance->guard);
fibril_condvar_initialize(&instance->async_doorbell);
ret = hc_init_memory(instance);
if (ret != EOK) {
usb_log_error("HC(%p): Failed to create EHCI memory structures:"
" %s.", instance, str_error(ret));
return ret;
}
usb_log_info("HC(%p): Initializing RH(%p).", instance, &instance->rh);
ehci_rh_init(
&instance->rh, instance->caps, instance->registers, &instance->guard,
"ehci rh");
ehci_bus_init(&instance->bus, instance);
hc_device_setup(hcd, (bus_t *) &instance->bus);
return EOK;
}
int hc_gone(hc_device_t *hcd)
{
hc_t *hc = hcd_to_hc(hcd);
endpoint_list_fini(&hc->async_list);
endpoint_list_fini(&hc->int_list);
dma_buffer_free(&hc->dma_buffer);
return EOK;
}
void hc_enqueue_endpoint(hc_t *instance, const endpoint_t *ep)
{
assert(instance);
assert(ep);
ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
usb_log_debug("HC(%p) enqueue EP(%d:%d:%s:%s)", instance,
ep->device->address, ep->endpoint,
usb_str_transfer_type_short(ep->transfer_type),
usb_str_direction(ep->direction));
switch (ep->transfer_type) {
case USB_TRANSFER_CONTROL:
case USB_TRANSFER_BULK:
endpoint_list_append_ep(&instance->async_list, ehci_ep);
break;
case USB_TRANSFER_INTERRUPT:
endpoint_list_append_ep(&instance->int_list, ehci_ep);
break;
case USB_TRANSFER_ISOCHRONOUS:
break;
}
}
void hc_dequeue_endpoint(hc_t *instance, const endpoint_t *ep)
{
assert(instance);
assert(ep);
ehci_endpoint_t *ehci_ep = ehci_endpoint_get(ep);
usb_log_debug("HC(%p) dequeue EP(%d:%d:%s:%s)", instance,
ep->device->address, ep->endpoint,
usb_str_transfer_type_short(ep->transfer_type),
usb_str_direction(ep->direction));
switch (ep->transfer_type) {
case USB_TRANSFER_INTERRUPT:
endpoint_list_remove_ep(&instance->int_list, ehci_ep);
case USB_TRANSFER_ISOCHRONOUS:
return;
case USB_TRANSFER_CONTROL:
case USB_TRANSFER_BULK:
endpoint_list_remove_ep(&instance->async_list, ehci_ep);
break;
}
fibril_mutex_lock(&instance->guard);
usb_log_debug("HC(%p): Waiting for doorbell", instance);
EHCI_SET(instance->registers->usbcmd, USB_CMD_IRQ_ASYNC_DOORBELL);
fibril_condvar_wait(&instance->async_doorbell, &instance->guard);
usb_log_debug2("HC(%p): Got doorbell", instance);
fibril_mutex_unlock(&instance->guard);
}
errno_t ehci_hc_status(bus_t *bus_base, uint32_t *status)
{
assert(bus_base);
assert(status);
ehci_bus_t *bus = (ehci_bus_t *) bus_base;
hc_t *hc = bus->hc;
assert(hc);
*status = 0;
if (hc->registers) {
*status = EHCI_RD(hc->registers->usbsts);
EHCI_WR(hc->registers->usbsts, *status);
}
usb_log_debug2("HC(%p): Read status: %x", hc, *status);
return EOK;
}
errno_t ehci_hc_schedule(usb_transfer_batch_t *batch)
{
assert(batch);
ehci_bus_t *bus = (ehci_bus_t *) endpoint_get_bus(batch->ep);
hc_t *hc = bus->hc;
assert(hc);
if (batch->target.address == ehci_rh_get_address(&hc->rh)) {
usb_log_debug("HC(%p): Scheduling BATCH(%p) for RH(%p)",
hc, batch, &hc->rh);
return ehci_rh_schedule(&hc->rh, batch);
}
endpoint_t *const ep = batch->ep;
ehci_endpoint_t *const ehci_ep = ehci_endpoint_get(ep);
ehci_transfer_batch_t *ehci_batch = ehci_transfer_batch_get(batch);
int err;
if ((err = ehci_transfer_batch_prepare(ehci_batch)))
return err;
fibril_mutex_lock(&hc->guard);
if ((err = endpoint_activate_locked(ep, batch))) {
fibril_mutex_unlock(&hc->guard);
return err;
}
usb_log_debug("HC(%p): Committing BATCH(%p)", hc, batch);
ehci_transfer_batch_commit(ehci_batch);
usb_log_debug2("HC(%p): Appending BATCH(%p)", hc, batch);
list_append(&ehci_ep->pending_link, &hc->pending_endpoints);
fibril_mutex_unlock(&hc->guard);
return EOK;
}
void ehci_hc_interrupt(bus_t *bus_base, uint32_t status)
{
assert(bus_base);
ehci_bus_t *bus = (ehci_bus_t *) bus_base;
hc_t *hc = bus->hc;
assert(hc);
usb_log_debug2("HC(%p): Interrupt: %" PRIx32, hc, status);
if (status & USB_STS_PORT_CHANGE_FLAG) {
ehci_rh_interrupt(&hc->rh);
}
if (status & USB_STS_IRQ_ASYNC_ADVANCE_FLAG) {
fibril_mutex_lock(&hc->guard);
usb_log_debug2("HC(%p): Signaling doorbell", hc);
fibril_condvar_broadcast(&hc->async_doorbell);
fibril_mutex_unlock(&hc->guard);
}
if (status & (USB_STS_IRQ_FLAG | USB_STS_ERR_IRQ_FLAG)) {
fibril_mutex_lock(&hc->guard);
usb_log_debug2("HC(%p): Scanning %zu pending endpoints", hc,
list_count(&hc->pending_endpoints));
list_foreach_safe(hc->pending_endpoints, current, next) {
ehci_endpoint_t *ep =
list_get_instance(current, ehci_endpoint_t, pending_link);
ehci_transfer_batch_t *batch =
ehci_transfer_batch_get(ep->base.active_batch);
assert(batch);
if (ehci_transfer_batch_check_completed(batch)) {
endpoint_deactivate_locked(&ep->base);
list_remove(current);
hc_reset_toggles(&batch->base, &ehci_ep_toggle_reset);
usb_transfer_batch_finish(&batch->base);
}
}
fibril_mutex_unlock(&hc->guard);
}
if (status & USB_STS_HOST_ERROR_FLAG) {
usb_log_fatal("HCD(%p): HOST SYSTEM ERROR!", hc);
}
}
int hc_start(hc_device_t *hcd)
{
hc_t *instance = hcd_to_hc(hcd);
usb_log_debug("HC(%p): Starting HW.", instance);
if (!(EHCI_RD(instance->registers->usbsts) & USB_STS_HC_HALTED_FLAG)) {
EHCI_WR(instance->registers->usbintr, 0);
EHCI_WR(instance->registers->usbsts, 0x3f);
EHCI_WR(instance->registers->usbcmd, 0);
while ((EHCI_RD(instance->registers->usbsts) & USB_STS_HC_HALTED_FLAG) == 0) {
fibril_usleep(1);
}
usb_log_info("HC(%p): EHCI turned off.", instance);
} else {
usb_log_info("HC(%p): EHCI was not running.", instance);
}
EHCI_SET(instance->registers->usbcmd, USB_CMD_HC_RESET_FLAG);
usb_log_info("HC(%p): Waiting for HW reset.", instance);
while (EHCI_RD(instance->registers->usbcmd) & USB_CMD_HC_RESET_FLAG) {
fibril_usleep(1);
}
usb_log_debug("HC(%p): HW reset OK.", instance);
EHCI_WR(instance->registers->ctrldssegment, 0);
assert(instance->periodic_list);
uintptr_t phys_base =
addr_to_phys((void *)instance->periodic_list);
assert((phys_base & USB_PERIODIC_LIST_BASE_MASK) == phys_base);
EHCI_WR(instance->registers->periodiclistbase, phys_base);
EHCI_SET(instance->registers->usbcmd, USB_CMD_PERIODIC_SCHEDULE_FLAG);
usb_log_debug("HC(%p): Enabled periodic list.", instance);
phys_base = addr_to_phys((void *)instance->async_list.list_head);
assert((phys_base & USB_ASYNCLIST_MASK) == phys_base);
EHCI_WR(instance->registers->asynclistaddr, phys_base);
EHCI_SET(instance->registers->usbcmd, USB_CMD_ASYNC_SCHEDULE_FLAG);
usb_log_debug("HC(%p): Enabled async list.", instance);
EHCI_SET(instance->registers->usbcmd, USB_CMD_RUN_FLAG);
EHCI_SET(instance->registers->configflag, USB_CONFIG_FLAG_FLAG);
usb_log_debug("HC(%p): HW started.", instance);
usb_log_debug2("HC(%p): Registers: "
"\tUSBCMD(%p): %x(0x00080000 = at least 1ms between interrupts)"
"\tUSBSTS(%p): %x(0x00001000 = HC halted)"
"\tUSBINT(%p): %x(0x0 = no interrupts)."
"\tCONFIG(%p): %x(0x0 = ports controlled by companion hc).",
instance,
&instance->registers->usbcmd, EHCI_RD(instance->registers->usbcmd),
&instance->registers->usbsts, EHCI_RD(instance->registers->usbsts),
&instance->registers->usbintr, EHCI_RD(instance->registers->usbintr),
&instance->registers->configflag, EHCI_RD(instance->registers->configflag));
EHCI_WR(instance->registers->usbsts, EHCI_RD(instance->registers->usbsts));
EHCI_WR(instance->registers->usbintr, EHCI_USED_INTERRUPTS);
return EOK;
}
int hc_setup_roothub(hc_device_t *hcd)
{
return hc_setup_virtual_root_hub(hcd, USB_SPEED_HIGH);
}
errno_t hc_init_memory(hc_t *instance)
{
assert(instance);
usb_log_debug2("HC(%p): Initializing Async list(%p).", instance,
&instance->async_list);
errno_t ret = endpoint_list_init(&instance->async_list, "ASYNC");
if (ret != EOK) {
usb_log_error("HC(%p): Failed to setup ASYNC list: %s",
instance, str_error(ret));
return ret;
}
endpoint_list_chain(&instance->async_list, &instance->async_list);
usb_log_debug2("HC(%p): Initializing Interrupt list (%p).", instance,
&instance->int_list);
ret = endpoint_list_init(&instance->int_list, "INT");
if (ret != EOK) {
usb_log_error("HC(%p): Failed to setup INT list: %s",
instance, str_error(ret));
endpoint_list_fini(&instance->async_list);
return ret;
}
if (dma_buffer_alloc(&instance->dma_buffer, PAGE_SIZE)) {
usb_log_error("HC(%p): Failed to get ISO schedule page.",
instance);
endpoint_list_fini(&instance->async_list);
endpoint_list_fini(&instance->int_list);
return ENOMEM;
}
instance->periodic_list = instance->dma_buffer.virt;
usb_log_debug2("HC(%p): Initializing Periodic list.", instance);
for (unsigned i = 0; i < PAGE_SIZE / sizeof(link_pointer_t); ++i) {
instance->periodic_list[i] =
LINK_POINTER_QH(addr_to_phys(instance->int_list.list_head));
}
return EOK;
}
HelenOS homepage, sources at GitHub