david/ipxe
david
/
ipxe
Archived
1
0
Fork 0

[efi] Expose an UNDI interface alongside the existing SNP interface

UEFI UNDI is a hideously ugly lump of poorly specified garbage bolted
on as an appendix of the UEFI specification.  My personal favourite
line from the UNDI 'specification' is section E.2.2, which states
"Basically, the rule is: Do it right, or don't do it at all".  The
author appears to believe that such exhortations are a viable
substitute for documenting what it is that the wretched reader is
supposed to, in fact, do.

(Second favourite is the section listing the pros and cons of various
driver types.  This fails to identify a single con for the mythical
"Hardware UNDI", a design so insanely intrinsically slow that it
appears to have been the inspiration for the EFI_USB_IO_PROTOCOL.)

UNDI is functionally isomorphic to the substantially less preposterous
EFI_SIMPLE_NETWORK_PROTOCOL.  Provide an UNDI interface (as a thin
wrapper around the existing SNP interface) to allow for use by
third-party software that has made poor life choices.

Signed-off-by: Michael Brown <mcb30@ipxe.org>
This commit is contained in:
Michael Brown 2015-09-27 17:21:24 +01:00
parent 9ff6d08bf5
commit 300a371bfb
1 changed files with 676 additions and 23 deletions

View File

@ -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,