mirror of
				https://github.com/RGBCube/serenity
				synced 2025-10-31 16:52:43 +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
	
	 Liav A
						Liav A