blob: f40783758d61370e4c834d5f841422230a6a52cb [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.
#pragma once
#include <bits/limits.h>
#include "trb-sizes.h"
#include "xhci-hw.h"
#include <ddk/io-buffer.h>
#include <fbl/intrusive_double_list.h>
#include <fbl/mutex.h>
#include <memory>
#include <zircon/listnode.h>
namespace usb_xhci {
class IoBufferContainer;
// IO Buffer Container to allow nesting an IoBuffer
// in an intrusive doubly-linked list.
class IoBufferContainer : public fbl::DoublyLinkedListable<std::unique_ptr<IoBufferContainer>> {
public:
IoBufferContainer(ddk::IoBuffer buffer) : buffer_(std::move(buffer)) {}
const ddk::IoBuffer* operator->() const { return &buffer_; }
const ddk::IoBuffer& operator*() { return buffer_; }
private:
ddk::IoBuffer buffer_;
};
// Represents a virtual memory address mapping.
// Contains information about the virtual range,
// and physical starting address for contiguous mappings.
struct VirtualAddress {
VirtualAddress() {}
VirtualAddress(zx_vaddr_t virt_start, size_t virt_end)
: virt_start(virt_start), virt_end(virt_end) {}
VirtualAddress(size_t virt_start)
: virt_start(virt_start), virt_end((virt_start + PAGE_SIZE) - 1) {}
bool operator<(const VirtualAddress& other) const {
return (virt_start / PAGE_SIZE) < (other.virt_start / PAGE_SIZE);
}
bool operator==(const VirtualAddress& other) const {
return (virt_start / PAGE_SIZE) == (other.virt_start / PAGE_SIZE);
}
size_t GetKey() const { return virt_start / PAGE_SIZE; }
zx_vaddr_t virt_start = 0;
zx_vaddr_t virt_end = 0;
size_t phys_start = 0;
};
// Constant-size map implementation with O(n) lookup time,
// and O(1) insertion time. Maintains a mapping of Keys to Values.
template <typename Key, typename Value, size_t Count> class XhciMap {
public:
XhciMap() {}
// Retrieves a given key, or creates one if it doesn't already exist
// Asserts if the number of keys exceeds the statically-allocated buffer
// size.
Value& operator[](const Key& key) {
for (size_t i = 0; i < len_; i++) {
if (data_[i].first == key) {
return data_[i].second;
}
}
assert(len_ < Count);
data_[len_].first = key;
return data_[len_++].second;
}
// Retrieves the std::pair<Key, Value> for a given Key.
// Returns nullptr if the key is not found in the map.
std::pair<Key, Value>* get(const Key& key) {
for (size_t i = 0; i < len_; i++) {
if (data_[i].first == key) {
return data_ + i;
}
}
return nullptr;
}
// Removes all entries from this map
void clear() {
for (size_t i = 0; i < len_; i++) {
data_[i] = {};
}
len_ = 0;
}
private:
size_t len_ = 0;
std::pair<Key, Value> data_[Count];
};
// used for both command ring and transfer rings
struct xhci_transfer_ring_t {
fbl::DoublyLinkedList<std::unique_ptr<IoBufferContainer>> buffers;
XhciMap<VirtualAddress, uint64_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> virt_to_phys_map;
// Map of physical page indices to virtual addresses
XhciMap<zx_paddr_t, zx_vaddr_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> phys_to_virt_map;
xhci_trb_t* start;
xhci_trb_t* current_trb; // next to be filled by producer
uint8_t pcs; // producer cycle status
xhci_trb_t* dequeue_ptr; // next to be processed by consumer
// (not used for command ring)
size_t size; // number of TRBs in ring
bool full; // true if there are no available TRBs,
// this is needed to differentiate between
// an empty and full ring state
fbl::Mutex xfer_lock;
};
struct EventMapping {
uint64_t phys = 0;
xhci_trb_t* next = nullptr;
EventMapping() {}
EventMapping(uint64_t phys) : phys(phys) {}
};
struct xhci_event_ring_t {
fbl::DoublyLinkedList<std::unique_ptr<IoBufferContainer>> buffers;
XhciMap<VirtualAddress, EventMapping, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> virt_to_phys_map;
XhciMap<zx_paddr_t, zx_vaddr_t, TRANSFER_RING_SIZE / sizeof(xhci_trb_t)> phys_to_virt_map;
xhci_trb_t* start;
xhci_trb_t* current;
xhci_trb_t* end;
uint8_t ccs; // consumer cycle status
fbl::Mutex xfer_lock;
};
zx_status_t xhci_transfer_ring_init(xhci_transfer_ring_t* tr, zx_handle_t bti_handle, int count);
void xhci_transfer_ring_free(xhci_transfer_ring_t* ring);
size_t xhci_transfer_ring_free_trbs(xhci_transfer_ring_t* ring);
zx_status_t xhci_event_ring_init(xhci_event_ring_t*, zx_handle_t bti_handle,
erst_entry_t* erst_array, int count);
void xhci_event_ring_free(xhci_event_ring_t* ring);
void xhci_clear_trb(xhci_trb_t* trb);
// Converts a transfer trb into a NO-OP transfer TRB, does nothing if it is the LINK TRB.
void xhci_set_transfer_noop_trb(xhci_trb_t* trb);
xhci_trb_t* xhci_read_trb_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* trb);
xhci_trb_t* xhci_next_evt(xhci_event_ring_t* ring, xhci_trb_t* trb);
xhci_trb_t* xhci_get_next_trb(xhci_transfer_ring_t* ring, xhci_trb_t* trb);
void xhci_increment_ring(xhci_transfer_ring_t* ring);
void xhci_set_dequeue_ptr(xhci_transfer_ring_t* ring, xhci_trb_t* new_ptr);
// Returns the TRB corresponding to the given physical address, or nullptr if the address is
// invalid.
xhci_trb_t* xhci_transfer_ring_phys_to_trb(xhci_transfer_ring_t* ring, zx_paddr_t phys);
static inline zx_paddr_t xhci_transfer_ring_current_phys(xhci_transfer_ring_t* ring) {
auto physmap =
ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(ring->current_trb)));
if (physmap == nullptr) {
abort();
}
return physmap->second +
(reinterpret_cast<size_t>(ring->current_trb) - physmap->first.virt_start);
}
static inline zx_paddr_t xhci_event_ring_current_phys(xhci_event_ring_t* ring) {
auto physmap =
ring->virt_to_phys_map.get(VirtualAddress(reinterpret_cast<size_t>(ring->current)));
if (physmap == nullptr) {
abort();
}
return physmap->second.phys +
(reinterpret_cast<size_t>(ring->current) - physmap->first.virt_start);
}
struct xhci_t;
// Enlarges the XHCI rings. The caller must ensure exclusive ownership of the rings
// before invoking this function. Refer to XHCI 4.9.2.3
zx_status_t xhci_enlarge_ring(xhci_t* xhci, xhci_transfer_ring_t* transfer);
} // namespace usb_xhci