/* * Copyright (C) 2024 Michael Brown . * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301, USA. * * You can also choose to distribute this program under the terms of * the Unmodified Binary Distribution Licence (as given in the file * COPYING.UBDL), provided that you have satisfied its requirements. */ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); /** @file * * MNP NIC driver * */ #include #include #include #include #include #include #include #include #include #include #include #include /** An MNP transmit or receive token */ struct mnp_token { /** MNP completion token */ EFI_MANAGED_NETWORK_COMPLETION_TOKEN token; /** Token is owned by MNP */ int busy; }; /** An MNP NIC */ struct mnp_nic { /** EFI device */ struct efi_device *efidev; /** Managed network protocol */ EFI_MANAGED_NETWORK_PROTOCOL *mnp; /** Generic device */ struct device dev; /** Transmit token */ struct mnp_token tx; /** Transmit descriptor */ EFI_MANAGED_NETWORK_TRANSMIT_DATA txdata; /** Transmit I/O buffer */ struct io_buffer *txbuf; /** Receive token */ struct mnp_token rx; }; /** * Transmit or receive token event * * @v event Event * @v context Event context */ static VOID EFIAPI mnpnet_event ( EFI_EVENT event __unused, VOID *context ) { struct mnp_token *token = context; /* Sanity check */ assert ( token->busy ); /* Mark token as no longer owned by MNP */ token->busy = 0; } /** * Transmit packet * * @v netdev Network device * @v iobuf I/O buffer * @ret rc Return status code */ static int mnpnet_transmit ( struct net_device *netdev, struct io_buffer *iobuf ) { struct mnp_nic *mnp = netdev->priv; struct ll_protocol *ll_protocol = netdev->ll_protocol; EFI_STATUS efirc; int rc; /* Do nothing if shutdown is in progress */ if ( efi_shutdown_in_progress ) return -ECANCELED; /* Defer the packet if there is already a transmission in progress */ if ( mnp->txbuf ) { netdev_tx_defer ( netdev, iobuf ); return 0; } /* Construct transmit token */ mnp->txdata.DataLength = ( iob_len ( iobuf ) - ll_protocol->ll_header_len ); mnp->txdata.HeaderLength = ll_protocol->ll_header_len; mnp->txdata.FragmentCount = 1; mnp->txdata.FragmentTable[0].FragmentLength = iob_len ( iobuf ); mnp->txdata.FragmentTable[0].FragmentBuffer = iobuf->data; mnp->tx.token.Packet.TxData = &mnp->txdata; /* Record as in use */ mnp->tx.busy = 1; /* Transmit packet */ if ( ( efirc = mnp->mnp->Transmit ( mnp->mnp, &mnp->tx.token ) ) != 0 ){ rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not transmit: %s\n", netdev->name, strerror ( rc ) ); mnp->tx.busy = 0; return rc; } /* Record I/O buffer */ mnp->txbuf = iobuf; return 0; } /** * Refill receive token * * @v netdev Network device */ static void mnpnet_refill_rx ( struct net_device *netdev ) { struct mnp_nic *mnp = netdev->priv; EFI_STATUS efirc; int rc; /* Do nothing if receive token is still in use */ if ( mnp->rx.busy ) return; /* Mark as in use */ mnp->rx.busy = 1; /* Queue receive token */ if ( ( efirc = mnp->mnp->Receive ( mnp->mnp, &mnp->rx.token ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not receive: %s\n", netdev->name, strerror ( rc ) ); /* Wait for next refill */ mnp->rx.busy = 0; return; } } /** * Poll for completed packets * * @v netdev Network device */ static void mnpnet_poll_tx ( struct net_device *netdev ) { struct mnp_nic *mnp = netdev->priv; struct io_buffer *iobuf; EFI_STATUS efirc; int rc; /* Do nothing if transmit token is still in use */ if ( mnp->tx.busy ) return; /* Do nothing unless we have a completion */ if ( ! mnp->txbuf ) return; /* Get completion status */ efirc = mnp->tx.token.Status; rc = ( efirc ? -EEFI ( efirc ) : 0 ); /* Complete transmission */ iobuf = mnp->txbuf; mnp->txbuf = NULL; netdev_tx_complete_err ( netdev, iobuf, rc ); } /** * Poll for received packets * * @v netdev Network device */ static void mnpnet_poll_rx ( struct net_device *netdev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct mnp_nic *mnp = netdev->priv; EFI_MANAGED_NETWORK_RECEIVE_DATA *rxdata; struct io_buffer *iobuf; size_t len; EFI_STATUS efirc; int rc; /* Do nothing unless we have a completion */ if ( mnp->rx.busy ) return; rxdata = mnp->rx.token.Packet.RxData; /* Get completion status */ if ( ( efirc = mnp->rx.token.Status ) != 0 ) { rc = -EEFI ( efirc ); netdev_rx_err ( netdev, NULL, rc ); goto recycle; } /* Allocate and fill I/O buffer */ len = rxdata->PacketLength; iobuf = alloc_iob ( len ); if ( ! iobuf ) { netdev_rx_err ( netdev, NULL, -ENOMEM ); goto recycle; } memcpy ( iob_put ( iobuf, len ), rxdata->MediaHeader, len ); /* Hand off to network stack */ netdev_rx ( netdev, iobuf ); recycle: /* Recycle token */ bs->SignalEvent ( rxdata->RecycleEvent ); } /** * Poll for completed packets * * @v netdev Network device */ static void mnpnet_poll ( struct net_device *netdev ) { struct mnp_nic *mnp = netdev->priv; /* Do nothing if shutdown is in progress */ if ( efi_shutdown_in_progress ) return; /* Poll interface */ mnp->mnp->Poll ( mnp->mnp ); /* Process any transmit completions */ mnpnet_poll_tx ( netdev ); /* Process any receive completions */ mnpnet_poll_rx ( netdev ); /* Refill receive token */ mnpnet_refill_rx ( netdev ); } /** * Open network device * * @v netdev Network device * @ret rc Return status code */ static int mnpnet_open ( struct net_device *netdev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; static EFI_MANAGED_NETWORK_CONFIG_DATA config = { .EnableUnicastReceive = TRUE, .EnableMulticastReceive = TRUE, .EnableBroadcastReceive = TRUE, .EnablePromiscuousReceive = TRUE, .FlushQueuesOnReset = TRUE, .DisableBackgroundPolling = TRUE, }; struct mnp_nic *mnp = netdev->priv; EFI_STATUS efirc; int rc; /* Create transmit event */ if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, mnpnet_event, &mnp->tx, &mnp->tx.token.Event ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not create TX event: %s\n", netdev->name, strerror ( rc ) ); goto err_tx_event; } /* Create receive event */ if ( ( efirc = bs->CreateEvent ( EVT_NOTIFY_SIGNAL, TPL_NOTIFY, mnpnet_event, &mnp->rx, &mnp->rx.token.Event ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not create RX event: %s\n", netdev->name, strerror ( rc ) ); goto err_rx_event; } /* Configure MNP */ if ( ( efirc = mnp->mnp->Configure ( mnp->mnp, &config ) ) != 0 ) { rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not configure: %s\n", netdev->name, strerror ( rc ) ); goto err_configure; } /* Refill receive token */ mnpnet_refill_rx ( netdev ); return 0; mnp->mnp->Configure ( mnp->mnp, NULL ); err_configure: bs->CloseEvent ( mnp->rx.token.Event ); err_rx_event: bs->CloseEvent ( mnp->tx.token.Event ); err_tx_event: return rc; } /** * Close network device * * @v netdev Network device */ static void mnpnet_close ( struct net_device *netdev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; struct mnp_nic *mnp = netdev->priv; /* Reset MNP (unless whole system shutdown is in progress) */ if ( ! efi_shutdown_in_progress ) mnp->mnp->Configure ( mnp->mnp, NULL ); /* Close events */ bs->CloseEvent ( mnp->rx.token.Event ); bs->CloseEvent ( mnp->tx.token.Event ); /* Reset tokens */ mnp->tx.busy = 0; mnp->rx.busy = 0; /* Discard any incomplete I/O buffer */ if ( mnp->txbuf ) { netdev_tx_complete_err ( netdev, mnp->txbuf, -ECANCELED ); mnp->txbuf = NULL; } } /** MNP network device operations */ static struct net_device_operations mnpnet_operations = { .open = mnpnet_open, .close = mnpnet_close, .transmit = mnpnet_transmit, .poll = mnpnet_poll, }; /** * Attach driver to device * * @v efidev EFI device * @ret rc Return status code */ int mnpnet_start ( struct efi_device *efidev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_HANDLE device = efidev->device; EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid; EFI_SIMPLE_NETWORK_MODE mode; union { EFI_MANAGED_NETWORK_PROTOCOL *mnp; void *interface; } u; struct net_device *netdev; struct mnp_nic *mnp; EFI_STATUS efirc; int rc; /* Allocate and initalise structure */ netdev = alloc_etherdev ( sizeof ( *mnp ) ); if ( ! netdev ) { rc = -ENOMEM; goto err_alloc; } netdev_init ( netdev, &mnpnet_operations ); mnp = netdev->priv; mnp->efidev = efidev; efidev_set_drvdata ( efidev, netdev ); /* Populate underlying device information */ efi_device_info ( device, "MNP", &mnp->dev ); mnp->dev.driver_name = "MNP"; mnp->dev.parent = &efidev->dev; list_add ( &mnp->dev.siblings, &efidev->dev.children ); INIT_LIST_HEAD ( &mnp->dev.children ); netdev->dev = &mnp->dev; /* Create MNP child */ if ( ( rc = efi_service_add ( device, binding, &efidev->child ) ) != 0 ) { DBGC ( mnp, "MNP %s could not create child: %s\n", efi_handle_name ( device ), strerror ( rc ) ); goto err_service; } /* Open MNP protocol */ if ( ( efirc = bs->OpenProtocol ( efidev->child, &efi_managed_network_protocol_guid, &u.interface, efi_image_handle, efidev->child, ( EFI_OPEN_PROTOCOL_BY_DRIVER | EFI_OPEN_PROTOCOL_EXCLUSIVE )))!=0){ rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not open MNP protocol: %s\n", efi_handle_name ( device ), strerror ( rc ) ); goto err_open; } mnp->mnp = u.mnp; /* Get configuration */ efirc = mnp->mnp->GetModeData ( mnp->mnp, NULL, &mode ); if ( ( efirc != 0 ) && ( efirc != EFI_NOT_STARTED ) ) { rc = -EEFI ( efirc ); DBGC ( mnp, "MNP %s could not get mode data: %s\n", efi_handle_name ( device ), strerror ( rc ) ); goto err_mode; } /* Populate network device parameters */ if ( mode.HwAddressSize != netdev->ll_protocol->hw_addr_len ) { DBGC ( device, "MNP %s has invalid hardware address length " "%d\n", efi_handle_name ( device ), mode.HwAddressSize ); rc = -ENOTSUP; goto err_hw_addr_len; } memcpy ( netdev->hw_addr, &mode.PermanentAddress, netdev->ll_protocol->hw_addr_len ); if ( mode.HwAddressSize != netdev->ll_protocol->ll_addr_len ) { DBGC ( device, "MNP %s has invalid link-layer address length " "%d\n", efi_handle_name ( device ), mode.HwAddressSize ); rc = -ENOTSUP; goto err_ll_addr_len; } memcpy ( netdev->ll_addr, &mode.CurrentAddress, netdev->ll_protocol->ll_addr_len ); /* Register network device */ if ( ( rc = register_netdev ( netdev ) ) != 0 ) goto err_register; DBGC ( mnp, "MNP %s registered as %s\n", efi_handle_name ( device ), netdev->name ); /* Mark as link up: we don't handle link state */ netdev_link_up ( netdev ); return 0; unregister_netdev ( netdev ); err_register: err_ll_addr_len: err_hw_addr_len: err_mode: bs->CloseProtocol ( efidev->child, &efi_managed_network_protocol_guid, efi_image_handle, efidev->child ); err_open: efi_service_del ( device, binding, efidev->child ); err_service: list_del ( &mnp->dev.siblings ); netdev_nullify ( netdev ); netdev_put ( netdev ); err_alloc: return rc; } /** * Detach driver from device * * @v efidev EFI device */ void mnpnet_stop ( struct efi_device *efidev ) { EFI_BOOT_SERVICES *bs = efi_systab->BootServices; EFI_GUID *binding = &efi_managed_network_service_binding_protocol_guid; struct net_device *netdev = efidev_get_drvdata ( efidev ); struct mnp_nic *mnp = netdev->priv; /* Unregister network device */ unregister_netdev ( netdev ); /* Close MNP protocol */ bs->CloseProtocol ( efidev->child, &efi_managed_network_protocol_guid, efi_image_handle, efidev->child ); /* Remove MNP child (unless whole system shutdown is in progress) */ if ( ! efi_shutdown_in_progress ) efi_service_del ( efidev->device, binding, efidev->child ); /* Free network device */ list_del ( &mnp->dev.siblings ); netdev_nullify ( netdev ); netdev_put ( netdev ); } /** * Create temporary MNP network device * * @v handle MNP service binding handle * @v netdev Network device to fill in * @ret rc Return status code */ int mnptemp_create ( EFI_HANDLE handle, struct net_device **netdev ) { struct efi_device *efidev; int rc; /* Create temporary EFI device */ efidev = efidev_alloc ( handle ); if ( ! efidev ) { DBGC ( handle, "MNP %s could not create temporary device\n", efi_handle_name ( handle ) ); rc = -ENOMEM; goto err_alloc; } /* Start temporary network device */ if ( ( rc = mnpnet_start ( efidev ) ) != 0 ) { DBGC ( handle, "MNP %s could not start MNP: %s\n", efi_handle_name ( handle ), strerror ( rc ) ); goto err_start; } /* Fill in network device */ *netdev = efidev_get_drvdata ( efidev ); return 0; mnpnet_stop ( efidev ); err_start: efidev_free ( efidev ); err_alloc: return rc; } /** * Destroy temporary MNP network device * * @v netdev Network device */ void mnptemp_destroy ( struct net_device *netdev ) { struct mnp_nic *mnp = netdev->priv; struct efi_device *efidev = mnp->efidev; /* Recycle any cached DHCP packet */ cachedhcp_recycle ( netdev ); /* Stop temporary network device */ mnpnet_stop ( efidev ); /* Free temporary EFI device */ efidev_free ( efidev ); }