/* * Copyright (C) 2008 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 ); #include #include #include #include #include #include #include #include #include /** @file * * Network device configuration settings * */ /** Network device predefined settings */ const struct setting mac_setting __setting ( SETTING_NETDEV, mac ) = { .name = "mac", .description = "MAC address", .type = &setting_type_hex, }; const struct setting hwaddr_setting __setting ( SETTING_NETDEV, hwaddr ) = { .name = "hwaddr", .description = "Hardware address", .type = &setting_type_hex, }; const struct setting bustype_setting __setting ( SETTING_NETDEV, bustype ) = { .name = "bustype", .description = "Bus type", .type = &setting_type_string, }; const struct setting busloc_setting __setting ( SETTING_NETDEV, busloc ) = { .name = "busloc", .description = "Bus location", .type = &setting_type_uint32, }; const struct setting busid_setting __setting ( SETTING_NETDEV, busid ) = { .name = "busid", .description = "Bus ID", .type = &setting_type_hex, }; const struct setting chip_setting __setting ( SETTING_NETDEV, chip ) = { .name = "chip", .description = "Chip", .type = &setting_type_string, }; const struct setting ifname_setting __setting ( SETTING_NETDEV, ifname ) = { .name = "ifname", .description = "Interface name", .type = &setting_type_string, }; const struct setting mtu_setting __setting ( SETTING_NETDEV, mtu ) = { .name = "mtu", .description = "MTU", .type = &setting_type_int16, .tag = DHCP_MTU, }; /** * Store link-layer address setting * * @v netdev Network device * @v data Setting data, or NULL to clear setting * @v len Length of setting data * @ret rc Return status code */ static int netdev_store_mac ( struct net_device *netdev, const void *data, size_t len ) { struct ll_protocol *ll_protocol = netdev->ll_protocol; /* Record new MAC address */ if ( data ) { if ( len != netdev->ll_protocol->ll_addr_len ) return -EINVAL; memcpy ( netdev->ll_addr, data, len ); } else { /* Reset MAC address if clearing setting */ ll_protocol->init_addr ( netdev->hw_addr, netdev->ll_addr ); } return 0; } /** * Fetch link-layer address setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_mac ( struct net_device *netdev, void *data, size_t len ) { size_t max_len = netdev->ll_protocol->ll_addr_len; if ( len > max_len ) len = max_len; memcpy ( data, netdev->ll_addr, len ); return max_len; } /** * Fetch hardware address setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_hwaddr ( struct net_device *netdev, void *data, size_t len ) { size_t max_len = netdev->ll_protocol->hw_addr_len; if ( len > max_len ) len = max_len; memcpy ( data, netdev->hw_addr, len ); return max_len; } /** * Fetch bus type setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_bustype ( struct net_device *netdev, void *data, size_t len ) { static const char *bustypes[] = { [BUS_TYPE_PCI] = "PCI", [BUS_TYPE_ISAPNP] = "ISAPNP", [BUS_TYPE_EISA] = "EISA", [BUS_TYPE_MCA] = "MCA", [BUS_TYPE_ISA] = "ISA", [BUS_TYPE_TAP] = "TAP", [BUS_TYPE_EFI] = "EFI", [BUS_TYPE_XEN] = "XEN", [BUS_TYPE_HV] = "HV", [BUS_TYPE_USB] = "USB", }; struct device_description *desc = &netdev->dev->desc; const char *bustype; assert ( desc->bus_type < ( sizeof ( bustypes ) / sizeof ( bustypes[0] ) ) ); bustype = bustypes[desc->bus_type]; if ( ! bustype ) return -ENOENT; strncpy ( data, bustype, len ); return strlen ( bustype ); } /** * Fetch bus location setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_busloc ( struct net_device *netdev, void *data, size_t len ) { struct device_description *desc = &netdev->dev->desc; uint32_t busloc; busloc = cpu_to_be32 ( desc->location ); if ( len > sizeof ( busloc ) ) len = sizeof ( busloc ); memcpy ( data, &busloc, len ); return sizeof ( busloc ); } /** * Fetch bus ID setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_busid ( struct net_device *netdev, void *data, size_t len ) { struct device_description *desc = &netdev->dev->desc; struct dhcp_netdev_desc dhcp_desc; dhcp_desc.type = desc->bus_type; dhcp_desc.vendor = htons ( desc->vendor ); dhcp_desc.device = htons ( desc->device ); if ( len > sizeof ( dhcp_desc ) ) len = sizeof ( dhcp_desc ); memcpy ( data, &dhcp_desc, len ); return sizeof ( dhcp_desc ); } /** * Fetch chip setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_chip ( struct net_device *netdev, void *data, size_t len ) { const char *chip = netdev->dev->driver_name; strncpy ( data, chip, len ); return strlen ( chip ); } /** * Fetch ifname setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch_ifname ( struct net_device *netdev, void *data, size_t len ) { const char *ifname = netdev->name; strncpy ( data, ifname, len ); return strlen ( ifname ); } /** A network device setting operation */ struct netdev_setting_operation { /** Setting */ const struct setting *setting; /** Store setting (or NULL if not supported) * * @v netdev Network device * @v data Setting data, or NULL to clear setting * @v len Length of setting data * @ret rc Return status code */ int ( * store ) ( struct net_device *netdev, const void *data, size_t len ); /** Fetch setting * * @v netdev Network device * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ int ( * fetch ) ( struct net_device *netdev, void *data, size_t len ); }; /** Network device settings */ static struct netdev_setting_operation netdev_setting_operations[] = { { &mac_setting, netdev_store_mac, netdev_fetch_mac }, { &hwaddr_setting, NULL, netdev_fetch_hwaddr }, { &bustype_setting, NULL, netdev_fetch_bustype }, { &busloc_setting, NULL, netdev_fetch_busloc }, { &busid_setting, NULL, netdev_fetch_busid }, { &chip_setting, NULL, netdev_fetch_chip }, { &ifname_setting, NULL, netdev_fetch_ifname }, }; /** * Store value of network device setting * * @v settings Settings block * @v setting Setting to store * @v data Setting data, or NULL to clear setting * @v len Length of setting data * @ret rc Return status code */ static int netdev_store ( struct settings *settings, const struct setting *setting, const void *data, size_t len ) { struct net_device *netdev = container_of ( settings, struct net_device, settings.settings ); struct netdev_setting_operation *op; unsigned int i; /* Handle network device-specific settings */ for ( i = 0 ; i < ( sizeof ( netdev_setting_operations ) / sizeof ( netdev_setting_operations[0] ) ) ; i++ ) { op = &netdev_setting_operations[i]; if ( setting_cmp ( setting, op->setting ) == 0 ) { if ( op->store ) { return op->store ( netdev, data, len ); } else { return -ENOTSUP; } } } return generic_settings_store ( settings, setting, data, len ); } /** * Fetch value of network device setting * * @v settings Settings block * @v setting Setting to fetch * @v data Buffer to fill with setting data * @v len Length of buffer * @ret len Length of setting data, or negative error */ static int netdev_fetch ( struct settings *settings, struct setting *setting, void *data, size_t len ) { struct net_device *netdev = container_of ( settings, struct net_device, settings.settings ); struct netdev_setting_operation *op; unsigned int i; /* Handle network device-specific settings */ for ( i = 0 ; i < ( sizeof ( netdev_setting_operations ) / sizeof ( netdev_setting_operations[0] ) ) ; i++ ) { op = &netdev_setting_operations[i]; if ( setting_cmp ( setting, op->setting ) == 0 ) return op->fetch ( netdev, data, len ); } return generic_settings_fetch ( settings, setting, data, len ); } /** * Clear network device settings * * @v settings Settings block */ static void netdev_clear ( struct settings *settings ) { generic_settings_clear ( settings ); } /** Network device configuration settings operations */ struct settings_operations netdev_settings_operations = { .store = netdev_store, .fetch = netdev_fetch, .clear = netdev_clear, }; /** * Redirect "netX" settings block * * @v settings Settings block * @ret settings Underlying settings block */ static struct settings * netdev_redirect ( struct settings *settings ) { struct net_device *netdev; /* Redirect to most recently opened network device */ netdev = last_opened_netdev(); if ( netdev ) { return netdev_settings ( netdev ); } else { return settings; } } /** "netX" settings operations */ static struct settings_operations netdev_redirect_settings_operations = { .redirect = netdev_redirect, }; /** "netX" settings */ static struct settings netdev_redirect_settings = { .refcnt = NULL, .siblings = LIST_HEAD_INIT ( netdev_redirect_settings.siblings ), .children = LIST_HEAD_INIT ( netdev_redirect_settings.children ), .op = &netdev_redirect_settings_operations, }; /** Initialise "netX" settings */ static void netdev_redirect_settings_init ( void ) { int rc; if ( ( rc = register_settings ( &netdev_redirect_settings, NULL, "netX" ) ) != 0 ) { DBG ( "Could not register netX settings: %s\n", strerror ( rc ) ); return; } } /** "netX" settings initialiser */ struct init_fn netdev_redirect_settings_init_fn __init_fn ( INIT_LATE ) = { .initialise = netdev_redirect_settings_init, }; /** * Apply network device settings * * @ret rc Return status code */ static int apply_netdev_settings ( void ) { struct net_device *netdev; struct settings *settings; struct ll_protocol *ll_protocol; size_t max_mtu; size_t old_mtu; size_t mtu; int rc; /* Process settings for each network device */ for_each_netdev ( netdev ) { /* Get network device settings */ settings = netdev_settings ( netdev ); /* Get MTU */ mtu = fetch_uintz_setting ( settings, &mtu_setting ); /* Do nothing unless MTU is specified */ if ( ! mtu ) continue; /* Limit MTU to maximum supported by hardware */ ll_protocol = netdev->ll_protocol; max_mtu = ( netdev->max_pkt_len - ll_protocol->ll_header_len ); if ( mtu > max_mtu ) { DBGC ( netdev, "NETDEV %s cannot support MTU %zd (max " "%zd)\n", netdev->name, mtu, max_mtu ); mtu = max_mtu; } /* Update maximum packet length */ old_mtu = netdev->mtu; netdev->mtu = mtu; if ( mtu != old_mtu ) { DBGC ( netdev, "NETDEV %s MTU is %zd\n", netdev->name, mtu ); } /* Close and reopen network device if MTU has increased */ if ( netdev_is_open ( netdev ) && ( mtu > old_mtu ) ) { netdev_close ( netdev ); if ( ( rc = netdev_open ( netdev ) ) != 0 ) { DBGC ( netdev, "NETDEV %s could not reopen: " "%s\n", netdev->name, strerror ( rc ) ); return rc; } } } return 0; } /** Network device settings applicator */ struct settings_applicator netdev_applicator __settings_applicator = { .apply = apply_netdev_settings, };