📄 ohci.c
字号:
/*** Copyright 2003, Travis Geiselbrecht. All rights reserved.** Distributed under the terms of the NewOS License.*/#include <kernel/kernel.h>#include <kernel/debug.h>#include <kernel/heap.h>#include <kernel/vm.h>#include <kernel/thread.h>#include <kernel/sem.h>#include <kernel/module.h>#include <kernel/int.h>#include <string.h>#include <kernel/bus/pci/pci.h>#include <kernel/bus/usb/usb.h>#include <kernel/bus/usb/usb_hc.h>#include "ohci.h"#define debug_level_flow 6#define debug_level_error 10#define debug_level_info 10#define DEBUG_MSG_PREFIX "OHCI - "#include <kernel/debug_ext.h>#define phys_to_virt(oi, phys) (((addr_t)(phys) - (oi)->hcca_phys) + (addr_t)(oi)->hcca)static ohci *oi_list = NULL;static ohci_td *allocate_td(ohci *oi){ ohci_td *td; // pull a td from the freelist mutex_lock(&oi->td_freelist_lock); td = oi->td_freelist; if(td) oi->td_freelist = td->next; mutex_unlock(&oi->td_freelist_lock); if(!td) return NULL; td->flags = 0; td->curr_buf_ptr = 0; td->next_td = 0; td->buf_end = 0; return td;}static void free_td(ohci *oi, ohci_td *td){ mutex_lock(&oi->td_freelist_lock); td->next = oi->td_freelist; oi->td_freelist = td; mutex_unlock(&oi->td_freelist_lock);}static ohci_ed *create_ed(void){ ohci_ed *ed; ed = kmalloc(sizeof(ohci_ed)); if(!ed) return NULL; memset(ed, 0, sizeof(ohci_ed)); vm_get_page_mapping(vm_get_kernel_aspace_id(), (addr_t)ed, &ed->phys_addr); return ed;}static void enqueue_ed(ohci_ed *ed){ ohci_queue *queue = ed->queue; ohci *oi = ed->oi; mutex_lock(&oi->hc_list_lock); // stick it in our queue ed->prev_ed = NULL; ed->next_ed = queue->head; if(ed->next_ed != NULL) ed->next_ed->prev_ed = ed; queue->head = ed; // put it in the hardware's queue ed->next = *queue->head_phys; *queue->head_phys = (uint32)ed->phys_addr; mutex_unlock(&oi->hc_list_lock);}static ohci_ed *create_endpoint(ohci *oi, usb_endpoint_descriptor *endpoint, int address, bool lowspeed){ ohci_ed *ed; ohci_td *td; ed = create_ed(); if(!ed) return ed; // save a pointer to the original usb endpoint structure ed->usb_ed = endpoint; ed->oi = oi; // figure out what queue it should be in switch(endpoint->attributes & 0x3) { case USB_ENDPOINT_ATTR_CONTROL: ed->queue = &oi->control_queue; break; case USB_ENDPOINT_ATTR_ISO: // not supported break; case USB_ENDPOINT_ATTR_BULK: ed->queue = &oi->bulk_queue; break; case USB_ENDPOINT_ATTR_INT: ed->queue = &oi->interrupt_queue[INT_Q_32MS]; // XXX find the correct queue and rotate through them break; } td = allocate_td(oi); // set some hardware flags based on the usb endpoint's data ed->flags = (endpoint->max_packet_size << 16) | ((endpoint->endpoint_address & 0xf) << 7) | (address & 0x7f); if((endpoint->attributes & 0x3) == USB_ENDPOINT_ATTR_CONTROL) { ed->flags |= OHCI_ED_FLAGS_DIR_TD; } else { if(endpoint->endpoint_address & 0x80) ed->flags |= OHCI_ED_FLAGS_DIR_IN; else ed->flags |= OHCI_ED_FLAGS_DIR_OUT; } if(lowspeed) ed->flags |= OHCI_ED_FLAGS_SPEED; // stick the null transfer descriptor in our endpoint descriptors list ed->tail_td = td; ed->tail = ed->head = td->phys_addr; ed->prev_ed = ed->next_ed = NULL; // enqueue this descriptor enqueue_ed(ed); return ed;}static int ohci_done_list_processor(void *_oi){ ohci *oi = (ohci *)_oi; ohci_td *td; ohci_td *done_list; addr_t td_phys; for(;;) { sem_acquire(oi->done_list_sem, 1); if(oi->hcca->done_head != 0) SHOW_FLOW(6, "WDH 0x%x (0x%lx)", oi->hcca->done_head, phys_to_virt(oi, oi->hcca->done_head)); done_list = NULL; // pull all of the entries from the done list, put them in another list in reverse order td_phys = oi->hcca->done_head & 0xfffffff0; while(td_phys != 0) { td = (ohci_td *)phys_to_virt(oi, td_phys); td_phys = td->next_td; td->next = done_list; done_list = td; } // ack the interrupt oi->hcca->done_head = 0; oi->regs->interrupt_status = INT_WDH; td = done_list; while(done_list) { td = done_list; bool transfer_complete = false; SHOW_FLOW(6, "dealing with %p: CC 0x%x", td, OHCI_TD_FLAGS_CC(td->flags)); if(OHCI_TD_FLAGS_CC(td->flags) == OHCI_CC_NoError) { if(td->last_in_transfer) { SHOW_FLOW(6, "transfer %p now finished", td->usb_transfer); td->usb_transfer->status = NO_ERROR; transfer_complete = true; } } else { // there was an error, the endpoint is halted td->usb_transfer->status = ERR_GENERAL; // XXX make smarter transfer_complete = true; SHOW_FLOW(6, "stalled transfer, part of usb transfer %p", td->usb_transfer); // walk the head pointer of this endpoint and remove the rest of this transfer's descriptors // the next one after this one should be at the head of the list td_phys = td->ed->head & 0xfffffff0; while(td_phys != 0) { ohci_td *temp_td = (ohci_td *)phys_to_virt(oi, td_phys); SHOW_FLOW(6, "stalled transfer at %p (0x%lx), usb transfer %p", temp_td, td_phys, temp_td->usb_transfer); if(temp_td->usb_transfer != td->usb_transfer) break; // this transfer descriptor is part of the errored transfer td_phys = temp_td->next_td; free_td(oi, temp_td); } SHOW_FLOW(6, "setting head to 0x%lx, tail is at 0x%x", td_phys, td->ed->tail); td->ed->head = td_phys; // set the new head pointer oi->regs->command_status = COMMAND_CLF | COMMAND_BLF; } if(transfer_complete) { if(td->usb_transfer->callback != NULL) td->usb_transfer->callback(td->usb_transfer, td->usb_transfer->cookie); if(td->usb_transfer->completion_sem >= 0) sem_release(td->usb_transfer->completion_sem, 1); } done_list = td->next; free_td(oi, td); } } return 0;}static int ohci_interrupt(void *arg){ ohci *oi = (ohci *)arg; uint32 int_stat = oi->regs->interrupt_status; int ret = INT_NO_RESCHEDULE; int i; if((int_stat & oi->regs->interrupt_enable) == 0) return INT_NO_RESCHEDULE; SHOW_FLOW(7, "oi %p int 0x%x (%d) enabled 0x%x disabled 0x%x", oi, int_stat, oi->rh_ports, oi->regs->interrupt_enable, oi->regs->interrupt_disable); if(int_stat & INT_SO) { SHOW_FLOW0(8, "\tscheduler overrun"); } if(int_stat & INT_WDH) { SHOW_FLOW0(8, "\twriteback done head"); int_stat &= ~INT_WDH; // dont ack it here sem_release_etc(oi->done_list_sem, 1, SEM_FLAG_NO_RESCHED); ret = INT_RESCHEDULE; } if(int_stat & INT_SOF) { SHOW_FLOW0(8, "\tstart of frame"); } if(int_stat & INT_RD) { SHOW_FLOW0(8, "\tresume detected"); } if(int_stat & INT_UE) { SHOW_FLOW0(8, "\tunrecoverable error"); } if(int_stat & INT_FNO) { SHOW_FLOW0(8, "\tframe number overrun"); } if(int_stat & INT_RHSC) { SHOW_FLOW0(8, "\troot status change"); SHOW_FLOW(8, "\trh_status 0x%x", oi->regs->rh_status); for(i = 0; i < oi->rh_ports; i++) SHOW_FLOW(8, "\t\trh_port_status %d: 0x%x", i, oi->regs->rh_port_status[i]); } if(int_stat & INT_OC) { SHOW_FLOW0(8, "\townership change"); } if(int_stat & INT_MIE) { SHOW_FLOW0(8, "\tmaster interrupt enable"); } oi->regs->interrupt_status = int_stat; return ret;}static int ohci_create_endpoint(hc_cookie *cookie, hc_endpoint **hc_endpoint, usb_endpoint_descriptor *usb_endpoint, int address, bool lowspeed){ ohci *oi = (ohci *)cookie; *hc_endpoint = create_endpoint(oi, usb_endpoint, address, lowspeed); if(*hc_endpoint == NULL) return ERR_NO_MEMORY; return NO_ERROR;}static int ohci_destroy_endpoint(hc_cookie *cookie, hc_endpoint *hc_endpoint){ // XXX implement return NO_ERROR;}static int ohci_enqueue_transfer(hc_cookie *cookie, hc_endpoint *endpoint, usb_hc_transfer *transfer){ ohci *oi = (ohci *)cookie; ohci_ed *ed = (ohci_ed *)endpoint; usb_endpoint_descriptor *usb_ed = ed->usb_ed; switch(usb_ed->attributes & 0x3) { case USB_ENDPOINT_ATTR_CONTROL: { ohci_td *td0, *td1, *td2, *td3; int dir; // the direction of the transfer is based off of the first byte in the setup data dir = *((uint8 *)transfer->setup_buf) & 0x80 ? 1 : 0; mutex_lock(&oi->hc_list_lock); SHOW_FLOW(6, "head 0x%x, tail 0x%x", ed->head, ed->tail); // allocate the transfer descriptors needed td0 = ed->tail_td; td2 = allocate_td(oi); if(transfer->data_buf != NULL) td1 = allocate_td(oi); else td1 = td2; td3 = allocate_td(oi); // this will be the new null descriptor // setup descriptor td0->flags = OHCI_TD_FLAGS_CC_NOT | OHCI_TD_FLAGS_DIR_SETUP | OHCI_TD_FLAGS_IRQ_NONE | OHCI_TD_FLAGS_TOGGLE_0; vm_get_page_mapping(vm_get_kernel_aspace_id(), (addr_t)transfer->setup_buf, (addr_t *)&td0->curr_buf_ptr); td0->buf_end = td0->curr_buf_ptr + transfer->setup_len - 1; td0->next_td = td1->phys_addr; // might be td2 td0->transfer_head = td0; td0->transfer_next = td1; td0->usb_transfer = transfer; td0->ed = ed; td0->last_in_transfer = false; SHOW_FLOW(6, "td0 %p (0x%lx) flags 0x%x, curr_buf_ptr 0x%x, buf_end 0x%x, next_td 0x%x", td0, td0->phys_addr, td0->flags, td0->curr_buf_ptr, td0->buf_end, td0->next_td); if(transfer->data_buf != NULL) { // data descriptor td1->flags = OHCI_TD_FLAGS_CC_NOT | OHCI_TD_FLAGS_TOGGLE_1| OHCI_TD_FLAGS_IRQ_NONE | OHCI_TD_FLAGS_ROUNDING | (dir ? OHCI_TD_FLAGS_DIR_IN : OHCI_TD_FLAGS_DIR_OUT); vm_get_page_mapping(vm_get_kernel_aspace_id(), (addr_t)transfer->data_buf, (addr_t *)&td1->curr_buf_ptr); td1->buf_end = td1->curr_buf_ptr + transfer->data_len - 1; td1->next_td = td2->phys_addr; td1->transfer_head = td0; td1->transfer_next = td2; td1->usb_transfer = transfer; td1->ed = ed; td1->last_in_transfer = false; SHOW_FLOW(6, "td1 %p (0x%lx) flags 0x%x, curr_buf_ptr 0x%x, buf_end 0x%x, next_td 0x%x", td1, td1->phys_addr, td1->flags, td1->curr_buf_ptr, td1->buf_end, td1->next_td); } // ack descriptor td2->flags = OHCI_TD_FLAGS_CC_NOT | OHCI_TD_FLAGS_TOGGLE_1 | (dir ? OHCI_TD_FLAGS_DIR_OUT : OHCI_TD_FLAGS_DIR_IN); td2->curr_buf_ptr = 0; td2->buf_end = 0; td2->next_td = td3->phys_addr; td2->transfer_head = td0; td2->transfer_next = NULL; td2->usb_transfer = transfer; td2->ed = ed; td2->last_in_transfer = true; SHOW_FLOW(6, "td2 %p (0x%lx) flags 0x%x, curr_buf_ptr 0x%x, buf_end 0x%x, next_td 0x%x", td2, td2->phys_addr, td2->flags, td2->curr_buf_ptr, td2->buf_end, td2->next_td); // next null descriptor is td3, it should be nulled out from allocate_td() td3->ed = NULL; td3->usb_transfer = NULL; td3->transfer_head = td3->transfer_next = NULL; SHOW_FLOW(6, "td3 %p (0x%lx) flags 0x%x, curr_buf_ptr 0x%x, buf_end 0x%x, next_td 0x%x", td3, td3->phys_addr, td3->flags, td3->curr_buf_ptr, td3->buf_end, td3->next_td); ed->tail_td = td3; ed->tail = td3->phys_addr; oi->regs->command_status = COMMAND_CLF; mutex_unlock(&oi->hc_list_lock); break; } case USB_ENDPOINT_ATTR_INT: { ohci_td *td0, *td1; mutex_lock(&oi->hc_list_lock); td0 = ed->tail_td; td1 = allocate_td(oi); // XXX only deals with non page boundary data transfers td0->flags = OHCI_TD_FLAGS_CC_NOT | OHCI_TD_FLAGS_ROUNDING; vm_get_page_mapping(vm_get_kernel_aspace_id(), (addr_t)transfer->data_buf, (addr_t *)&td0->curr_buf_ptr); td0->buf_end = td0->curr_buf_ptr + transfer->data_len - 1; td0->next_td = td1->phys_addr; td0->transfer_head = td0; td0->transfer_next = td1; td0->usb_transfer = transfer; td0->ed = ed; td0->last_in_transfer = true; SHOW_FLOW(6, "td0 %p (0x%lx) flags 0x%x, curr_buf_ptr 0x%x, buf_end 0x%x, next_td 0x%x",
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -