1 //------------------------------------------------------------------------------ 2 // Copyright (c) 2004, Niels S. Reedijk 3 // 4 // Permission is hereby granted, free of charge, to any person obtaining a 5 // copy of this software and associated documentation files (the "Software"), 6 // to deal in the Software without restriction, including without limitation 7 // the rights to use, copy, modify, merge, publish, distribute, sublicense, 8 // and/or sell copies of the Software, and to permit persons to whom the 9 // Software is furnished to do so, subject to the following conditions: 10 // 11 // The above copyright notice and this permission notice shall be included in 12 // all copies or substantial portions of the Software. 13 // 14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 19 // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 20 // DEALINGS IN THE SOFTWARE. 21 22 #include <module.h> 23 #include <PCI.h> 24 #include <USB.h> 25 #include <KernelExport.h> 26 #include <stdlib.h> 27 28 #include "uhci.h" 29 #include "uhci_hardware.h" 30 #include <usb_p.h> 31 32 33 /* ++++++++++ 34 This is the implementation of the UHCI controller for the OpenBeOS USB stack 35 ++++++++++ */ 36 37 static int32 38 uhci_std_ops( int32 op , ... ) 39 { 40 switch (op) 41 { 42 case B_MODULE_INIT: 43 TRACE( "uhci_module: init the module\n" ); 44 return B_OK; 45 case B_MODULE_UNINIT: 46 TRACE( "uhci_module: uninit the module\n" ); 47 break; 48 default: 49 return EINVAL; 50 } 51 return B_OK; 52 } 53 54 static bool 55 uhci_add_to( Stack &stack ) 56 { 57 status_t status; 58 pci_info *item; 59 bool found = false; 60 int i; 61 62 #ifdef UHCI_DEBUG 63 set_dprintf_enabled( true ); 64 load_driver_symbols( "uhci" ); 65 #endif 66 67 // Try if the PCI module is loaded (it would be weird if it wouldn't, but alas) 68 if( ( status = get_module( B_PCI_MODULE_NAME, (module_info **)&( UHCI::pci_module ) ) ) != B_OK) 69 { 70 TRACE( "USB_ UHCI: init_hardware(): Get PCI module failed! %lu \n", status); 71 return status; 72 } 73 74 TRACE( "usb_uhci init_hardware(): Setting up hardware\n" ); 75 76 // TODO: in the future we might want to support multiple host controllers. 77 item = new pci_info; 78 for ( i = 0 ; UHCI::pci_module->get_nth_pci_info( i , item ) == B_OK ; i++ ) 79 { 80 //class_base = 0C (serial bus) class_sub = 03 (usb) prog_int: 00 (UHCI) 81 if ( ( item->class_base == 0x0C ) && ( item->class_sub == 0x03 ) && 82 ( item->class_api == 0x00 ) ) 83 { 84 if ((item->u.h0.interrupt_line == 0) || (item->u.h0.interrupt_line == 0xFF)) 85 { 86 TRACE( "USB UHCI: init_hardware(): found with invalid IRQ - check IRQ assignement\n"); 87 continue; 88 } 89 TRACE("USB UHCI: init_hardware(): found at IRQ %u \n", item->u.h0.interrupt_line); 90 UHCI *bus = new UHCI( item , &stack ); 91 if ( bus->InitCheck() != B_OK ) 92 { 93 TRACE( "USB UHCI::InitCheck() failed, error %li\n" , bus->InitCheck() ); 94 delete bus; 95 break; 96 } 97 98 stack.AddBusManager( bus ); 99 bus->Start(); 100 found = true; 101 break; 102 } 103 } 104 105 if ( found == false ) 106 { 107 TRACE( "USB UHCI: init hardware(): no devices found\n" ); 108 free( item ); 109 put_module( B_PCI_MODULE_NAME ); 110 return ENODEV; 111 } 112 return B_OK; //Hardware found 113 } 114 115 116 117 118 host_controller_info uhci_module = { 119 { 120 "busses/usb/uhci/nielx", 121 NULL, // No flag like B_KEEP_LOADED : the usb module does that 122 uhci_std_ops 123 }, 124 NULL , 125 uhci_add_to 126 }; 127 128 module_info *modules[] = 129 { 130 (module_info *) &uhci_module, 131 NULL 132 }; 133 134 /* ++++++++++ 135 This is the implementation of the UHCI controller for the OpenBeOS USB stack 136 ++++++++++ */ 137 138 int32 uhci_interrupt_handler( void *data ) 139 { 140 int32 retval; 141 spinlock slock = 0; 142 cpu_status status = disable_interrupts(); 143 acquire_spinlock( &slock ); 144 retval = ((UHCI*)data)->Interrupt(); 145 release_spinlock( &slock ); 146 restore_interrupts( status ); 147 return retval; 148 } 149 150 UHCI::UHCI( pci_info *info , Stack *stack ) 151 { 152 //Do nothing yet 153 dprintf( "UHCI: constructing new BusManager\n" ); 154 m_pcii = info; 155 m_stack = stack; 156 m_reg_base = UHCI::pci_module->read_pci_config(m_pcii->bus, m_pcii->device, m_pcii->function, PCI_memory_base, 4); 157 m_reg_base &= PCI_address_io_mask; 158 TRACE( "USB UHCI: iospace offset: %lx\n" , m_reg_base ); 159 m_rh_address = 255; //Invalidate the RH address 160 { 161 /* enable pci address access */ 162 uint16 cmd; 163 cmd = UHCI::pci_module->read_pci_config(m_pcii->bus, m_pcii->device, m_pcii->function, PCI_command, 2); 164 cmd = cmd | PCI_command_io | PCI_command_master | PCI_command_memory; 165 UHCI::pci_module->write_pci_config(m_pcii->bus, m_pcii->device, m_pcii->function, PCI_command, 2, cmd ); 166 /* make sure we gain controll of the UHCI controller instead of the BIOS - function 2 */ 167 UHCI::pci_module->write_pci_config(m_pcii->bus, m_pcii->device, 2, PCI_LEGSUP, 2, PCI_LEGSUP_USBPIRQDEN ); 168 } 169 170 //Do a host reset 171 GlobalReset(); 172 if ( Reset() != B_OK ) 173 { 174 TRACE( "USB UHCI: init_hardare(): host failed to reset\n" ); 175 m_initok = false; 176 return; 177 } 178 179 // Poll the status of the two ports 180 // rh_update_port_status(); 181 // TRACE( "USB UHCI: init_hardware(): port1: %x port2: %x\n", 182 // m_data->port_status[0].status , m_data->port_status[1].status ); 183 184 //Set up the frame list 185 void *phy; 186 m_framearea = stack->AllocateArea( (void **)&(m_framelist[0]) , &(phy) , 187 4096 , "uhci framelist" ); 188 m_framelist_phy = reinterpret_cast<addr_t>(phy); 189 if ( m_framearea < B_OK ) 190 { 191 TRACE( "USB UHCI: init_hardware(): unable to create an area for the frame pointer list\n" ); 192 m_initok = false; 193 return; 194 } 195 196 /* 197 According tot the *BSD usb sources, there needs to be a stray transfer 198 descriptor in order to get some chipset to work nicely (PIIX or something 199 like that). 200 */ 201 uhci_td *straytd; 202 if ( m_stack->AllocateChunk( (void **)&(straytd) , &phy , 32 ) != B_OK ) 203 { 204 dprintf( "USB UHCI::UHCI() Failed to allocate a stray transfer descriptor\n" ); 205 delete_area( m_framearea ); 206 m_initok = false; 207 return; 208 } 209 straytd->link_phy = TD_TERMINATE; 210 straytd->this_phy = reinterpret_cast<addr_t>(phy); 211 straytd->link_log = 0; 212 straytd->buffer_log = 0; 213 straytd->status = 0; 214 straytd->token = TD_TOKEN_NULL | 0x7f << TD_TOKEN_DEVADDR_SHIFT | 0x69; 215 straytd->buffer_phy = 0; 216 217 /* 218 Set up the virtual structure. I stole this idea from the linux usb stack, 219 the idea is that for every interrupt interval there is a queue head. These 220 things all link together and eventually point to the control and bulk 221 virtual queue heads. 222 */ 223 224 for( int i = 0 ; i < 12 ; i++ ) 225 { 226 void *phy; 227 //Must be aligned on 16-byte boundaries 228 if ( m_stack->AllocateChunk( (void **)&(m_qh_virtual[i]) , 229 &phy , 32 ) != B_OK ) 230 { 231 dprintf( "USB UHCI: init_hardware(): failed allocation of skeleton qh %i, aborting\n", i ); 232 delete_area( m_framearea ); 233 m_initok = false; 234 return; 235 } 236 //chunk allocated 237 m_qh_virtual[i]->this_phy = reinterpret_cast<addr_t>(phy); 238 m_qh_virtual[i]->element_phy = QH_TERMINATE; 239 m_qh_virtual[i]->element_log = 0; 240 241 //Link this qh to its previous qh 242 if ( i != 0 ) 243 { 244 m_qh_virtual[i-1]->link_phy = m_qh_virtual[i]->this_phy | QH_NEXT_IS_QH ; 245 m_qh_virtual[i-1]->link_log = m_qh_virtual[i]; 246 } 247 } 248 // Make sure the qh_terminate terminates 249 m_qh_virtual[11]->link_phy = straytd->this_phy; 250 m_qh_virtual[11]->link_log = straytd; 251 252 //Insert the queues in the frame list. The linux developers mentioned 253 // in a comment that they used some magic to distribute the elements all 254 // over the place, but I don't really think that it is useful right now 255 // (or do I know how I should do that), instead, I just take the frame 256 // number and determine where it should begin 257 258 //NOTE, in c++ this is butt-ugly. We have a addr_t *array (because with 259 //an addr_t *array we can apply pointer arithmetic), uhci_qh *pointers 260 //that need to be put through the logical | to make sure the pointer is 261 //invalid for the hc. The result of that needs to be converted into a 262 //addr_t. Get it? 263 for( int i = 0 ; i < 1024 ; i++ ) 264 { 265 int frame = i+1; 266 if ( ( frame % 256 ) == 0 ) 267 m_framelist[i] = m_qh_interrupt_256->this_phy | FRAMELIST_NEXT_IS_QH; 268 else if ( ( frame % 128 ) == 0 ) 269 m_framelist[i] = m_qh_interrupt_128->this_phy | FRAMELIST_NEXT_IS_QH; 270 else if ( ( frame % 64 ) == 0 ) 271 m_framelist[i] = m_qh_interrupt_64->this_phy | FRAMELIST_NEXT_IS_QH; 272 else if ( ( frame % 32 ) == 0 ) 273 m_framelist[i] = m_qh_interrupt_32->this_phy | FRAMELIST_NEXT_IS_QH; 274 else if ( ( frame % 16 ) == 0 ) 275 m_framelist[i] = m_qh_interrupt_16->this_phy | FRAMELIST_NEXT_IS_QH; 276 else if ( ( frame % 8 ) == 0 ) 277 m_framelist[i] = m_qh_interrupt_8->this_phy | FRAMELIST_NEXT_IS_QH; 278 else if ( ( frame % 4 ) == 0 ) 279 m_framelist[i] = m_qh_interrupt_4->this_phy | FRAMELIST_NEXT_IS_QH; 280 else if ( ( frame % 2 ) == 0 ) 281 m_framelist[i] = m_qh_interrupt_2->this_phy | FRAMELIST_NEXT_IS_QH; 282 else 283 m_framelist[i] = m_qh_interrupt_1->this_phy | FRAMELIST_NEXT_IS_QH; 284 } 285 286 //Set base pointer 287 UHCI::pci_module->write_io_32( m_reg_base + UHCI_FRBASEADD , (int32)(m_framelist_phy) ); 288 UHCI::pci_module->write_io_16( m_reg_base + UHCI_FRNUM , 0 ); 289 290 //Set up the root hub 291 m_rh_address = AllocateAddress(); 292 m_rh = new UHCIRootHub( this , m_rh_address ); 293 SetRootHub( m_rh ); 294 295 //Install the interrupt handler 296 install_io_interrupt_handler( m_pcii->u.h0.interrupt_line , uhci_interrupt_handler , (void *)this , 0 ); 297 UHCI::pci_module->write_io_16( m_reg_base + UHCI_USBSTS , 0xffff ); 298 UHCI::pci_module->write_io_16( m_reg_base + UHCI_USBINTR , UHCI_USBINTR_CRC | UHCI_USBINTR_RESUME | UHCI_USBINTR_IOC | UHCI_USBINTR_SHORT ); 299 } 300 301 status_t UHCI::Start() 302 { 303 //Start the host controller, then start the Busmanager 304 TRACE("USB UCHI::STart() usbcmd reg %u, usbsts reg %u\n" , UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBCMD ) , UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBSTS ) ); 305 UHCI::pci_module->write_io_16( m_reg_base + UHCI_USBCMD , UHCI_USBCMD_RS ); 306 307 bool running = false; 308 uint16 status = 0; 309 for ( int i = 0 ; i <= 10 ; i++ ) 310 { 311 status = UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBSTS ); 312 dprintf( "UHCI::Start() current loop %u, status %u\n" , i , status ); 313 if ( status & UHCI_USBSTS_HCHALT ) 314 snooze( 1000 ); 315 else 316 { 317 running = true; 318 break; 319 } 320 } 321 322 if (!running) 323 { 324 TRACE( "UHCI::Start() Controller won't start running\n" ); 325 return B_ERROR; 326 } 327 328 TRACE( "UHCI::Start() Controller is started. USBSTS: %u curframe: %u \n" , UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBSTS ) , UHCI::pci_module->read_io_16( m_reg_base + UHCI_FRNUM ) ); 329 return BusManager::Start(); 330 } 331 332 status_t UHCI::SubmitTransfer( Transfer *t ) 333 { 334 dprintf( "UHCI::SubmitPacket( Transfer &t ) called!!!\n" ); 335 336 //Short circuit the root hub 337 if ( m_rh_address == t->GetPipe()->GetDeviceAddress() ) 338 return m_rh->SubmitTransfer( t ); 339 340 if ( t->GetPipe()->GetType() == Pipe::Control ) 341 return InsertControl( t ); 342 343 return B_ERROR; 344 } 345 346 void UHCI::GlobalReset() 347 { 348 UHCI::pci_module->write_io_16( m_reg_base + UHCI_USBCMD , UHCI_USBCMD_GRESET ); 349 snooze( 100000 ); 350 UHCI::pci_module->write_io_16( m_reg_base + UHCI_USBCMD , 0 ); 351 } 352 353 status_t UHCI::Reset() 354 { 355 UHCI::pci_module->write_io_16( m_reg_base + UHCI_USBCMD , UHCI_USBCMD_HCRESET ); 356 snooze( 100000 ); 357 if ( UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBCMD ) & UHCI_USBCMD_HCRESET ) 358 return B_ERROR; 359 return B_OK; 360 } 361 362 int32 UHCI::Interrupt() 363 { 364 uint16 status = UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBSTS ); 365 TRACE( "USB UHCI::Interrupt()\n" ); 366 //Check if we really had an interrupt 367 if ( !( status | UHCI_INTERRUPT_MASK ) ) 368 return B_UNHANDLED_INTERRUPT; 369 370 //Get funky 371 if ( status | UHCI_USBSTS_USBINT ) 372 { 373 //A transfer finished 374 TRACE( "USB UHCI::Interrupt() transfer finished! [party]\n" ); 375 } 376 else if ( status | UHCI_USBSTS_ERRINT ) 377 { 378 TRACE( "USB UHCI::Interrupt() transfer error! [cry]\n" ); 379 } 380 return B_HANDLED_INTERRUPT; 381 } 382 383 status_t UHCI::InsertControl( Transfer *t ) 384 { 385 TRACE("USB UCHI::InsertControl() frnum %u , usbsts reg %u\n" , UHCI::pci_module->read_io_16( m_reg_base + UHCI_FRNUM ), UHCI::pci_module->read_io_16( m_reg_base + UHCI_USBSTS ) ); 386 387 //HACK: this one is to prevent rogue transfers from happening 388 if ( t->GetBuffer() != 0 ) 389 return B_ERROR; 390 391 //Please note that any data structures must be aligned on a 16 byte boundary 392 //Also, due to the strange ways of C++' void* handling, this code is much messier 393 //than it actually should be. Forgive me. Or blame the compiler. 394 //First, set up a Queue Head for the transfer 395 uhci_qh *topqh; 396 void *topqh_phy; 397 if ( m_stack->AllocateChunk( (void **)&topqh , &topqh_phy , 32 ) < B_OK ) 398 { 399 TRACE( "UHCI::InsertControl(): Failed to allocate a QH\n" ); 400 return ENOMEM; 401 } 402 topqh->link_phy = QH_TERMINATE; 403 topqh->link_log = 0; 404 topqh->this_phy = (addr_t)topqh_phy; 405 406 407 //Allocate the transfer descriptor for the transfer 408 uhci_td *firsttd; 409 void *firsttd_phy; 410 if ( m_stack->AllocateChunk( (void**)&firsttd , &firsttd_phy , 32 ) < B_OK ) 411 { 412 TRACE( "UHCI::InsertControl(): Failed to allocate the first TD\n" ); 413 m_stack->FreeChunk( topqh , topqh_phy , 32 ); 414 return ENOMEM; 415 } 416 firsttd->this_phy = (addr_t)firsttd_phy; 417 //Set the 'status' field of the td 418 if ( t->GetPipe()->GetSpeed() == Pipe::LowSpeed ) 419 firsttd->status = TD_STATUS_LOWSPEED | TD_STATUS_ACTIVE; 420 else 421 firsttd->status = TD_STATUS_ACTIVE; 422 423 //Set the 'token' field of the td 424 firsttd->token = ( ( sizeof(usb_request_data) - 1 ) << 21 ) | ( t->GetPipe()->GetEndpointAddress() << 15 ) 425 | ( t->GetPipe()->GetDeviceAddress() << 8 ) | ( 0x2D ); 426 //Create a physical space for the setup request 427 if ( m_stack->AllocateChunk( &(firsttd->buffer_log) , &(firsttd->buffer_phy) , sizeof (usb_request_data) ) ) 428 { 429 TRACE( "UHCI::InsertControl(): Unable to allocate space for the SETUP buffer\n" ); 430 m_stack->FreeChunk( topqh , topqh_phy , 32 ); 431 m_stack->FreeChunk( firsttd , firsttd_phy , 32 ); 432 return ENOMEM; 433 } 434 memcpy( t->GetRequestData() , firsttd->buffer_log , sizeof(usb_request_data) ); 435 436 //Link this thing in the queue head 437 topqh->element_phy = (addr_t)firsttd_phy; 438 topqh->element_log = firsttd; 439 440 441 //TODO: split the buffer into max transfer sizes 442 443 444 //Finally, create a status td 445 uhci_td *statustd; 446 void *statustd_phy; 447 if ( m_stack->AllocateChunk( (void **)&statustd , &statustd_phy , 32 ) < B_OK ) 448 { 449 TRACE( "UHCI::InsertControl(): Failed to allocate the status TD\n" ); 450 return ENOMEM; 451 } 452 //Set the 'status' field of the td to interrupt on complete 453 if ( t->GetPipe()->GetSpeed() == Pipe::LowSpeed ) 454 statustd->status = TD_STATUS_LOWSPEED | TD_STATUS_IOC; 455 else 456 statustd->status = TD_STATUS_IOC; 457 458 //Set the 'token' field of the td (always DATA1) and a null buffer 459 statustd->token = TD_TOKEN_NULL | TD_TOKEN_DATA1 | ( t->GetPipe()->GetEndpointAddress() << 15 ) 460 | ( t->GetPipe()->GetDeviceAddress() << 8 ) | 0x69 ; 461 462 //Invalidate the buffer field 463 statustd->buffer_phy = statustd->buffer_log = 0; 464 465 //Link into the previous transfer descriptor 466 firsttd->link_phy = (addr_t)statustd_phy | TD_DEPTH_FIRST; 467 firsttd->link_log = statustd; 468 469 //This is the end of this chain, so don't link to any next QH/TD 470 statustd->link_phy = QH_TERMINATE; 471 statustd->link_log = 0; 472 473 474 //First, add the transfer to the list of transfers 475 t->SetHostPrivate( new hostcontroller_priv ); 476 t->GetHostPrivate()->topqh = topqh; 477 t->GetHostPrivate()->firsttd = firsttd; 478 t->GetHostPrivate()->lasttd = statustd; 479 m_transfers.PushBack( t ); 480 481 //Secondly, append the qh to the control list 482 //a) if the control queue is empty, make this the first element 483 if ( ( m_qh_control->element_phy & QH_TERMINATE ) != 0 ) 484 { 485 m_qh_control->element_phy = topqh->this_phy; 486 m_qh_control->link_log = (void *)topqh; 487 TRACE( "USB UHCI::InsertControl() First transfer in QUeue\n" ); 488 } 489 //b) there are control transfers linked, append to the queue 490 else 491 { 492 uhci_qh *qh = (uhci_qh *)(m_qh_control->link_log); 493 while ( ( qh->link_phy & QH_TERMINATE ) == 0 ) 494 { 495 TRACE( "USB UHCI::InsertControl() Looping\n" ); 496 qh = (uhci_qh *)(qh->link_log); 497 } 498 qh->link_phy = topqh->this_phy; 499 qh->link_log = (void *)topqh; 500 TRACE( "USB UHCI::InsertControl() Appended transfers in queue\n" ); 501 } 502 return EINPROGRESS; 503 } 504 505 pci_module_info *UHCI::pci_module = 0; 506