mirror of
https://github.com/RGBCube/serenity
synced 2025-05-31 15:48:12 +00:00
Kernel/USB: Move the USB components as a subfolder to the Bus directory
This commit is contained in:
parent
6568bb47cb
commit
5073bf8e75
15 changed files with 26 additions and 26 deletions
39
Kernel/Bus/USB/PacketTypes.h
Normal file
39
Kernel/Bus/USB/PacketTypes.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
// Setup descriptor bit definitions
|
||||
static constexpr u8 BM_REQUEST_HOST_TO_DEVICE = (0 << 7);
|
||||
static constexpr u8 BM_REQUEST_DEVICE_TO_HOST = (1 << 7);
|
||||
static constexpr u8 BM_REQUEST_TYPE_STANDARD = (0 << 5);
|
||||
static constexpr u8 BM_REQUEST_TYPE_CLASS = (1 << 5);
|
||||
static constexpr u8 BM_REQUEST_TYPE_VENDOR = (2 << 5);
|
||||
static constexpr u8 BM_REQUEST_TYPE_RESERVED = (3 << 5);
|
||||
static constexpr u8 BM_REQUEST_RECIPEINT_DEVICE = (0 << 0);
|
||||
static constexpr u8 BM_REQUEST_RECIPIENT_INTERFACE = (1 << 0);
|
||||
static constexpr u8 BM_REQUEST_RECIPIENT_ENDPOINT = (2 << 0);
|
||||
static constexpr u8 BM_REQUEST_RECIPIENT_OTHER = (3 << 0);
|
||||
|
||||
//
|
||||
// This is also known as the "setup" packet. It's attached to the
|
||||
// first TD in the chain and is the first piece of data sent to the
|
||||
// USB device over the bus.
|
||||
// https://beyondlogic.org/usbnutshell/usb6.shtml#StandardEndpointRequests
|
||||
//
|
||||
struct USBRequestData {
|
||||
u8 request_type;
|
||||
u8 request;
|
||||
u16 value;
|
||||
u16 index;
|
||||
u16 length;
|
||||
};
|
||||
|
||||
}
|
791
Kernel/Bus/USB/UHCIController.cpp
Normal file
791
Kernel/Bus/USB/UHCIController.cpp
Normal file
|
@ -0,0 +1,791 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/JsonArraySerializer.h>
|
||||
#include <AK/JsonObjectSerializer.h>
|
||||
#include <AK/Platform.h>
|
||||
#include <Kernel/Bus/USB/UHCIController.h>
|
||||
#include <Kernel/Bus/USB/USBRequest.h>
|
||||
#include <Kernel/CommandLine.h>
|
||||
#include <Kernel/Debug.h>
|
||||
#include <Kernel/KBufferBuilder.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/ProcessExposed.h>
|
||||
#include <Kernel/Sections.h>
|
||||
#include <Kernel/StdLib.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
#include <Kernel/VM/AnonymousVMObject.h>
|
||||
#include <Kernel/VM/MemoryManager.h>
|
||||
|
||||
static constexpr u8 MAXIMUM_NUMBER_OF_TDS = 128; // Upper pool limit. This consumes the second page we have allocated
|
||||
static constexpr u8 MAXIMUM_NUMBER_OF_QHS = 64;
|
||||
static constexpr u8 RETRY_COUNTER_RELOAD = 3;
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
static UHCIController* s_the;
|
||||
|
||||
static constexpr u16 UHCI_USBCMD_RUN = 0x0001;
|
||||
static constexpr u16 UHCI_USBCMD_HOST_CONTROLLER_RESET = 0x0002;
|
||||
static constexpr u16 UHCI_USBCMD_GLOBAL_RESET = 0x0004;
|
||||
static constexpr u16 UHCI_USBCMD_ENTER_GLOBAL_SUSPEND_MODE = 0x0008;
|
||||
static constexpr u16 UHCI_USBCMD_FORCE_GLOBAL_RESUME = 0x0010;
|
||||
static constexpr u16 UHCI_USBCMD_SOFTWARE_DEBUG = 0x0020;
|
||||
static constexpr u16 UHCI_USBCMD_CONFIGURE_FLAG = 0x0040;
|
||||
static constexpr u16 UHCI_USBCMD_MAX_PACKET = 0x0080;
|
||||
|
||||
static constexpr u16 UHCI_USBSTS_HOST_CONTROLLER_HALTED = 0x0020;
|
||||
static constexpr u16 UHCI_USBSTS_HOST_CONTROLLER_PROCESS_ERROR = 0x0010;
|
||||
static constexpr u16 UHCI_USBSTS_PCI_BUS_ERROR = 0x0008;
|
||||
static constexpr u16 UHCI_USBSTS_RESUME_RECEIVED = 0x0004;
|
||||
static constexpr u16 UHCI_USBSTS_USB_ERROR_INTERRUPT = 0x0002;
|
||||
static constexpr u16 UHCI_USBSTS_USB_INTERRUPT = 0x0001;
|
||||
|
||||
static constexpr u8 UHCI_USBINTR_TIMEOUT_CRC_ENABLE = 0x01;
|
||||
static constexpr u8 UHCI_USBINTR_RESUME_INTR_ENABLE = 0x02;
|
||||
static constexpr u8 UHCI_USBINTR_IOC_ENABLE = 0x04;
|
||||
static constexpr u8 UHCI_USBINTR_SHORT_PACKET_INTR_ENABLE = 0x08;
|
||||
|
||||
static constexpr u16 UHCI_FRAMELIST_FRAME_COUNT = 1024; // Each entry is 4 bytes in our allocated page
|
||||
static constexpr u16 UHCI_FRAMELIST_FRAME_INVALID = 0x0001;
|
||||
|
||||
// Port stuff
|
||||
static constexpr u8 UHCI_ROOT_PORT_COUNT = 2;
|
||||
static constexpr u16 UHCI_PORTSC_CURRRENT_CONNECT_STATUS = 0x0001;
|
||||
static constexpr u16 UHCI_PORTSC_CONNECT_STATUS_CHANGED = 0x0002;
|
||||
static constexpr u16 UHCI_PORTSC_PORT_ENABLED = 0x0004;
|
||||
static constexpr u16 UHCI_PORTSC_PORT_ENABLE_CHANGED = 0x0008;
|
||||
static constexpr u16 UHCI_PORTSC_LINE_STATUS = 0x0030;
|
||||
static constexpr u16 UHCI_PORTSC_RESUME_DETECT = 0x40;
|
||||
static constexpr u16 UHCI_PORTSC_LOW_SPEED_DEVICE = 0x0100;
|
||||
static constexpr u16 UHCI_PORTSC_PORT_RESET = 0x0200;
|
||||
static constexpr u16 UHCI_PORTSC_SUSPEND = 0x1000;
|
||||
|
||||
// *BSD and a few other drivers seem to use this number
|
||||
static constexpr u8 UHCI_NUMBER_OF_ISOCHRONOUS_TDS = 128;
|
||||
static constexpr u16 UHCI_NUMBER_OF_FRAMES = 1024;
|
||||
|
||||
class ProcFSUSBBusFolder;
|
||||
static ProcFSUSBBusFolder* s_procfs_usb_bus_folder;
|
||||
|
||||
class ProcFSUSBDeviceInformation : public ProcFSGlobalInformation {
|
||||
friend class ProcFSUSBBusFolder;
|
||||
|
||||
public:
|
||||
virtual ~ProcFSUSBDeviceInformation() override {};
|
||||
|
||||
static NonnullRefPtr<ProcFSUSBDeviceInformation> create(USB::Device&);
|
||||
|
||||
RefPtr<USB::Device> device() const { return m_device; }
|
||||
|
||||
protected:
|
||||
explicit ProcFSUSBDeviceInformation(USB::Device& device)
|
||||
: ProcFSGlobalInformation(String::formatted("{}", device.address()))
|
||||
, m_device(device)
|
||||
{
|
||||
}
|
||||
virtual bool output(KBufferBuilder& builder) override
|
||||
{
|
||||
VERIFY(m_device); // Something has gone very wrong if this isn't true
|
||||
|
||||
JsonArraySerializer array { builder };
|
||||
|
||||
auto obj = array.add_object();
|
||||
obj.add("usb_spec_compliance_bcd", m_device->device_descriptor().usb_spec_compliance_bcd);
|
||||
obj.add("device_class", m_device->device_descriptor().device_class);
|
||||
obj.add("device_sub_class", m_device->device_descriptor().device_sub_class);
|
||||
obj.add("device_protocol", m_device->device_descriptor().device_protocol);
|
||||
obj.add("max_packet_size", m_device->device_descriptor().max_packet_size);
|
||||
obj.add("vendor_id", m_device->device_descriptor().vendor_id);
|
||||
obj.add("product_id", m_device->device_descriptor().product_id);
|
||||
obj.add("device_release_bcd", m_device->device_descriptor().device_release_bcd);
|
||||
obj.add("manufacturer_id_descriptor_index", m_device->device_descriptor().manufacturer_id_descriptor_index);
|
||||
obj.add("product_string_descriptor_index", m_device->device_descriptor().product_string_descriptor_index);
|
||||
obj.add("serial_number_descriptor_index", m_device->device_descriptor().serial_number_descriptor_index);
|
||||
obj.add("num_configurations", m_device->device_descriptor().num_configurations);
|
||||
obj.finish();
|
||||
array.finish();
|
||||
return true;
|
||||
}
|
||||
IntrusiveListNode<ProcFSUSBDeviceInformation, RefPtr<ProcFSUSBDeviceInformation>> m_list_node;
|
||||
RefPtr<USB::Device> m_device;
|
||||
};
|
||||
|
||||
class ProcFSUSBBusFolder final : public ProcFSExposedFolder {
|
||||
friend class ProcFSComponentsRegistrar;
|
||||
|
||||
public:
|
||||
static void initialize();
|
||||
void plug(USB::Device&);
|
||||
void unplug(USB::Device&);
|
||||
|
||||
virtual KResultOr<size_t> entries_count() const override;
|
||||
virtual KResult traverse_as_directory(unsigned, Function<bool(const FS::DirectoryEntryView&)>) const override;
|
||||
virtual RefPtr<ProcFSExposedComponent> lookup(StringView name) override;
|
||||
|
||||
private:
|
||||
ProcFSUSBBusFolder(const ProcFSBusDirectory&);
|
||||
|
||||
RefPtr<ProcFSUSBDeviceInformation> device_node_for(USB::Device& device);
|
||||
|
||||
IntrusiveList<ProcFSUSBDeviceInformation, RefPtr<ProcFSUSBDeviceInformation>, &ProcFSUSBDeviceInformation::m_list_node> m_device_nodes;
|
||||
mutable SpinLock<u8> m_lock;
|
||||
};
|
||||
|
||||
KResultOr<size_t> ProcFSUSBBusFolder::entries_count() const
|
||||
{
|
||||
ScopedSpinLock lock(m_lock);
|
||||
return m_device_nodes.size_slow();
|
||||
}
|
||||
KResult ProcFSUSBBusFolder::traverse_as_directory(unsigned fsid, Function<bool(const FS::DirectoryEntryView&)> callback) const
|
||||
{
|
||||
ScopedSpinLock lock(m_lock);
|
||||
VERIFY(m_parent_folder);
|
||||
callback({ ".", { fsid, component_index() }, 0 });
|
||||
callback({ "..", { fsid, m_parent_folder->component_index() }, 0 });
|
||||
|
||||
for (auto& device_node : m_device_nodes) {
|
||||
InodeIdentifier identifier = { fsid, device_node.component_index() };
|
||||
callback({ device_node.name(), identifier, 0 });
|
||||
}
|
||||
return KSuccess;
|
||||
}
|
||||
RefPtr<ProcFSExposedComponent> ProcFSUSBBusFolder::lookup(StringView name)
|
||||
{
|
||||
ScopedSpinLock lock(m_lock);
|
||||
for (auto& device_node : m_device_nodes) {
|
||||
if (device_node.name() == name) {
|
||||
return device_node;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<ProcFSUSBDeviceInformation> ProcFSUSBBusFolder::device_node_for(USB::Device& device)
|
||||
{
|
||||
RefPtr<USB::Device> checked_device = device;
|
||||
for (auto& device_node : m_device_nodes) {
|
||||
if (device_node.device().ptr() == checked_device.ptr())
|
||||
return device_node;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
void ProcFSUSBBusFolder::plug(USB::Device& new_device)
|
||||
{
|
||||
ScopedSpinLock lock(m_lock);
|
||||
auto device_node = device_node_for(new_device);
|
||||
VERIFY(!device_node);
|
||||
m_device_nodes.append(ProcFSUSBDeviceInformation::create(new_device));
|
||||
}
|
||||
void ProcFSUSBBusFolder::unplug(USB::Device& deleted_device)
|
||||
{
|
||||
ScopedSpinLock lock(m_lock);
|
||||
auto device_node = device_node_for(deleted_device);
|
||||
VERIFY(device_node);
|
||||
device_node->m_list_node.remove();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT ProcFSUSBBusFolder::ProcFSUSBBusFolder(const ProcFSBusDirectory& buses_folder)
|
||||
: ProcFSExposedFolder("usb"sv, buses_folder)
|
||||
{
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT void ProcFSUSBBusFolder::initialize()
|
||||
{
|
||||
auto folder = adopt_ref(*new ProcFSUSBBusFolder(ProcFSComponentsRegistrar::the().buses_folder()));
|
||||
ProcFSComponentsRegistrar::the().register_new_bus_folder(folder);
|
||||
s_procfs_usb_bus_folder = folder;
|
||||
}
|
||||
|
||||
NonnullRefPtr<ProcFSUSBDeviceInformation> ProcFSUSBDeviceInformation::create(USB::Device& device)
|
||||
{
|
||||
return adopt_ref(*new ProcFSUSBDeviceInformation(device));
|
||||
}
|
||||
|
||||
UHCIController& UHCIController::the()
|
||||
{
|
||||
return *s_the;
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT void UHCIController::detect()
|
||||
{
|
||||
if (kernel_command_line().disable_uhci_controller())
|
||||
return;
|
||||
|
||||
// FIXME: We create the /proc/bus/usb representation here, but it should really be handled
|
||||
// in a more broad singleton than this once we refactor things in USB subsystem.
|
||||
ProcFSUSBBusFolder::initialize();
|
||||
|
||||
PCI::enumerate([&](const PCI::Address& address, PCI::ID id) {
|
||||
if (address.is_null())
|
||||
return;
|
||||
|
||||
if (PCI::get_class(address) == 0xc && PCI::get_subclass(address) == 0x03 && PCI::get_programming_interface(address) == 0) {
|
||||
if (!s_the) {
|
||||
s_the = new UHCIController(address, id);
|
||||
s_the->spawn_port_proc();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT UHCIController::UHCIController(PCI::Address address, PCI::ID id)
|
||||
: PCI::Device(address)
|
||||
, m_io_base(PCI::get_BAR4(pci_address()) & ~1)
|
||||
{
|
||||
dmesgln("UHCI: Controller found {} @ {}", id, address);
|
||||
dmesgln("UHCI: I/O base {}", m_io_base);
|
||||
dmesgln("UHCI: Interrupt line: {}", PCI::get_interrupt_line(pci_address()));
|
||||
|
||||
reset();
|
||||
start();
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT UHCIController::~UHCIController()
|
||||
{
|
||||
}
|
||||
|
||||
RefPtr<USB::Device> const UHCIController::get_device_at_port(USB::Device::PortNumber port)
|
||||
{
|
||||
if (!m_devices.at(to_underlying(port)))
|
||||
return nullptr;
|
||||
|
||||
return m_devices.at(to_underlying(port));
|
||||
}
|
||||
|
||||
RefPtr<USB::Device> const UHCIController::get_device_from_address(u8 device_address)
|
||||
{
|
||||
for (auto const& device : m_devices) {
|
||||
if (!device)
|
||||
continue;
|
||||
|
||||
if (device->address() == device_address)
|
||||
return device;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void UHCIController::reset()
|
||||
{
|
||||
stop();
|
||||
|
||||
write_usbcmd(UHCI_USBCMD_HOST_CONTROLLER_RESET);
|
||||
|
||||
// FIXME: Timeout
|
||||
for (;;) {
|
||||
if (read_usbcmd() & UHCI_USBCMD_HOST_CONTROLLER_RESET)
|
||||
continue;
|
||||
break;
|
||||
}
|
||||
|
||||
// Let's allocate the physical page for the Frame List (which is 4KiB aligned)
|
||||
auto framelist_vmobj = ContiguousVMObject::create_with_size(PAGE_SIZE);
|
||||
m_framelist = MemoryManager::the().allocate_kernel_region_with_vmobject(*framelist_vmobj, PAGE_SIZE, "UHCI Framelist", Region::Access::Write);
|
||||
dbgln("UHCI: Allocated framelist at physical address {}", m_framelist->physical_page(0)->paddr());
|
||||
dbgln("UHCI: Framelist is at virtual address {}", m_framelist->vaddr());
|
||||
write_sofmod(64); // 1mS frame time
|
||||
|
||||
create_structures();
|
||||
setup_schedule();
|
||||
|
||||
write_flbaseadd(m_framelist->physical_page(0)->paddr().get()); // Frame list (physical) address
|
||||
write_frnum(0); // Set the initial frame number
|
||||
|
||||
// FIXME: Work out why interrupts lock up the entire system....
|
||||
// Disable UHCI Controller from raising an IRQ
|
||||
write_usbintr(0);
|
||||
dbgln("UHCI: Reset completed");
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT void UHCIController::create_structures()
|
||||
{
|
||||
// Let's allocate memory for both the QH and TD pools
|
||||
// First the QH pool and all of the Interrupt QH's
|
||||
auto qh_pool_vmobject = ContiguousVMObject::create_with_size(2 * PAGE_SIZE);
|
||||
m_qh_pool = MemoryManager::the().allocate_kernel_region_with_vmobject(*qh_pool_vmobject, 2 * PAGE_SIZE, "UHCI Queue Head Pool", Region::Access::Write);
|
||||
memset(m_qh_pool->vaddr().as_ptr(), 0, 2 * PAGE_SIZE); // Zero out both pages
|
||||
|
||||
// Let's populate our free qh list (so we have some we can allocate later on)
|
||||
m_free_qh_pool.resize(MAXIMUM_NUMBER_OF_TDS);
|
||||
for (size_t i = 0; i < m_free_qh_pool.size(); i++) {
|
||||
auto placement_addr = reinterpret_cast<void*>(m_qh_pool->vaddr().get() + (i * sizeof(QueueHead)));
|
||||
auto paddr = static_cast<u32>(m_qh_pool->physical_page(0)->paddr().get() + (i * sizeof(QueueHead)));
|
||||
m_free_qh_pool.at(i) = new (placement_addr) QueueHead(paddr);
|
||||
}
|
||||
|
||||
// Create the Full Speed, Low Speed Control and Bulk Queue Heads
|
||||
m_interrupt_transfer_queue = allocate_queue_head();
|
||||
m_lowspeed_control_qh = allocate_queue_head();
|
||||
m_fullspeed_control_qh = allocate_queue_head();
|
||||
m_bulk_qh = allocate_queue_head();
|
||||
m_dummy_qh = allocate_queue_head();
|
||||
|
||||
// Now the Transfer Descriptor pool
|
||||
auto td_pool_vmobject = ContiguousVMObject::create_with_size(2 * PAGE_SIZE);
|
||||
m_td_pool = MemoryManager::the().allocate_kernel_region_with_vmobject(*td_pool_vmobject, 2 * PAGE_SIZE, "UHCI Transfer Descriptor Pool", Region::Access::Write);
|
||||
memset(m_td_pool->vaddr().as_ptr(), 0, 2 * PAGE_SIZE);
|
||||
|
||||
// Set up the Isochronous Transfer Descriptor list
|
||||
m_iso_td_list.resize(UHCI_NUMBER_OF_ISOCHRONOUS_TDS);
|
||||
for (size_t i = 0; i < m_iso_td_list.size(); i++) {
|
||||
auto placement_addr = reinterpret_cast<void*>(m_td_pool->vaddr().get() + (i * sizeof(Kernel::USB::TransferDescriptor)));
|
||||
auto paddr = static_cast<u32>(m_td_pool->physical_page(0)->paddr().get() + (i * sizeof(Kernel::USB::TransferDescriptor)));
|
||||
|
||||
// Place a new Transfer Descriptor with a 1:1 in our region
|
||||
// The pointer returned by `new()` lines up exactly with the value
|
||||
// that we store in `paddr`, meaning our member functions directly
|
||||
// access the raw descriptor (that we later send to the controller)
|
||||
m_iso_td_list.at(i) = new (placement_addr) Kernel::USB::TransferDescriptor(paddr);
|
||||
auto transfer_descriptor = m_iso_td_list.at(i);
|
||||
transfer_descriptor->set_in_use(true); // Isochronous transfers are ALWAYS marked as in use (in case we somehow get allocated one...)
|
||||
transfer_descriptor->set_isochronous();
|
||||
transfer_descriptor->link_queue_head(m_interrupt_transfer_queue->paddr());
|
||||
|
||||
if constexpr (UHCI_VERBOSE_DEBUG)
|
||||
transfer_descriptor->print();
|
||||
}
|
||||
|
||||
m_free_td_pool.resize(MAXIMUM_NUMBER_OF_TDS);
|
||||
for (size_t i = 0; i < m_free_td_pool.size(); i++) {
|
||||
auto placement_addr = reinterpret_cast<void*>(m_td_pool->vaddr().offset(PAGE_SIZE).get() + (i * sizeof(Kernel::USB::TransferDescriptor)));
|
||||
auto paddr = static_cast<u32>(m_td_pool->physical_page(1)->paddr().get() + (i * sizeof(Kernel::USB::TransferDescriptor)));
|
||||
|
||||
// Place a new Transfer Descriptor with a 1:1 in our region
|
||||
// The pointer returned by `new()` lines up exactly with the value
|
||||
// that we store in `paddr`, meaning our member functions directly
|
||||
// access the raw descriptor (that we later send to the controller)
|
||||
m_free_td_pool.at(i) = new (placement_addr) Kernel::USB::TransferDescriptor(paddr);
|
||||
|
||||
if constexpr (UHCI_VERBOSE_DEBUG) {
|
||||
auto transfer_descriptor = m_free_td_pool.at(i);
|
||||
transfer_descriptor->print();
|
||||
}
|
||||
}
|
||||
|
||||
if constexpr (UHCI_DEBUG) {
|
||||
dbgln("UHCI: Pool information:");
|
||||
dbgln(" qh_pool: {}, length: {}", PhysicalAddress(m_qh_pool->physical_page(0)->paddr()), m_qh_pool->range().size());
|
||||
dbgln(" td_pool: {}, length: {}", PhysicalAddress(m_td_pool->physical_page(0)->paddr()), m_td_pool->range().size());
|
||||
}
|
||||
}
|
||||
|
||||
UNMAP_AFTER_INIT void UHCIController::setup_schedule()
|
||||
{
|
||||
//
|
||||
// https://github.com/alkber/minix3-usbsubsystem/blob/master/usb/uhci-hcd.c
|
||||
//
|
||||
// This lad probably has the best explanation as to how this is actually done. I'll try and
|
||||
// explain it here to so that there's no need for anyone to go hunting for this shit again, because
|
||||
// the USB spec and Intel explain next to nothing.
|
||||
// According to the USB spec (and the UHCI datasheet), 90% of the bandwidth should be used for
|
||||
// Isochronous and """Interrupt""" related transfers, with the rest being used for control and bulk
|
||||
// transfers.
|
||||
// That is, most of the time, the schedule is going to be executing either an Isochronous transfer
|
||||
// in our framelist, or an Interrupt transfer. The allocation in `create_structures` reflects this.
|
||||
//
|
||||
// Each frame has it's own Isochronous transfer Transfer Descriptor(s) that point to each other
|
||||
// horizontally in the list. The end of these transfers then point to the Interrupt Queue Headers,
|
||||
// in which we can attach Transfer Descriptors (related to Interrupt Transfers). These are attached
|
||||
// to the Queue Head _vertically_. We need to ensure that these are executed every 8ms, so they are inserted
|
||||
// at different points in the schedule (TODO: How do we do this?!?!). After the Interrupt Transfer Queue Heads,
|
||||
// we attach the Control Queue Heads. We need two in total, one for Low Speed devices, and one for Full Speed
|
||||
// USB devices. Finally, we attach the Bulk Transfer Queue Head.
|
||||
// Not specified in the datasheet, however, is another Queue Head with an "inactive" Transfer Descriptor. This
|
||||
// is to circumvent a bug in the silicon of the PIIX4's UHCI controller.
|
||||
// https://github.com/openbsd/src/blob/master/sys/dev/usb/uhci.c#L390
|
||||
//
|
||||
|
||||
m_interrupt_transfer_queue->link_next_queue_head(m_lowspeed_control_qh);
|
||||
m_interrupt_transfer_queue->terminate_element_link_ptr();
|
||||
|
||||
m_lowspeed_control_qh->link_next_queue_head(m_fullspeed_control_qh);
|
||||
m_lowspeed_control_qh->terminate_element_link_ptr();
|
||||
|
||||
m_fullspeed_control_qh->link_next_queue_head(m_bulk_qh);
|
||||
m_fullspeed_control_qh->terminate_element_link_ptr();
|
||||
|
||||
m_bulk_qh->link_next_queue_head(m_dummy_qh);
|
||||
m_bulk_qh->terminate_element_link_ptr();
|
||||
|
||||
auto piix4_td_hack = allocate_transfer_descriptor();
|
||||
piix4_td_hack->terminate();
|
||||
piix4_td_hack->set_max_len(0x7ff); // Null data packet
|
||||
piix4_td_hack->set_device_address(0x7f);
|
||||
piix4_td_hack->set_packet_id(PacketID::IN);
|
||||
m_dummy_qh->terminate_with_stray_descriptor(piix4_td_hack);
|
||||
m_dummy_qh->terminate_element_link_ptr();
|
||||
|
||||
u32* framelist = reinterpret_cast<u32*>(m_framelist->vaddr().as_ptr());
|
||||
for (int frame = 0; frame < UHCI_NUMBER_OF_FRAMES; frame++) {
|
||||
// Each frame pointer points to iso_td % NUM_ISO_TDS
|
||||
framelist[frame] = m_iso_td_list.at(frame % UHCI_NUMBER_OF_ISOCHRONOUS_TDS)->paddr();
|
||||
}
|
||||
|
||||
m_interrupt_transfer_queue->print();
|
||||
m_lowspeed_control_qh->print();
|
||||
m_fullspeed_control_qh->print();
|
||||
m_bulk_qh->print();
|
||||
m_dummy_qh->print();
|
||||
}
|
||||
|
||||
QueueHead* UHCIController::allocate_queue_head() const
|
||||
{
|
||||
for (QueueHead* queue_head : m_free_qh_pool) {
|
||||
if (!queue_head->in_use()) {
|
||||
queue_head->set_in_use(true);
|
||||
dbgln_if(UHCI_DEBUG, "UHCI: Allocated a new Queue Head! Located @ {} ({})", VirtualAddress(queue_head), PhysicalAddress(queue_head->paddr()));
|
||||
return queue_head;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr; // Huh!? We're outta queue heads!
|
||||
}
|
||||
|
||||
TransferDescriptor* UHCIController::allocate_transfer_descriptor() const
|
||||
{
|
||||
for (TransferDescriptor* transfer_descriptor : m_free_td_pool) {
|
||||
if (!transfer_descriptor->in_use()) {
|
||||
transfer_descriptor->set_in_use(true);
|
||||
dbgln_if(UHCI_DEBUG, "UHCI: Allocated a new Transfer Descriptor! Located @ {} ({})", VirtualAddress(transfer_descriptor), PhysicalAddress(transfer_descriptor->paddr()));
|
||||
return transfer_descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr; // Huh?! We're outta TDs!!
|
||||
}
|
||||
|
||||
void UHCIController::stop()
|
||||
{
|
||||
write_usbcmd(read_usbcmd() & ~UHCI_USBCMD_RUN);
|
||||
// FIXME: Timeout
|
||||
for (;;) {
|
||||
if (read_usbsts() & UHCI_USBSTS_HOST_CONTROLLER_HALTED)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void UHCIController::start()
|
||||
{
|
||||
write_usbcmd(read_usbcmd() | UHCI_USBCMD_RUN);
|
||||
// FIXME: Timeout
|
||||
for (;;) {
|
||||
if (!(read_usbsts() & UHCI_USBSTS_HOST_CONTROLLER_HALTED))
|
||||
break;
|
||||
}
|
||||
dbgln("UHCI: Started");
|
||||
}
|
||||
|
||||
TransferDescriptor* UHCIController::create_transfer_descriptor(Pipe& pipe, PacketID direction, size_t data_len)
|
||||
{
|
||||
TransferDescriptor* td = allocate_transfer_descriptor();
|
||||
if (td == nullptr) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
u16 max_len = (data_len > 0) ? (data_len - 1) : 0x7ff;
|
||||
VERIFY(max_len <= 0x4FF || max_len == 0x7FF); // According to the datasheet, anything in the range of 0x500 to 0x7FE are illegal
|
||||
|
||||
td->set_token((max_len << TD_TOKEN_MAXLEN_SHIFT) | ((pipe.data_toggle() ? 1 : 0) << TD_TOKEN_DATA_TOGGLE_SHIFT) | (pipe.endpoint_address() << TD_TOKEN_ENDPOINT_SHIFT) | (pipe.device_address() << TD_TOKEN_DEVICE_ADDR_SHIFT) | (static_cast<u8>(direction)));
|
||||
pipe.set_toggle(!pipe.data_toggle());
|
||||
|
||||
if (pipe.type() == Pipe::Type::Isochronous) {
|
||||
td->set_isochronous();
|
||||
} else {
|
||||
if (direction == PacketID::IN) {
|
||||
td->set_short_packet_detect();
|
||||
}
|
||||
}
|
||||
|
||||
// Set low-speed bit if the device connected to port is a low=speed device (probably unlikely...)
|
||||
if (pipe.device_speed() == Pipe::DeviceSpeed::LowSpeed) {
|
||||
td->set_lowspeed();
|
||||
}
|
||||
|
||||
td->set_active();
|
||||
td->set_error_retry_counter(RETRY_COUNTER_RELOAD);
|
||||
|
||||
return td;
|
||||
}
|
||||
|
||||
KResult UHCIController::create_chain(Pipe& pipe, PacketID direction, Ptr32<u8>& buffer_address, size_t max_size, size_t transfer_size, TransferDescriptor** td_chain, TransferDescriptor** last_td)
|
||||
{
|
||||
// We need to create `n` transfer descriptors based on the max
|
||||
// size of each transfer (which we've learned from the device already by reading
|
||||
// its device descriptor, or 8 bytes). Each TD then has its buffer pointer
|
||||
// set to the initial buffer address + (max_size * index), where index is
|
||||
// the ID of the TD in the chain.
|
||||
size_t byte_count = 0;
|
||||
TransferDescriptor* current_td = nullptr;
|
||||
TransferDescriptor* prev_td = nullptr;
|
||||
TransferDescriptor* first_td = nullptr;
|
||||
|
||||
// Keep creating transfer descriptors while we still have some data
|
||||
while (byte_count < transfer_size) {
|
||||
size_t packet_size = transfer_size - byte_count;
|
||||
if (packet_size > max_size) {
|
||||
packet_size = max_size;
|
||||
}
|
||||
|
||||
current_td = create_transfer_descriptor(pipe, direction, packet_size);
|
||||
if (current_td == nullptr) {
|
||||
free_descriptor_chain(first_td);
|
||||
return ENOMEM;
|
||||
}
|
||||
|
||||
if (Checked<FlatPtr>::addition_would_overflow(reinterpret_cast<FlatPtr>(&*buffer_address), byte_count))
|
||||
return EOVERFLOW;
|
||||
|
||||
auto buffer_pointer = Ptr32<u8>(buffer_address + byte_count);
|
||||
current_td->set_buffer_address(buffer_pointer);
|
||||
byte_count += packet_size;
|
||||
|
||||
if (prev_td != nullptr)
|
||||
prev_td->insert_next_transfer_descriptor(current_td);
|
||||
else
|
||||
first_td = current_td;
|
||||
|
||||
prev_td = current_td;
|
||||
}
|
||||
|
||||
*last_td = current_td;
|
||||
*td_chain = first_td;
|
||||
return KSuccess;
|
||||
}
|
||||
|
||||
void UHCIController::free_descriptor_chain(TransferDescriptor* first_descriptor)
|
||||
{
|
||||
TransferDescriptor* descriptor = first_descriptor;
|
||||
|
||||
while (descriptor) {
|
||||
descriptor->free();
|
||||
descriptor = descriptor->next_td();
|
||||
}
|
||||
}
|
||||
|
||||
KResultOr<size_t> UHCIController::submit_control_transfer(Transfer& transfer)
|
||||
{
|
||||
Pipe& pipe = transfer.pipe(); // Short circuit the pipe related to this transfer
|
||||
bool direction_in = (transfer.request().request_type & USB_DEVICE_REQUEST_DEVICE_TO_HOST) == USB_DEVICE_REQUEST_DEVICE_TO_HOST;
|
||||
|
||||
TransferDescriptor* setup_td = create_transfer_descriptor(pipe, PacketID::SETUP, sizeof(USBRequestData));
|
||||
if (!setup_td)
|
||||
return ENOMEM;
|
||||
|
||||
setup_td->set_buffer_address(transfer.buffer_physical().as_ptr());
|
||||
|
||||
// Create a new descriptor chain
|
||||
TransferDescriptor* last_data_descriptor;
|
||||
TransferDescriptor* data_descriptor_chain;
|
||||
auto buffer_address = Ptr32<u8>(transfer.buffer_physical().as_ptr() + sizeof(USBRequestData));
|
||||
auto transfer_chain_create_result = create_chain(pipe,
|
||||
direction_in ? PacketID::IN : PacketID::OUT,
|
||||
buffer_address,
|
||||
pipe.max_packet_size(),
|
||||
transfer.transfer_data_size(),
|
||||
&data_descriptor_chain,
|
||||
&last_data_descriptor);
|
||||
|
||||
if (transfer_chain_create_result != KSuccess)
|
||||
return transfer_chain_create_result;
|
||||
|
||||
// Status TD always has toggle set to 1
|
||||
pipe.set_toggle(true);
|
||||
|
||||
TransferDescriptor* status_td = create_transfer_descriptor(pipe, direction_in ? PacketID::OUT : PacketID::IN, 0);
|
||||
if (!status_td) {
|
||||
free_descriptor_chain(data_descriptor_chain);
|
||||
return ENOMEM;
|
||||
}
|
||||
status_td->terminate();
|
||||
|
||||
// Link transfers together
|
||||
if (data_descriptor_chain) {
|
||||
setup_td->insert_next_transfer_descriptor(data_descriptor_chain);
|
||||
last_data_descriptor->insert_next_transfer_descriptor(status_td);
|
||||
} else {
|
||||
setup_td->insert_next_transfer_descriptor(status_td);
|
||||
}
|
||||
|
||||
// Cool, everything should be chained together now! Let's print it out
|
||||
if constexpr (UHCI_VERBOSE_DEBUG) {
|
||||
dbgln("Setup TD");
|
||||
setup_td->print();
|
||||
if (data_descriptor_chain) {
|
||||
dbgln("Data TD");
|
||||
data_descriptor_chain->print();
|
||||
}
|
||||
dbgln("Status TD");
|
||||
status_td->print();
|
||||
}
|
||||
|
||||
QueueHead* transfer_queue = allocate_queue_head();
|
||||
if (!transfer_queue) {
|
||||
free_descriptor_chain(data_descriptor_chain);
|
||||
return 0;
|
||||
}
|
||||
|
||||
transfer_queue->attach_transfer_descriptor_chain(setup_td);
|
||||
transfer_queue->set_transfer(&transfer);
|
||||
|
||||
m_fullspeed_control_qh->attach_transfer_queue(*transfer_queue);
|
||||
|
||||
size_t transfer_size = 0;
|
||||
while (!transfer.complete())
|
||||
transfer_size = poll_transfer_queue(*transfer_queue);
|
||||
|
||||
free_descriptor_chain(transfer_queue->get_first_td());
|
||||
transfer_queue->free();
|
||||
|
||||
return transfer_size;
|
||||
}
|
||||
|
||||
size_t UHCIController::poll_transfer_queue(QueueHead& transfer_queue)
|
||||
{
|
||||
Transfer* transfer = transfer_queue.transfer();
|
||||
TransferDescriptor* descriptor = transfer_queue.get_first_td();
|
||||
bool transfer_still_in_progress = false;
|
||||
size_t transfer_size = 0;
|
||||
|
||||
while (descriptor) {
|
||||
u32 status = descriptor->status();
|
||||
|
||||
if (status & TransferDescriptor::StatusBits::Active) {
|
||||
transfer_still_in_progress = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (status & TransferDescriptor::StatusBits::ErrorMask) {
|
||||
transfer->set_complete();
|
||||
transfer->set_error_occurred();
|
||||
dbgln_if(UHCI_DEBUG, "UHCIController: Transfer failed! Reason: {:08x}", status);
|
||||
return 0;
|
||||
}
|
||||
|
||||
transfer_size += descriptor->actual_packet_length();
|
||||
descriptor = descriptor->next_td();
|
||||
}
|
||||
|
||||
if (!transfer_still_in_progress)
|
||||
transfer->set_complete();
|
||||
|
||||
return transfer_size;
|
||||
}
|
||||
|
||||
void UHCIController::spawn_port_proc()
|
||||
{
|
||||
RefPtr<Thread> usb_hotplug_thread;
|
||||
|
||||
Process::create_kernel_process(usb_hotplug_thread, "UHCIHotplug", [&] {
|
||||
for (;;) {
|
||||
for (int port = 0; port < UHCI_ROOT_PORT_COUNT; port++) {
|
||||
u16 port_data = 0;
|
||||
|
||||
if (port == 1) {
|
||||
// Let's see what's happening on port 1
|
||||
// Current status
|
||||
port_data = read_portsc1();
|
||||
if (port_data & UHCI_PORTSC_CONNECT_STATUS_CHANGED) {
|
||||
if (port_data & UHCI_PORTSC_CURRRENT_CONNECT_STATUS) {
|
||||
dmesgln("UHCI: Device attach detected on Root Port 1!");
|
||||
|
||||
// Reset the port
|
||||
port_data = read_portsc1();
|
||||
write_portsc1(port_data | UHCI_PORTSC_PORT_RESET);
|
||||
IO::delay(500);
|
||||
|
||||
write_portsc1(port_data & ~UHCI_PORTSC_PORT_RESET);
|
||||
IO::delay(500);
|
||||
|
||||
write_portsc1(port_data & (~UHCI_PORTSC_PORT_ENABLE_CHANGED | ~UHCI_PORTSC_CONNECT_STATUS_CHANGED));
|
||||
|
||||
port_data = read_portsc1();
|
||||
write_portsc1(port_data | UHCI_PORTSC_PORT_ENABLED);
|
||||
dbgln("port should be enabled now: {:#04x}\n", read_portsc1());
|
||||
|
||||
USB::Device::DeviceSpeed speed = (port_data & UHCI_PORTSC_LOW_SPEED_DEVICE) ? USB::Device::DeviceSpeed::LowSpeed : USB::Device::DeviceSpeed::FullSpeed;
|
||||
auto device = USB::Device::try_create(USB::Device::PortNumber::Port1, speed);
|
||||
|
||||
if (device.is_error())
|
||||
dmesgln("UHCI: Device creation failed on port 1 ({})", device.error());
|
||||
|
||||
m_devices.at(0) = device.value();
|
||||
VERIFY(s_procfs_usb_bus_folder);
|
||||
s_procfs_usb_bus_folder->plug(device.value());
|
||||
} else {
|
||||
// FIXME: Clean up (and properly) the RefPtr to the device in m_devices
|
||||
VERIFY(s_procfs_usb_bus_folder);
|
||||
VERIFY(m_devices.at(0));
|
||||
dmesgln("UHCI: Device detach detected on Root Port 1");
|
||||
s_procfs_usb_bus_folder->unplug(*m_devices.at(0));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
port_data = UHCIController::the().read_portsc2();
|
||||
if (port_data & UHCI_PORTSC_CONNECT_STATUS_CHANGED) {
|
||||
if (port_data & UHCI_PORTSC_CURRRENT_CONNECT_STATUS) {
|
||||
dmesgln("UHCI: Device attach detected on Root Port 2");
|
||||
|
||||
// Reset the port
|
||||
port_data = read_portsc2();
|
||||
write_portsc2(port_data | UHCI_PORTSC_PORT_RESET);
|
||||
for (size_t i = 0; i < 50000; ++i)
|
||||
IO::in8(0x80);
|
||||
|
||||
write_portsc2(port_data & ~UHCI_PORTSC_PORT_RESET);
|
||||
for (size_t i = 0; i < 100000; ++i)
|
||||
IO::in8(0x80);
|
||||
|
||||
write_portsc2(port_data & (~UHCI_PORTSC_PORT_ENABLE_CHANGED | ~UHCI_PORTSC_CONNECT_STATUS_CHANGED));
|
||||
|
||||
port_data = read_portsc2();
|
||||
write_portsc1(port_data | UHCI_PORTSC_PORT_ENABLED);
|
||||
dbgln("port should be enabled now: {:#04x}\n", read_portsc1());
|
||||
USB::Device::DeviceSpeed speed = (port_data & UHCI_PORTSC_LOW_SPEED_DEVICE) ? USB::Device::DeviceSpeed::LowSpeed : USB::Device::DeviceSpeed::FullSpeed;
|
||||
auto device = USB::Device::try_create(USB::Device::PortNumber::Port2, speed);
|
||||
|
||||
if (device.is_error())
|
||||
dmesgln("UHCI: Device creation failed on port 2 ({})", device.error());
|
||||
|
||||
m_devices.at(1) = device.value();
|
||||
VERIFY(s_procfs_usb_bus_folder);
|
||||
s_procfs_usb_bus_folder->plug(device.value());
|
||||
} else {
|
||||
// FIXME: Clean up (and properly) the RefPtr to the device in m_devices
|
||||
VERIFY(s_procfs_usb_bus_folder);
|
||||
VERIFY(m_devices.at(1));
|
||||
dmesgln("UHCI: Device detach detected on Root Port 2");
|
||||
s_procfs_usb_bus_folder->unplug(*m_devices.at(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
(void)Thread::current()->sleep(Time::from_seconds(1));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
bool UHCIController::handle_irq(const RegisterState&)
|
||||
{
|
||||
u32 status = read_usbsts();
|
||||
|
||||
// Shared IRQ. Not ours!
|
||||
if (!status)
|
||||
return false;
|
||||
|
||||
if constexpr (UHCI_DEBUG) {
|
||||
dbgln("UHCI: Interrupt happened!");
|
||||
dbgln("Value of USBSTS: {:#04x}", read_usbsts());
|
||||
}
|
||||
|
||||
// Write back USBSTS to clear bits
|
||||
write_usbsts(status);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
100
Kernel/Bus/USB/UHCIController.h
Normal file
100
Kernel/Bus/USB/UHCIController.h
Normal file
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* Copyright (c) 2020, Andreas Kling <kling@serenityos.org>
|
||||
* Copyright (c) 2020-2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Platform.h>
|
||||
|
||||
#include <AK/NonnullOwnPtr.h>
|
||||
#include <Kernel/Bus/PCI/Device.h>
|
||||
#include <Kernel/Bus/USB/UHCIDescriptorTypes.h>
|
||||
#include <Kernel/Bus/USB/USBDevice.h>
|
||||
#include <Kernel/Bus/USB/USBTransfer.h>
|
||||
#include <Kernel/IO.h>
|
||||
#include <Kernel/Process.h>
|
||||
#include <Kernel/Time/TimeManagement.h>
|
||||
#include <Kernel/VM/ContiguousVMObject.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
class UHCIController final : public PCI::Device {
|
||||
|
||||
public:
|
||||
static void detect();
|
||||
static UHCIController& the();
|
||||
|
||||
virtual ~UHCIController() override;
|
||||
|
||||
virtual const char* purpose() const override { return "UHCI"; }
|
||||
|
||||
void reset();
|
||||
void stop();
|
||||
void start();
|
||||
void spawn_port_proc();
|
||||
|
||||
void do_debug_transfer();
|
||||
|
||||
KResultOr<size_t> submit_control_transfer(Transfer& transfer);
|
||||
|
||||
RefPtr<USB::Device> const get_device_at_port(USB::Device::PortNumber);
|
||||
RefPtr<USB::Device> const get_device_from_address(u8 device_address);
|
||||
|
||||
private:
|
||||
UHCIController(PCI::Address, PCI::ID);
|
||||
|
||||
u16 read_usbcmd() { return m_io_base.offset(0).in<u16>(); }
|
||||
u16 read_usbsts() { return m_io_base.offset(0x2).in<u16>(); }
|
||||
u16 read_usbintr() { return m_io_base.offset(0x4).in<u16>(); }
|
||||
u16 read_frnum() { return m_io_base.offset(0x6).in<u16>(); }
|
||||
u32 read_flbaseadd() { return m_io_base.offset(0x8).in<u32>(); }
|
||||
u8 read_sofmod() { return m_io_base.offset(0xc).in<u8>(); }
|
||||
u16 read_portsc1() { return m_io_base.offset(0x10).in<u16>(); }
|
||||
u16 read_portsc2() { return m_io_base.offset(0x12).in<u16>(); }
|
||||
|
||||
void write_usbcmd(u16 value) { m_io_base.offset(0).out(value); }
|
||||
void write_usbsts(u16 value) { m_io_base.offset(0x2).out(value); }
|
||||
void write_usbintr(u16 value) { m_io_base.offset(0x4).out(value); }
|
||||
void write_frnum(u16 value) { m_io_base.offset(0x6).out(value); }
|
||||
void write_flbaseadd(u32 value) { m_io_base.offset(0x8).out(value); }
|
||||
void write_sofmod(u8 value) { m_io_base.offset(0xc).out(value); }
|
||||
void write_portsc1(u16 value) { m_io_base.offset(0x10).out(value); }
|
||||
void write_portsc2(u16 value) { m_io_base.offset(0x12).out(value); }
|
||||
|
||||
virtual bool handle_irq(const RegisterState&) override;
|
||||
|
||||
void create_structures();
|
||||
void setup_schedule();
|
||||
size_t poll_transfer_queue(QueueHead& transfer_queue);
|
||||
|
||||
TransferDescriptor* create_transfer_descriptor(Pipe& pipe, PacketID direction, size_t data_len);
|
||||
KResult create_chain(Pipe& pipe, PacketID direction, Ptr32<u8>& buffer_address, size_t max_size, size_t transfer_size, TransferDescriptor** td_chain, TransferDescriptor** last_td);
|
||||
void free_descriptor_chain(TransferDescriptor* first_descriptor);
|
||||
|
||||
QueueHead* allocate_queue_head() const;
|
||||
TransferDescriptor* allocate_transfer_descriptor() const;
|
||||
|
||||
private:
|
||||
IOAddress m_io_base;
|
||||
|
||||
Vector<QueueHead*> m_free_qh_pool;
|
||||
Vector<TransferDescriptor*> m_free_td_pool;
|
||||
Vector<TransferDescriptor*> m_iso_td_list;
|
||||
|
||||
QueueHead* m_interrupt_transfer_queue;
|
||||
QueueHead* m_lowspeed_control_qh;
|
||||
QueueHead* m_fullspeed_control_qh;
|
||||
QueueHead* m_bulk_qh;
|
||||
QueueHead* m_dummy_qh; // Needed for PIIX4 hack
|
||||
|
||||
OwnPtr<Region> m_framelist;
|
||||
OwnPtr<Region> m_qh_pool;
|
||||
OwnPtr<Region> m_td_pool;
|
||||
|
||||
Array<RefPtr<USB::Device>, 2> m_devices; // Devices connected to the root ports (of which there are two)
|
||||
};
|
||||
|
||||
}
|
367
Kernel/Bus/USB/UHCIDescriptorTypes.h
Normal file
367
Kernel/Bus/USB/UHCIDescriptorTypes.h
Normal file
|
@ -0,0 +1,367 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Ptr32.h>
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Bus/USB/USBTransfer.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
enum class PacketID : u8 {
|
||||
IN = 0x69,
|
||||
OUT = 0xe1,
|
||||
SETUP = 0x2d
|
||||
};
|
||||
|
||||
// Transfer Descriptor register bit offsets/masks
|
||||
constexpr u16 TD_CONTROL_STATUS_ACTLEN = 0x7ff;
|
||||
constexpr u8 TD_CONTROL_STATUS_ACTIVE_SHIFT = 23;
|
||||
constexpr u8 TD_CONTROL_STATUS_INT_ON_COMPLETE_SHIFT = 24;
|
||||
constexpr u8 TD_CONTROL_STATUS_ISOCHRONOUS_SHIFT = 25;
|
||||
constexpr u8 TD_CONTROL_STATUS_LS_DEVICE_SHIFT = 26;
|
||||
constexpr u8 TD_CONTROL_STATUS_ERR_CTR_SHIFT_SHIFT = 27;
|
||||
constexpr u8 TD_CONTROL_STATUS_SPD_SHIFT = 29;
|
||||
|
||||
constexpr u8 TD_TOKEN_PACKET_ID_SHIFT = 0;
|
||||
constexpr u8 TD_TOKEN_DEVICE_ADDR_SHIFT = 8;
|
||||
constexpr u8 TD_TOKEN_ENDPOINT_SHIFT = 15;
|
||||
constexpr u8 TD_TOKEN_DATA_TOGGLE_SHIFT = 19;
|
||||
constexpr u8 TD_TOKEN_MAXLEN_SHIFT = 21;
|
||||
|
||||
//
|
||||
// Transfer Descriptor
|
||||
//
|
||||
// Describes a single transfer event from, or to the Universal Serial Bus.
|
||||
// These are, generally, attached to Queue Heads, and then executed by the
|
||||
// USB Host Controller.
|
||||
// Must be 16-byte aligned
|
||||
//
|
||||
struct QueueHead;
|
||||
struct alignas(16) TransferDescriptor final {
|
||||
enum LinkPointerBits {
|
||||
Terminate = 1,
|
||||
QHSelect = 2,
|
||||
DepthFlag = 4,
|
||||
};
|
||||
|
||||
enum StatusBits {
|
||||
Reserved = (1 << 16),
|
||||
BitStuffError = (1 << 17),
|
||||
CRCTimeoutError = (1 << 18),
|
||||
NAKReceived = (1 << 19),
|
||||
BabbleDetected = (1 << 20),
|
||||
DataBufferError = (1 << 21),
|
||||
Stalled = (1 << 22),
|
||||
Active = (1 << 23),
|
||||
ErrorMask = BitStuffError | CRCTimeoutError | NAKReceived | BabbleDetected | DataBufferError | Stalled
|
||||
};
|
||||
|
||||
enum ControlBits {
|
||||
InterruptOnComplete = (1 << 24),
|
||||
IsochronousSelect = (1 << 25),
|
||||
LowSpeedDevice = (1 << 26),
|
||||
ShortPacketDetect = (1 << 29),
|
||||
};
|
||||
|
||||
TransferDescriptor() = delete;
|
||||
TransferDescriptor(u32 paddr)
|
||||
: m_paddr(paddr)
|
||||
{
|
||||
}
|
||||
~TransferDescriptor() = delete; // Prevent anything except placement new on this object
|
||||
|
||||
u32 link_ptr() const { return m_link_ptr; }
|
||||
u32 paddr() const { return m_paddr; }
|
||||
u32 status() const { return m_control_status; }
|
||||
u32 token() const { return m_token; }
|
||||
u32 buffer_ptr() const { return m_buffer_ptr; }
|
||||
u16 actual_packet_length() const { return (m_control_status + 1) & 0x7ff; }
|
||||
|
||||
bool in_use() const { return m_in_use; }
|
||||
bool stalled() const { return m_control_status & StatusBits::Stalled; }
|
||||
bool last_in_chain() const { return m_link_ptr & LinkPointerBits::Terminate; }
|
||||
bool active() const { return m_control_status & StatusBits::Active; }
|
||||
|
||||
void set_active()
|
||||
{
|
||||
u32 ctrl = m_control_status;
|
||||
ctrl |= StatusBits::Active;
|
||||
m_control_status = ctrl;
|
||||
}
|
||||
|
||||
void set_isochronous()
|
||||
{
|
||||
u32 ctrl = m_control_status;
|
||||
ctrl |= ControlBits::IsochronousSelect;
|
||||
m_control_status = ctrl;
|
||||
}
|
||||
|
||||
void set_interrupt_on_complete()
|
||||
{
|
||||
u32 ctrl = m_control_status;
|
||||
ctrl |= ControlBits::InterruptOnComplete;
|
||||
m_control_status = ctrl;
|
||||
}
|
||||
|
||||
void set_lowspeed()
|
||||
{
|
||||
u32 ctrl = m_control_status;
|
||||
ctrl |= ControlBits::LowSpeedDevice;
|
||||
m_control_status = ctrl;
|
||||
}
|
||||
|
||||
void set_error_retry_counter(u8 num_retries)
|
||||
{
|
||||
VERIFY(num_retries <= 3);
|
||||
u32 ctrl = m_control_status;
|
||||
ctrl |= (num_retries << 27);
|
||||
m_control_status = ctrl;
|
||||
}
|
||||
|
||||
void set_short_packet_detect()
|
||||
{
|
||||
u32 ctrl = m_control_status;
|
||||
ctrl |= ControlBits::ShortPacketDetect;
|
||||
m_control_status = ctrl;
|
||||
}
|
||||
|
||||
void set_control_status(u32 control_status) { m_control_status = control_status; }
|
||||
void set_in_use(bool in_use) { m_in_use = in_use; }
|
||||
void set_max_len(u16 max_len)
|
||||
{
|
||||
VERIFY(max_len < 0x500 || max_len == 0x7ff);
|
||||
m_token |= (max_len << 21);
|
||||
}
|
||||
|
||||
void set_device_endpoint(u8 endpoint)
|
||||
{
|
||||
VERIFY(endpoint <= 0xf);
|
||||
m_token |= (endpoint << 18);
|
||||
}
|
||||
|
||||
void set_device_address(u8 address)
|
||||
{
|
||||
VERIFY(address <= 0x7f);
|
||||
m_token |= (address << 8);
|
||||
}
|
||||
|
||||
void set_data_toggle(bool toggle)
|
||||
{
|
||||
m_token |= ((toggle ? (1 << 19) : 0));
|
||||
}
|
||||
|
||||
void set_packet_id(PacketID pid) { m_token |= static_cast<u32>(pid); }
|
||||
void link_queue_head(u32 qh_paddr)
|
||||
{
|
||||
m_link_ptr = qh_paddr;
|
||||
m_link_ptr |= LinkPointerBits::QHSelect;
|
||||
}
|
||||
|
||||
void print()
|
||||
{
|
||||
dbgln("UHCI: TD({:#04x}) @ {:#04x}: link_ptr={:#04x}, status={:#04x}, token={:#04x}, buffer_ptr={:#04x}", this, m_paddr, m_link_ptr, (u32)m_control_status, m_token, m_buffer_ptr);
|
||||
|
||||
// Now let's print the flags!
|
||||
dbgln("UHCI: TD({:#04x}) @ {:#04x}: link_ptr={}{}{}, status={}{}{}{}{}{}{}",
|
||||
this,
|
||||
m_paddr,
|
||||
(last_in_chain()) ? "T " : "",
|
||||
(m_link_ptr & static_cast<u32>(LinkPointerBits::QHSelect)) ? "QH " : "",
|
||||
(m_link_ptr & static_cast<u32>(LinkPointerBits::DepthFlag)) ? "Vf " : "",
|
||||
(m_control_status & static_cast<u32>(StatusBits::BitStuffError)) ? "BITSTUFF " : "",
|
||||
(m_control_status & static_cast<u32>(StatusBits::CRCTimeoutError)) ? "CRCTIMEOUT " : "",
|
||||
(m_control_status & static_cast<u32>(StatusBits::NAKReceived)) ? "NAK " : "",
|
||||
(m_control_status & static_cast<u32>(StatusBits::BabbleDetected)) ? "BABBLE " : "",
|
||||
(m_control_status & static_cast<u32>(StatusBits::DataBufferError)) ? "DATAERR " : "",
|
||||
(stalled()) ? "STALL " : "",
|
||||
(active()) ? "ACTIVE " : "");
|
||||
}
|
||||
|
||||
// FIXME: For the love of God, use AK SMART POINTERS PLEASE!!
|
||||
TransferDescriptor* next_td() { return m_next_td; }
|
||||
const TransferDescriptor* next_td() const { return m_next_td; }
|
||||
void set_next_td(TransferDescriptor* td) { m_next_td = td; }
|
||||
|
||||
TransferDescriptor* prev_td() { return m_prev_td; }
|
||||
const TransferDescriptor* prev_td() const { return m_prev_td; }
|
||||
void set_previous_td(TransferDescriptor* td) { m_prev_td = td; }
|
||||
|
||||
void insert_next_transfer_descriptor(TransferDescriptor* td)
|
||||
{
|
||||
m_link_ptr = td->paddr();
|
||||
td->set_previous_td(this);
|
||||
set_next_td(td);
|
||||
|
||||
// Let's set some bits for the link ptr
|
||||
m_link_ptr |= static_cast<u32>(LinkPointerBits::DepthFlag);
|
||||
}
|
||||
|
||||
void terminate() { m_link_ptr |= static_cast<u32>(LinkPointerBits::Terminate); }
|
||||
|
||||
void set_buffer_address(Ptr32<u8> buffer)
|
||||
{
|
||||
u8* buffer_address = &*buffer;
|
||||
m_buffer_ptr = reinterpret_cast<uintptr_t>(buffer_address);
|
||||
}
|
||||
|
||||
// DEBUG FUNCTIONS!
|
||||
void set_token(u32 token)
|
||||
{
|
||||
m_token = token;
|
||||
}
|
||||
|
||||
void set_status(u32 status)
|
||||
{
|
||||
m_control_status = status;
|
||||
}
|
||||
|
||||
void free()
|
||||
{
|
||||
m_link_ptr = 0;
|
||||
m_control_status = 0;
|
||||
m_token = 0;
|
||||
m_in_use = false;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 m_link_ptr; // Points to another Queue Head or Transfer Descriptor
|
||||
volatile u32 m_control_status; // Control and status bits
|
||||
u32 m_token; // Contains all information required to fill in a USB Start Token
|
||||
u32 m_buffer_ptr; // Points to a data buffer for this transaction (i.e what we want to send or recv)
|
||||
|
||||
// These values will be ignored by the controller, but we can use them for configuration/bookkeeping
|
||||
u32 m_paddr; // Physical address where this TransferDescriptor is located
|
||||
Ptr32<TransferDescriptor> m_next_td { nullptr }; // Pointer to first TD
|
||||
Ptr32<TransferDescriptor> m_prev_td { nullptr }; // Pointer to first TD
|
||||
bool m_in_use; // Has this TD been allocated (and therefore in use)?
|
||||
};
|
||||
|
||||
static_assert(sizeof(TransferDescriptor) == 32); // Transfer Descriptor is always 8 Dwords
|
||||
|
||||
//
|
||||
// Queue Head
|
||||
//
|
||||
// Description here please!
|
||||
//
|
||||
struct alignas(16) QueueHead {
|
||||
enum class LinkPointerBits : u32 {
|
||||
Terminate = 1,
|
||||
QHSelect = 2,
|
||||
};
|
||||
|
||||
QueueHead() = delete;
|
||||
QueueHead(u32 paddr)
|
||||
: m_paddr(paddr)
|
||||
{
|
||||
}
|
||||
~QueueHead() = delete; // Prevent anything except placement new on this object
|
||||
|
||||
u32 link_ptr() const { return m_link_ptr; }
|
||||
u32 element_link_ptr() const { return m_element_link_ptr; }
|
||||
u32 paddr() const { return m_paddr; }
|
||||
bool in_use() const { return m_in_use; }
|
||||
|
||||
void set_in_use(bool in_use) { m_in_use = in_use; }
|
||||
void set_link_ptr(u32 val) { m_link_ptr = val; }
|
||||
|
||||
// FIXME: For the love of God, use AK SMART POINTERS PLEASE!!
|
||||
QueueHead* next_qh() { return m_next_qh; }
|
||||
const QueueHead* next_qh() const { return m_next_qh; }
|
||||
void set_next_qh(QueueHead* qh) { m_next_qh = qh; }
|
||||
|
||||
QueueHead* prev_qh() { return m_prev_qh; }
|
||||
const QueueHead* prev_qh() const { return m_prev_qh; }
|
||||
void set_previous_qh(QueueHead* qh)
|
||||
{
|
||||
m_prev_qh = qh;
|
||||
}
|
||||
|
||||
void link_next_queue_head(QueueHead* qh)
|
||||
{
|
||||
m_link_ptr = qh->paddr();
|
||||
m_link_ptr |= static_cast<u32>(LinkPointerBits::QHSelect);
|
||||
}
|
||||
|
||||
void attach_transfer_queue(QueueHead& qh)
|
||||
{
|
||||
m_element_link_ptr = qh.paddr();
|
||||
m_element_link_ptr = m_element_link_ptr | static_cast<u32>(LinkPointerBits::QHSelect);
|
||||
}
|
||||
|
||||
// FIXME: Find out best way to walk queue and free everything
|
||||
void free_transfer_queue([[maybe_unused]] QueueHead* qh)
|
||||
{
|
||||
TODO();
|
||||
}
|
||||
|
||||
void terminate_with_stray_descriptor(TransferDescriptor* td)
|
||||
{
|
||||
m_link_ptr = td->paddr();
|
||||
m_link_ptr |= static_cast<u32>(LinkPointerBits::Terminate);
|
||||
}
|
||||
|
||||
// TODO: Should we pass in an array or vector of TDs instead????
|
||||
void attach_transfer_descriptor_chain(TransferDescriptor* td)
|
||||
{
|
||||
m_first_td = td;
|
||||
m_element_link_ptr = td->paddr();
|
||||
}
|
||||
|
||||
TransferDescriptor* get_first_td()
|
||||
{
|
||||
return m_first_td;
|
||||
}
|
||||
|
||||
void terminate() { m_link_ptr |= static_cast<u32>(LinkPointerBits::Terminate); }
|
||||
|
||||
void terminate_element_link_ptr()
|
||||
{
|
||||
m_element_link_ptr = static_cast<u32>(LinkPointerBits::Terminate);
|
||||
}
|
||||
|
||||
void set_transfer(Transfer* transfer)
|
||||
{
|
||||
m_transfer = transfer;
|
||||
}
|
||||
|
||||
Transfer* transfer()
|
||||
{
|
||||
return m_transfer;
|
||||
}
|
||||
|
||||
void print()
|
||||
{
|
||||
dbgln("UHCI: QH({:#04x}) @ {:#04x}: link_ptr={:#04x}, element_link_ptr={:#04x}", this, m_paddr, m_link_ptr, (FlatPtr)m_element_link_ptr);
|
||||
}
|
||||
|
||||
void free()
|
||||
{
|
||||
m_link_ptr = 0;
|
||||
m_element_link_ptr = 0;
|
||||
m_first_td = nullptr;
|
||||
m_transfer = nullptr;
|
||||
m_in_use = false;
|
||||
}
|
||||
|
||||
private:
|
||||
u32 m_link_ptr { 0 }; // Pointer to the next horizontal object that the controller will execute after this one
|
||||
volatile u32 m_element_link_ptr { 0 }; // Pointer to the first data object in the queue (can be modified by hw)
|
||||
|
||||
// These values will be ignored by the controller, but we can use them for configuration/bookkeeping
|
||||
// Any addresses besides `paddr` are assumed virtual and can be dereferenced
|
||||
u32 m_paddr { 0 }; // Physical address where this QueueHead is located
|
||||
Ptr32<QueueHead> m_next_qh { nullptr }; // Next QH
|
||||
Ptr32<QueueHead> m_prev_qh { nullptr }; // Previous QH
|
||||
Ptr32<TransferDescriptor> m_first_td { nullptr }; // Pointer to first TD
|
||||
Ptr32<Transfer> m_transfer { nullptr }; // Pointer to transfer linked to this queue head
|
||||
bool m_in_use { false }; // Is this QH currently in use?
|
||||
};
|
||||
|
||||
static_assert(sizeof(QueueHead) == 32); // Queue Head is always 8 Dwords
|
||||
}
|
104
Kernel/Bus/USB/USBDescriptors.h
Normal file
104
Kernel/Bus/USB/USBDescriptors.h
Normal file
|
@ -0,0 +1,104 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
struct [[gnu::packed]] USBDescriptorCommon {
|
||||
u8 length;
|
||||
u8 descriptor_type;
|
||||
};
|
||||
|
||||
//
|
||||
// Device Descriptor
|
||||
// =================
|
||||
//
|
||||
// This descriptor type (stored on the device), represents the device, and gives
|
||||
// information related to it, such as the USB specification it complies to,
|
||||
// as well as the vendor and product ID of the device.
|
||||
//
|
||||
// https://beyondlogic.org/usbnutshell/usb5.shtml#DeviceDescriptors
|
||||
struct [[gnu::packed]] USBDeviceDescriptor {
|
||||
USBDescriptorCommon descriptor_header;
|
||||
u16 usb_spec_compliance_bcd;
|
||||
u8 device_class;
|
||||
u8 device_sub_class;
|
||||
u8 device_protocol;
|
||||
u8 max_packet_size;
|
||||
u16 vendor_id;
|
||||
u16 product_id;
|
||||
u16 device_release_bcd;
|
||||
u8 manufacturer_id_descriptor_index;
|
||||
u8 product_string_descriptor_index;
|
||||
u8 serial_number_descriptor_index;
|
||||
u8 num_configurations;
|
||||
};
|
||||
|
||||
//
|
||||
// Configuration Descriptor
|
||||
// ========================
|
||||
//
|
||||
// A USB device can have multiple configurations, which tells us about how the
|
||||
// device is physically configured (e.g how it's powered, max power consumption etc).
|
||||
//
|
||||
struct [[gnu::packed]] USBConfigurationDescriptor {
|
||||
USBDescriptorCommon descriptor_header;
|
||||
u16 total_length;
|
||||
u8 number_of_interfaces;
|
||||
u8 configuration_value;
|
||||
u8 configuration_string_descriptor_index;
|
||||
u8 attributes_bitmap;
|
||||
u8 max_power_in_ma;
|
||||
};
|
||||
|
||||
//
|
||||
// Interface Descriptor
|
||||
// ====================
|
||||
//
|
||||
// An interface descriptor describes to us one or more endpoints, grouped
|
||||
// together to define a singular function of a device.
|
||||
// As an example, a USB webcam might have two interface descriptors; one
|
||||
// for the camera, and one for the microphone.
|
||||
//
|
||||
struct [[gnu::packed]] USBInterfaceDescriptor {
|
||||
USBDescriptorCommon descriptor_header;
|
||||
u8 interface_id;
|
||||
u8 alternate_setting;
|
||||
u8 number_of_endpoints;
|
||||
u8 interface_class_code;
|
||||
u8 interface_sub_class_code;
|
||||
u8 interface_protocol;
|
||||
u8 interface_string_descriptor_index;
|
||||
};
|
||||
|
||||
//
|
||||
// Endpoint Descriptor
|
||||
// ===================
|
||||
//
|
||||
// The lowest leaf in the configuration tree. And endpoint descriptor describes
|
||||
// the physical transfer properties of the endpoint (that isn't endpoint0).
|
||||
// The description given by this structure is used by a pipe to create a
|
||||
// "connection" from the host to the device.
|
||||
// https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/usb-endpoints-and-their-pipes
|
||||
struct [[gnu::packed]] USBEndpointDescriptor {
|
||||
USBDescriptorCommon descriptor_header;
|
||||
u8 endpoint_address;
|
||||
u8 endpoint_attributes_bitmap;
|
||||
u16 max_packet_size;
|
||||
u8 poll_interval_in_frames;
|
||||
};
|
||||
|
||||
static constexpr u8 DESCRIPTOR_TYPE_DEVICE = 0x01;
|
||||
static constexpr u8 DESCRIPTOR_TYPE_CONFIGURATION = 0x02;
|
||||
static constexpr u8 DESCRIPTOR_TYPE_STRING = 0x03;
|
||||
static constexpr u8 DESCRIPTOR_TYPE_INTERFACE = 0x04;
|
||||
static constexpr u8 DESCRIPTOR_TYPE_ENDPOINT = 0x05;
|
||||
static constexpr u8 DESCRIPTOR_TYPE_DEVICE_QUALIFIER = 0x06;
|
||||
|
||||
}
|
105
Kernel/Bus/USB/USBDevice.cpp
Normal file
105
Kernel/Bus/USB/USBDevice.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <Kernel/Bus/USB/UHCIController.h>
|
||||
#include <Kernel/Bus/USB/USBDescriptors.h>
|
||||
#include <Kernel/Bus/USB/USBDevice.h>
|
||||
#include <Kernel/Bus/USB/USBRequest.h>
|
||||
|
||||
static u32 s_next_usb_address = 1; // Next address we hand out to a device once it's plugged into the machine
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
KResultOr<NonnullRefPtr<Device>> Device::try_create(PortNumber port, DeviceSpeed speed)
|
||||
{
|
||||
auto pipe_or_error = Pipe::try_create_pipe(Pipe::Type::Control, Pipe::Direction::Bidirectional, 0, 8, 0);
|
||||
if (pipe_or_error.is_error())
|
||||
return pipe_or_error.error();
|
||||
|
||||
auto device = AK::try_create<Device>(port, speed, pipe_or_error.release_value());
|
||||
if (!device)
|
||||
return ENOMEM;
|
||||
|
||||
auto enumerate_result = device->enumerate();
|
||||
if (enumerate_result.is_error())
|
||||
return enumerate_result;
|
||||
|
||||
return device.release_nonnull();
|
||||
}
|
||||
|
||||
Device::Device(PortNumber port, DeviceSpeed speed, NonnullOwnPtr<Pipe> default_pipe)
|
||||
: m_device_port(port)
|
||||
, m_device_speed(speed)
|
||||
, m_address(0)
|
||||
, m_default_pipe(move(default_pipe))
|
||||
{
|
||||
}
|
||||
|
||||
KResult Device::enumerate()
|
||||
{
|
||||
USBDeviceDescriptor dev_descriptor {};
|
||||
|
||||
// FIXME: 0x100 is a magic number for now, as I'm not quite sure how these are constructed....
|
||||
// Send 8-bytes to get at least the `max_packet_size` from the device
|
||||
auto transfer_length_or_error = m_default_pipe->control_transfer(USB_DEVICE_REQUEST_DEVICE_TO_HOST, USB_REQUEST_GET_DESCRIPTOR, 0x100, 0, 8, &dev_descriptor);
|
||||
|
||||
if (transfer_length_or_error.is_error())
|
||||
return transfer_length_or_error.error();
|
||||
|
||||
auto transfer_length = transfer_length_or_error.release_value();
|
||||
|
||||
// FIXME: This shouldn't crash! Do some correct error handling on me please!
|
||||
VERIFY(transfer_length > 0);
|
||||
|
||||
// Ensure that this is actually a valid device descriptor...
|
||||
VERIFY(dev_descriptor.descriptor_header.descriptor_type == DESCRIPTOR_TYPE_DEVICE);
|
||||
m_default_pipe->set_max_packet_size(dev_descriptor.max_packet_size);
|
||||
|
||||
transfer_length_or_error = m_default_pipe->control_transfer(USB_DEVICE_REQUEST_DEVICE_TO_HOST, USB_REQUEST_GET_DESCRIPTOR, 0x100, 0, sizeof(USBDeviceDescriptor), &dev_descriptor);
|
||||
|
||||
if (transfer_length_or_error.is_error())
|
||||
return transfer_length_or_error.error();
|
||||
|
||||
transfer_length = transfer_length_or_error.release_value();
|
||||
|
||||
// FIXME: This shouldn't crash! Do some correct error handling on me please!
|
||||
VERIFY(transfer_length > 0);
|
||||
|
||||
// Ensure that this is actually a valid device descriptor...
|
||||
VERIFY(dev_descriptor.descriptor_header.descriptor_type == DESCRIPTOR_TYPE_DEVICE);
|
||||
|
||||
if constexpr (USB_DEBUG) {
|
||||
dbgln("USB Device Descriptor for {:04x}:{:04x}", dev_descriptor.vendor_id, dev_descriptor.product_id);
|
||||
dbgln("Device Class: {:02x}", dev_descriptor.device_class);
|
||||
dbgln("Device Sub-Class: {:02x}", dev_descriptor.device_sub_class);
|
||||
dbgln("Device Protocol: {:02x}", dev_descriptor.device_protocol);
|
||||
dbgln("Max Packet Size: {:02x} bytes", dev_descriptor.max_packet_size);
|
||||
dbgln("Number of configurations: {:02x}", dev_descriptor.num_configurations);
|
||||
}
|
||||
|
||||
// Attempt to set devices address on the bus
|
||||
transfer_length_or_error = m_default_pipe->control_transfer(USB_DEVICE_REQUEST_HOST_TO_DEVICE, USB_REQUEST_SET_ADDRESS, s_next_usb_address, 0, 0, nullptr);
|
||||
|
||||
if (transfer_length_or_error.is_error())
|
||||
return transfer_length_or_error.error();
|
||||
|
||||
transfer_length = transfer_length_or_error.release_value();
|
||||
|
||||
VERIFY(transfer_length > 0);
|
||||
m_address = s_next_usb_address++;
|
||||
|
||||
memcpy(&m_device_descriptor, &dev_descriptor, sizeof(USBDeviceDescriptor));
|
||||
return KSuccess;
|
||||
}
|
||||
|
||||
Device::~Device()
|
||||
{
|
||||
}
|
||||
|
||||
}
|
59
Kernel/Bus/USB/USBDevice.h
Normal file
59
Kernel/Bus/USB/USBDevice.h
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Bus/USB/USBPipe.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
//
|
||||
// Some nice info from FTDI on device enumeration and how some of this
|
||||
// glues together:
|
||||
//
|
||||
// https://www.ftdichip.com/Support/Documents/TechnicalNotes/TN_113_Simplified%20Description%20of%20USB%20Device%20Enumeration.pdf
|
||||
class Device : public RefCounted<Device> {
|
||||
public:
|
||||
enum class PortNumber : u8 {
|
||||
Port1 = 0,
|
||||
Port2
|
||||
};
|
||||
|
||||
enum class DeviceSpeed : u8 {
|
||||
FullSpeed = 0,
|
||||
LowSpeed
|
||||
};
|
||||
|
||||
public:
|
||||
static KResultOr<NonnullRefPtr<Device>> try_create(PortNumber, DeviceSpeed);
|
||||
|
||||
Device(PortNumber, DeviceSpeed, NonnullOwnPtr<Pipe> default_pipe);
|
||||
~Device();
|
||||
|
||||
KResult enumerate();
|
||||
|
||||
PortNumber port() const { return m_device_port; }
|
||||
DeviceSpeed speed() const { return m_device_speed; }
|
||||
|
||||
u8 address() const { return m_address; }
|
||||
|
||||
const USBDeviceDescriptor& device_descriptor() const { return m_device_descriptor; }
|
||||
|
||||
private:
|
||||
PortNumber m_device_port; // What port is this device attached to
|
||||
DeviceSpeed m_device_speed; // What speed is this device running at
|
||||
u8 m_address { 0 }; // USB address assigned to this device
|
||||
|
||||
// Device description
|
||||
u16 m_vendor_id { 0 }; // This device's vendor ID assigned by the USB group
|
||||
u16 m_product_id { 0 }; // This device's product ID assigned by the USB group
|
||||
USBDeviceDescriptor m_device_descriptor; // Device Descriptor obtained from USB Device
|
||||
|
||||
NonnullOwnPtr<Pipe> m_default_pipe; // Default communication pipe (endpoint0) used during enumeration
|
||||
};
|
||||
}
|
60
Kernel/Bus/USB/USBEndpoint.h
Normal file
60
Kernel/Bus/USB/USBEndpoint.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Kernel/Bus/USB/USBDescriptors.h>
|
||||
#include <Kernel/Bus/USB/USBPipe.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
//
|
||||
// An endpoint is the "end point" of communication of a USB device. That is, data is read from and written
|
||||
// to an endpoint via a USB pipe. As an example, during device enumeration (where we assign an address to the
|
||||
// device), we communicate with the device over the default endpoint, endpoint0, which all devices _must_
|
||||
// contain to be compliant with the USB specification.
|
||||
//
|
||||
// And endpoint describes characteristics about the transfer between the host and the device, such as:
|
||||
// - The endpoint number
|
||||
// - Max packet size of send/recv of the endpoint
|
||||
// - Transfer type (bulk, interrupt, isochronous etc)
|
||||
//
|
||||
// Take for example a USB multifunction device, such as a keyboard/mouse combination. The mouse
|
||||
// may need to be polled every n milliseconds, meaning the transfer may be isochronous (streamed),
|
||||
// while the keyboard part would only generate data once we push a key (hence an interrupt transfer).
|
||||
// Each of these data sources would be a _different_ endpoint on the device that we read from.
|
||||
class USBEndpoint {
|
||||
static constexpr u8 ENDPOINT_ADDRESS_NUMBER_MASK = 0x0f;
|
||||
static constexpr u8 ENDPOINT_ADDRESS_DIRECTION_MASK = 0x80;
|
||||
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_MASK = 0x03;
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_CONTROL = 0x00;
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_ISOCHRONOUS = 0x01;
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_BULK = 0x02;
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_INTERRUPT = 0x03;
|
||||
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_ISO_MODE_SYNC_TYPE = 0x0c;
|
||||
static constexpr u8 ENDPOINT_ATTRIBUTES_ISO_MODE_USAGE_TYPE = 0x30;
|
||||
|
||||
public:
|
||||
const USBEndpointDescriptor& descriptor() const { return m_descriptor; }
|
||||
|
||||
bool is_control() const { return (m_descriptor.endpoint_attributes_bitmap & ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_MASK) == ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_CONTROL; }
|
||||
bool is_isochronous() const { return (m_descriptor.endpoint_attributes_bitmap & ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_MASK) == ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_ISOCHRONOUS; }
|
||||
bool is_bulk() const { return (m_descriptor.endpoint_attributes_bitmap & ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_MASK) == ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_BULK; }
|
||||
bool is_interrupt() const { return (m_descriptor.endpoint_attributes_bitmap & ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_MASK) == ENDPOINT_ATTRIBUTES_TRANSFER_TYPE_INTERRUPT; }
|
||||
|
||||
u16 max_packet_size() const { return m_descriptor.max_packet_size; }
|
||||
u8 polling_interval() const { return m_descriptor.poll_interval_in_frames; }
|
||||
|
||||
private:
|
||||
USBEndpoint(/* TODO */);
|
||||
USBEndpointDescriptor m_descriptor;
|
||||
|
||||
USBPipe m_pipe;
|
||||
};
|
||||
|
||||
}
|
84
Kernel/Bus/USB/USBPipe.cpp
Normal file
84
Kernel/Bus/USB/USBPipe.cpp
Normal file
|
@ -0,0 +1,84 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Bus/USB/PacketTypes.h>
|
||||
#include <Kernel/Bus/USB/UHCIController.h>
|
||||
#include <Kernel/Bus/USB/USBPipe.h>
|
||||
#include <Kernel/Bus/USB/USBTransfer.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
KResultOr<NonnullOwnPtr<Pipe>> Pipe::try_create_pipe(Type type, Direction direction, u8 endpoint_address, u16 max_packet_size, i8 device_address, u8 poll_interval)
|
||||
{
|
||||
auto pipe = adopt_own_if_nonnull(new (nothrow) Pipe(type, direction, endpoint_address, max_packet_size, device_address, poll_interval));
|
||||
if (!pipe)
|
||||
return ENOMEM;
|
||||
|
||||
return pipe.release_nonnull();
|
||||
}
|
||||
|
||||
Pipe::Pipe(Type type, Pipe::Direction direction, u16 max_packet_size)
|
||||
: m_type(type)
|
||||
, m_direction(direction)
|
||||
, m_endpoint_address(0)
|
||||
, m_max_packet_size(max_packet_size)
|
||||
, m_poll_interval(0)
|
||||
, m_data_toggle(false)
|
||||
{
|
||||
}
|
||||
|
||||
Pipe::Pipe(Type type, Direction direction, USBEndpointDescriptor& endpoint [[maybe_unused]])
|
||||
: m_type(type)
|
||||
, m_direction(direction)
|
||||
{
|
||||
// TODO: decode endpoint structure
|
||||
}
|
||||
|
||||
Pipe::Pipe(Type type, Direction direction, u8 endpoint_address, u16 max_packet_size, u8 poll_interval, i8 device_address)
|
||||
: m_type(type)
|
||||
, m_direction(direction)
|
||||
, m_device_address(device_address)
|
||||
, m_endpoint_address(endpoint_address)
|
||||
, m_max_packet_size(max_packet_size)
|
||||
, m_poll_interval(poll_interval)
|
||||
, m_data_toggle(false)
|
||||
{
|
||||
}
|
||||
|
||||
KResultOr<size_t> Pipe::control_transfer(u8 request_type, u8 request, u16 value, u16 index, u16 length, void* data)
|
||||
{
|
||||
USBRequestData usb_request;
|
||||
|
||||
usb_request.request_type = request_type;
|
||||
usb_request.request = request;
|
||||
usb_request.value = value;
|
||||
usb_request.index = index;
|
||||
usb_request.length = length;
|
||||
|
||||
auto transfer = Transfer::try_create(*this, length);
|
||||
|
||||
if (!transfer)
|
||||
return ENOMEM;
|
||||
|
||||
transfer->set_setup_packet(usb_request);
|
||||
|
||||
dbgln_if(USB_DEBUG, "Pipe: Transfer allocated @ {:08x}", transfer->buffer_physical());
|
||||
auto transfer_len_or_error = UHCIController::the().submit_control_transfer(*transfer);
|
||||
|
||||
if (transfer_len_or_error.is_error())
|
||||
return transfer_len_or_error.error();
|
||||
|
||||
auto transfer_length = transfer_len_or_error.release_value();
|
||||
|
||||
// TODO: Check transfer for completion and copy data from transfer buffer into data
|
||||
if (length > 0)
|
||||
memcpy(reinterpret_cast<u8*>(data), transfer->buffer().as_ptr() + sizeof(USBRequestData), length);
|
||||
|
||||
dbgln_if(USB_DEBUG, "Pipe: Control Transfer complete!");
|
||||
return transfer_length;
|
||||
}
|
||||
|
||||
}
|
77
Kernel/Bus/USB/USBPipe.h
Normal file
77
Kernel/Bus/USB/USBPipe.h
Normal file
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/Types.h>
|
||||
#include <Kernel/Bus/USB/USBDescriptors.h>
|
||||
#include <Kernel/VM/Region.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
//
|
||||
// A pipe is the logical connection between a memory buffer on the PC (host) and
|
||||
// an endpoint on the device. In this implementation, the data buffer the pipe connects
|
||||
// to is the physical buffer created when a Transfer is allocated.
|
||||
//
|
||||
class Pipe {
|
||||
public:
|
||||
enum class Type : u8 {
|
||||
Control = 0,
|
||||
Isochronous = 1,
|
||||
Bulk = 2,
|
||||
Interrupt = 3
|
||||
};
|
||||
|
||||
enum class Direction : u8 {
|
||||
Out = 0,
|
||||
In = 1,
|
||||
Bidirectional = 2
|
||||
};
|
||||
|
||||
enum class DeviceSpeed : u8 {
|
||||
LowSpeed,
|
||||
FullSpeed
|
||||
};
|
||||
|
||||
public:
|
||||
static KResultOr<NonnullOwnPtr<Pipe>> try_create_pipe(Type type, Direction direction, u8 endpoint_address, u16 max_packet_size, i8 device_address, u8 poll_interval = 0);
|
||||
|
||||
Type type() const { return m_type; }
|
||||
Direction direction() const { return m_direction; }
|
||||
DeviceSpeed device_speed() const { return m_speed; }
|
||||
|
||||
i8 device_address() const { return m_device_address; }
|
||||
u8 endpoint_address() const { return m_endpoint_address; }
|
||||
u16 max_packet_size() const { return m_max_packet_size; }
|
||||
u8 poll_interval() const { return m_poll_interval; }
|
||||
bool data_toggle() const { return m_data_toggle; }
|
||||
|
||||
void set_max_packet_size(u16 max_size) { m_max_packet_size = max_size; }
|
||||
void set_toggle(bool toggle) { m_data_toggle = toggle; }
|
||||
void set_device_address(i8 addr) { m_device_address = addr; }
|
||||
|
||||
KResultOr<size_t> control_transfer(u8 request_type, u8 request, u16 value, u16 index, u16 length, void* data);
|
||||
|
||||
Pipe(Type type, Direction direction, u16 max_packet_size);
|
||||
Pipe(Type type, Direction direction, USBEndpointDescriptor& endpoint);
|
||||
Pipe(Type type, Direction direction, u8 endpoint_address, u16 max_packet_size, u8 poll_interval, i8 device_address);
|
||||
|
||||
private:
|
||||
friend class Device;
|
||||
|
||||
Type m_type;
|
||||
Direction m_direction;
|
||||
DeviceSpeed m_speed;
|
||||
|
||||
i8 m_device_address { 0 }; // Device address of this pipe
|
||||
u8 m_endpoint_address { 0 }; // Corresponding endpoint address for this pipe
|
||||
u16 m_max_packet_size { 0 }; // Max packet size for this pipe
|
||||
u8 m_poll_interval { 0 }; // Polling interval (in frames)
|
||||
bool m_data_toggle { false }; // Data toggle for stuffing bit
|
||||
};
|
||||
}
|
37
Kernel/Bus/USB/USBRequest.h
Normal file
37
Kernel/Bus/USB/USBRequest.h
Normal file
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/Types.h>
|
||||
|
||||
//
|
||||
// USB Request directions
|
||||
//
|
||||
// As per Section 9.4 of the USB Specification, it is noted that Requeset Types that
|
||||
// Device to Host have bit 7 of `bmRequestType` set. These are here as a convenience,
|
||||
// as we construct the request at the call-site to make reading transfers easier.
|
||||
//
|
||||
static constexpr u8 USB_DEVICE_REQUEST_DEVICE_TO_HOST = 0x80;
|
||||
static constexpr u8 USB_DEVICE_REQUEST_HOST_TO_DEVICE = 0x00;
|
||||
static constexpr u8 USB_INTERFACE_REQUEST_DEVICE_TO_HOST = 0x81;
|
||||
static constexpr u8 USB_INTERFACE_REQUEST_HOST_TO_DEVICE = 0x01;
|
||||
static constexpr u8 USB_ENDPOINT_REQUEST_DEVICE_TO_HOST = 0x82;
|
||||
static constexpr u8 USB_ENDPOINT_REQUEST_HOST_TO_DEVICE = 0x02;
|
||||
|
||||
//
|
||||
// Standard USB request types
|
||||
//
|
||||
// These are found in Section 9.4 of the USB Spec
|
||||
//
|
||||
static constexpr u8 USB_REQUEST_GET_STATUS = 0x00;
|
||||
static constexpr u8 USB_REQUEST_CLEAR_FEATURE = 0x01;
|
||||
static constexpr u8 USB_REQUEST_SET_FEATURE = 0x03;
|
||||
static constexpr u8 USB_REQUEST_SET_ADDRESS = 0x05;
|
||||
static constexpr u8 USB_REQUEST_GET_DESCRIPTOR = 0x06;
|
||||
static constexpr u8 USB_REQUEST_SET_DESCRIPTOR = 0x07;
|
||||
static constexpr u8 USB_REQUEST_GET_CONFIGURATION = 0x08;
|
||||
static constexpr u8 USB_REQUEST_SET_CONFIGURATION = 0x09;
|
51
Kernel/Bus/USB/USBTransfer.cpp
Normal file
51
Kernel/Bus/USB/USBTransfer.cpp
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#include <Kernel/Bus/USB/USBTransfer.h>
|
||||
#include <Kernel/VM/MemoryManager.h>
|
||||
|
||||
namespace Kernel::USB {
|
||||
|
||||
RefPtr<Transfer> Transfer::try_create(Pipe& pipe, u16 len)
|
||||
{
|
||||
auto vmobject = ContiguousVMObject::create_with_size(PAGE_SIZE);
|
||||
if (!vmobject)
|
||||
return nullptr;
|
||||
|
||||
return AK::try_create<Transfer>(pipe, len, *vmobject);
|
||||
}
|
||||
|
||||
Transfer::Transfer(Pipe& pipe, u16 len, ContiguousVMObject& vmobject)
|
||||
: m_pipe(pipe)
|
||||
, m_transfer_data_size(len)
|
||||
{
|
||||
// Initialize data buffer for transfer
|
||||
// This will definitely need to be refactored in the future, I doubt this will scale well...
|
||||
m_data_buffer = MemoryManager::the().allocate_kernel_region_with_vmobject(vmobject, PAGE_SIZE, "USB Transfer Buffer", Region::Access::Read | Region::Access::Write);
|
||||
}
|
||||
|
||||
Transfer::~Transfer()
|
||||
{
|
||||
}
|
||||
|
||||
void Transfer::set_setup_packet(const USBRequestData& request)
|
||||
{
|
||||
// Kind of a nasty hack... Because the kernel isn't in the business
|
||||
// of handing out physical pointers that we can directly write to,
|
||||
// we set the address of the setup packet to be the first 8 bytes of
|
||||
// the data buffer, which we then set to the physical address.
|
||||
auto* request_data = reinterpret_cast<USBRequestData*>(buffer().as_ptr());
|
||||
|
||||
request_data->request_type = request.request_type;
|
||||
request_data->request = request.request;
|
||||
request_data->value = request.value;
|
||||
request_data->index = request.index;
|
||||
request_data->length = request.length;
|
||||
|
||||
m_request = request;
|
||||
}
|
||||
|
||||
}
|
51
Kernel/Bus/USB/USBTransfer.h
Normal file
51
Kernel/Bus/USB/USBTransfer.h
Normal file
|
@ -0,0 +1,51 @@
|
|||
/*
|
||||
* Copyright (c) 2021, Jesse Buhagiar <jooster669@gmail.com>
|
||||
*
|
||||
* SPDX-License-Identifier: BSD-2-Clause
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AK/OwnPtr.h>
|
||||
#include <AK/RefPtr.h>
|
||||
#include <Kernel/Bus/USB/PacketTypes.h>
|
||||
#include <Kernel/Bus/USB/USBPipe.h>
|
||||
#include <Kernel/VM/ContiguousVMObject.h>
|
||||
#include <Kernel/VM/PhysicalPage.h>
|
||||
#include <Kernel/VM/Region.h>
|
||||
|
||||
// TODO: Callback stuff in this class please!
|
||||
namespace Kernel::USB {
|
||||
|
||||
class Transfer : public RefCounted<Transfer> {
|
||||
public:
|
||||
static RefPtr<Transfer> try_create(Pipe& pipe, u16 len);
|
||||
|
||||
public:
|
||||
Transfer() = delete;
|
||||
Transfer(Pipe& pipe, u16 len, ContiguousVMObject&);
|
||||
~Transfer();
|
||||
|
||||
void set_setup_packet(const USBRequestData& request);
|
||||
void set_complete() { m_complete = true; }
|
||||
void set_error_occurred() { m_error_occurred = true; }
|
||||
|
||||
// `const` here makes sure we don't blow up by writing to a physical address
|
||||
const USBRequestData& request() const { return m_request; }
|
||||
const Pipe& pipe() const { return m_pipe; }
|
||||
Pipe& pipe() { return m_pipe; }
|
||||
VirtualAddress buffer() const { return m_data_buffer->vaddr(); }
|
||||
PhysicalAddress buffer_physical() const { return m_data_buffer->physical_page(0)->paddr(); }
|
||||
u16 transfer_data_size() const { return m_transfer_data_size; }
|
||||
bool complete() const { return m_complete; }
|
||||
bool error_occurred() const { return m_error_occurred; }
|
||||
|
||||
private:
|
||||
Pipe& m_pipe; // Pipe that initiated this transfer
|
||||
USBRequestData m_request; // USB request
|
||||
OwnPtr<Region> m_data_buffer; // DMA Data buffer for transaction
|
||||
u16 m_transfer_data_size { 0 }; // Size of the transfer's data stage
|
||||
bool m_complete { false }; // Has this transfer been completed?
|
||||
bool m_error_occurred { false }; // Did an error occur during this transfer?
|
||||
};
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue