QEMU resetting EHCI just after it's initialization

Question about which tools to use, bugs, the best way to implement a function, etc should go here. Don't forget to see if your question is answered in the wiki first! When in doubt post here.
Post Reply
dainbow
Posts: 1
Joined: Sun Dec 11, 2022 10:57 pm

QEMU resetting EHCI just after it's initialization

Post by dainbow »

Hi, I wrote a code to initialize EHCI, but after some little time (about 1 second) after I switched on EHCI, qemu writes message in console:
processing error - resetting ehci HC
. My processor is at long mode, but ehci x64 bit flag isn't set. Maybe I need somehow to connect x64 EHCI to qemu? But I didn't find any flags to do it. Is QEMU supports x64 EHCI?

Code: Select all

ehci_controller.caps = (struct EhciCaps*)((uintptr_t)bar0);
    ehci_controller.regs = (struct EhciRegs*)((uintptr_t)bar0 + ehci_controller.caps->cap_length);
    ehci_controller.ehci_base2 = (uint32_t)(bar0);
    ehci_controller.ehci_base = (uint32_t)(uintptr_t)&ehci_controller.regs->usb_cmd;

    ehci_controller.frame_list = (uint32_t *)((uintptr_t)frame_list - KERN_BASE_ADDR);
#ifdef EHCI_DEBUG
    cprintf("frame_list: %p\n", ehci_controller.frame_list);
#endif // EHCI_DEBUG

    /* Init queue and td pools */
    ehci_controller.qh_pool = (struct EhciQueueHeader *)((uintptr_t)queues_pool - KERN_BASE_ADDR);
    ehci_controller.td_pool = (struct EhciTransferDescriptor *)((uintptr_t)transfer_descriptors_pool - KERN_BASE_ADDR);

    memset(ehci_controller.qh_pool, 0, sizeof (struct EhciQueueHeader) * EHCI_QH_MAX);
    memset(ehci_controller.td_pool, 0, sizeof (struct EhciTransferDescriptor) * EHCI_TD_MAX);
#ifdef EHCI_DEBUG
    cprintf("EHCI pools allocated\n");
#endif // EHCI_DEBUG

    /* Make sure we reset the controller */
    ehci_reset_controller();

    /* Async queue setup */
    struct EhciQueueHeader *async_qh = ehci_alloc_queue_header();

    async_qh->qh_link_pointer = ((uint32_t)((uintptr_t)async_qh)) | PTR_QH;
    async_qh->chars = QH_CHARS_HEAD; 
    async_qh->caps = 0;
    async_qh->cur_link = 0;
    async_qh->next_link = PTR_TERMINATE;
    async_qh->alt_link = 0;
    async_qh->token = 0;

    for (uint32_t i = 0; i < QUEUE_BUFFERS_AMOUNT; ++i) {
        async_qh->buffer[i] = 0;
    }

    async_qh->transfer = 0;                        
    async_qh->queue_list.prev = &async_qh->queue_list;
    async_qh->queue_list.next = &async_qh->queue_list;
    ehci_controller.async_qh = async_qh;

    /* Periodic table setup */
    struct EhciQueueHeader *periodic_qh = ehci_alloc_queue_header();

    periodic_qh->qh_link_pointer = PTR_TERMINATE;
    periodic_qh->chars = 0;
    periodic_qh->caps = 0;
    periodic_qh->cur_link = 0;
    periodic_qh->next_link = PTR_TERMINATE;
    periodic_qh->alt_link = 0;
    periodic_qh->token = 0;

    for (uint32_t i = 0; i < QUEUE_BUFFERS_AMOUNT; ++i) {
        periodic_qh->buffer[i] = 0;
    }

    periodic_qh->transfer = 0;                         
    periodic_qh->queue_list.prev = &periodic_qh->queue_list;
    periodic_qh->queue_list.next = &periodic_qh->queue_list;

    ehci_controller.periodic_qh = periodic_qh;
    for (uint32_t i = 0; i < FRAME_LIST_SIZE; i++) {
        ehci_controller.frame_list[i] = PTR_QH | ((uint32_t)(uintptr_t)periodic_qh);
    }

    /* Check extended capabilities */
    uint32_t eecp = (RCR(HCC_PARAMS) & HCCPARAMS_EECP_MASK) >> HCCPARAMS_EECP_SHIFT;
    if (eecp >= 0x40) {
        uint32_t legacy_support = pci_config_read_dword(info->bus, info->device, info->function, eecp + USBLEGSUP);
        if (legacy_support & USBLEGSUP_HC_BIOS) {
        #ifdef EHCI_DEBUG
            cprintf("Disabling BIOS legacy support\n");
        #endif

            pci_config_write_dword(info->bus, info->device, info->function, eecp + USBLEGSUP, legacy_support | USBLEGSUP_HC_OS);
            for (;;) {
                legacy_support = pci_config_read_dword(info->bus, info->device, info->function, eecp + USBLEGSUP);
                if (((legacy_support & USBLEGSUP_HC_BIOS) == 0) && (legacy_support & USBLEGSUP_HC_OS)) {
                    break;
                }
            }
        }
    }

    /* Disable interrupts */
    WOR(USB_INTR, 0);

    /* Setup frame list */
    WOR(FRAME_INDEX, 0);
    WOR(PERIODIC_LIST_BASE, (uint32_t)(uintptr_t)ehci_controller.frame_list);
    WOR(ASYNC_LIST_ADDR, (uint32_t)(uintptr_t)ehci_controller.async_qh);
    WOR(CTRL_D_SEGMENT, 0);

    /* Clear USB status, just set first bits to 1's to discard last changes */
    WOR(USB_STS, ~0);

    /* Enable controller */
    /* (8 << CMD_ITC_SHIFT) - interrupt threshold control - 8 micro-frames */
    WOR(USB_CMD, (8 << CMD_ITC_SHIFT) | CMD_ASE | CMD_PSE | CMD_RS);

    /* Wait for halt */
    while (ROR(USB_STS) & STS_HCHALTED);

#ifdef EHCI_DEBUG
    cprintf("Ctrl d segment reg is 0x%x\n", ROR(CTRL_D_SEGMENT));
    cprintf("Async list address is 0x%x\n", ROR(ASYNC_LIST_ADDR));
    cprintf("Periodic list base if 0x%x\n", ROR(PERIODIC_LIST_BASE));
#endif

    /* Sets that all devices should be managed by the EHCI */
    WOR(CONFIG_FLAG, 1);
#ifdef EHCI_DEBUG
    cprintf("EHCI setup done. Probing ports.\n");
#endif
Octocontrabass
Member
Member
Posts: 5563
Joined: Mon Mar 25, 2013 7:01 pm

Re: QEMU resetting EHCI just after it's initialization

Post by Octocontrabass »

dainbow wrote:Hi, I wrote a code to initialize EHCI, but after some little time (about 1 second) after I switched on EHCI, qemu writes message in console:
That error message comes from this line in the QEMU source code, but I'm not familiar enough with EHCI to figure out what situations cause that message to appear.
dainbow wrote:My processor is at long mode, but ehci x64 bit flag isn't set.
That's normal. Many PC devices only support 32-bit addressing, even with a 64-bit CPU.
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: QEMU resetting EHCI just after it's initialization

Post by BenLunt »

Hi,

First off, I don't like the EHCI for various reasons, and my suggestion to you is to skip it and move on to one of the other controllers, but this is just my humble opinion.

Second, I have a few questions/comments:
1) You don't have a URL to any other code, so I am assuming the 'ehci_controller' structure is byte packed. yes?
2) While you initialize the 'next_link', to be safe, you should initialize the 'alt_link' as well:

Code: Select all

   async_qh->next_link = PTR_TERMINATE;
   async_qh->alt_link = 0;   //<------- should be PTR_TERMINATE
...and later...

Code: Select all

periodic_qh->alt_link = 0;
...should be set the same. There were faulty EHCI controllers out there that wouldn't handle the NULL pointer well.
3) There should be more than one queue in your Async list.

Code: Select all

async_qh->qh_link_pointer = ((uint32_t)((uintptr_t)async_qh)) | PTR_QH;
Points back to itself. This is valid, but usually not normal. It is normal to have multiple (at least 2) queues in your Async list. A Queue is 48 bytes and must be 32-byte aligned. Does 'ehci_alloc_queue_header()' return a 32-byte aligned memory location?
4) Does 'ehci_alloc_queue_header()' return a 4k aligned address? The Periodical List must be 4k aligned.
5) With...

Code: Select all

 /* Wait for halt */
    while (ROR(USB_STS) & STS_HCHALTED);
...Maybe change to

Code: Select all

 /* After setting the Run but, wait for halt bit to clear, indicating we actually started */
    while (ROR(USB_STS) & STS_HCHALTED);
...just so your comment doesn't throw off your thinking. Also, have you taken steps to make sure the while() loop is not optimized out?
6) Do you have a bootable image file you can share?

Ben
- https://www.fysnet.net/the_universal_serial_bus.htm
User avatar
BenLunt
Member
Member
Posts: 941
Joined: Sat Nov 22, 2014 6:33 pm
Location: USA
Contact:

Re: QEMU resetting EHCI just after it's initialization

Post by BenLunt »

Just wondering if you made any progress.

Ben
Post Reply