hc.c

Go to the documentation of this file.
00001 /*
00002  * Copyright (c) 2011 Jan Vesely
00003  * All rights reserved.
00004  *
00005  * Redistribution and use in source and binary forms, with or without
00006  * modification, are permitted provided that the following conditions
00007  * are met:
00008  *
00009  * - Redistributions of source code must retain the above copyright
00010  *   notice, this list of conditions and the following disclaimer.
00011  * - Redistributions in binary form must reproduce the above copyright
00012  *   notice, this list of conditions and the following disclaimer in the
00013  *   documentation and/or other materials provided with the distribution.
00014  * - The name of the author may not be used to endorse or promote products
00015  *   derived from this software without specific prior written permission.
00016  *
00017  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
00018  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
00019  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
00020  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
00021  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
00022  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
00023  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
00024  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
00025  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
00026  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
00027  */
00034 #include <errno.h>
00035 #include <str_error.h>
00036 #include <adt/list.h>
00037 #include <libarch/ddi.h>
00038 
00039 #include <usb/debug.h>
00040 #include <usb/usb.h>
00041 #include <usb/ddfiface.h>
00042 
00043 #include "hc.h"
00044 #include "hcd_endpoint.h"
00045 
00046 #define OHCI_USED_INTERRUPTS \
00047     (I_SO | I_WDH | I_UE | I_RHSC)
00048 static int interrupt_emulator(hc_t *instance);
00049 static void hc_gain_control(hc_t *instance);
00050 static int hc_init_transfer_lists(hc_t *instance);
00051 static int hc_init_memory(hc_t *instance);
00052 /*----------------------------------------------------------------------------*/
00059 int hc_register_hub(hc_t *instance, ddf_fun_t *hub_fun)
00060 {
00061         assert(instance);
00062         assert(hub_fun);
00063 
00064         const usb_address_t hub_address =
00065             device_keeper_get_free_address(&instance->manager, USB_SPEED_FULL);
00066         if (hub_address <= 0) {
00067                 usb_log_error("Failed(%d) to get OHCI root hub address.\n",
00068                     hub_address);
00069                 return hub_address;
00070         }
00071         instance->rh.address = hub_address;
00072         usb_device_keeper_bind(
00073             &instance->manager, hub_address, hub_fun->handle);
00074 
00075 #define CHECK_RET_RELEASE(ret, message...) \
00076 if (ret != EOK) { \
00077         usb_log_error(message); \
00078         hc_remove_endpoint(instance, hub_address, 0, USB_DIRECTION_BOTH); \
00079         usb_device_keeper_release(&instance->manager, hub_address); \
00080         return ret; \
00081 } else (void)0
00082 
00083         int ret = hc_add_endpoint(instance, hub_address, 0, USB_SPEED_FULL,
00084             USB_TRANSFER_CONTROL, USB_DIRECTION_BOTH, 64, 0, 0);
00085         CHECK_RET_RELEASE(ret, "Failed(%d) to add OHCI rh endpoint 0.\n", ret);
00086 
00087         char *match_str = NULL;
00088         /* DDF needs heap allocated string */
00089         ret = asprintf(&match_str, "usb&class=hub");
00090         ret = ret > 0 ? 0 : ret;
00091         CHECK_RET_RELEASE(ret, "Failed(%d) to create match-id string.\n", ret);
00092 
00093         ret = ddf_fun_add_match_id(hub_fun, match_str, 100);
00094         CHECK_RET_RELEASE(ret, "Failed(%d) add root hub match-id.\n", ret);
00095 
00096         ret = ddf_fun_bind(hub_fun);
00097         CHECK_RET_RELEASE(ret, "Failed(%d) to bind root hub function.\n", ret);
00098 
00099         return EOK;
00100 #undef CHECK_RET_RELEASE
00101 }
00102 /*----------------------------------------------------------------------------*/
00111 int hc_init(hc_t *instance, uintptr_t regs, size_t reg_size, bool interrupts)
00112 {
00113         assert(instance);
00114         int ret = EOK;
00115 #define CHECK_RET_RETURN(ret, message...) \
00116 if (ret != EOK) { \
00117         usb_log_error(message); \
00118         return ret; \
00119 } else (void)0
00120 
00121         ret = pio_enable((void*)regs, reg_size, (void**)&instance->registers);
00122         CHECK_RET_RETURN(ret,
00123             "Failed(%d) to gain access to device registers: %s.\n",
00124             ret, str_error(ret));
00125 
00126         list_initialize(&instance->pending_batches);
00127         usb_device_keeper_init(&instance->manager);
00128         ret = usb_endpoint_manager_init(&instance->ep_manager,
00129             BANDWIDTH_AVAILABLE_USB11);
00130         CHECK_RET_RETURN(ret, "Failed to initialize endpoint manager: %s.\n",
00131             str_error(ret));
00132 
00133         ret = hc_init_memory(instance);
00134         CHECK_RET_RETURN(ret, "Failed to create OHCI memory structures: %s.\n",
00135             str_error(ret));
00136 #undef CHECK_RET_RETURN
00137 
00138         fibril_mutex_initialize(&instance->guard);
00139         hc_gain_control(instance);
00140 
00141         rh_init(&instance->rh, instance->registers);
00142 
00143         if (!interrupts) {
00144                 instance->interrupt_emulator =
00145                     fibril_create((int(*)(void*))interrupt_emulator, instance);
00146                 fibril_add_ready(instance->interrupt_emulator);
00147         }
00148 
00149         return EOK;
00150 }
00151 /*----------------------------------------------------------------------------*/
00165 int hc_add_endpoint(
00166     hc_t *instance, usb_address_t address, usb_endpoint_t endpoint,
00167     usb_speed_t speed, usb_transfer_type_t type, usb_direction_t direction,
00168     size_t mps, size_t size, unsigned interval)
00169 {
00170         endpoint_t *ep = malloc(sizeof(endpoint_t));
00171         if (ep == NULL)
00172                 return ENOMEM;
00173         int ret =
00174             endpoint_init(ep, address, endpoint, direction, type, speed, mps);
00175         if (ret != EOK) {
00176                 free(ep);
00177                 return ret;
00178         }
00179 
00180         hcd_endpoint_t *hcd_ep = hcd_endpoint_assign(ep);
00181         if (hcd_ep == NULL) {
00182                 endpoint_destroy(ep);
00183                 return ENOMEM;
00184         }
00185 
00186         ret = usb_endpoint_manager_register_ep(&instance->ep_manager, ep, size);
00187         if (ret != EOK) {
00188                 hcd_endpoint_clear(ep);
00189                 endpoint_destroy(ep);
00190                 return ret;
00191         }
00192 
00193         /* Enqueue hcd_ep */
00194         switch (ep->transfer_type) {
00195         case USB_TRANSFER_CONTROL:
00196                 instance->registers->control &= ~C_CLE;
00197                 endpoint_list_add_ep(
00198                     &instance->lists[ep->transfer_type], hcd_ep);
00199                 instance->registers->control_current = 0;
00200                 instance->registers->control |= C_CLE;
00201                 break;
00202         case USB_TRANSFER_BULK:
00203                 instance->registers->control &= ~C_BLE;
00204                 endpoint_list_add_ep(
00205                     &instance->lists[ep->transfer_type], hcd_ep);
00206                 instance->registers->control |= C_BLE;
00207                 break;
00208         case USB_TRANSFER_ISOCHRONOUS:
00209         case USB_TRANSFER_INTERRUPT:
00210                 instance->registers->control &= (~C_PLE & ~C_IE);
00211                 endpoint_list_add_ep(
00212                     &instance->lists[ep->transfer_type], hcd_ep);
00213                 instance->registers->control |= C_PLE | C_IE;
00214                 break;
00215         default:
00216                 break;
00217         }
00218 
00219         return EOK;
00220 }
00221 /*----------------------------------------------------------------------------*/
00230 int hc_remove_endpoint(hc_t *instance, usb_address_t address,
00231     usb_endpoint_t endpoint, usb_direction_t direction)
00232 {
00233         assert(instance);
00234         fibril_mutex_lock(&instance->guard);
00235         endpoint_t *ep = usb_endpoint_manager_get_ep(&instance->ep_manager,
00236             address, endpoint, direction, NULL);
00237         if (ep == NULL) {
00238                 usb_log_error("Endpoint unregister failed: No such EP.\n");
00239                 fibril_mutex_unlock(&instance->guard);
00240                 return ENOENT;
00241         }
00242 
00243         hcd_endpoint_t *hcd_ep = hcd_endpoint_get(ep);
00244         if (hcd_ep) {
00245                 /* Dequeue hcd_ep */
00246                 switch (ep->transfer_type) {
00247                 case USB_TRANSFER_CONTROL:
00248                         instance->registers->control &= ~C_CLE;
00249                         endpoint_list_remove_ep(
00250                             &instance->lists[ep->transfer_type], hcd_ep);
00251                         instance->registers->control_current = 0;
00252                         instance->registers->control |= C_CLE;
00253                         break;
00254                 case USB_TRANSFER_BULK:
00255                         instance->registers->control &= ~C_BLE;
00256                         endpoint_list_remove_ep(
00257                             &instance->lists[ep->transfer_type], hcd_ep);
00258                         instance->registers->control |= C_BLE;
00259                         break;
00260                 case USB_TRANSFER_ISOCHRONOUS:
00261                 case USB_TRANSFER_INTERRUPT:
00262                         instance->registers->control &= (~C_PLE & ~C_IE);
00263                         endpoint_list_remove_ep(
00264                             &instance->lists[ep->transfer_type], hcd_ep);
00265                         instance->registers->control |= C_PLE | C_IE;
00266                         break;
00267                 default:
00268                         break;
00269                 }
00270                 hcd_endpoint_clear(ep);
00271         } else {
00272                 usb_log_warning("Endpoint without hcd equivalent structure.\n");
00273         }
00274         int ret = usb_endpoint_manager_unregister_ep(&instance->ep_manager,
00275             address, endpoint, direction);
00276         fibril_mutex_unlock(&instance->guard);
00277         return ret;
00278 }
00279 /*----------------------------------------------------------------------------*/
00289 endpoint_t * hc_get_endpoint(hc_t *instance, usb_address_t address,
00290     usb_endpoint_t endpoint, usb_direction_t direction, size_t *bw)
00291 {
00292         assert(instance);
00293         fibril_mutex_lock(&instance->guard);
00294         endpoint_t *ep = usb_endpoint_manager_get_ep(&instance->ep_manager,
00295             address, endpoint, direction, bw);
00296         fibril_mutex_unlock(&instance->guard);
00297         return ep;
00298 }
00299 /*----------------------------------------------------------------------------*/
00306 int hc_schedule(hc_t *instance, usb_transfer_batch_t *batch)
00307 {
00308         assert(instance);
00309         assert(batch);
00310         assert(batch->ep);
00311 
00312         /* Check for root hub communication */
00313         if (batch->ep->address == instance->rh.address) {
00314                 return rh_request(&instance->rh, batch);
00315         }
00316 
00317         fibril_mutex_lock(&instance->guard);
00318         list_append(&batch->link, &instance->pending_batches);
00319         batch_commit(batch);
00320 
00321         /* Control and bulk schedules need a kick to start working */
00322         switch (batch->ep->transfer_type)
00323         {
00324         case USB_TRANSFER_CONTROL:
00325                 instance->registers->command_status |= CS_CLF;
00326                 break;
00327         case USB_TRANSFER_BULK:
00328                 instance->registers->command_status |= CS_BLF;
00329                 break;
00330         default:
00331                 break;
00332         }
00333         fibril_mutex_unlock(&instance->guard);
00334         return EOK;
00335 }
00336 /*----------------------------------------------------------------------------*/
00342 void hc_interrupt(hc_t *instance, uint32_t status)
00343 {
00344         assert(instance);
00345         if ((status & ~I_SF) == 0) /* ignore sof status */
00346                 return;
00347         usb_log_debug2("OHCI(%p) interrupt: %x.\n", instance, status);
00348         if (status & I_RHSC)
00349                 rh_interrupt(&instance->rh);
00350 
00351         if (status & I_WDH) {
00352                 fibril_mutex_lock(&instance->guard);
00353                 usb_log_debug2("HCCA: %p-%#" PRIx32 " (%p).\n", instance->hcca,
00354                     instance->registers->hcca,
00355                     (void *) addr_to_phys(instance->hcca));
00356                 usb_log_debug2("Periodic current: %#" PRIx32 ".\n",
00357                     instance->registers->periodic_current);
00358 
00359                 link_t *current = instance->pending_batches.next;
00360                 while (current != &instance->pending_batches) {
00361                         link_t *next = current->next;
00362                         usb_transfer_batch_t *batch =
00363                             usb_transfer_batch_from_link(current);
00364 
00365                         if (batch_is_complete(batch)) {
00366                                 list_remove(current);
00367                                 usb_transfer_batch_finish(batch);
00368                         }
00369                         current = next;
00370                 }
00371                 fibril_mutex_unlock(&instance->guard);
00372         }
00373 
00374         if (status & I_UE) {
00375                 hc_start_hw(instance);
00376         }
00377 
00378 }
00379 /*----------------------------------------------------------------------------*/
00385 int interrupt_emulator(hc_t *instance)
00386 {
00387         assert(instance);
00388         usb_log_info("Started interrupt emulator.\n");
00389         while (1) {
00390                 const uint32_t status = instance->registers->interrupt_status;
00391                 instance->registers->interrupt_status = status;
00392                 hc_interrupt(instance, status);
00393                 async_usleep(10000);
00394         }
00395         return EOK;
00396 }
00397 /*----------------------------------------------------------------------------*/
00402 void hc_gain_control(hc_t *instance)
00403 {
00404         assert(instance);
00405         usb_log_debug("Requesting OHCI control.\n");
00406         /* Turn off legacy emulation */
00407         volatile uint32_t *ohci_emulation_reg =
00408             (uint32_t*)((char*)instance->registers + 0x100);
00409         usb_log_debug("OHCI legacy register %p: %x.\n",
00410             ohci_emulation_reg, *ohci_emulation_reg);
00411         /* Do not change A20 state */
00412         *ohci_emulation_reg &= 0x100;
00413         usb_log_debug("OHCI legacy register %p: %x.\n",
00414             ohci_emulation_reg, *ohci_emulation_reg);
00415 
00416         /* Interrupt routing enabled => smm driver is active */
00417         if (instance->registers->control & C_IR) {
00418                 usb_log_debug("SMM driver: request ownership change.\n");
00419                 instance->registers->command_status |= CS_OCR;
00420                 while (instance->registers->control & C_IR) {
00421                         async_usleep(1000);
00422                 }
00423                 usb_log_info("SMM driver: Ownership taken.\n");
00424                 instance->registers->control &= (C_HCFS_RESET << C_HCFS_SHIFT);
00425                 async_usleep(50000);
00426                 return;
00427         }
00428 
00429         const unsigned hc_status =
00430             (instance->registers->control >> C_HCFS_SHIFT) & C_HCFS_MASK;
00431         /* Interrupt routing disabled && status != USB_RESET => BIOS active */
00432         if (hc_status != C_HCFS_RESET) {
00433                 usb_log_debug("BIOS driver found.\n");
00434                 if (hc_status == C_HCFS_OPERATIONAL) {
00435                         usb_log_info("BIOS driver: HC operational.\n");
00436                         return;
00437                 }
00438                 /* HC is suspended assert resume for 20ms */
00439                 instance->registers->control &= (C_HCFS_RESUME << C_HCFS_SHIFT);
00440                 async_usleep(20000);
00441                 usb_log_info("BIOS driver: HC resumed.\n");
00442                 return;
00443         }
00444 
00445         /* HC is in reset (hw startup) => no other driver
00446          * maintain reset for at least the time specified in USB spec (50 ms)*/
00447         usb_log_info("HC found in reset.\n");
00448         async_usleep(50000);
00449 }
00450 /*----------------------------------------------------------------------------*/
00455 void hc_start_hw(hc_t *instance)
00456 {
00457         /* OHCI guide page 42 */
00458         assert(instance);
00459         usb_log_debug2("Started hc initialization routine.\n");
00460 
00461         /* Save contents of fm_interval register */
00462         const uint32_t fm_interval = instance->registers->fm_interval;
00463         usb_log_debug2("Old value of HcFmInterval: %x.\n", fm_interval);
00464 
00465         /* Reset hc */
00466         usb_log_debug2("HC reset.\n");
00467         size_t time = 0;
00468         instance->registers->command_status = CS_HCR;
00469         while (instance->registers->command_status & CS_HCR) {
00470                 async_usleep(10);
00471                 time += 10;
00472         }
00473         usb_log_debug2("HC reset complete in %zu us.\n", time);
00474 
00475         /* Restore fm_interval */
00476         instance->registers->fm_interval = fm_interval;
00477         assert((instance->registers->command_status & CS_HCR) == 0);
00478 
00479         /* hc is now in suspend state */
00480         usb_log_debug2("HC should be in suspend state(%x).\n",
00481             instance->registers->control);
00482 
00483         /* Use HCCA */
00484         instance->registers->hcca = addr_to_phys(instance->hcca);
00485 
00486         /* Use queues */
00487         instance->registers->bulk_head =
00488             instance->lists[USB_TRANSFER_BULK].list_head_pa;
00489         usb_log_debug2("Bulk HEAD set to: %p (%#" PRIx32 ").\n",
00490             instance->lists[USB_TRANSFER_BULK].list_head,
00491             instance->lists[USB_TRANSFER_BULK].list_head_pa);
00492 
00493         instance->registers->control_head =
00494             instance->lists[USB_TRANSFER_CONTROL].list_head_pa;
00495         usb_log_debug2("Control HEAD set to: %p (%#" PRIx32 ").\n",
00496             instance->lists[USB_TRANSFER_CONTROL].list_head,
00497             instance->lists[USB_TRANSFER_CONTROL].list_head_pa);
00498 
00499         /* Enable queues */
00500         instance->registers->control |= (C_PLE | C_IE | C_CLE | C_BLE);
00501         usb_log_debug2("All queues enabled(%x).\n",
00502             instance->registers->control);
00503 
00504         /* Enable interrupts */
00505         instance->registers->interrupt_enable = OHCI_USED_INTERRUPTS;
00506         usb_log_debug2("Enabled interrupts: %x.\n",
00507             instance->registers->interrupt_enable);
00508         instance->registers->interrupt_enable = I_MI;
00509 
00510         /* Set periodic start to 90% */
00511         uint32_t frame_length = ((fm_interval >> FMI_FI_SHIFT) & FMI_FI_MASK);
00512         instance->registers->periodic_start = (frame_length / 10) * 9;
00513         usb_log_debug2("All periodic start set to: %x(%u - 90%% of %d).\n",
00514             instance->registers->periodic_start,
00515             instance->registers->periodic_start, frame_length);
00516 
00517         instance->registers->control &= (C_HCFS_OPERATIONAL << C_HCFS_SHIFT);
00518         usb_log_info("OHCI HC up and running(%x).\n",
00519             instance->registers->control);
00520 }
00521 /*----------------------------------------------------------------------------*/
00527 int hc_init_transfer_lists(hc_t *instance)
00528 {
00529         assert(instance);
00530 #define SETUP_ENDPOINT_LIST(type) \
00531 do { \
00532         const char *name = usb_str_transfer_type(type); \
00533         int ret = endpoint_list_init(&instance->lists[type], name); \
00534         if (ret != EOK) { \
00535                 usb_log_error("Failed(%d) to setup %s endpoint list.\n", \
00536                     ret, name); \
00537                 endpoint_list_fini(&instance->lists[USB_TRANSFER_ISOCHRONOUS]);\
00538                 endpoint_list_fini(&instance->lists[USB_TRANSFER_INTERRUPT]); \
00539                 endpoint_list_fini(&instance->lists[USB_TRANSFER_CONTROL]); \
00540                 endpoint_list_fini(&instance->lists[USB_TRANSFER_BULK]); \
00541                 return ret; \
00542         } \
00543 } while (0)
00544 
00545         SETUP_ENDPOINT_LIST(USB_TRANSFER_ISOCHRONOUS);
00546         SETUP_ENDPOINT_LIST(USB_TRANSFER_INTERRUPT);
00547         SETUP_ENDPOINT_LIST(USB_TRANSFER_CONTROL);
00548         SETUP_ENDPOINT_LIST(USB_TRANSFER_BULK);
00549 #undef SETUP_ENDPOINT_LIST
00550         endpoint_list_set_next(&instance->lists[USB_TRANSFER_INTERRUPT],
00551             &instance->lists[USB_TRANSFER_ISOCHRONOUS]);
00552 
00553         return EOK;
00554 }
00555 /*----------------------------------------------------------------------------*/
00561 int hc_init_memory(hc_t *instance)
00562 {
00563         assert(instance);
00564 
00565         bzero(&instance->rh, sizeof(instance->rh));
00566         /* Init queues */
00567         const int ret = hc_init_transfer_lists(instance);
00568         if (ret != EOK) {
00569                 return ret;
00570         }
00571 
00572         /*Init HCCA */
00573         instance->hcca = malloc32(sizeof(hcca_t));
00574         if (instance->hcca == NULL)
00575                 return ENOMEM;
00576         bzero(instance->hcca, sizeof(hcca_t));
00577         usb_log_debug2("OHCI HCCA initialized at %p.\n", instance->hcca);
00578 
00579         unsigned i = 0;
00580         for (; i < 32; ++i) {
00581                 instance->hcca->int_ep[i] =
00582                     instance->lists[USB_TRANSFER_INTERRUPT].list_head_pa;
00583         }
00584         usb_log_debug2("Interrupt HEADs set to: %p (%#" PRIx32 ").\n",
00585             instance->lists[USB_TRANSFER_INTERRUPT].list_head,
00586             instance->lists[USB_TRANSFER_INTERRUPT].list_head_pa);
00587 
00588         /* Init interrupt code */
00589         instance->interrupt_code.cmds = instance->interrupt_commands;
00590         instance->interrupt_code.cmdcount = OHCI_NEEDED_IRQ_COMMANDS;
00591         {
00592                 /* Read status register */
00593                 instance->interrupt_commands[0].cmd = CMD_MEM_READ_32;
00594                 instance->interrupt_commands[0].dstarg = 1;
00595                 instance->interrupt_commands[0].addr =
00596                     (void*)&instance->registers->interrupt_status;
00597 
00598                 /* Test whether we are the interrupt cause */
00599                 instance->interrupt_commands[1].cmd = CMD_BTEST;
00600                 instance->interrupt_commands[1].value =
00601                     OHCI_USED_INTERRUPTS;
00602                 instance->interrupt_commands[1].srcarg = 1;
00603                 instance->interrupt_commands[1].dstarg = 2;
00604 
00605                 /* Predicate cleaning and accepting */
00606                 instance->interrupt_commands[2].cmd = CMD_PREDICATE;
00607                 instance->interrupt_commands[2].value = 2;
00608                 instance->interrupt_commands[2].srcarg = 2;
00609 
00610                 /* Write-clean status register */
00611                 instance->interrupt_commands[3].cmd = CMD_MEM_WRITE_A_32;
00612                 instance->interrupt_commands[3].srcarg = 1;
00613                 instance->interrupt_commands[3].addr =
00614                     (void*)&instance->registers->interrupt_status;
00615 
00616                 /* Accept interrupt */
00617                 instance->interrupt_commands[4].cmd = CMD_ACCEPT;
00618         }
00619 
00620         return EOK;
00621 }

Generated on Thu Jun 2 07:45:44 2011 for HelenOS/USB by  doxygen 1.4.7