xref: /haiku/src/add-ons/kernel/busses/usb/uhci.cpp (revision 4f00613311d0bd6b70fa82ce19931c41f071ea4e)
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