blob: 8c5305037846f70a1581b270e6664618bf3f65e0 [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <assert.h>
#include <fbl/auto_lock.h>
#include <hw/arch_ops.h>
#include <bits/limits.h>
#include "xhci.h"
namespace usb_xhci {
zx_status_t xhci_transfer_ring_init(xhci_transfer_ring_t* ring, zx_handle_t bti_handle, int count) {
fbl::AllocChecker ac;
ddk::IoBuffer buffer;
zx_status_t status =
buffer.Init(bti_handle, count * sizeof(xhci_trb_t), IO_BUFFER_RW | XHCI_IO_BUFFER_UNCACHED);
if (status != ZX_OK)
return status;
status = buffer.PhysMap();
if (status != ZX_OK)
return status;
std::unique_ptr<IoBufferContainer> container(new (&ac) IoBufferContainer(std::move(buffer)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
size_t phys_count = (*container)->phys_count();
const zx_paddr_t* sg_list = (*container)->phys_list();
ring->start = static_cast<xhci_trb_t*>((*container)->virt());
ring->current_trb = ring->start;
ring->dequeue_ptr = ring->start;
ring->full = false;
ring->size = count - phys_count; // subtract 1 for LINK TRB at the end
ring->pcs = TRB_C;
// set link TRB at end to point back to the beginning
ring->start[count - 1].ptr = sg_list[0];
trb_set_control(&ring->start[count - 1], TRB_LINK, TRB_TC);
for (size_t i = 0; i < phys_count; i++) {
if (i + 1 < phys_count) {
xhci_trb_t* trb =
reinterpret_cast<xhci_trb_t*>(reinterpret_cast<size_t>(ring->start) +
(i * PAGE_SIZE) + (PAGE_SIZE - sizeof(xhci_trb_t)));
XHCI_WRITE64(&trb->ptr, sg_list[i + 1]);
XHCI_WRITE32(&trb->control, TRB_LINK << TRB_TYPE_START);
}
VirtualAddress address_mapping(reinterpret_cast<size_t>((*container)->virt()) +
(PAGE_SIZE * i));
address_mapping.phys_start = sg_list[i];
ring->virt_to_phys_map[address_mapping] = address_mapping.phys_start;
ring->phys_to_virt_map[address_mapping.phys_start / PAGE_SIZE] = address_mapping.virt_start;
}
ring->buffers.push_back(std::move(container));
return ZX_OK;
}
void xhci_transfer_ring_free(xhci_transfer_ring_t* ring) {
ring->buffers.clear();
ring->virt_to_phys_map.clear();
ring->phys_to_virt_map.clear();
}
// return the number of free TRBs in the ring
size_t xhci_transfer_ring_free_trbs(xhci_transfer_ring_t* ring) {
xhci_trb_t* current = ring->current_trb;
xhci_trb_t* dequeue_ptr = ring->dequeue_ptr;
if (ring->full) {
assert(current == dequeue_ptr);
return 0;
}
auto size = ring->size;
if (current < dequeue_ptr) {
current += size;
}
size_t busy_count = current - dequeue_ptr;
return size - busy_count;
}
zx_status_t xhci_event_ring_init(xhci_event_ring_t* ring, zx_handle_t bti_handle,
erst_entry_t* erst_array, int count) {
// allocate a read-only buffer for TRBs
fbl::AllocChecker ac;
ddk::IoBuffer buffer;
zx_status_t status =
buffer.Init(bti_handle, count * sizeof(xhci_trb_t), IO_BUFFER_RW | XHCI_IO_BUFFER_UNCACHED);
if (status != ZX_OK)
return status;
status = buffer.PhysMap();
if (status != ZX_OK)
return status;
size_t phys_count = buffer.phys_count();
const zx_paddr_t* sg_list = buffer.phys_list();
std::unique_ptr<IoBufferContainer> container(new (&ac) IoBufferContainer(std::move(buffer)));
if (!ac.check()) {
return ZX_ERR_NO_MEMORY;
}
ring->start = static_cast<xhci_trb_t*>((*container)->virt());
ring->current = ring->start;
ring->end = ring->start + count;
ring->ccs = TRB_C;
for (size_t i = 0; i < phys_count; i++) {
VirtualAddress address_mapping(reinterpret_cast<size_t>((*container)->virt()) +
(PAGE_SIZE * i));
address_mapping.phys_start = sg_list[i];
ring->virt_to_phys_map[address_mapping] = address_mapping.phys_start;
ring->phys_to_virt_map[address_mapping.phys_start / PAGE_SIZE] = address_mapping.virt_start;
XHCI_WRITE64(&erst_array[i].ptr, sg_list[i]);
XHCI_WRITE32(&erst_array[i].size, PAGE_SIZE / sizeof(xhci_trb_t));
}
ring->buffers.push_back(std::move(container));
return ZX_OK;
}
void xhci_event_ring_free(xhci_event_ring_t* ring) {
ring->buffers.clear();
ring->virt_to_phys_map.clear();
}
void xhci_clear_trb(xhci_trb_t* trb) {
XHCI_WRITE64(&trb->ptr, 0);
XHCI_WRITE32(&trb->status, 0);
XHCI_WRITE32(&trb->control, 0);
}
void xhci_set_transfer_noop_trb(xhci_trb_t* trb) {
uint32_t control = XHCI_READ32(&trb->control);
if ((control & TRB_TYPE_MASK) == (TRB_LINK << TRB_TYPE_START)) {
// Don't do anything if it's the LINK TRB.
return;
}
XHCI_WRITE64(&trb->ptr, 0);
XHCI_WRITE32(&trb->status, 0);
// Preserve the cycle bit of the TRB.
trb_set_control(trb, TRB_TRANSFER_NOOP, control & TRB_C);
}
xhci_trb_t* xhci_read_trb_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* trb) {
// convert physical address to virtual
uintptr_t ptr = trb->ptr;
return reinterpret_cast<xhci_trb_t*>(ring->phys_to_virt_map[ptr / PAGE_SIZE] +
(ptr % PAGE_SIZE));
}
xhci_trb_t* xhci_next_evt(xhci_event_ring_t* ring, xhci_trb_t* trb) {
return trb + 1;
}
xhci_trb_t* xhci_get_next_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb) {
trb++;
uint32_t control = XHCI_READ32(&trb->control);
if ((control & TRB_TYPE_MASK) == (TRB_LINK << TRB_TYPE_START)) {
trb = xhci_read_trb_ptr(ring, trb);
}
return trb;
}
void xhci_increment_ring(xhci_transfer_ring_t* ring) {
xhci_trb_t* trb = ring->current_trb;
uint32_t control = XHCI_READ32(&trb->control);
uint32_t chain = control & TRB_CHAIN;
if (ring->pcs) {
XHCI_WRITE32(&trb->control, control | ring->pcs);
}
trb = ++ring->current_trb;
// check for LINK TRB
control = XHCI_READ32(&trb->control);
if ((control & TRB_TYPE_MASK) == (TRB_LINK << TRB_TYPE_START)) {
control = (control & ~(TRB_CHAIN | TRB_C)) | chain | ring->pcs;
XHCI_WRITE32(&trb->control, control);
// toggle pcs if necessary
if (control & TRB_TC) {
ring->pcs ^= TRB_C;
}
ring->current_trb = xhci_read_trb_ptr(ring, trb);
}
if (ring->current_trb == ring->dequeue_ptr) {
// We've just enqueued something, so if the pointers are equal,
// the ring must be full.
ring->full = true;
}
}
void xhci_set_dequeue_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* new_ptr) {
ring->dequeue_ptr = new_ptr;
ring->full = false;
}
xhci_trb_t* xhci_transfer_ring_phys_to_trb(xhci_transfer_ring_t* ring, zx_paddr_t phys) {
return reinterpret_cast<xhci_trb_t*>(ring->phys_to_virt_map[phys / PAGE_SIZE] +
(phys % PAGE_SIZE));
}
} // namespace usb_xhci