/* * Copyright (c) 2020-2021, Andreas Kling * Copyright (c) 2020, Jesse Buhagiar * * SPDX-License-Identifier: BSD-2-Clause */ #include #include #include #include #include #include #include #include #include #include #include #include #include static constexpr u8 RETRY_COUNTER_RELOAD = 3; namespace Kernel::USB { 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_CURRENT_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; static constexpr u16 UHCI_PORTSC_NON_WRITE_CLEAR_BIT_MASK = 0x1FF5; // This is used to mask out the Write Clear bits making sure we don't accidentally clear them. // *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; ErrorOr> UHCIController::try_to_initialize(PCI::DeviceIdentifier const& pci_device_identifier) { // NOTE: This assumes that address is pointing to a valid UHCI controller. auto registers_io_window = TRY(IOWindow::create_for_pci_device_bar(pci_device_identifier, PCI::HeaderType0BaseRegister::BAR4)); auto controller = TRY(adopt_nonnull_lock_ref_or_enomem(new (nothrow) UHCIController(pci_device_identifier, move(registers_io_window)))); TRY(controller->initialize()); return controller; } ErrorOr UHCIController::initialize() { dmesgln_pci(*this, "Controller found {} @ {}", PCI::get_hardware_id(device_identifier()), device_identifier().address()); dmesgln_pci(*this, "I/O base {}", m_registers_io_window); dmesgln_pci(*this, "Interrupt line: {}", interrupt_number()); TRY(spawn_async_poll_process()); TRY(spawn_port_process()); TRY(reset()); return start(); } UNMAP_AFTER_INIT UHCIController::UHCIController(PCI::DeviceIdentifier const& pci_device_identifier, NonnullOwnPtr registers_io_window) : PCI::Device(const_cast(pci_device_identifier)) , IRQHandler(pci_device_identifier.interrupt_line().value()) , m_registers_io_window(move(registers_io_window)) { } UNMAP_AFTER_INIT UHCIController::~UHCIController() = default; ErrorOr UHCIController::reset() { TRY(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) m_framelist = TRY(MM.allocate_dma_buffer_page("UHCI Framelist"sv, Memory::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 TRY(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"); return {}; } UNMAP_AFTER_INIT ErrorOr UHCIController::create_structures() { m_queue_head_pool = TRY(UHCIDescriptorPool::try_create("Queue Head Pool"sv)); // Doesn't do anything other than give interrupt transfer queues something to set as prev QH so that we don't have to handle that as an extra edge case m_schedule_begin_anchor = allocate_queue_head(); // Create the Interrupt, Full Speed, Low Speed Control and Bulk Queue Heads m_interrupt_qh_anchor = allocate_queue_head(); m_ls_control_qh_anchor = allocate_queue_head(); m_fs_control_qh_anchor = allocate_queue_head(); m_bulk_qh_anchor = allocate_queue_head(); // Now the Transfer Descriptor pool m_transfer_descriptor_pool = TRY(UHCIDescriptorPool::try_create("Transfer Descriptor Pool"sv)); m_isochronous_transfer_pool = TRY(MM.allocate_dma_buffer_page("UHCI Isochronous Descriptor Pool"sv, Memory::Region::Access::ReadWrite)); // 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(m_isochronous_transfer_pool->vaddr().get() + (i * sizeof(Kernel::USB::TransferDescriptor))); auto paddr = static_cast(m_isochronous_transfer_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(); if constexpr (UHCI_VERBOSE_DEBUG) transfer_descriptor->print(); } if constexpr (UHCI_DEBUG) { dbgln("UHCI: Pool information:"); m_queue_head_pool->print_pool_information(); m_transfer_descriptor_pool->print_pool_information(); } return {}; } 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_schedule_begin_anchor->link_next_queue_head(m_interrupt_qh_anchor); m_schedule_begin_anchor->terminate_element_link_ptr(); m_interrupt_qh_anchor->link_next_queue_head(m_ls_control_qh_anchor); m_interrupt_qh_anchor->terminate_element_link_ptr(); m_ls_control_qh_anchor->link_next_queue_head(m_fs_control_qh_anchor); m_ls_control_qh_anchor->terminate_element_link_ptr(); m_fs_control_qh_anchor->link_next_queue_head(m_bulk_qh_anchor); m_fs_control_qh_anchor->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_bulk_qh_anchor->link_next_queue_head(m_fs_control_qh_anchor); m_bulk_qh_anchor->attach_transfer_descriptor_chain(piix4_td_hack); u32* framelist = reinterpret_cast(m_framelist->vaddr().as_ptr()); for (int frame_num = 0; frame_num < UHCI_NUMBER_OF_FRAMES; frame_num++) { auto& frame_iso_td = m_iso_td_list.at(frame_num % UHCI_NUMBER_OF_ISOCHRONOUS_TDS); frame_iso_td->link_queue_head(m_schedule_begin_anchor->paddr()); framelist[frame_num] = frame_iso_td->paddr(); } m_interrupt_qh_anchor->print(); m_ls_control_qh_anchor->print(); m_fs_control_qh_anchor->print(); m_bulk_qh_anchor->print(); } QueueHead* UHCIController::allocate_queue_head() { return m_queue_head_pool->try_take_free_descriptor(); } TransferDescriptor* UHCIController::allocate_transfer_descriptor() { return m_transfer_descriptor_pool->try_take_free_descriptor(); } ErrorOr UHCIController::stop() { write_usbcmd(read_usbcmd() & ~UHCI_USBCMD_RUN); // FIXME: Timeout for (;;) { if (read_usbsts() & UHCI_USBSTS_HOST_CONTROLLER_HALTED) break; } return {}; } ErrorOr UHCIController::start() { write_usbcmd(read_usbcmd() | UHCI_USBCMD_RUN); // FIXME: Timeout for (;;) { if (!(read_usbsts() & UHCI_USBSTS_HOST_CONTROLLER_HALTED)) break; } dbgln("UHCI: Started"); m_root_hub = TRY(UHCIRootHub::try_create(*this)); TRY(m_root_hub->setup({})); return {}; } 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(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; } ErrorOr UHCIController::create_chain(Pipe& pipe, PacketID direction, Ptr32& 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::addition_would_overflow(reinterpret_cast(&*buffer_address), byte_count)) return EOVERFLOW; auto buffer_pointer = Ptr32(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 {}; } void UHCIController::free_descriptor_chain(TransferDescriptor* first_descriptor) { TransferDescriptor* descriptor = first_descriptor; while (descriptor) { TransferDescriptor* next = descriptor->next_td(); descriptor->free(); m_transfer_descriptor_pool->release_to_pool(descriptor); descriptor = next; } } void UHCIController::enqueue_qh(QueueHead* transfer_queue, QueueHead* anchor) { SpinlockLocker locker(m_schedule_lock); auto prev_qh = anchor->prev_qh(); prev_qh->link_next_queue_head(transfer_queue); transfer_queue->link_next_queue_head(anchor); } void UHCIController::dequeue_qh(QueueHead* transfer_queue) { SpinlockLocker locker(m_schedule_lock); transfer_queue->prev_qh()->link_next_queue_head(transfer_queue->next_qh()); } ErrorOr UHCIController::create_transfer_queue(Transfer& transfer) { Pipe& pipe = transfer.pipe(); // Create a new descriptor chain TransferDescriptor* last_data_descriptor; TransferDescriptor* data_descriptor_chain; auto buffer_address = Ptr32(transfer.buffer_physical().as_ptr()); TRY(create_chain(pipe, transfer.pipe().direction() == Pipe::Direction::In ? PacketID::IN : PacketID::OUT, buffer_address, pipe.max_packet_size(), transfer.transfer_data_size(), &data_descriptor_chain, &last_data_descriptor)); last_data_descriptor->terminate(); if constexpr (UHCI_VERBOSE_DEBUG) { if (data_descriptor_chain) { dbgln("Data TD"); data_descriptor_chain->print(); } } QueueHead* transfer_queue = allocate_queue_head(); if (!transfer_queue) { free_descriptor_chain(data_descriptor_chain); return ENOMEM; } transfer_queue->attach_transfer_descriptor_chain(data_descriptor_chain); transfer_queue->set_transfer(&transfer); return transfer_queue; } ErrorOr UHCIController::submit_async_transfer(NonnullOwnPtr async_handle, QueueHead* anchor, QueueHead* transfer_queue) { { SpinlockLocker locker { m_async_lock }; auto iter = find_if(m_active_async_transfers.begin(), m_active_async_transfers.end(), [](auto& handle) { return handle == nullptr; }); if (iter == m_active_async_transfers.end()) return ENOMEM; *iter = move(async_handle); } enqueue_qh(transfer_queue, anchor); return {}; } void UHCIController::cancel_async_transfer(NonnullLockRefPtr transfer) { SpinlockLocker locker { m_async_lock }; auto iter = find_if(m_active_async_transfers.begin(), m_active_async_transfers.end(), [transfer](auto& handle) { return handle != nullptr && handle->transfer.ptr() == transfer.ptr(); }); if (iter == m_active_async_transfers.end()) { dbgln("Error: couldn't cancel supplied async transfer"); return; // We can't really do anything here, so just give up } auto& transfer_queue = (*iter)->qh; dequeue_qh(transfer_queue); free_descriptor_chain(transfer_queue->get_first_td()); transfer_queue->free(); m_queue_head_pool->release_to_pool(transfer_queue); *iter = nullptr; } ErrorOr 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_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST) == USB_REQUEST_TRANSFER_DIRECTION_DEVICE_TO_HOST; dbgln_if(UHCI_DEBUG, "UHCI: Received control transfer for address {}. Root Hub is at address {}.", pipe.device_address(), m_root_hub->device_address()); // Short-circuit the root hub. if (pipe.device_address() == m_root_hub->device_address()) return m_root_hub->handle_control_transfer(transfer); 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(transfer.buffer_physical().as_ptr() + sizeof(USBRequestData)); TRY(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)); // 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 ENOMEM; } transfer_queue->attach_transfer_descriptor_chain(setup_td); transfer_queue->set_transfer(&transfer); enqueue_qh(transfer_queue, m_fs_control_qh_anchor); size_t transfer_size = 0; while (!transfer.complete()) { dbgln_if(USB_DEBUG, "Control transfer size: {}", transfer_size); transfer_size = poll_transfer_queue(*transfer_queue); } dequeue_qh(transfer_queue); free_descriptor_chain(transfer_queue->get_first_td()); transfer_queue->free(); m_queue_head_pool->release_to_pool(transfer_queue); return transfer_size; } ErrorOr UHCIController::submit_bulk_transfer(Transfer& transfer) { auto transfer_queue = TRY(create_transfer_queue(transfer)); enqueue_qh(transfer_queue, m_bulk_qh_anchor); dbgln_if(UHCI_DEBUG, "UHCI: Received bulk transfer for address {}. Root Hub is at address {}.", transfer.pipe().device_address(), m_root_hub->device_address()); size_t transfer_size = 0; while (!transfer.complete()) { transfer_size = poll_transfer_queue(*transfer_queue); dbgln_if(USB_DEBUG, "Bulk transfer size: {}", transfer_size); } dequeue_qh(transfer_queue); free_descriptor_chain(transfer_queue->get_first_td()); transfer_queue->free(); m_queue_head_pool->release_to_pool(transfer_queue); return transfer_size; } ErrorOr UHCIController::submit_async_interrupt_transfer(NonnullLockRefPtr transfer, u16 ms_interval) { dbgln_if(UHCI_DEBUG, "UHCI: Received interrupt transfer for address {}. Root Hub is at address {}.", transfer->pipe().device_address(), m_root_hub->device_address()); if (ms_interval == 0) { return EINVAL; } auto transfer_queue = TRY(create_transfer_queue(*transfer)); auto async_transfer_handle = TRY(adopt_nonnull_own_or_enomem(new (nothrow) AsyncTransferHandle { transfer, transfer_queue, ms_interval })); TRY(submit_async_transfer(move(async_transfer_handle), m_interrupt_qh_anchor, transfer_queue)); return {}; } 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::NAKReceived) { transfer_still_in_progress = false; break; } 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; } ErrorOr UHCIController::spawn_port_process() { TRY(Process::create_kernel_process("UHCI Hot Plug Task"sv, [&] { while (!Process::current().is_dying()) { if (m_root_hub) m_root_hub->check_for_port_updates(); (void)Thread::current()->sleep(Duration::from_seconds(1)); } Process::current().sys$exit(0); VERIFY_NOT_REACHED(); })); return {}; } ErrorOr UHCIController::spawn_async_poll_process() { TRY(Process::create_kernel_process("UHCI Async Poll Task"sv, [&] { u16 poll_interval_ms = 1024; while (!Process::current().is_dying()) { { SpinlockLocker locker { m_async_lock }; for (OwnPtr& handle : m_active_async_transfers) { if (handle != nullptr) { poll_interval_ms = min(poll_interval_ms, handle->ms_poll_interval); QueueHead* qh = handle->qh; for (auto td = qh->get_first_td(); td != nullptr && !td->active(); td = td->next_td()) { if (td->next_td() == nullptr) { // Finished QH handle->transfer->invoke_async_callback(); qh->reinitialize(); // Set the QH to be active again } } } } } (void)Thread::current()->sleep(Duration::from_milliseconds(poll_interval_ms)); } Process::current().sys$exit(0); VERIFY_NOT_REACHED(); })); return {}; } bool UHCIController::handle_irq(RegisterState const&) { 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; } void UHCIController::get_port_status(Badge, u8 port, HubStatus& hub_port_status) { // The check is done by UHCIRootHub. VERIFY(port < NUMBER_OF_ROOT_PORTS); u16 status = port == 0 ? read_portsc1() : read_portsc2(); if (status & UHCI_PORTSC_CURRENT_CONNECT_STATUS) hub_port_status.status |= PORT_STATUS_CURRENT_CONNECT_STATUS; if (status & UHCI_PORTSC_CONNECT_STATUS_CHANGED) hub_port_status.change |= PORT_STATUS_CONNECT_STATUS_CHANGED; if (status & UHCI_PORTSC_PORT_ENABLED) hub_port_status.status |= PORT_STATUS_PORT_ENABLED; if (status & UHCI_PORTSC_PORT_ENABLE_CHANGED) hub_port_status.change |= PORT_STATUS_PORT_ENABLED_CHANGED; if (status & UHCI_PORTSC_LOW_SPEED_DEVICE) hub_port_status.status |= PORT_STATUS_LOW_SPEED_DEVICE_ATTACHED; if (status & UHCI_PORTSC_PORT_RESET) hub_port_status.status |= PORT_STATUS_RESET; if (m_port_reset_change_statuses & (1 << port)) hub_port_status.change |= PORT_STATUS_RESET_CHANGED; if (status & UHCI_PORTSC_SUSPEND) hub_port_status.status |= PORT_STATUS_SUSPEND; if (m_port_suspend_change_statuses & (1 << port)) hub_port_status.change |= PORT_STATUS_SUSPEND_CHANGED; // UHCI ports are always powered. hub_port_status.status |= PORT_STATUS_PORT_POWER; dbgln_if(UHCI_DEBUG, "UHCI: get_port_status status={:#04x} change={:#04x}", hub_port_status.status, hub_port_status.change); } void UHCIController::reset_port(u8 port) { // We still have to reset the port manually because UHCI does not automatically enable the port after reset. // Additionally, the USB 2.0 specification says the SetPortFeature(PORT_ENABLE) request is not specified and that the _ideal_ behavior is to return a Request Error. // Source: USB 2.0 Specification Section 11.24.2.7.1.2 // This means the hub code cannot rely on using it. // The check is done by UHCIRootHub and set_port_feature. VERIFY(port < NUMBER_OF_ROOT_PORTS); u16 port_data = port == 0 ? read_portsc1() : read_portsc2(); port_data &= UHCI_PORTSC_NON_WRITE_CLEAR_BIT_MASK; port_data |= UHCI_PORTSC_PORT_RESET; if (port == 0) write_portsc1(port_data); else write_portsc2(port_data); // Wait at least 50 ms for the port to reset. // This is T DRSTR in the USB 2.0 Specification Page 186 Table 7-13. constexpr u16 reset_delay = 50 * 1000; microseconds_delay(reset_delay); port_data &= ~UHCI_PORTSC_PORT_RESET; if (port == 0) write_portsc1(port_data); else write_portsc2(port_data); // Wait 10 ms for the port to recover. // This is T RSTRCY in the USB 2.0 Specification Page 188 Table 7-14. constexpr u16 reset_recovery_delay = 10 * 1000; microseconds_delay(reset_recovery_delay); port_data = port == 0 ? read_portsc1() : read_portsc2(); port_data |= UHCI_PORTSC_PORT_ENABLED; if (port == 0) write_portsc1(port_data); else write_portsc2(port_data); dbgln_if(UHCI_DEBUG, "UHCI: Port should be enabled now: {:#04x}", port == 0 ? read_portsc1() : read_portsc2()); m_port_reset_change_statuses |= (1 << port); } ErrorOr UHCIController::set_port_feature(Badge, u8 port, HubFeatureSelector feature_selector) { // The check is done by UHCIRootHub. VERIFY(port < NUMBER_OF_ROOT_PORTS); dbgln_if(UHCI_DEBUG, "UHCI: set_port_feature: port={} feature_selector={}", port, (u8)feature_selector); switch (feature_selector) { case HubFeatureSelector::PORT_POWER: // Ignore the request. UHCI ports are always powered. break; case HubFeatureSelector::PORT_RESET: reset_port(port); break; case HubFeatureSelector::PORT_SUSPEND: { u16 port_data = port == 0 ? read_portsc1() : read_portsc2(); port_data &= UHCI_PORTSC_NON_WRITE_CLEAR_BIT_MASK; port_data |= UHCI_PORTSC_SUSPEND; if (port == 0) write_portsc1(port_data); else write_portsc2(port_data); m_port_suspend_change_statuses |= (1 << port); break; } default: dbgln("UHCI: Unknown feature selector in set_port_feature: {}", (u8)feature_selector); return EINVAL; } return {}; } ErrorOr UHCIController::clear_port_feature(Badge, u8 port, HubFeatureSelector feature_selector) { // The check is done by UHCIRootHub. VERIFY(port < NUMBER_OF_ROOT_PORTS); dbgln_if(UHCI_DEBUG, "UHCI: clear_port_feature: port={} feature_selector={}", port, (u8)feature_selector); u16 port_data = port == 0 ? read_portsc1() : read_portsc2(); port_data &= UHCI_PORTSC_NON_WRITE_CLEAR_BIT_MASK; switch (feature_selector) { case HubFeatureSelector::PORT_ENABLE: port_data &= ~UHCI_PORTSC_PORT_ENABLED; break; case HubFeatureSelector::PORT_SUSPEND: port_data &= ~UHCI_PORTSC_SUSPEND; break; case HubFeatureSelector::PORT_POWER: // Ignore the request. UHCI ports are always powered. break; case HubFeatureSelector::C_PORT_CONNECTION: // This field is Write Clear. port_data |= UHCI_PORTSC_CONNECT_STATUS_CHANGED; break; case HubFeatureSelector::C_PORT_RESET: m_port_reset_change_statuses &= ~(1 << port); break; case HubFeatureSelector::C_PORT_ENABLE: // This field is Write Clear. port_data |= UHCI_PORTSC_PORT_ENABLE_CHANGED; break; case HubFeatureSelector::C_PORT_SUSPEND: m_port_suspend_change_statuses &= ~(1 << port); break; default: dbgln("UHCI: Unknown feature selector in clear_port_feature: {}", (u8)feature_selector); return EINVAL; } dbgln_if(UHCI_DEBUG, "UHCI: clear_port_feature: writing {:#04x} to portsc{}.", port_data, port + 1); if (port == 0) write_portsc1(port_data); else write_portsc2(port_data); return {}; } }