diff --git a/src/drivers/driver.h b/src/drivers/driver.h index 77a2cebe0..766899605 100644 --- a/src/drivers/driver.h +++ b/src/drivers/driver.h @@ -711,7 +711,7 @@ struct wpa_driver_ops { * @priv: private driver interface data * * Returns: Pointer to the interface name. This can differ from the - * interface name used in init() call. + * interface name used in init() call. Init() is called first. * * This optional function can be used to allow the driver interface to * replace the interface name with something else, e.g., based on an @@ -945,6 +945,13 @@ struct wpa_driver_ops { int (*set_mode)(void *priv, int mode); }; +/* Function to check whether a driver is for wired connections */ +static inline int IS_WIRED(const struct wpa_driver_ops *drv) +{ + return os_strcmp(drv->name, "wired") == 0 || + os_strcmp(drv->name, "roboswitch") == 0; +} + /** * enum wpa_event_type - Event type for wpa_supplicant_event() calls */ diff --git a/src/drivers/driver_roboswitch.c b/src/drivers/driver_roboswitch.c new file mode 100644 index 000000000..dceb66761 --- /dev/null +++ b/src/drivers/driver_roboswitch.c @@ -0,0 +1,409 @@ +/* + * WPA Supplicant - roboswitch driver interface + * Copyright (c) 2008 Jouke Witteveen + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Alternatively, this software may be distributed under the terms of BSD + * license. + * + * See README and COPYING for more details. + */ + +#include "includes.h" +#include +#include +#include +#include + +#include "common.h" +#include "driver.h" + +#define ROBO_PHY_ADDR 0x1E /* RoboSwitch PHY address */ + +/* MII access registers */ +#define ROBO_MII_PAGE 0x10 /* MII page register */ +#define ROBO_MII_ADDR 0x11 /* MII address register */ +#define ROBO_MII_DATA_OFFSET 0x18 /* Start of MII data registers */ + +#define ROBO_MII_PAGE_ENABLE 0x01 /* MII page op code */ +#define ROBO_MII_ADDR_WRITE 0x01 /* MII address write op code */ +#define ROBO_MII_ADDR_READ 0x02 /* MII address read op code */ +#define ROBO_MII_DATA_MAX 4 /* Consecutive MII data registers */ +#define ROBO_MII_RETRY_MAX 10 /* Read attempts before giving up */ + +/* Page numbers */ +#define ROBO_ARLCTRL_PAGE 0x04 /* ARL control page */ +#define ROBO_VLAN_PAGE 0x34 /* VLAN page */ + +/* ARL control page registers */ +#define ROBO_ARLCTRL_CONF 0x00 /* ARL configuration register */ +#define ROBO_ARLCTRL_ADDR_1 0x10 /* Multiport address 1 */ +#define ROBO_ARLCTRL_VEC_1 0x16 /* Multiport vector 1 */ +#define ROBO_ARLCTRL_ADDR_2 0x20 /* Multiport address 2 */ +#define ROBO_ARLCTRL_VEC_2 0x26 /* Multiport vector 2 */ + +/* VLAN page registers */ +#define ROBO_VLAN_ACCESS 0x06 /* VLAN table Access register */ +#define ROBO_VLAN_READ 0x0C /* VLAN read register */ + + +static const u8 pae_group_addr[ETH_ALEN] = +{ 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }; + + +struct wpa_driver_roboswitch_data { + void *ctx; + char ifname[IFNAMSIZ + 1]; + struct ifreq ifr; + int fd; + u16 ports; +}; + + +/* Copied from the kernel-only part of mii.h. */ +static inline struct mii_ioctl_data *if_mii(struct ifreq *rq) +{ + return (struct mii_ioctl_data *) &rq->ifr_ifru; +} + + +static u16 wpa_driver_roboswitch_mdio_read( + struct wpa_driver_roboswitch_data *drv, u8 reg) +{ + struct mii_ioctl_data *mii = if_mii(&drv->ifr); + + mii->phy_id = ROBO_PHY_ADDR; + mii->reg_num = reg; + + if (ioctl(drv->fd, SIOCGMIIREG, &drv->ifr) < 0) { + perror("ioctl[SIOCGMIIREG]"); + return 0x00; + } + return mii->val_out; +} + + +static void wpa_driver_roboswitch_mdio_write( + struct wpa_driver_roboswitch_data *drv, u8 reg, u16 val) +{ + struct mii_ioctl_data *mii = if_mii(&drv->ifr); + + mii->phy_id = ROBO_PHY_ADDR; + mii->reg_num = reg; + mii->val_in = val; + + if (ioctl(drv->fd, SIOCSMIIREG, &drv->ifr) < 0) { + perror("ioctl[SIOCSMIIREG"); + } +} + + +static int wpa_driver_roboswitch_reg(struct wpa_driver_roboswitch_data *drv, + u8 page, u8 reg, u8 op) +{ + int i; + + /* set page number */ + wpa_driver_roboswitch_mdio_write(drv, ROBO_MII_PAGE, + (page << 8) | ROBO_MII_PAGE_ENABLE); + /* set register address */ + wpa_driver_roboswitch_mdio_write(drv, ROBO_MII_ADDR, (reg << 8) | op); + + /* check if operation completed */ + for (i = 0; i < ROBO_MII_RETRY_MAX; ++i) { + if ((wpa_driver_roboswitch_mdio_read(drv, ROBO_MII_ADDR) & 3) == + 0) { + return 0; + } + } + /* timeout */ + return -1; +} + + +static int wpa_driver_roboswitch_read(struct wpa_driver_roboswitch_data *drv, + u8 page, u8 reg, u16 *val, int len) +{ + int i; + + if (len > ROBO_MII_DATA_MAX || + wpa_driver_roboswitch_reg(drv, page, reg, ROBO_MII_ADDR_READ) < 0) { + return -1; + } + for (i = 0; i < len; ++i) { + val[i] = wpa_driver_roboswitch_mdio_read(drv, + ROBO_MII_DATA_OFFSET + i); + } + return 0; +} + + +static int wpa_driver_roboswitch_write(struct wpa_driver_roboswitch_data *drv, + u8 page, u8 reg, u16 *val, int len) +{ + int i; + + if (len > ROBO_MII_DATA_MAX) return -1; + for (i = 0; i < len; ++i) { + wpa_driver_roboswitch_mdio_write(drv, ROBO_MII_DATA_OFFSET + i, + val[i]); + } + return wpa_driver_roboswitch_reg(drv, page, reg, ROBO_MII_ADDR_WRITE); +} + + +static int wpa_driver_roboswitch_get_ssid(void *priv, u8 *ssid) +{ + ssid[0] = 0; + return 0; +} + + +static int wpa_driver_roboswitch_get_bssid(void *priv, u8 *bssid) +{ + /* Report PAE group address as the "BSSID" for wired connection. */ + os_memcpy(bssid, pae_group_addr, ETH_ALEN); + return 0; +} + + +static const char * wpa_driver_roboswitch_get_ifname(void *priv) +{ + struct wpa_driver_roboswitch_data *drv = priv; + return drv->ifname; +} + + +static int wpa_driver_roboswitch_join(struct wpa_driver_roboswitch_data *drv, + const u8 *addr) +{ + int i; + u16 _read, zero = 0; + /* For reasons of simplicity we assume ETH_ALEN is even. */ + u16 addr_word[ETH_ALEN/2]; + /* RoboSwitch uses 16-bit Big Endian addresses. */ + /* The ordering of the words is reversed in the MII registers. */ + for (i = 0; i < ETH_ALEN; i += 2) { + addr_word[(ETH_ALEN - i) / 2 - 1] = WPA_GET_BE16(addr + i); + } + + /* check if multiport addresses are not yet enabled */ + if (wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_CONF, &_read, 1) < 0) { + return -1; + } + if (!(_read & (1 << 4))){ + _read |= 1 << 4; + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_1, addr_word, 3); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_1, &drv->ports, 1); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_2, &zero, 1); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_CONF, &_read, 1); + return 0; + } + /* check if multiport address 1 is free */ + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, ROBO_ARLCTRL_VEC_1, + &_read, 1); + if (_read == 0) { + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_1, addr_word, 3); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_1, &drv->ports, 1); + return 0; + } + /* check if multiport address 2 is free */ + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, ROBO_ARLCTRL_VEC_2, + &_read, 1); + if (_read == 0) { + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_2, addr_word, 3); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_2, &drv->ports, 1); + return 0; + } + /* out of free multiport addresses */ + return -1; +} + + +static int wpa_driver_roboswitch_leave(struct wpa_driver_roboswitch_data *drv, + const u8 *addr) +{ + int i; + u8 mport[4] = { ROBO_ARLCTRL_VEC_1, ROBO_ARLCTRL_ADDR_1, + ROBO_ARLCTRL_VEC_2, ROBO_ARLCTRL_ADDR_2 }; + u16 _read[3], zero = 0; + /* same as at join */ + u16 addr_word[ETH_ALEN/2]; + for (i = 0; i < ETH_ALEN; i += 2) { + addr_word[(ETH_ALEN - i) / 2 - 1] = WPA_GET_BE16(addr + i); + } + + /* find our address/vector pair */ + for (i = 0; i < 4; i += 2) { + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, mport[i], + _read, 1); + if (_read[0] == drv->ports) { + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + mport[i + 1], _read, 3); + if (os_memcmp(read, addr_word, 6) == 0) + break; + } + } + /* check if we found our address/vector pair and deactivate it */ + if (i == 4) return -1; + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, mport[i], &zero, 1); + + /* leave the multiport registers in a sane state */ + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, ROBO_ARLCTRL_VEC_1, + _read, 1); + if (_read[0] == 0) { + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_2, _read, 1); + if (_read[0] == 0) { + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_CONF, _read, 1); + _read[0] &= ~(1 << 4); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_CONF, _read, 1); + } else { + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_2, _read, + 3); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_ADDR_1, _read, + 3); + wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_2, _read, 1); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_1, _read, + 1); + wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, + ROBO_ARLCTRL_VEC_2, &zero, + 1); + } + } + return 0; +} + + +static void * wpa_driver_roboswitch_init(void *ctx, const char *ifname) +{ + struct wpa_driver_roboswitch_data *drv; + int len = -1, sep = -1; + u16 vlan = 0, vlan_read[2]; + + drv = os_zalloc(sizeof(*drv)); + if (drv == NULL) return NULL; + drv->ctx = ctx; + + while (ifname[++len]) if (ifname[len] == '.') sep = len; + if (sep < 0 || sep >= len - 1) { + wpa_printf(MSG_INFO, "%s: No . pair in " + "interfacename %s", __func__, ifname); + os_free(drv); + return NULL; + } + if (sep > IFNAMSIZ) { + wpa_printf(MSG_INFO, "%s: Interfacename %s is too long", + __func__, ifname); + os_free(drv); + return NULL; + } + os_memcpy(drv->ifname, ifname, sep); + drv->ifname[sep] = '\0'; + while (++sep < len) { + if (ifname[sep] < '0' || ifname[sep] > '9') { + wpa_printf(MSG_INFO, "%s: Invalid vlan specification " + "in interfacename %s", __func__, ifname); + os_free(drv); + return NULL; + } + vlan *= 10; + vlan += ifname[sep] - '0'; + if (vlan > 255) { + wpa_printf(MSG_INFO, "%s: VLAN out of range in " + "interfacename %s", __func__, ifname); + os_free(drv); + return NULL; + } + } + + drv->fd = socket(PF_INET, SOCK_DGRAM, 0); + if (drv->fd < 0) { + wpa_printf(MSG_INFO, "%s: Unable to create socket", __func__); + os_free(drv); + return NULL; + } + + os_memset(&drv->ifr, 0, sizeof(drv->ifr)); + os_strlcpy(drv->ifr.ifr_name, drv->ifname, IFNAMSIZ); + if (ioctl(drv->fd, SIOCGMIIPHY, &drv->ifr) < 0) { + perror("ioctl[SIOCGMIIPHY]"); + os_free(drv); + return NULL; + } + if (if_mii(&drv->ifr)->phy_id != ROBO_PHY_ADDR) { + wpa_printf(MSG_INFO, "%s: Invalid phy address (not a " + "RoboSwitch?)", __func__); + os_free(drv); + return NULL; + } + + vlan |= 1 << 13; + /* The BCM5365 uses a different register and is not accounted for. */ + wpa_driver_roboswitch_write(drv, ROBO_VLAN_PAGE, ROBO_VLAN_ACCESS, + &vlan, 1); + wpa_driver_roboswitch_read(drv, ROBO_VLAN_PAGE, ROBO_VLAN_READ, + vlan_read, 2); + if (!(vlan_read[1] & (1 << 4))) { + wpa_printf(MSG_INFO, "%s: Could not get port information for " + "VLAN %d", __func__, vlan & ~(1 << 13)); + os_free(drv); + return NULL; + } + drv->ports = vlan_read[0] & 0x001F; + /* add the MII port */ + drv->ports |= 1 << 8; + if (wpa_driver_roboswitch_join(drv, pae_group_addr) < 0) { + wpa_printf(MSG_INFO, "%s: Unable to join PAE group", __func__); + os_free(drv); + return NULL; + } else { + wpa_printf(MSG_DEBUG, "%s: Added PAE group address to " + "RoboSwitch ARL", __func__); + } + + return drv; +} + + +static void wpa_driver_roboswitch_deinit(void *priv) +{ + struct wpa_driver_roboswitch_data *drv = priv; + + if (wpa_driver_roboswitch_leave(drv, pae_group_addr) < 0) { + wpa_printf(MSG_DEBUG, "%s: Unable to leave PAE group", + __func__); + } + + close(drv->fd); + os_free(drv); +} + + +const struct wpa_driver_ops wpa_driver_roboswitch_ops = { + .name = "roboswitch", + .desc = "wpa_supplicant roboswitch driver", + .get_ssid = wpa_driver_roboswitch_get_ssid, + .get_bssid = wpa_driver_roboswitch_get_bssid, + .init = wpa_driver_roboswitch_init, + .deinit = wpa_driver_roboswitch_deinit, + .get_ifname = wpa_driver_roboswitch_get_ifname, +}; diff --git a/src/drivers/drivers.c b/src/drivers/drivers.c index 93e3c9e84..d278797d7 100644 --- a/src/drivers/drivers.c +++ b/src/drivers/drivers.c @@ -70,6 +70,10 @@ extern struct wpa_driver_ops wpa_driver_ps3_ops; /* driver_ps3.c */ #ifdef CONFIG_DRIVER_IPHONE extern struct wpa_driver_ops wpa_driver_iphone_ops; /* driver_iphone.m */ #endif /* CONFIG_DRIVER_IPHONE */ +#ifdef CONFIG_DRIVER_ROBOSWITCH +/* driver_roboswitch.c */ +extern struct wpa_driver_ops wpa_driver_roboswitch_ops; +#endif /* CONFIG_DRIVER_ROBOSWITCH */ struct wpa_driver_ops *wpa_supplicant_drivers[] = @@ -128,5 +132,8 @@ struct wpa_driver_ops *wpa_supplicant_drivers[] = #ifdef CONFIG_DRIVER_IPHONE &wpa_driver_iphone_ops, #endif /* CONFIG_DRIVER_IPHONE */ +#ifdef CONFIG_DRIVER_ROBOSWITCH + &wpa_driver_roboswitch_ops, +#endif /* CONFIG_DRIVER_ROBOSWITCH */ NULL }; diff --git a/wpa_supplicant/ChangeLog b/wpa_supplicant/ChangeLog index fa53d473b..263291e6b 100644 --- a/wpa_supplicant/ChangeLog +++ b/wpa_supplicant/ChangeLog @@ -21,6 +21,8 @@ ChangeLog for wpa_supplicant * updated userspace MLME instructions to match with the current Linux mac80211 implementation; please also note that this can only be used with driver_nl80211.c (the old code from driver_wext.c was removed) + * added support (Linux only) for RoboSwitch chipsets (often found in + consumer grade routers); driver interface 'roboswitch' 2008-11-01 - v0.6.5 * added support for SHA-256 as X.509 certificate digest when using the diff --git a/wpa_supplicant/Makefile b/wpa_supplicant/Makefile index 277894c13..a8350086f 100644 --- a/wpa_supplicant/Makefile +++ b/wpa_supplicant/Makefile @@ -235,6 +235,11 @@ OBJS_d += ../src/drivers/MobileApple80211.o LIBS += -framework CoreFoundation endif +ifdef CONFIG_DRIVER_ROBOSWITCH +CFLAGS += -DCONFIG_DRIVER_ROBOSWITCH +OBJS_d += ../src/drivers/driver_roboswitch.o +endif + ifndef CONFIG_L2_PACKET CONFIG_L2_PACKET=linux endif diff --git a/wpa_supplicant/README b/wpa_supplicant/README index fb6636609..643a727a8 100644 --- a/wpa_supplicant/README +++ b/wpa_supplicant/README @@ -525,6 +525,7 @@ drivers: broadcom = Broadcom wl.o driver ipw = Intel ipw2100/2200 driver (old; use wext with Linux 2.6.13 or newer) wired = wpa_supplicant wired Ethernet driver + roboswitch = wpa_supplicant Broadcom switch driver bsd = BSD 802.11 support (Atheros, etc.) ndis = Windows NDIS driver @@ -694,8 +695,8 @@ network={ } -6) Authentication for wired Ethernet. This can be used with 'wired' interface - (-Dwired on command line). +6) Authentication for wired Ethernet. This can be used with 'wired' or + 'roboswitch' interface (-Dwired or -Droboswitch on command line). ctrl_interface=/var/run/wpa_supplicant ctrl_interface_group=wheel diff --git a/wpa_supplicant/defconfig b/wpa_supplicant/defconfig index 1de76788d..abf2408d0 100644 --- a/wpa_supplicant/defconfig +++ b/wpa_supplicant/defconfig @@ -106,6 +106,9 @@ CONFIG_DRIVER_WEXT=y # Driver interface for wired Ethernet drivers CONFIG_DRIVER_WIRED=y +# Driver interface for the Broadcom RoboSwitch family +#CONFIG_DRIVER_ROBOSWITCH=y + # Enable IEEE 802.1X Supplicant (automatically included if any EAP method is # included) CONFIG_IEEE8021X_EAPOL=y diff --git a/wpa_supplicant/doc/docbook/wpa_supplicant.conf.sgml b/wpa_supplicant/doc/docbook/wpa_supplicant.conf.sgml index ebefe115a..462039d9e 100644 --- a/wpa_supplicant/doc/docbook/wpa_supplicant.conf.sgml +++ b/wpa_supplicant/doc/docbook/wpa_supplicant.conf.sgml @@ -174,7 +174,8 @@ network={ Authentication for wired Ethernet. This can be used with - wired interface (-Dwired on command line). + wired or roboswitch interface + (-Dwired or -Droboswitch on command line).
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=wheel diff --git a/wpa_supplicant/doc/docbook/wpa_supplicant.sgml b/wpa_supplicant/doc/docbook/wpa_supplicant.sgml index 84f59908b..d4a7706a8 100644 --- a/wpa_supplicant/doc/docbook/wpa_supplicant.sgml +++ b/wpa_supplicant/doc/docbook/wpa_supplicant.sgml @@ -309,6 +309,13 @@ + + roboswitch + + wpa_supplicant Broadcom switch driver + + + bsd diff --git a/wpa_supplicant/scan.c b/wpa_supplicant/scan.c index 876710978..3c5bad96e 100644 --- a/wpa_supplicant/scan.c +++ b/wpa_supplicant/scan.c @@ -70,9 +70,9 @@ static void wpa_supplicant_scan(void *eloop_ctx, void *timeout_ctx) wpa_s->scan_req = 0; if (wpa_s->conf->ap_scan != 0 && - wpa_s->driver && os_strcmp(wpa_s->driver->name, "wired") == 0) { - wpa_printf(MSG_DEBUG, "Using wired driver - overriding " - "ap_scan configuration"); + wpa_s->driver && IS_WIRED(wpa_s->driver)) { + wpa_printf(MSG_DEBUG, "Using wired authentication - " + "overriding ap_scan configuration"); wpa_s->conf->ap_scan = 0; } diff --git a/wpa_supplicant/wpa_supplicant.c b/wpa_supplicant/wpa_supplicant.c index 59024d87a..dba3f133a 100644 --- a/wpa_supplicant/wpa_supplicant.c +++ b/wpa_supplicant/wpa_supplicant.c @@ -212,7 +212,7 @@ void wpa_supplicant_req_auth_timeout(struct wpa_supplicant *wpa_s, int sec, int usec) { if (wpa_s->conf && wpa_s->conf->ap_scan == 0 && - wpa_s->driver && os_strcmp(wpa_s->driver->name, "wired") == 0) + wpa_s->driver && IS_WIRED(wpa_s->driver)) return; wpa_msg(wpa_s, MSG_DEBUG, "Setting authentication timeout: %d sec " @@ -273,8 +273,7 @@ void wpa_supplicant_initiate_eapol(struct wpa_supplicant *wpa_s) EAPOL_REQUIRE_KEY_BROADCAST; } - if (wpa_s->conf && wpa_s->driver && - os_strcmp(wpa_s->driver->name, "wired") == 0) { + if (wpa_s->conf && wpa_s->driver && IS_WIRED(wpa_s->driver)) { eapol_conf.required_keys = 0; } } @@ -1388,7 +1387,7 @@ struct wpa_ssid * wpa_supplicant_get_ssid(struct wpa_supplicant *wpa_s) } wired = wpa_s->conf->ap_scan == 0 && wpa_s->driver && - os_strcmp(wpa_s->driver->name, "wired") == 0; + IS_WIRED(wpa_s->driver); entry = wpa_s->conf->ssid; while (entry) {