Re: USB on real hw
Posted: Thu Feb 02, 2023 7:47 am
I've pushed it do github, here it is
https://github.com/Bonfra04/BonsOS/blob/master/serial.img
https://github.com/Bonfra04/BonsOS/blob/master/serial.img
I mean yea this is wrong but while scanning through the code this wrong read only appears inside the logs. At line 58 for example (during the setup) the reading is performed correctly.BenLunt wrote: 1) At line 253 in uhci.c, https://github.com/Bonfra04/BonsOS/blob ... hci.c#L253, you are reading a WORD when the register is a byte.
That is a typical bug that would've take me months to find XD, sadly this happens later than where the code gets stuck in real HW (which is during the assignment of the USB address, even before retrieving the device descriptros)BenLunt wrote: 2) At line 17 in scsi.c, https://github.com/Bonfra04/BonsOS/blob ... scsi.c#L17, you are setting the command length to 12. However, command 0x25 (Read Capacity) expects the length to be 10. Remove line 75 from scsi_types.h, https://github.com/Bonfra04/BonsOS/blob ... ypes.h#L75, and see what happens.
Code: Select all
00425899964i[APIC0 ] set timer divide factor to 16
Luckily, the code I am adding to Bochs found this little error. I am adding a bunch of checks to all four controller types and the devices supported to better help find bugs. The new code should be pushed to the Bochs Github as soon as I thoroughly test it.Bonfra wrote:That is a typical bug that would've take me months to find XD...
Something that I didn't even check before, I don't know why, I just assumed I guess. Your code tries to set the address first thing. My book clearly states that you cannot do this. You must get the first 8 bytes of the Device Descriptor, reset the device, set the address, then get the full Device Descriptor. This is a must. Some devices (more than you think) expect this, even though this is not USB compliant, and the device won't function correctly without it.Bonfra wrote:...sadly this happens later than where the code gets stuck in real HW (which is during the assignment of the USB address, even before retrieving the device descriptros)
So something like this should do it right?BenLunt wrote: 1) reset device
2) get the first 8 bytes of the Device Descriptor
3) reset device
4) set address
5) get all 18 bytes of the Device Descriptor
6) now you can do the rest in an order that pertains to the device.
Code: Select all
for(uint64_t i = 0; i < bus->hci.num_ports; i++)
{
if(!bus->hci.driver->reset_port(bus->hci.data, i))
continue;
if(bus->hci.driver->port_status(bus->hci.data, i) == USB_PORT_STATUS_NOT_CONNECT)
continue;
kernel_trace("Found connected USB device");
uint8_t eight[8];
if(usb_get_standard_descriptor(&(usb_device_t){.bus = bus, .addr = 0}, USB_DESCRIPTOR_DEVICE, 0, eight, 8) != USB_TRANSFER_STATUS_OK)
return;
kernel_trace("Got first 8 bytes of device descriptor: %x %x %x %x %x %x %x %x", eight[0], eight[1], eight[2], eight[3], eight[4], eight[5], eight[6], eight[7]);
if(!bus->hci.driver->reset_port(bus->hci.data, i))
continue;
if(bus->hci.driver->port_status(bus->hci.data, i) == USB_PORT_STATUS_NOT_CONNECT)
continue;
kernel_log("Registering...");
usb_register_device(bus);
}
Code: Select all
- Find any EHCI controller on the PCI bus;
- Set its op.configFlag bit to zero so that it passes control to the companion (should be done by the BIOS since It's enabled in the settings but to be sure). Nothing else is touched in the EHCI controller (not even reset);
- Find any UHCI controller on the PCI bus;
- Ensure it is port mapped
- Set its PCI privilege to MMIO | DMA | PIO
- Disable legacy support by writing 0x8F00 to the LEGSUP register
- Reset the controller
- Set and clear 5 times the GRESET bit on USBCMD with an 11 secs delay
- Check for default values on all registers
- Clear the status register
- Set the HCRESET bit on USBCMD
- Wait for 42ms and check again for a successful reset
- Disable interrupts by writing 0 to USBINTR
- Set FRNUM to 0
- Allocate a 1024 dword buffer 0x100 aligned and set all entries to be only the TERMINATE bit
- Put this address into FRBASEADD
- Set SOFMOD to SOFMOD_64
- Clear the USBSTS again by writing 0xFF
- Check the number of available ports with bit 7a nd 1:3 of PORTSC
- Finish the setup by writing USBCMD_CF | USBCMD_RUNSTOP to USBCMD
Code: Select all
- Reset the port
- Set the PORT_RESET bit on PORTSC
- Wait 50ms and clear the bit
- Wait another 10ms for recovery time
- Wait for the port to become enabled
- Check for CONNECT_STATUS
- Check for ENABLE_CHANGE | STATUS_CHANGE
- Check for ENABLE
- Set ENABLE, slep for 10ms, and repeat the process
- Discard the port if it doesn't have the CONNECT_STATUS bit set
- Get the first 8 bytes of the device descriptor
Code: Select all
- Build a request packet with (0x1000 aligned):
- type = USB_REQUEST_DIR_DEVICE_TO_HOST | USB_REQUEST_TYPE_STANDARD | USB_REQUEST_RECP_DEVICE;
- request = USB_REQUEST_GET_DESCRIPTOR;
- value = (USB_DESCRIPTOR_DEVICE << 8) | 0;
- index = 0;
- size = 8
- Use the request packet to build three USB packets (first 0x1000 aligned, subsequent follow):
- the first one has:
- type = USB_PACKET_TYPE_SETUP;
- maxlen = 8;
- buffer = <the physical address of the setup packet>;
- toggle = 0;
- the second one has:
- type = USB_PACKET_TYPE_IN;
- maxlen = 8;
- buffer = <the physical address of the 0x1000 buffer where to save the 8 bytes>;
- toggle = 1;
- the third one has:
- type = USB_PACKET_TYPE_OUT;
- maxlen = 0x800;
- buffer = NULL;
- toggle = 1;
- Build the TDs starting from the above structure (first 0x1000 aligned, subsequent follow)
- Create a queue head (0x1000 aligned) that points to the TDs
- Clear all the framelist with FRAMELIST_TERMINATE
- Set index one of the framelist to point to the queue head
- Start the schedule
- Clear USBCMD_RUNSTOP
- Write/Clear USBSTS_INT
- Set FRNUM to 0
- Set USBCMD_RUNSTOP
- Wait for USBSTS_INT to be set
- Stop the schedule
- Clear USBCMD_RUNSTOP
- Write/Clear USBSTS_INT
What if I'm requesting something like the configuration descriptor which is only 9 bytes? do i still need to allocate a buffer of 64, request all of them and then only use 9? or can i just request 9?Klakap wrote:If you have connected full speed device, you have to request all 18 bytes at once [...] And when you will read other descriptors from full speed device, also make sure you are requesting maximum possible bytes - 64.
For full speed device, you need one IN TD with maxlen 9, for low speed device, you need two IN TD, first with maxlen 8 and second with maxlen 1.Bonfra wrote: What if I'm requesting something like the configuration descriptor which is only 9 bytes? do i still need to allocate a buffer of 64, request all of them and then only use 9? or can i just request 9?
First, I agree with the speed verification. As the first request, if you detect a full-speed device, you should request 64 bytes, expecting a short packet on a short return. If you detect a low-speed device, you should request only 8 bytes, not necessarily expecting a short packet. The port's register set will indicate the speed.Klakap wrote:It looks like you are not reading speed of connected device. But it is vitally important thing. If you have connected full speed device, you have to request all 18 bytes at once. Otherwise there is hardware that will do all kind of strange things. For example, on one of my testing computer such piece of hardware resets whole computer just because I requested 8 bytes, not 18 bytes from descriptor. And when you will read other descriptors from full speed device, also make sure you are requesting maximum possible bytes - 64.
You must first get the max packet size from the Device Descriptor, which may be much less than 64 bytes on a full-speed device. Once retrieved, you should use this value for every sequential packet, expecting a possible short-packet on the last request.The data payloads for such a multiple data payload IRP are expected to be of the maximum packet size until the last data payload that contains the remainder of the overall IRP.
Because of bad choices by manufacturers testing their products only on a well-known well-used operating system that incorrectly interpreted a statement about a device in early development, the first request after a reset must be for the Device Descriptor *and* it must be a request for 8 bytes on low-speed devices, and 64-bytes on full speed devices. However, after that, this is no longer the case. You do not have to request as much as possible. It is preferred that you do, but it is not a requirement. Also, not all full-speed devices allow you to transfer 64-bytes per IN request. A full-speed mouse may only allow 8 bytes at a time, or less. This is why you request the Device Descriptor as the first descriptor so that you can retrieve the max packet size the device can transfer. From there, you only request a max size of this retrieved value, which can be less than 64.Klakap wrote:I mean that through every IN TD you have to transfer as much bytes as possible. Low speed devices allow you to transfer maximum of 8 bytes per one IN TD. But full speed devices allow you to transfer 64 bytes per one IN TD. So when you are requesting descriptor that return 18 bytes, for full speed devices you have to transfer them all in one IN TD. For low speed devices you need to use three IN TDs. Of course, do not forget to check how many bytes are you requesting in SETUP packet. Try to request all 18 bytes, and transfer them in one IN TD.
This is correct. For (most) full-speed devices, you only need one IN TD. For low-speed devices, you need at least two. However, again, you must adhere to the max_packet_size, not the speed of the device. A full-speed device can have a max_packet_size of 8 bytes.Klakap wrote:For full speed device, you need one IN TD with maxlen 9, for low speed device, you need two IN TD, first with maxlen 8 and second with maxlen 1.
My notes agree that QEMU does emulate a mouse at both full- and high-speed. However, with that being said, may I reiterate, only the first request of the Device Descriptor should be a 64-byte request. Once you know the max_packet_size due to this request, all further requests must not be more than this max_packet_size, which in this example you give, must not be more than 8 bytes for this particular full speed device. (https://gitlab.com/qemu-project/qemu/-/ ... hid.c#L265)Klakap wrote:When you read device descriptor, you have to assume size by speed and then you should use value from device descriptor. For example if I recall correctly, QEMU handle mouse and keyboard in way that it emulates them as full speed device, so device descriptor should be readed in one 18 bytes IN TD, however max control endpoint size in descriptor is 8, so you should read all other descriptors by 8 bytes long IN TD.
Code: Select all
- Reset the port
- Set the PORT_RESET bit on PORTSC
- Wait 50ms and clear the bit
- Wait another 10ms for recovery time
- Wait for the port to become enabled
- Check for CONNECT_STATUS
- Check for ENABLE_CHANGE | STATUS_CHANGE
- Check for ENABLE
- Set ENABLE, slep for 10ms, and repeat the process
- Discard the port if it doesn't have the CONNECT_STATUS bit set
- Get the first 8/64 bytes of the device descriptor (based on the speed) // It was stuck here before
- Reset the port again
- Get the full device descriptor
- Request to set the address
Code: Select all
setup packet:
- type = USB_REQUEST_DIR_HOST_TO_DEVICE | USB_REQUEST_TYPE_STANDARD;
- request = USB_REQUEST_SET_ADDRESS;
- value = <new address>;
- index = 0;
- size = 0;
which creates two usb packets
first one:
- type = USB_PACKET_TYPE_SETUP;
- maxlen = 8;
- buffer = <the 0x1000 physical address of the setup packet>;
- toggle = 0;
second one:
- type = USB_PACKET_TYPE_IN
- maxlen = 0x800;
- buffer = NULL;
- toggle = 1;