diff --git a/src/interface/efi/efi_snp.c b/src/interface/efi/efi_snp.c index c6a56ab79..09a1bf4c7 100644 --- a/src/interface/efi/efi_snp.c +++ b/src/interface/efi/efi_snp.c @@ -183,7 +183,7 @@ efi_snp_start ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { struct efi_snp_device *snpdev = container_of ( snp, struct efi_snp_device, snp ); - DBGC2 ( snpdev, "SNPDEV %p START\n", snpdev ); + DBGC ( snpdev, "SNPDEV %p START\n", snpdev ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -205,7 +205,7 @@ efi_snp_stop ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { struct efi_snp_device *snpdev = container_of ( snp, struct efi_snp_device, snp ); - DBGC2 ( snpdev, "SNPDEV %p STOP\n", snpdev ); + DBGC ( snpdev, "SNPDEV %p STOP\n", snpdev ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -213,6 +213,7 @@ efi_snp_stop ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { snpdev->started = 0; efi_snp_set_state ( snpdev ); + return 0; } @@ -231,9 +232,9 @@ efi_snp_initialize ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, container_of ( snp, struct efi_snp_device, snp ); int rc; - DBGC2 ( snpdev, "SNPDEV %p INITIALIZE (%ld extra RX, %ld extra TX)\n", - snpdev, ( ( unsigned long ) extra_rx_bufsize ), - ( ( unsigned long ) extra_tx_bufsize ) ); + DBGC ( snpdev, "SNPDEV %p INITIALIZE (%ld extra RX, %ld extra TX)\n", + snpdev, ( ( unsigned long ) extra_rx_bufsize ), + ( ( unsigned long ) extra_tx_bufsize ) ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -262,8 +263,8 @@ efi_snp_reset ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ext_verify ) { container_of ( snp, struct efi_snp_device, snp ); int rc; - DBGC2 ( snpdev, "SNPDEV %p RESET (%s extended verification)\n", - snpdev, ( ext_verify ? "with" : "without" ) ); + DBGC ( snpdev, "SNPDEV %p RESET (%s extended verification)\n", + snpdev, ( ext_verify ? "with" : "without" ) ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -294,7 +295,7 @@ efi_snp_shutdown ( EFI_SIMPLE_NETWORK_PROTOCOL *snp ) { struct efi_snp_device *snpdev = container_of ( snp, struct efi_snp_device, snp ); - DBGC2 ( snpdev, "SNPDEV %p SHUTDOWN\n", snpdev ); + DBGC ( snpdev, "SNPDEV %p SHUTDOWN\n", snpdev ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -326,9 +327,9 @@ efi_snp_receive_filters ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, UINT32 enable, container_of ( snp, struct efi_snp_device, snp ); unsigned int i; - DBGC2 ( snpdev, "SNPDEV %p RECEIVE_FILTERS %08x&~%08x%s %ld mcast\n", - snpdev, enable, disable, ( mcast_reset ? " reset" : "" ), - ( ( unsigned long ) mcast_count ) ); + DBGC ( snpdev, "SNPDEV %p RECEIVE_FILTERS %08x&~%08x%s %ld mcast\n", + snpdev, enable, disable, ( mcast_reset ? " reset" : "" ), + ( ( unsigned long ) mcast_count ) ); for ( i = 0 ; i < mcast_count ; i++ ) { DBGC2_HDA ( snpdev, i, &mcast[i], snpdev->netdev->ll_protocol->ll_addr_len ); @@ -359,8 +360,8 @@ efi_snp_station_address ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, container_of ( snp, struct efi_snp_device, snp ); struct ll_protocol *ll_protocol = snpdev->netdev->ll_protocol; - DBGC2 ( snpdev, "SNPDEV %p STATION_ADDRESS %s\n", snpdev, - ( reset ? "reset" : ll_protocol->ntoa ( new ) ) ); + DBGC ( snpdev, "SNPDEV %p STATION_ADDRESS %s\n", snpdev, + ( reset ? "reset" : ll_protocol->ntoa ( new ) ) ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -396,8 +397,8 @@ efi_snp_statistics ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN reset, container_of ( snp, struct efi_snp_device, snp ); EFI_NETWORK_STATISTICS stats_buf; - DBGC2 ( snpdev, "SNPDEV %p STATISTICS%s", snpdev, - ( reset ? " reset" : "" ) ); + DBGC ( snpdev, "SNPDEV %p STATISTICS%s", snpdev, + ( reset ? " reset" : "" ) ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -449,7 +450,7 @@ efi_snp_mcast_ip_to_mac ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN ipv6, ip_str = ( ipv6 ? "(IPv6)" /* FIXME when we have inet6_ntoa() */ : inet_ntoa ( *( ( struct in_addr * ) ip ) ) ); - DBGC2 ( snpdev, "SNPDEV %p MCAST_IP_TO_MAC %s\n", snpdev, ip_str ); + DBGC ( snpdev, "SNPDEV %p MCAST_IP_TO_MAC %s\n", snpdev, ip_str ); /* Fail if net device is currently claimed for use by iPXE */ if ( efi_snp_claimed ) @@ -482,9 +483,9 @@ efi_snp_nvdata ( EFI_SIMPLE_NETWORK_PROTOCOL *snp, BOOLEAN read, struct efi_snp_device *snpdev = container_of ( snp, struct efi_snp_device, snp ); - DBGC2 ( snpdev, "SNPDEV %p NVDATA %s %lx+%lx\n", snpdev, - ( read ? "read" : "write" ), ( ( unsigned long ) offset ), - ( ( unsigned long ) len ) ); + DBGC ( snpdev, "SNPDEV %p NVDATA %s %lx+%lx\n", snpdev, + ( read ? "read" : "write" ), ( ( unsigned long ) offset ), + ( ( unsigned long ) len ) ); if ( ! read ) DBGC2_HDA ( snpdev, offset, data, len ); @@ -802,6 +803,608 @@ static EFI_SIMPLE_NETWORK_PROTOCOL efi_snp_device_snp = { .Receive = efi_snp_receive, }; +/****************************************************************************** + * + * UNDI protocol + * + ****************************************************************************** + */ + +/** Union type for command parameter blocks */ +typedef union { + PXE_CPB_STATION_ADDRESS station_address; + PXE_CPB_FILL_HEADER fill_header; + PXE_CPB_FILL_HEADER_FRAGMENTED fill_header_fragmented; + PXE_CPB_TRANSMIT transmit; + PXE_CPB_RECEIVE receive; +} PXE_CPB_ANY; + +/** Union type for data blocks */ +typedef union { + PXE_DB_GET_INIT_INFO get_init_info; + PXE_DB_STATION_ADDRESS station_address; + PXE_DB_GET_STATUS get_status; + PXE_DB_RECEIVE receive; +} PXE_DB_ANY; + +/** + * Calculate UNDI byte checksum + * + * @v data Data + * @v len Length of data + * @ret sum Checksum + */ +static uint8_t efi_undi_checksum ( void *data, size_t len ) { + uint8_t *bytes = data; + uint8_t sum = 0; + + while ( len-- ) + sum += *bytes++; + return sum; +} + +/** + * Get UNDI SNP device interface number + * + * @v snpdev SNP device + * @ret ifnum UNDI interface number + */ +static unsigned int efi_undi_ifnum ( struct efi_snp_device *snpdev ) { + + /* iPXE network device indexes are one-based (leaving zero + * meaning "unspecified"). UNDI interface numbers are + * zero-based. + */ + return ( snpdev->netdev->index - 1 ); +} + +/** + * Identify UNDI SNP device + * + * @v ifnum Interface number + * @ret snpdev SNP device, or NULL if not found + */ +static struct efi_snp_device * efi_undi_snpdev ( unsigned int ifnum ) { + struct efi_snp_device *snpdev; + + list_for_each_entry ( snpdev, &efi_snp_devices, list ) { + if ( efi_undi_ifnum ( snpdev ) == ifnum ) + return snpdev; + } + return NULL; +} + +/** + * Convert EFI status code to UNDI status code + * + * @v efirc EFI status code + * @ret statcode UNDI status code + */ +static PXE_STATCODE efi_undi_statcode ( EFI_STATUS efirc ) { + + switch ( efirc ) { + case EFI_INVALID_PARAMETER: return PXE_STATCODE_INVALID_PARAMETER; + case EFI_UNSUPPORTED: return PXE_STATCODE_UNSUPPORTED; + case EFI_OUT_OF_RESOURCES: return PXE_STATCODE_BUFFER_FULL; + case EFI_PROTOCOL_ERROR: return PXE_STATCODE_DEVICE_FAILURE; + case EFI_NOT_READY: return PXE_STATCODE_NO_DATA; + default: + return PXE_STATCODE_INVALID_CDB; + } +} + +/** + * Get state + * + * @v snpdev SNP device + * @v cdb Command description block + * @ret efirc EFI status code + */ +static EFI_STATUS efi_undi_get_state ( struct efi_snp_device *snpdev, + PXE_CDB *cdb ) { + EFI_SIMPLE_NETWORK_MODE *mode = &snpdev->mode; + + DBGC ( snpdev, "UNDI %p GET STATE\n", snpdev ); + + /* Return current state */ + if ( mode->State == EfiSimpleNetworkInitialized ) { + cdb->StatFlags |= PXE_STATFLAGS_GET_STATE_INITIALIZED; + } else if ( mode->State == EfiSimpleNetworkStarted ) { + cdb->StatFlags |= PXE_STATFLAGS_GET_STATE_STARTED; + } else { + cdb->StatFlags |= PXE_STATFLAGS_GET_STATE_STOPPED; + } + + return 0; +} + +/** + * Start + * + * @v snpdev SNP device + * @ret efirc EFI status code + */ +static EFI_STATUS efi_undi_start ( struct efi_snp_device *snpdev ) { + EFI_STATUS efirc; + + DBGC ( snpdev, "UNDI %p START\n", snpdev ); + + /* Start SNP device */ + if ( ( efirc = efi_snp_start ( &snpdev->snp ) ) != 0 ) + return efirc; + + return 0; +} + +/** + * Stop + * + * @v snpdev SNP device + * @ret efirc EFI status code + */ +static EFI_STATUS efi_undi_stop ( struct efi_snp_device *snpdev ) { + EFI_STATUS efirc; + + DBGC ( snpdev, "UNDI %p STOP\n", snpdev ); + + /* Stop SNP device */ + if ( ( efirc = efi_snp_stop ( &snpdev->snp ) ) != 0 ) + return efirc; + + return 0; +} + +/** + * Get initialisation information + * + * @v snpdev SNP device + * @v cdb Command description block + * @v db Data block + * @ret efirc EFI status code + */ +static EFI_STATUS efi_undi_get_init_info ( struct efi_snp_device *snpdev, + PXE_CDB *cdb, + PXE_DB_GET_INIT_INFO *db ) { + struct net_device *netdev = snpdev->netdev; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + + DBGC ( snpdev, "UNDI %p GET INIT INFO\n", snpdev ); + + /* Populate structure */ + memset ( db, 0, sizeof ( *db ) ); + db->FrameDataLen = ( netdev->max_pkt_len - ll_protocol->ll_header_len ); + db->MediaHeaderLen = ll_protocol->ll_header_len; + db->HWaddrLen = ll_protocol->ll_addr_len; + db->IFtype = ntohs ( ll_protocol->ll_proto ); + cdb->StatFlags |= ( PXE_STATFLAGS_CABLE_DETECT_SUPPORTED | + PXE_STATFLAGS_GET_STATUS_NO_MEDIA_SUPPORTED ); + + return 0; +} + +/** + * Initialise + * + * @v snpdev SNP device + * @v cdb Command description block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_initialize ( struct efi_snp_device *snpdev, + PXE_CDB *cdb ) { + struct net_device *netdev = snpdev->netdev; + EFI_STATUS efirc; + + DBGC ( snpdev, "UNDI %p INITIALIZE\n", snpdev ); + + /* Reset SNP device */ + if ( ( efirc = efi_snp_initialize ( &snpdev->snp, 0, 0 ) ) != 0 ) + return efirc; + + /* Report link state */ + if ( ! netdev_link_ok ( netdev ) ) + cdb->StatFlags |= PXE_STATFLAGS_INITIALIZED_NO_MEDIA; + + return 0; +} + +/** + * Reset + * + * @v snpdev SNP device + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_reset ( struct efi_snp_device *snpdev ) { + EFI_STATUS efirc; + + DBGC ( snpdev, "UNDI %p RESET\n", snpdev ); + + /* Reset SNP device */ + if ( ( efirc = efi_snp_reset ( &snpdev->snp, 0 ) ) != 0 ) + return efirc; + + return 0; +} + +/** + * Shutdown + * + * @v snpdev SNP device + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_shutdown ( struct efi_snp_device *snpdev ) { + EFI_STATUS efirc; + + DBGC ( snpdev, "UNDI %p SHUTDOWN\n", snpdev ); + + /* Reset SNP device */ + if ( ( efirc = efi_snp_shutdown ( &snpdev->snp ) ) != 0 ) + return efirc; + + return 0; +} + +/** + * Get/set receive filters + * + * @v snpdev SNP device + * @v cdb Command description block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_receive_filters ( struct efi_snp_device *snpdev, + PXE_CDB *cdb ) { + + DBGC ( snpdev, "UNDI %p RECEIVE FILTERS\n", snpdev ); + + /* Mark everything as supported */ + cdb->StatFlags |= ( PXE_STATFLAGS_RECEIVE_FILTER_UNICAST | + PXE_STATFLAGS_RECEIVE_FILTER_BROADCAST | + PXE_STATFLAGS_RECEIVE_FILTER_PROMISCUOUS | + PXE_STATFLAGS_RECEIVE_FILTER_ALL_MULTICAST ); + + return 0; +} + +/** + * Get/set station address + * + * @v snpdev SNP device + * @v cdb Command description block + * @v cpb Command parameter block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_station_address ( struct efi_snp_device *snpdev, + PXE_CDB *cdb, + PXE_CPB_STATION_ADDRESS *cpb, + PXE_DB_STATION_ADDRESS *db ) { + struct net_device *netdev = snpdev->netdev; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + void *mac; + int reset; + EFI_STATUS efirc; + + DBGC ( snpdev, "UNDI %p STATION ADDRESS\n", snpdev ); + + /* Update address if applicable */ + reset = ( cdb->OpFlags & PXE_OPFLAGS_STATION_ADDRESS_RESET ); + mac = ( cpb ? &cpb->StationAddr : NULL ); + if ( ( reset || mac ) && + ( ( efirc = efi_snp_station_address ( &snpdev->snp, reset, + mac ) ) != 0 ) ) + return efirc; + + /* Fill in current addresses, if applicable */ + if ( db ) { + memset ( db, 0, sizeof ( *db ) ); + memcpy ( &db->StationAddr, netdev->ll_addr, + ll_protocol->ll_addr_len ); + memcpy ( &db->BroadcastAddr, netdev->ll_broadcast, + ll_protocol->ll_addr_len ); + memcpy ( &db->PermanentAddr, netdev->hw_addr, + ll_protocol->hw_addr_len ); + } + + return 0; +} + +/** + * Get interrupt status + * + * @v snpdev SNP device + * @v cdb Command description block + * @v db Data block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_get_status ( struct efi_snp_device *snpdev, + PXE_CDB *cdb, PXE_DB_GET_STATUS *db ) { + UINT32 interrupts; + VOID *txbuf; + struct io_buffer *rxbuf; + EFI_STATUS efirc; + + DBGC2 ( snpdev, "UNDI %p GET STATUS\n", snpdev ); + + /* Get status */ + if ( ( efirc = efi_snp_get_status ( &snpdev->snp, &interrupts, + &txbuf ) ) != 0 ) + return efirc; + + /* Report status */ + memset ( db, 0, sizeof ( *db ) ); + if ( interrupts & EFI_SIMPLE_NETWORK_RECEIVE_INTERRUPT ) + cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_RECEIVE; + if ( interrupts & EFI_SIMPLE_NETWORK_TRANSMIT_INTERRUPT ) + cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_TRANSMIT; + if ( txbuf ) { + db->TxBuffer[0] = ( ( intptr_t ) txbuf ); + } else { + cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_NO_TXBUFS_WRITTEN; + /* The specification states clearly that UNDI drivers + * should set TXBUF_QUEUE_EMPTY if all completed + * buffer addresses are written into the returned data + * block. However, SnpDxe chooses to interpret + * TXBUF_QUEUE_EMPTY as a synonym for + * NO_TXBUFS_WRITTEN, thereby rendering it entirely + * pointless. Work around this UEFI stupidity, as per + * usual. + */ + if ( snpdev->tx_prod == snpdev->tx_cons ) + cdb->StatFlags |= + PXE_STATFLAGS_GET_STATUS_TXBUF_QUEUE_EMPTY; + } + rxbuf = list_first_entry ( &snpdev->rx, struct io_buffer, list ); + if ( rxbuf ) + db->RxFrameLen = iob_len ( rxbuf ); + if ( ! netdev_link_ok ( snpdev->netdev ) ) + cdb->StatFlags |= PXE_STATFLAGS_GET_STATUS_NO_MEDIA; + + return 0; +} + +/** + * Fill header + * + * @v snpdev SNP device + * @v cdb Command description block + * @v cpb Command parameter block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_fill_header ( struct efi_snp_device *snpdev, + PXE_CDB *cdb, PXE_CPB_ANY *cpb ) { + struct net_device *netdev = snpdev->netdev; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + PXE_CPB_FILL_HEADER *whole = &cpb->fill_header; + PXE_CPB_FILL_HEADER_FRAGMENTED *fragged = &cpb->fill_header_fragmented; + VOID *data; + void *dest; + void *src; + uint16_t proto; + struct io_buffer iobuf; + int rc; + + /* SnpDxe will (pointlessly) use PXE_CPB_FILL_HEADER_FRAGMENTED + * even though we choose to explicitly not claim support for + * fragments via PXE_ROMID_IMP_FRAG_SUPPORTED. + */ + if ( cdb->OpFlags & PXE_OPFLAGS_FILL_HEADER_FRAGMENTED ) { + data = ( ( void * ) ( intptr_t ) fragged->FragDesc[0].FragAddr); + dest = &fragged->DestAddr; + src = &fragged->SrcAddr; + proto = fragged->Protocol; + } else { + data = ( ( void * ) ( intptr_t ) whole->MediaHeader ); + dest = &whole->DestAddr; + src = &whole->SrcAddr; + proto = whole->Protocol; + } + + /* Construct link-layer header */ + iob_populate ( &iobuf, data, 0, ll_protocol->ll_header_len ); + iob_reserve ( &iobuf, ll_protocol->ll_header_len ); + if ( ( rc = ll_protocol->push ( netdev, &iobuf, dest, src, + proto ) ) != 0 ) + return EFIRC ( rc ); + + return 0; +} + +/** + * Transmit + * + * @v snpdev SNP device + * @v cpb Command parameter block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_transmit ( struct efi_snp_device *snpdev, + PXE_CPB_TRANSMIT *cpb ) { + VOID *data = ( ( void * ) ( intptr_t ) cpb->FrameAddr ); + EFI_STATUS efirc; + + DBGC2 ( snpdev, "UNDI %p TRANSMIT\n", snpdev ); + + /* Transmit packet */ + if ( ( efirc = efi_snp_transmit ( &snpdev->snp, 0, cpb->DataLen, + data, NULL, NULL, NULL ) ) != 0 ) + return efirc; + + return 0; +} + +/** + * Receive + * + * @v snpdev SNP device + * @v cpb Command parameter block + * @v efirc EFI status code + */ +static EFI_STATUS efi_undi_receive ( struct efi_snp_device *snpdev, + PXE_CPB_RECEIVE *cpb, + PXE_DB_RECEIVE *db ) { + struct net_device *netdev = snpdev->netdev; + struct ll_protocol *ll_protocol = netdev->ll_protocol; + VOID *data = ( ( void * ) ( intptr_t ) cpb->BufferAddr ); + UINTN hdr_len; + UINTN len = cpb->BufferLen; + EFI_MAC_ADDRESS src; + EFI_MAC_ADDRESS dest; + UINT16 proto; + EFI_STATUS efirc; + + DBGC2 ( snpdev, "UNDI %p RECEIVE\n", snpdev ); + + /* Receive packet */ + if ( ( efirc = efi_snp_receive ( &snpdev->snp, &hdr_len, &len, data, + &src, &dest, &proto ) ) != 0 ) + return efirc; + + /* Describe frame */ + memset ( db, 0, sizeof ( *db ) ); + memcpy ( &db->SrcAddr, &src, ll_protocol->ll_addr_len ); + memcpy ( &db->DestAddr, &dest, ll_protocol->ll_addr_len ); + db->FrameLen = len; + db->Protocol = proto; + db->MediaHeaderLen = ll_protocol->ll_header_len; + db->Type = PXE_FRAME_TYPE_PROMISCUOUS; + + return 0; +} + +/** UNDI entry point */ +static EFIAPI VOID efi_undi_issue ( UINT64 cdb_phys ) { + PXE_CDB *cdb = ( ( void * ) ( intptr_t ) cdb_phys ); + PXE_CPB_ANY *cpb = ( ( void * ) ( intptr_t ) cdb->CPBaddr ); + PXE_DB_ANY *db = ( ( void * ) ( intptr_t ) cdb->DBaddr ); + struct efi_snp_device *snpdev; + EFI_STATUS efirc; + + /* Identify device */ + snpdev = efi_undi_snpdev ( cdb->IFnum ); + if ( ! snpdev ) { + DBGC ( cdb, "UNDI invalid interface number %d\n", cdb->IFnum ); + cdb->StatCode = PXE_STATCODE_INVALID_CDB; + cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED; + return; + } + + /* Fail if net device is currently claimed for use by iPXE */ + if ( efi_snp_claimed ) { + cdb->StatCode = PXE_STATCODE_BUSY; + cdb->StatFlags = PXE_STATFLAGS_COMMAND_FAILED; + return; + } + + /* Handle opcode */ + cdb->StatCode = PXE_STATCODE_SUCCESS; + cdb->StatFlags = PXE_STATFLAGS_COMMAND_COMPLETE; + switch ( cdb->OpCode ) { + + case PXE_OPCODE_GET_STATE: + efirc = efi_undi_get_state ( snpdev, cdb ); + break; + + case PXE_OPCODE_START: + efirc = efi_undi_start ( snpdev ); + break; + + case PXE_OPCODE_STOP: + efirc = efi_undi_stop ( snpdev ); + break; + + case PXE_OPCODE_GET_INIT_INFO: + efirc = efi_undi_get_init_info ( snpdev, cdb, + &db->get_init_info ); + break; + + case PXE_OPCODE_INITIALIZE: + efirc = efi_undi_initialize ( snpdev, cdb ); + break; + + case PXE_OPCODE_RESET: + efirc = efi_undi_reset ( snpdev ); + break; + + case PXE_OPCODE_SHUTDOWN: + efirc = efi_undi_shutdown ( snpdev ); + break; + + case PXE_OPCODE_RECEIVE_FILTERS: + efirc = efi_undi_receive_filters ( snpdev, cdb ); + break; + + case PXE_OPCODE_STATION_ADDRESS: + efirc = efi_undi_station_address ( snpdev, cdb, + &cpb->station_address, + &db->station_address ); + break; + + case PXE_OPCODE_GET_STATUS: + efirc = efi_undi_get_status ( snpdev, cdb, &db->get_status ); + break; + + case PXE_OPCODE_FILL_HEADER: + efirc = efi_undi_fill_header ( snpdev, cdb, cpb ); + break; + + case PXE_OPCODE_TRANSMIT: + efirc = efi_undi_transmit ( snpdev, &cpb->transmit ); + break; + + case PXE_OPCODE_RECEIVE: + efirc = efi_undi_receive ( snpdev, &cpb->receive, + &db->receive ); + break; + + default: + DBGC ( snpdev, "UNDI %p unsupported opcode %#04x\n", + snpdev, cdb->OpCode ); + efirc = EFI_UNSUPPORTED; + break; + } + + /* Convert EFI status code to UNDI status code */ + if ( efirc != 0 ) { + cdb->StatFlags &= ~PXE_STATFLAGS_STATUS_MASK; + cdb->StatFlags |= PXE_STATFLAGS_COMMAND_FAILED; + cdb->StatCode = efi_undi_statcode ( efirc ); + } +} + +/** UNDI interface + * + * Must be aligned on a 16-byte boundary, for no particularly good + * reason. + */ +static PXE_SW_UNDI efi_snp_undi __attribute__ (( aligned ( 16 ) )) = { + .Signature = PXE_ROMID_SIGNATURE, + .Len = sizeof ( efi_snp_undi ), + .Rev = PXE_ROMID_REV, + .MajorVer = PXE_ROMID_MAJORVER, + .MinorVer = PXE_ROMID_MINORVER, + .Implementation = ( PXE_ROMID_IMP_SW_VIRT_ADDR | + PXE_ROMID_IMP_STATION_ADDR_SETTABLE | + PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED | + PXE_ROMID_IMP_PROMISCUOUS_RX_SUPPORTED | + PXE_ROMID_IMP_BROADCAST_RX_SUPPORTED | + PXE_ROMID_IMP_TX_COMPLETE_INT_SUPPORTED | + PXE_ROMID_IMP_PACKET_RX_INT_SUPPORTED ), + /* SnpDxe checks that BusCnt is non-zero. It makes no further + * use of BusCnt, and never looks as BusType[]. As with much + * of the EDK2 code, this check seems to serve no purpose + * whatsoever but must nonetheless be humoured. + */ + .BusCnt = 1, + .BusType[0] = PXE_BUSTYPE ( 'i', 'P', 'X', 'E' ), +}; + +/** Network Identification Interface (NII) */ +static EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL efi_snp_device_nii = { + .Revision = EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION, + .StringId = "UNDI", + .Type = EfiNetworkInterfaceUndi, + .MajorVer = 3, + .MinorVer = 1, + .Ipv6Supported = TRUE, /* This is a raw packet interface, FFS! */ +}; + /****************************************************************************** * * Component name protocol @@ -939,6 +1542,8 @@ static int efi_snp_probe ( struct net_device *netdev ) { EFI_DEVICE_PATH_PROTOCOL *path_end; MAC_ADDR_DEVICE_PATH *macpath; size_t path_prefix_len = 0; + unsigned int ifcnt; + void *interface; EFI_STATUS efirc; int rc; @@ -986,10 +1591,17 @@ static int efi_snp_probe ( struct net_device *netdev ) { efi_snp_set_mode ( snpdev ); /* Populate the NII structure */ - snpdev->nii.Revision = - EFI_NETWORK_INTERFACE_IDENTIFIER_PROTOCOL_REVISION; - strncpy ( snpdev->nii.StringId, "iPXE", - sizeof ( snpdev->nii.StringId ) ); + memcpy ( &snpdev->nii, &efi_snp_device_nii, sizeof ( snpdev->nii ) ); + snpdev->nii.Id = ( ( intptr_t ) &efi_snp_undi ); + snpdev->nii.IfNum = efi_undi_ifnum ( snpdev ); + efi_snp_undi.EntryPoint = ( ( intptr_t ) efi_undi_issue ); + ifcnt = ( ( efi_snp_undi.IFcntExt << 8 ) | efi_snp_undi.IFcnt ); + if ( ifcnt < snpdev->nii.IfNum ) + ifcnt = snpdev->nii.IfNum; + efi_snp_undi.IFcnt = ( ifcnt & 0xff ); + efi_snp_undi.IFcntExt = ( ifcnt >> 8 ); + efi_snp_undi.Fudge -= efi_undi_checksum ( &efi_snp_undi, + sizeof ( efi_snp_undi ) ); /* Populate the component name structure */ efi_snprintf ( snpdev->driver_name, @@ -1056,6 +1668,37 @@ static int efi_snp_probe ( struct net_device *netdev ) { goto err_install_protocol_interface; } + /* SnpDxe will repeatedly start up and shut down our NII/UNDI + * interface (in order to obtain the MAC address) before + * discovering that it cannot install another SNP on the same + * handle. This causes the underlying network device to be + * unexpectedly closed. + * + * Prevent this by opening our own NII (and NII31) protocol + * instances to prevent SnpDxe from attempting to bind to + * them. + */ + if ( ( efirc = bs->OpenProtocol ( snpdev->handle, + &efi_nii_protocol_guid, &interface, + efi_image_handle, snpdev->handle, + ( EFI_OPEN_PROTOCOL_BY_DRIVER | + EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not open NII protocol: %s\n", + snpdev, strerror ( rc ) ); + goto err_open_nii; + } + if ( ( efirc = bs->OpenProtocol ( snpdev->handle, + &efi_nii31_protocol_guid, &interface, + efi_image_handle, snpdev->handle, + ( EFI_OPEN_PROTOCOL_BY_DRIVER | + EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ + rc = -EEFI ( efirc ); + DBGC ( snpdev, "SNPDEV %p could not open NII31 protocol: %s\n", + snpdev, strerror ( rc ) ); + goto err_open_nii31; + } + /* Add as child of EFI parent device */ if ( ( rc = efi_child_add ( efidev->device, snpdev->handle ) ) != 0 ) { DBGC ( snpdev, "SNPDEV %p could not become child of %s: %s\n", @@ -1090,6 +1733,12 @@ static int efi_snp_probe ( struct net_device *netdev ) { efi_snp_hii_uninstall ( snpdev ); efi_child_del ( efidev->device, snpdev->handle ); err_efi_child_add: + bs->CloseProtocol ( snpdev->handle, &efi_nii_protocol_guid, + efi_image_handle, snpdev->handle ); + err_open_nii: + bs->CloseProtocol ( snpdev->handle, &efi_nii31_protocol_guid, + efi_image_handle, snpdev->handle ); + err_open_nii31: bs->UninstallMultipleProtocolInterfaces ( snpdev->handle, &efi_simple_network_protocol_guid, &snpdev->snp, @@ -1158,6 +1807,10 @@ static void efi_snp_remove ( struct net_device *netdev ) { if ( snpdev->package_list ) efi_snp_hii_uninstall ( snpdev ); efi_child_del ( snpdev->efidev->device, snpdev->handle ); + bs->CloseProtocol ( snpdev->handle, &efi_nii_protocol_guid, + efi_image_handle, snpdev->handle ); + bs->CloseProtocol ( snpdev->handle, &efi_nii31_protocol_guid, + efi_image_handle, snpdev->handle ); bs->UninstallMultipleProtocolInterfaces ( snpdev->handle, &efi_simple_network_protocol_guid, &snpdev->snp,