From c0b61bad99badd575bcae3a25b9de0aa43e88f25 Mon Sep 17 00:00:00 2001 From: Fabrice Bacchella Date: Thu, 13 Aug 2015 12:44:21 +0200 Subject: [PATCH] [efi] Work around bugs in Emulex NII driver Modified-by: Michael Brown Signed-off-by: Michael Brown --- src/drivers/net/efi/nii.c | 86 ++++++++++++++++++++++++++++++++------- 1 file changed, 71 insertions(+), 15 deletions(-) diff --git a/src/drivers/net/efi/nii.c b/src/drivers/net/efi/nii.c index 9e0cb60b..b91848f5 100644 --- a/src/drivers/net/efi/nii.c +++ b/src/drivers/net/efi/nii.c @@ -575,9 +575,10 @@ static int nii_get_init_info ( struct nii_nic *nii, * Initialise UNDI * * @v nii NII NIC + * @v flags Flags * @ret rc Return status code */ -static int nii_initialise ( struct nii_nic *nii ) { +static int nii_initialise_flags ( struct nii_nic *nii, unsigned int flags ) { PXE_CPB_INITIALIZE cpb; PXE_DB_INITIALIZE db; unsigned int op; @@ -600,8 +601,7 @@ static int nii_initialise ( struct nii_nic *nii ) { memset ( &db, 0, sizeof ( db ) ); /* Issue command */ - op = NII_OP ( PXE_OPCODE_INITIALIZE, - PXE_OPFLAGS_INITIALIZE_DO_NOT_DETECT_CABLE ); + op = NII_OP ( PXE_OPCODE_INITIALIZE, flags ); if ( ( stat = nii_issue_cpb_db ( nii, op, &cpb, sizeof ( cpb ), &db, sizeof ( db ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); @@ -618,6 +618,36 @@ static int nii_initialise ( struct nii_nic *nii ) { return rc; } +/** + * Initialise UNDI + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_initialise ( struct nii_nic *nii ) { + unsigned int flags; + + /* Initialise UNDI */ + flags = PXE_OPFLAGS_INITIALIZE_DO_NOT_DETECT_CABLE; + return nii_initialise_flags ( nii, flags ); +} + +/** + * Initialise UNDI and detect cable + * + * @v nii NII NIC + * @ret rc Return status code + */ +static int nii_initialise_and_detect ( struct nii_nic *nii ) { + unsigned int flags; + + /* Initialise UNDI and detect cable. This is required to work + * around bugs in some Emulex NII drivers. + */ + flags = PXE_OPFLAGS_INITIALIZE_DETECT_CABLE; + return nii_initialise_flags ( nii, flags ); +} + /** * Shut down UNDI * @@ -650,6 +680,7 @@ static void nii_shutdown ( struct nii_nic *nii ) { static int nii_get_station_address ( struct nii_nic *nii, struct net_device *netdev ) { PXE_DB_STATION_ADDRESS db; + unsigned int op; int stat; int rc; @@ -658,8 +689,9 @@ static int nii_get_station_address ( struct nii_nic *nii, goto err_initialise; /* Issue command */ - if ( ( stat = nii_issue_db ( nii, PXE_OPCODE_STATION_ADDRESS, &db, - sizeof ( db ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_STATION_ADDRESS, + PXE_OPFLAGS_STATION_ADDRESS_READ ); + if ( ( stat = nii_issue_db ( nii, op, &db, sizeof ( db ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not get station address: %s\n", nii->dev.name, strerror ( rc ) ); @@ -689,18 +721,25 @@ static int nii_get_station_address ( struct nii_nic *nii, */ static int nii_set_station_address ( struct nii_nic *nii, struct net_device *netdev ) { + uint32_t implementation = nii->undi->Implementation; PXE_CPB_STATION_ADDRESS cpb; + unsigned int op; int stat; int rc; + /* Fail if setting station address is unsupported */ + if ( ! ( implementation & PXE_ROMID_IMP_STATION_ADDR_SETTABLE ) ) + return -ENOTSUP; + /* Construct parameter block */ memset ( &cpb, 0, sizeof ( cpb ) ); memcpy ( cpb.StationAddr, netdev->ll_addr, netdev->ll_protocol->ll_addr_len ); /* Issue command */ - if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_STATION_ADDRESS, - &cpb, sizeof ( cpb ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_STATION_ADDRESS, + PXE_OPFLAGS_STATION_ADDRESS_WRITE ); + if ( ( stat = nii_issue_cpb ( nii, op, &cpb, sizeof ( cpb ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not set station address: %s\n", nii->dev.name, strerror ( rc ) ); @@ -717,6 +756,7 @@ static int nii_set_station_address ( struct nii_nic *nii, * @ret rc Return status code */ static int nii_set_rx_filters ( struct nii_nic *nii ) { + uint32_t implementation = nii->undi->Implementation; unsigned int flags; unsigned int op; int stat; @@ -724,10 +764,13 @@ static int nii_set_rx_filters ( struct nii_nic *nii ) { /* Construct receive filter set */ flags = ( PXE_OPFLAGS_RECEIVE_FILTER_ENABLE | - PXE_OPFLAGS_RECEIVE_FILTER_UNICAST | - PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST | - PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS | - PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST ); + PXE_OPFLAGS_RECEIVE_FILTER_UNICAST ); + if ( implementation & PXE_ROMID_IMP_BROADCAST_RX_SUPPORTED ) + flags |= PXE_OPFLAGS_RECEIVE_FILTER_BROADCAST; + if ( implementation & PXE_ROMID_IMP_PROMISCUOUS_RX_SUPPORTED ) + flags |= PXE_OPFLAGS_RECEIVE_FILTER_PROMISCUOUS; + if ( implementation & PXE_ROMID_IMP_PROMISCUOUS_MULTICAST_RX_SUPPORTED ) + flags |= PXE_OPFLAGS_RECEIVE_FILTER_ALL_MULTICAST; /* Issue command */ op = NII_OP ( PXE_OPCODE_RECEIVE_FILTERS, flags ); @@ -752,6 +795,7 @@ static int nii_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { struct nii_nic *nii = netdev->priv; PXE_CPB_TRANSMIT cpb; + unsigned int op; int stat; int rc; @@ -768,8 +812,10 @@ static int nii_transmit ( struct net_device *netdev, cpb.MediaheaderLen = netdev->ll_protocol->ll_header_len; /* Transmit packet */ - if ( ( stat = nii_issue_cpb ( nii, PXE_OPCODE_TRANSMIT, &cpb, - sizeof ( cpb ) ) ) < 0 ) { + op = NII_OP ( PXE_OPCODE_TRANSMIT, + ( PXE_OPFLAGS_TRANSMIT_WHOLE | + PXE_OPFLAGS_TRANSMIT_DONT_BLOCK ) ); + if ( ( stat = nii_issue_cpb ( nii, op, &cpb, sizeof ( cpb ) ) ) < 0 ) { rc = -EIO_STAT ( stat ); DBGC ( nii, "NII %s could not transmit: %s\n", nii->dev.name, strerror ( rc ) ); @@ -924,8 +970,18 @@ static int nii_open ( struct net_device *netdev ) { struct nii_nic *nii = netdev->priv; int rc; - /* Initialise NIC */ - if ( ( rc = nii_initialise ( nii ) ) != 0 ) + /* Initialise NIC + * + * Some Emulex NII drivers have a bug which prevents packets + * from being sent or received unless we specifically ask it + * to detect cable presence during initialisation. Work + * around these buggy drivers by requesting cable detection at + * this point, even though we don't care about link state here + * (and would prefer to have the NIC initialise even if no + * cable is present, to match the behaviour of all other iPXE + * drivers). + */ + if ( ( rc = nii_initialise_and_detect ( nii ) ) != 0 ) goto err_initialise; /* Attempt to set station address */