[undi] Ensure only one UNDI instance is loaded
[gpxe.git] / src / arch / i386 / drivers / net / undinet.c
blob83b79e7fea13bdc0432d8e0f19d632f131401f3c
1 /*
2 * Copyright (C) 2007 Michael Brown <mbrown@fensystems.co.uk>.
4 * This program is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU General Public License as
6 * published by the Free Software Foundation; either version 2 of the
7 * License, or any later version.
9 * This program is distributed in the hope that it will be useful, but
10 * WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19 FILE_LICENCE ( GPL2_OR_LATER );
21 #include <string.h>
22 #include <pxe.h>
23 #include <realmode.h>
24 #include <pic8259.h>
25 #include <biosint.h>
26 #include <pnpbios.h>
27 #include <basemem_packet.h>
28 #include <gpxe/io.h>
29 #include <gpxe/iobuf.h>
30 #include <gpxe/netdevice.h>
31 #include <gpxe/if_ether.h>
32 #include <gpxe/ethernet.h>
33 #include <undi.h>
34 #include <undinet.h>
35 #include <pxeparent.h>
38 /** @file
40 * UNDI network device driver
44 /** An UNDI NIC */
45 struct undi_nic {
46 /** Assigned IRQ number */
47 unsigned int irq;
48 /** Currently processing ISR */
49 int isr_processing;
50 /** Bug workarounds */
51 int hacks;
54 /**
55 * @defgroup undi_hacks UNDI workarounds
56 * @{
59 /** Work around Etherboot 5.4 bugs */
60 #define UNDI_HACK_EB54 0x0001
62 /** @} */
64 static void undinet_close ( struct net_device *netdev );
66 /** Address of UNDI entry point */
67 static SEGOFF16_t undinet_entry;
69 /*****************************************************************************
71 * UNDI interrupt service routine
73 *****************************************************************************
76 /**
77 * UNDI interrupt service routine
79 * The UNDI ISR increments a counter (@c trigger_count) and exits.
81 extern void undiisr ( void );
83 /** IRQ number */
84 uint8_t __data16 ( undiisr_irq );
85 #define undiisr_irq __use_data16 ( undiisr_irq )
87 /** IRQ chain vector */
88 struct segoff __data16 ( undiisr_next_handler );
89 #define undiisr_next_handler __use_data16 ( undiisr_next_handler )
91 /** IRQ trigger count */
92 volatile uint8_t __data16 ( undiisr_trigger_count ) = 0;
93 #define undiisr_trigger_count __use_data16 ( undiisr_trigger_count )
95 /** Last observed trigger count */
96 static unsigned int last_trigger_count = 0;
98 /**
99 * Hook UNDI interrupt service routine
101 * @v irq IRQ number
103 static void undinet_hook_isr ( unsigned int irq ) {
105 assert ( irq <= IRQ_MAX );
106 assert ( undiisr_irq == 0 );
108 undiisr_irq = irq;
109 hook_bios_interrupt ( IRQ_INT ( irq ),
110 ( ( unsigned int ) undiisr ),
111 &undiisr_next_handler );
115 * Unhook UNDI interrupt service routine
117 * @v irq IRQ number
119 static void undinet_unhook_isr ( unsigned int irq ) {
121 assert ( irq <= IRQ_MAX );
123 unhook_bios_interrupt ( IRQ_INT ( irq ),
124 ( ( unsigned int ) undiisr ),
125 &undiisr_next_handler );
126 undiisr_irq = 0;
130 * Test to see if UNDI ISR has been triggered
132 * @ret triggered ISR has been triggered since last check
134 static int undinet_isr_triggered ( void ) {
135 unsigned int this_trigger_count;
137 /* Read trigger_count. Do this only once; it is volatile */
138 this_trigger_count = undiisr_trigger_count;
140 if ( this_trigger_count == last_trigger_count ) {
141 /* Not triggered */
142 return 0;
143 } else {
144 /* Triggered */
145 last_trigger_count = this_trigger_count;
146 return 1;
150 /*****************************************************************************
152 * UNDI network device interface
154 *****************************************************************************
157 /** UNDI transmit buffer descriptor */
158 static struct s_PXENV_UNDI_TBD __data16 ( undinet_tbd );
159 #define undinet_tbd __use_data16 ( undinet_tbd )
162 * Transmit packet
164 * @v netdev Network device
165 * @v iobuf I/O buffer
166 * @ret rc Return status code
168 static int undinet_transmit ( struct net_device *netdev,
169 struct io_buffer *iobuf ) {
170 struct s_PXENV_UNDI_TRANSMIT undi_transmit;
171 size_t len = iob_len ( iobuf );
172 int rc;
174 /* Technically, we ought to make sure that the previous
175 * transmission has completed before we re-use the buffer.
176 * However, many PXE stacks (including at least some Intel PXE
177 * stacks and Etherboot 5.4) fail to generate TX completions.
178 * In practice this won't be a problem, since our TX datapath
179 * has a very low packet volume and we can get away with
180 * assuming that a TX will be complete by the time we want to
181 * transmit the next packet.
184 /* Copy packet to UNDI I/O buffer */
185 if ( len > sizeof ( basemem_packet ) )
186 len = sizeof ( basemem_packet );
187 memcpy ( &basemem_packet, iobuf->data, len );
189 /* Create PXENV_UNDI_TRANSMIT data structure */
190 memset ( &undi_transmit, 0, sizeof ( undi_transmit ) );
191 undi_transmit.DestAddr.segment = rm_ds;
192 undi_transmit.DestAddr.offset = __from_data16 ( &undinet_tbd );
193 undi_transmit.TBD.segment = rm_ds;
194 undi_transmit.TBD.offset = __from_data16 ( &undinet_tbd );
196 /* Create PXENV_UNDI_TBD data structure */
197 undinet_tbd.ImmedLength = len;
198 undinet_tbd.Xmit.segment = rm_ds;
199 undinet_tbd.Xmit.offset = __from_data16 ( basemem_packet );
201 /* Issue PXE API call */
202 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_TRANSMIT,
203 &undi_transmit,
204 sizeof ( undi_transmit ) ) ) != 0 )
205 goto done;
207 /* Free I/O buffer */
208 netdev_tx_complete ( netdev, iobuf );
210 done:
211 return rc;
214 /**
215 * Poll for received packets
217 * @v netdev Network device
219 * Fun, fun, fun. UNDI drivers don't use polling; they use
220 * interrupts. We therefore cheat and pretend that an interrupt has
221 * occurred every time undinet_poll() is called. This isn't too much
222 * of a hack; PCI devices share IRQs and so the first thing that a
223 * proper ISR should do is call PXENV_UNDI_ISR to determine whether or
224 * not the UNDI NIC generated the interrupt; there is no harm done by
225 * spurious calls to PXENV_UNDI_ISR. Similarly, we wouldn't be
226 * handling them any more rapidly than the usual rate of
227 * undinet_poll() being called even if we did implement a full ISR.
228 * So it should work. Ha!
230 * Addendum (21/10/03). Some cards don't play nicely with this trick,
231 * so instead of doing it the easy way we have to go to all the hassle
232 * of installing a genuine interrupt service routine and dealing with
233 * the wonderful 8259 Programmable Interrupt Controller. Joy.
235 * Addendum (10/07/07). When doing things such as iSCSI boot, in
236 * which we have to co-operate with a running OS, we can't get away
237 * with the "ISR-just-increments-a-counter-and-returns" trick at all,
238 * because it involves tying up the PIC for far too long, and other
239 * interrupt-dependent components (e.g. local disks) start breaking.
240 * We therefore implement a "proper" ISR which calls PXENV_UNDI_ISR
241 * from within interrupt context in order to deassert the device
242 * interrupt, and sends EOI if applicable.
244 static void undinet_poll ( struct net_device *netdev ) {
245 struct undi_nic *undinic = netdev->priv;
246 struct s_PXENV_UNDI_ISR undi_isr;
247 struct io_buffer *iobuf = NULL;
248 size_t len;
249 size_t frag_len;
250 size_t max_frag_len;
251 int rc;
253 if ( ! undinic->isr_processing ) {
254 /* Do nothing unless ISR has been triggered */
255 if ( ! undinet_isr_triggered() ) {
256 /* Allow interrupt to occur */
257 __asm__ __volatile__ ( REAL_CODE ( "sti\n\t"
258 "nop\n\t"
259 "nop\n\t"
260 "cli\n\t" ) : : );
261 return;
264 /* Start ISR processing */
265 undinic->isr_processing = 1;
266 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_PROCESS;
267 } else {
268 /* Continue ISR processing */
269 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
272 /* Run through the ISR loop */
273 while ( 1 ) {
274 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
275 &undi_isr,
276 sizeof ( undi_isr ) ) ) != 0 )
277 break;
278 switch ( undi_isr.FuncFlag ) {
279 case PXENV_UNDI_ISR_OUT_TRANSMIT:
280 /* We don't care about transmit completions */
281 break;
282 case PXENV_UNDI_ISR_OUT_RECEIVE:
283 /* Packet fragment received */
284 len = undi_isr.FrameLength;
285 frag_len = undi_isr.BufferLength;
286 if ( ( len == 0 ) || ( len < frag_len ) ) {
287 /* Don't laugh. VMWare does it. */
288 DBGC ( undinic, "UNDINIC %p reported insane "
289 "fragment (%zd of %zd bytes)\n",
290 undinic, frag_len, len );
291 netdev_rx_err ( netdev, NULL, -EINVAL );
292 break;
294 if ( ! iobuf )
295 iobuf = alloc_iob ( len );
296 if ( ! iobuf ) {
297 DBGC ( undinic, "UNDINIC %p could not "
298 "allocate %zd bytes for RX buffer\n",
299 undinic, len );
300 /* Fragment will be dropped */
301 netdev_rx_err ( netdev, NULL, -ENOMEM );
302 goto done;
304 max_frag_len = iob_tailroom ( iobuf );
305 if ( frag_len > max_frag_len ) {
306 DBGC ( undinic, "UNDINIC %p fragment too big "
307 "(%zd+%zd does not fit into %zd)\n",
308 undinic, iob_len ( iobuf ), frag_len,
309 ( iob_len ( iobuf ) + max_frag_len ) );
310 frag_len = max_frag_len;
312 copy_from_real ( iob_put ( iobuf, frag_len ),
313 undi_isr.Frame.segment,
314 undi_isr.Frame.offset, frag_len );
315 if ( iob_len ( iobuf ) == len ) {
316 /* Whole packet received; deliver it */
317 netdev_rx ( netdev, iob_disown ( iobuf ) );
318 /* Etherboot 5.4 fails to return all packets
319 * under mild load; pretend it retriggered.
321 if ( undinic->hacks & UNDI_HACK_EB54 )
322 --last_trigger_count;
324 break;
325 case PXENV_UNDI_ISR_OUT_DONE:
326 /* Processing complete */
327 undinic->isr_processing = 0;
328 goto done;
329 default:
330 /* Should never happen. VMWare does it routinely. */
331 DBGC ( undinic, "UNDINIC %p ISR returned invalid "
332 "FuncFlag %04x\n", undinic, undi_isr.FuncFlag );
333 undinic->isr_processing = 0;
334 goto done;
336 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
339 done:
340 if ( iobuf ) {
341 DBGC ( undinic, "UNDINIC %p returned incomplete packet "
342 "(%zd of %zd)\n", undinic, iob_len ( iobuf ),
343 ( iob_len ( iobuf ) + iob_tailroom ( iobuf ) ) );
344 netdev_rx_err ( netdev, iobuf, -EINVAL );
349 * Open NIC
351 * @v netdev Net device
352 * @ret rc Return status code
354 static int undinet_open ( struct net_device *netdev ) {
355 struct undi_nic *undinic = netdev->priv;
356 struct s_PXENV_UNDI_SET_STATION_ADDRESS undi_set_address;
357 struct s_PXENV_UNDI_OPEN undi_open;
358 int rc;
360 /* Hook interrupt service routine and enable interrupt */
361 undinet_hook_isr ( undinic->irq );
362 enable_irq ( undinic->irq );
363 send_eoi ( undinic->irq );
365 /* Set station address. Required for some PXE stacks; will
366 * spuriously fail on others. Ignore failures. We only ever
367 * use it to set the MAC address to the card's permanent value
368 * anyway.
370 memcpy ( undi_set_address.StationAddress, netdev->ll_addr,
371 sizeof ( undi_set_address.StationAddress ) );
372 pxeparent_call ( undinet_entry, PXENV_UNDI_SET_STATION_ADDRESS,
373 &undi_set_address, sizeof ( undi_set_address ) );
375 /* Open NIC. We ask for promiscuous operation, since it's the
376 * only way to ask for all multicast addresses. On any
377 * switched network, it shouldn't really make a difference to
378 * performance.
380 memset ( &undi_open, 0, sizeof ( undi_open ) );
381 undi_open.PktFilter = ( FLTR_DIRECTED | FLTR_BRDCST | FLTR_PRMSCS );
382 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_OPEN,
383 &undi_open, sizeof ( undi_open ) ) ) != 0 )
384 goto err;
386 DBGC ( undinic, "UNDINIC %p opened\n", undinic );
387 return 0;
389 err:
390 undinet_close ( netdev );
391 return rc;
395 * Close NIC
397 * @v netdev Net device
399 static void undinet_close ( struct net_device *netdev ) {
400 struct undi_nic *undinic = netdev->priv;
401 struct s_PXENV_UNDI_ISR undi_isr;
402 struct s_PXENV_UNDI_CLOSE undi_close;
403 int rc;
405 /* Ensure ISR has exited cleanly */
406 while ( undinic->isr_processing ) {
407 undi_isr.FuncFlag = PXENV_UNDI_ISR_IN_GET_NEXT;
408 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_ISR,
409 &undi_isr,
410 sizeof ( undi_isr ) ) ) != 0 )
411 break;
412 switch ( undi_isr.FuncFlag ) {
413 case PXENV_UNDI_ISR_OUT_TRANSMIT:
414 case PXENV_UNDI_ISR_OUT_RECEIVE:
415 /* Continue draining */
416 break;
417 default:
418 /* Stop processing */
419 undinic->isr_processing = 0;
420 break;
424 /* Close NIC */
425 pxeparent_call ( undinet_entry, PXENV_UNDI_CLOSE,
426 &undi_close, sizeof ( undi_close ) );
428 /* Disable interrupt and unhook ISR */
429 disable_irq ( undinic->irq );
430 undinet_unhook_isr ( undinic->irq );
432 DBGC ( undinic, "UNDINIC %p closed\n", undinic );
436 * Enable/disable interrupts
438 * @v netdev Net device
439 * @v enable Interrupts should be enabled
441 static void undinet_irq ( struct net_device *netdev, int enable ) {
442 struct undi_nic *undinic = netdev->priv;
444 /* Cannot support interrupts yet */
445 DBGC ( undinic, "UNDINIC %p cannot %s interrupts\n",
446 undinic, ( enable ? "enable" : "disable" ) );
449 /** UNDI network device operations */
450 static struct net_device_operations undinet_operations = {
451 .open = undinet_open,
452 .close = undinet_close,
453 .transmit = undinet_transmit,
454 .poll = undinet_poll,
455 .irq = undinet_irq,
459 * Probe UNDI device
461 * @v undi UNDI device
462 * @ret rc Return status code
464 int undinet_probe ( struct undi_device *undi ) {
465 struct net_device *netdev;
466 struct undi_nic *undinic;
467 struct s_PXENV_START_UNDI start_undi;
468 struct s_PXENV_UNDI_STARTUP undi_startup;
469 struct s_PXENV_UNDI_INITIALIZE undi_initialize;
470 struct s_PXENV_UNDI_GET_INFORMATION undi_info;
471 struct s_PXENV_UNDI_GET_IFACE_INFO undi_iface;
472 struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
473 struct s_PXENV_UNDI_CLEANUP undi_cleanup;
474 struct s_PXENV_STOP_UNDI stop_undi;
475 int rc;
477 /* Allocate net device */
478 netdev = alloc_etherdev ( sizeof ( *undinic ) );
479 if ( ! netdev )
480 return -ENOMEM;
481 netdev_init ( netdev, &undinet_operations );
482 undinic = netdev->priv;
483 undi_set_drvdata ( undi, netdev );
484 netdev->dev = &undi->dev;
485 memset ( undinic, 0, sizeof ( *undinic ) );
486 undinet_entry = undi->entry;
487 DBGC ( undinic, "UNDINIC %p using UNDI %p\n", undinic, undi );
489 /* Hook in UNDI stack */
490 if ( ! ( undi->flags & UNDI_FL_STARTED ) ) {
491 memset ( &start_undi, 0, sizeof ( start_undi ) );
492 start_undi.AX = undi->pci_busdevfn;
493 start_undi.BX = undi->isapnp_csn;
494 start_undi.DX = undi->isapnp_read_port;
495 start_undi.ES = BIOS_SEG;
496 start_undi.DI = find_pnp_bios();
497 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_START_UNDI,
498 &start_undi,
499 sizeof ( start_undi ) ) ) != 0 )
500 goto err_start_undi;
502 undi->flags |= UNDI_FL_STARTED;
504 /* Bring up UNDI stack */
505 if ( ! ( undi->flags & UNDI_FL_INITIALIZED ) ) {
506 memset ( &undi_startup, 0, sizeof ( undi_startup ) );
507 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_STARTUP,
508 &undi_startup,
509 sizeof ( undi_startup ) ) ) != 0 )
510 goto err_undi_startup;
511 memset ( &undi_initialize, 0, sizeof ( undi_initialize ) );
512 if ( ( rc = pxeparent_call ( undinet_entry,
513 PXENV_UNDI_INITIALIZE,
514 &undi_initialize,
515 sizeof ( undi_initialize ))) != 0 )
516 goto err_undi_initialize;
518 undi->flags |= UNDI_FL_INITIALIZED;
520 /* Get device information */
521 memset ( &undi_info, 0, sizeof ( undi_info ) );
522 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_INFORMATION,
523 &undi_info, sizeof ( undi_info ) ) ) != 0 )
524 goto err_undi_get_information;
525 memcpy ( netdev->hw_addr, undi_info.PermNodeAddress, ETH_ALEN );
526 undinic->irq = undi_info.IntNumber;
527 if ( undinic->irq > IRQ_MAX ) {
528 DBGC ( undinic, "UNDINIC %p invalid IRQ %d\n",
529 undinic, undinic->irq );
530 goto err_bad_irq;
532 DBGC ( undinic, "UNDINIC %p is %s on IRQ %d\n",
533 undinic, eth_ntoa ( netdev->hw_addr ), undinic->irq );
535 /* Get interface information */
536 memset ( &undi_iface, 0, sizeof ( undi_iface ) );
537 if ( ( rc = pxeparent_call ( undinet_entry, PXENV_UNDI_GET_IFACE_INFO,
538 &undi_iface,
539 sizeof ( undi_iface ) ) ) != 0 )
540 goto err_undi_get_iface_info;
541 DBGC ( undinic, "UNDINIC %p has type %s, speed %d, flags %08x\n",
542 undinic, undi_iface.IfaceType, undi_iface.LinkSpeed,
543 undi_iface.ServiceFlags );
544 if ( strncmp ( ( ( char * ) undi_iface.IfaceType ), "Etherboot",
545 sizeof ( undi_iface.IfaceType ) ) == 0 ) {
546 DBGC ( undinic, "UNDINIC %p Etherboot 5.4 workaround enabled\n",
547 undinic );
548 undinic->hacks |= UNDI_HACK_EB54;
551 /* Mark as link up; we don't handle link state */
552 netdev_link_up ( netdev );
554 /* Register network device */
555 if ( ( rc = register_netdev ( netdev ) ) != 0 )
556 goto err_register;
558 DBGC ( undinic, "UNDINIC %p added\n", undinic );
559 return 0;
561 err_register:
562 err_undi_get_iface_info:
563 err_bad_irq:
564 err_undi_get_information:
565 err_undi_initialize:
566 /* Shut down UNDI stack */
567 memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
568 pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN, &undi_shutdown,
569 sizeof ( undi_shutdown ) );
570 memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
571 pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP, &undi_cleanup,
572 sizeof ( undi_cleanup ) );
573 undi->flags &= ~UNDI_FL_INITIALIZED;
574 err_undi_startup:
575 /* Unhook UNDI stack */
576 memset ( &stop_undi, 0, sizeof ( stop_undi ) );
577 pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
578 sizeof ( stop_undi ) );
579 undi->flags &= ~UNDI_FL_STARTED;
580 err_start_undi:
581 netdev_nullify ( netdev );
582 netdev_put ( netdev );
583 undi_set_drvdata ( undi, NULL );
584 return rc;
588 * Remove UNDI device
590 * @v undi UNDI device
592 void undinet_remove ( struct undi_device *undi ) {
593 struct net_device *netdev = undi_get_drvdata ( undi );
594 struct undi_nic *undinic = netdev->priv;
595 struct s_PXENV_UNDI_SHUTDOWN undi_shutdown;
596 struct s_PXENV_UNDI_CLEANUP undi_cleanup;
597 struct s_PXENV_STOP_UNDI stop_undi;
599 /* Unregister net device */
600 unregister_netdev ( netdev );
602 /* If we are preparing for an OS boot, or if we cannot exit
603 * via the PXE stack, then shut down the PXE stack.
605 if ( ! ( undi->flags & UNDI_FL_KEEP_ALL ) ) {
607 /* Shut down UNDI stack */
608 memset ( &undi_shutdown, 0, sizeof ( undi_shutdown ) );
609 pxeparent_call ( undinet_entry, PXENV_UNDI_SHUTDOWN,
610 &undi_shutdown, sizeof ( undi_shutdown ) );
611 memset ( &undi_cleanup, 0, sizeof ( undi_cleanup ) );
612 pxeparent_call ( undinet_entry, PXENV_UNDI_CLEANUP,
613 &undi_cleanup, sizeof ( undi_cleanup ) );
614 undi->flags &= ~UNDI_FL_INITIALIZED;
616 /* Unhook UNDI stack */
617 memset ( &stop_undi, 0, sizeof ( stop_undi ) );
618 pxeparent_call ( undinet_entry, PXENV_STOP_UNDI, &stop_undi,
619 sizeof ( stop_undi ) );
620 undi->flags &= ~UNDI_FL_STARTED;
623 /* Clear entry point */
624 memset ( &undinet_entry, 0, sizeof ( undinet_entry ) );
626 /* Free network device */
627 netdev_nullify ( netdev );
628 netdev_put ( netdev );
630 DBGC ( undinic, "UNDINIC %p removed\n", undinic );