Dmitry Shmidt | 8d520ff | 2011-05-09 14:06:53 -0700 | [diff] [blame^] | 1 | /* |
| 2 | * WPA Supplicant - roboswitch driver interface |
| 3 | * Copyright (c) 2008-2009 Jouke Witteveen |
| 4 | * |
| 5 | * This program is free software; you can redistribute it and/or modify |
| 6 | * it under the terms of the GNU General Public License version 2 as |
| 7 | * published by the Free Software Foundation. |
| 8 | * |
| 9 | * Alternatively, this software may be distributed under the terms of BSD |
| 10 | * license. |
| 11 | * |
| 12 | * See README and COPYING for more details. |
| 13 | */ |
| 14 | |
| 15 | #include "includes.h" |
| 16 | #include <sys/ioctl.h> |
| 17 | #include <linux/sockios.h> |
| 18 | #include <linux/if_ether.h> |
| 19 | #include <linux/mii.h> |
| 20 | #include <net/if.h> |
| 21 | |
| 22 | #include "common.h" |
| 23 | #include "driver.h" |
| 24 | #include "l2_packet/l2_packet.h" |
| 25 | |
| 26 | #define ROBO_PHY_ADDR 0x1e /* RoboSwitch PHY address */ |
| 27 | |
| 28 | /* MII access registers */ |
| 29 | #define ROBO_MII_PAGE 0x10 /* MII page register */ |
| 30 | #define ROBO_MII_ADDR 0x11 /* MII address register */ |
| 31 | #define ROBO_MII_DATA_OFFSET 0x18 /* Start of MII data registers */ |
| 32 | |
| 33 | #define ROBO_MII_PAGE_ENABLE 0x01 /* MII page op code */ |
| 34 | #define ROBO_MII_ADDR_WRITE 0x01 /* MII address write op code */ |
| 35 | #define ROBO_MII_ADDR_READ 0x02 /* MII address read op code */ |
| 36 | #define ROBO_MII_DATA_MAX 4 /* Consecutive MII data registers */ |
| 37 | #define ROBO_MII_RETRY_MAX 10 /* Read attempts before giving up */ |
| 38 | |
| 39 | /* Page numbers */ |
| 40 | #define ROBO_ARLCTRL_PAGE 0x04 /* ARL control page */ |
| 41 | #define ROBO_VLAN_PAGE 0x34 /* VLAN page */ |
| 42 | |
| 43 | /* ARL control page registers */ |
| 44 | #define ROBO_ARLCTRL_CONF 0x00 /* ARL configuration register */ |
| 45 | #define ROBO_ARLCTRL_ADDR_1 0x10 /* Multiport address 1 */ |
| 46 | #define ROBO_ARLCTRL_VEC_1 0x16 /* Multiport vector 1 */ |
| 47 | #define ROBO_ARLCTRL_ADDR_2 0x20 /* Multiport address 2 */ |
| 48 | #define ROBO_ARLCTRL_VEC_2 0x26 /* Multiport vector 2 */ |
| 49 | |
| 50 | /* VLAN page registers */ |
| 51 | #define ROBO_VLAN_ACCESS 0x08 /* VLAN table access register */ |
| 52 | #define ROBO_VLAN_ACCESS_5350 0x06 /* VLAN table access register (5350) */ |
| 53 | #define ROBO_VLAN_READ 0x0c /* VLAN read register */ |
| 54 | #define ROBO_VLAN_MAX 0xff /* Maximum number of VLANs */ |
| 55 | |
| 56 | |
| 57 | static const u8 pae_group_addr[ETH_ALEN] = |
| 58 | { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }; |
| 59 | |
| 60 | |
| 61 | struct wpa_driver_roboswitch_data { |
| 62 | void *ctx; |
| 63 | struct l2_packet_data *l2; |
| 64 | char ifname[IFNAMSIZ + 1]; |
| 65 | u8 own_addr[ETH_ALEN]; |
| 66 | struct ifreq ifr; |
| 67 | int fd, is_5350; |
| 68 | u16 ports; |
| 69 | }; |
| 70 | |
| 71 | |
| 72 | /* Copied from the kernel-only part of mii.h. */ |
| 73 | static inline struct mii_ioctl_data *if_mii(struct ifreq *rq) |
| 74 | { |
| 75 | return (struct mii_ioctl_data *) &rq->ifr_ifru; |
| 76 | } |
| 77 | |
| 78 | |
| 79 | /* |
| 80 | * RoboSwitch uses 16-bit Big Endian addresses. |
| 81 | * The ordering of the words is reversed in the MII registers. |
| 82 | */ |
| 83 | static void wpa_driver_roboswitch_addr_be16(const u8 addr[ETH_ALEN], u16 *be) |
| 84 | { |
| 85 | int i; |
| 86 | for (i = 0; i < ETH_ALEN; i += 2) |
| 87 | be[(ETH_ALEN - i) / 2 - 1] = WPA_GET_BE16(addr + i); |
| 88 | } |
| 89 | |
| 90 | |
| 91 | static u16 wpa_driver_roboswitch_mdio_read( |
| 92 | struct wpa_driver_roboswitch_data *drv, u8 reg) |
| 93 | { |
| 94 | struct mii_ioctl_data *mii = if_mii(&drv->ifr); |
| 95 | |
| 96 | mii->phy_id = ROBO_PHY_ADDR; |
| 97 | mii->reg_num = reg; |
| 98 | |
| 99 | if (ioctl(drv->fd, SIOCGMIIREG, &drv->ifr) < 0) { |
| 100 | perror("ioctl[SIOCGMIIREG]"); |
| 101 | return 0x00; |
| 102 | } |
| 103 | return mii->val_out; |
| 104 | } |
| 105 | |
| 106 | |
| 107 | static void wpa_driver_roboswitch_mdio_write( |
| 108 | struct wpa_driver_roboswitch_data *drv, u8 reg, u16 val) |
| 109 | { |
| 110 | struct mii_ioctl_data *mii = if_mii(&drv->ifr); |
| 111 | |
| 112 | mii->phy_id = ROBO_PHY_ADDR; |
| 113 | mii->reg_num = reg; |
| 114 | mii->val_in = val; |
| 115 | |
| 116 | if (ioctl(drv->fd, SIOCSMIIREG, &drv->ifr) < 0) { |
| 117 | perror("ioctl[SIOCSMIIREG"); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | |
| 122 | static int wpa_driver_roboswitch_reg(struct wpa_driver_roboswitch_data *drv, |
| 123 | u8 page, u8 reg, u8 op) |
| 124 | { |
| 125 | int i; |
| 126 | |
| 127 | /* set page number */ |
| 128 | wpa_driver_roboswitch_mdio_write(drv, ROBO_MII_PAGE, |
| 129 | (page << 8) | ROBO_MII_PAGE_ENABLE); |
| 130 | /* set register address */ |
| 131 | wpa_driver_roboswitch_mdio_write(drv, ROBO_MII_ADDR, (reg << 8) | op); |
| 132 | |
| 133 | /* check if operation completed */ |
| 134 | for (i = 0; i < ROBO_MII_RETRY_MAX; ++i) { |
| 135 | if ((wpa_driver_roboswitch_mdio_read(drv, ROBO_MII_ADDR) & 3) |
| 136 | == 0) |
| 137 | return 0; |
| 138 | } |
| 139 | /* timeout */ |
| 140 | return -1; |
| 141 | } |
| 142 | |
| 143 | |
| 144 | static int wpa_driver_roboswitch_read(struct wpa_driver_roboswitch_data *drv, |
| 145 | u8 page, u8 reg, u16 *val, int len) |
| 146 | { |
| 147 | int i; |
| 148 | |
| 149 | if (len > ROBO_MII_DATA_MAX || |
| 150 | wpa_driver_roboswitch_reg(drv, page, reg, ROBO_MII_ADDR_READ) < 0) |
| 151 | return -1; |
| 152 | |
| 153 | for (i = 0; i < len; ++i) { |
| 154 | val[i] = wpa_driver_roboswitch_mdio_read( |
| 155 | drv, ROBO_MII_DATA_OFFSET + i); |
| 156 | } |
| 157 | |
| 158 | return 0; |
| 159 | } |
| 160 | |
| 161 | |
| 162 | static int wpa_driver_roboswitch_write(struct wpa_driver_roboswitch_data *drv, |
| 163 | u8 page, u8 reg, u16 *val, int len) |
| 164 | { |
| 165 | int i; |
| 166 | |
| 167 | if (len > ROBO_MII_DATA_MAX) return -1; |
| 168 | for (i = 0; i < len; ++i) { |
| 169 | wpa_driver_roboswitch_mdio_write(drv, ROBO_MII_DATA_OFFSET + i, |
| 170 | val[i]); |
| 171 | } |
| 172 | return wpa_driver_roboswitch_reg(drv, page, reg, ROBO_MII_ADDR_WRITE); |
| 173 | } |
| 174 | |
| 175 | |
| 176 | static void wpa_driver_roboswitch_receive(void *priv, const u8 *src_addr, |
| 177 | const u8 *buf, size_t len) |
| 178 | { |
| 179 | struct wpa_driver_roboswitch_data *drv = priv; |
| 180 | |
| 181 | if (len > 14 && WPA_GET_BE16(buf + 12) == ETH_P_EAPOL && |
| 182 | os_memcmp(buf, drv->own_addr, ETH_ALEN) == 0) |
| 183 | drv_event_eapol_rx(drv->ctx, src_addr, buf + 14, len - 14); |
| 184 | } |
| 185 | |
| 186 | |
| 187 | static int wpa_driver_roboswitch_get_ssid(void *priv, u8 *ssid) |
| 188 | { |
| 189 | ssid[0] = 0; |
| 190 | return 0; |
| 191 | } |
| 192 | |
| 193 | |
| 194 | static int wpa_driver_roboswitch_get_bssid(void *priv, u8 *bssid) |
| 195 | { |
| 196 | /* Report PAE group address as the "BSSID" for wired connection. */ |
| 197 | os_memcpy(bssid, pae_group_addr, ETH_ALEN); |
| 198 | return 0; |
| 199 | } |
| 200 | |
| 201 | |
| 202 | static int wpa_driver_roboswitch_get_capa(void *priv, |
| 203 | struct wpa_driver_capa *capa) |
| 204 | { |
| 205 | os_memset(capa, 0, sizeof(*capa)); |
| 206 | capa->flags = WPA_DRIVER_FLAGS_WIRED; |
| 207 | return 0; |
| 208 | } |
| 209 | |
| 210 | |
| 211 | static int wpa_driver_roboswitch_set_param(void *priv, const char *param) |
| 212 | { |
| 213 | struct wpa_driver_roboswitch_data *drv = priv; |
| 214 | char *sep; |
| 215 | |
| 216 | if (param == NULL || os_strstr(param, "multicast_only=1") == NULL) { |
| 217 | sep = drv->ifname + os_strlen(drv->ifname); |
| 218 | *sep = '.'; |
| 219 | drv->l2 = l2_packet_init(drv->ifname, NULL, ETH_P_ALL, |
| 220 | wpa_driver_roboswitch_receive, drv, |
| 221 | 1); |
| 222 | if (drv->l2 == NULL) { |
| 223 | wpa_printf(MSG_INFO, "%s: Unable to listen on %s", |
| 224 | __func__, drv->ifname); |
| 225 | return -1; |
| 226 | } |
| 227 | *sep = '\0'; |
| 228 | l2_packet_get_own_addr(drv->l2, drv->own_addr); |
| 229 | } else { |
| 230 | wpa_printf(MSG_DEBUG, "%s: Ignoring unicast frames", __func__); |
| 231 | drv->l2 = NULL; |
| 232 | } |
| 233 | return 0; |
| 234 | } |
| 235 | |
| 236 | |
| 237 | static const char * wpa_driver_roboswitch_get_ifname(void *priv) |
| 238 | { |
| 239 | struct wpa_driver_roboswitch_data *drv = priv; |
| 240 | return drv->ifname; |
| 241 | } |
| 242 | |
| 243 | |
| 244 | static int wpa_driver_roboswitch_join(struct wpa_driver_roboswitch_data *drv, |
| 245 | u16 ports, const u8 *addr) |
| 246 | { |
| 247 | u16 read1[3], read2[3], addr_be16[3]; |
| 248 | |
| 249 | wpa_driver_roboswitch_addr_be16(addr, addr_be16); |
| 250 | |
| 251 | if (wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 252 | ROBO_ARLCTRL_CONF, read1, 1) < 0) |
| 253 | return -1; |
| 254 | if (!(read1[0] & (1 << 4))) { |
| 255 | /* multiport addresses are not yet enabled */ |
| 256 | read1[0] |= 1 << 4; |
| 257 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 258 | ROBO_ARLCTRL_ADDR_1, addr_be16, 3); |
| 259 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 260 | ROBO_ARLCTRL_VEC_1, &ports, 1); |
| 261 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 262 | ROBO_ARLCTRL_ADDR_2, addr_be16, 3); |
| 263 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 264 | ROBO_ARLCTRL_VEC_2, &ports, 1); |
| 265 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 266 | ROBO_ARLCTRL_CONF, read1, 1); |
| 267 | } else { |
| 268 | /* if both multiport addresses are the same we can add */ |
| 269 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 270 | ROBO_ARLCTRL_ADDR_1, read1, 3); |
| 271 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 272 | ROBO_ARLCTRL_ADDR_2, read2, 3); |
| 273 | if (os_memcmp(read1, read2, 6) != 0) |
| 274 | return -1; |
| 275 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 276 | ROBO_ARLCTRL_VEC_1, read1, 1); |
| 277 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 278 | ROBO_ARLCTRL_VEC_2, read2, 1); |
| 279 | if (read1[0] != read2[0]) |
| 280 | return -1; |
| 281 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 282 | ROBO_ARLCTRL_ADDR_1, addr_be16, 3); |
| 283 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 284 | ROBO_ARLCTRL_VEC_1, &ports, 1); |
| 285 | } |
| 286 | return 0; |
| 287 | } |
| 288 | |
| 289 | |
| 290 | static int wpa_driver_roboswitch_leave(struct wpa_driver_roboswitch_data *drv, |
| 291 | u16 ports, const u8 *addr) |
| 292 | { |
| 293 | u16 _read, addr_be16[3], addr_read[3], ports_read; |
| 294 | |
| 295 | wpa_driver_roboswitch_addr_be16(addr, addr_be16); |
| 296 | |
| 297 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, ROBO_ARLCTRL_CONF, |
| 298 | &_read, 1); |
| 299 | /* If ARL control is disabled, there is nothing to leave. */ |
| 300 | if (!(_read & (1 << 4))) return -1; |
| 301 | |
| 302 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 303 | ROBO_ARLCTRL_ADDR_1, addr_read, 3); |
| 304 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, ROBO_ARLCTRL_VEC_1, |
| 305 | &ports_read, 1); |
| 306 | /* check if we occupy multiport address 1 */ |
| 307 | if (os_memcmp(addr_read, addr_be16, 6) == 0 && ports_read == ports) { |
| 308 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 309 | ROBO_ARLCTRL_ADDR_2, addr_read, 3); |
| 310 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 311 | ROBO_ARLCTRL_VEC_2, &ports_read, 1); |
| 312 | /* and multiport address 2 */ |
| 313 | if (os_memcmp(addr_read, addr_be16, 6) == 0 && |
| 314 | ports_read == ports) { |
| 315 | _read &= ~(1 << 4); |
| 316 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 317 | ROBO_ARLCTRL_CONF, &_read, |
| 318 | 1); |
| 319 | } else { |
| 320 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 321 | ROBO_ARLCTRL_ADDR_1, |
| 322 | addr_read, 3); |
| 323 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 324 | ROBO_ARLCTRL_VEC_1, |
| 325 | &ports_read, 1); |
| 326 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 327 | ROBO_ARLCTRL_ADDR_2, |
| 328 | addr_read, 3); |
| 329 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 330 | ROBO_ARLCTRL_VEC_2, |
| 331 | &ports_read, 1); |
| 332 | } |
| 333 | } else { |
| 334 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 335 | ROBO_ARLCTRL_ADDR_2, addr_read, 3); |
| 336 | wpa_driver_roboswitch_read(drv, ROBO_ARLCTRL_PAGE, |
| 337 | ROBO_ARLCTRL_VEC_2, &ports_read, 1); |
| 338 | /* or multiport address 2 */ |
| 339 | if (os_memcmp(addr_read, addr_be16, 6) == 0 && |
| 340 | ports_read == ports) { |
| 341 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 342 | ROBO_ARLCTRL_ADDR_1, |
| 343 | addr_read, 3); |
| 344 | wpa_driver_roboswitch_write(drv, ROBO_ARLCTRL_PAGE, |
| 345 | ROBO_ARLCTRL_VEC_1, |
| 346 | &ports_read, 1); |
| 347 | } else return -1; |
| 348 | } |
| 349 | return 0; |
| 350 | } |
| 351 | |
| 352 | |
| 353 | static void * wpa_driver_roboswitch_init(void *ctx, const char *ifname) |
| 354 | { |
| 355 | struct wpa_driver_roboswitch_data *drv; |
| 356 | char *sep; |
| 357 | u16 vlan = 0, _read[2]; |
| 358 | |
| 359 | drv = os_zalloc(sizeof(*drv)); |
| 360 | if (drv == NULL) return NULL; |
| 361 | drv->ctx = ctx; |
| 362 | drv->own_addr[0] = '\0'; |
| 363 | |
| 364 | /* copy ifname and take a pointer to the second to last character */ |
| 365 | sep = drv->ifname + |
| 366 | os_strlcpy(drv->ifname, ifname, sizeof(drv->ifname)) - 2; |
| 367 | /* find the '.' seperating <interface> and <vlan> */ |
| 368 | while (sep > drv->ifname && *sep != '.') sep--; |
| 369 | if (sep <= drv->ifname) { |
| 370 | wpa_printf(MSG_INFO, "%s: No <interface>.<vlan> pair in " |
| 371 | "interface name %s", __func__, drv->ifname); |
| 372 | os_free(drv); |
| 373 | return NULL; |
| 374 | } |
| 375 | *sep = '\0'; |
| 376 | while (*++sep) { |
| 377 | if (*sep < '0' || *sep > '9') { |
| 378 | wpa_printf(MSG_INFO, "%s: Invalid vlan specification " |
| 379 | "in interface name %s", __func__, ifname); |
| 380 | os_free(drv); |
| 381 | return NULL; |
| 382 | } |
| 383 | vlan *= 10; |
| 384 | vlan += *sep - '0'; |
| 385 | if (vlan > ROBO_VLAN_MAX) { |
| 386 | wpa_printf(MSG_INFO, "%s: VLAN out of range in " |
| 387 | "interface name %s", __func__, ifname); |
| 388 | os_free(drv); |
| 389 | return NULL; |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | drv->fd = socket(PF_INET, SOCK_DGRAM, 0); |
| 394 | if (drv->fd < 0) { |
| 395 | wpa_printf(MSG_INFO, "%s: Unable to create socket", __func__); |
| 396 | os_free(drv); |
| 397 | return NULL; |
| 398 | } |
| 399 | |
| 400 | os_memset(&drv->ifr, 0, sizeof(drv->ifr)); |
| 401 | os_strlcpy(drv->ifr.ifr_name, drv->ifname, IFNAMSIZ); |
| 402 | if (ioctl(drv->fd, SIOCGMIIPHY, &drv->ifr) < 0) { |
| 403 | perror("ioctl[SIOCGMIIPHY]"); |
| 404 | os_free(drv); |
| 405 | return NULL; |
| 406 | } |
| 407 | if (if_mii(&drv->ifr)->phy_id != ROBO_PHY_ADDR) { |
| 408 | wpa_printf(MSG_INFO, "%s: Invalid phy address (not a " |
| 409 | "RoboSwitch?)", __func__); |
| 410 | os_free(drv); |
| 411 | return NULL; |
| 412 | } |
| 413 | |
| 414 | /* set and read back to see if the register can be used */ |
| 415 | _read[0] = ROBO_VLAN_MAX; |
| 416 | wpa_driver_roboswitch_write(drv, ROBO_VLAN_PAGE, ROBO_VLAN_ACCESS_5350, |
| 417 | _read, 1); |
| 418 | wpa_driver_roboswitch_read(drv, ROBO_VLAN_PAGE, ROBO_VLAN_ACCESS_5350, |
| 419 | _read + 1, 1); |
| 420 | drv->is_5350 = _read[0] == _read[1]; |
| 421 | |
| 422 | /* set the read bit */ |
| 423 | vlan |= 1 << 13; |
| 424 | wpa_driver_roboswitch_write(drv, ROBO_VLAN_PAGE, |
| 425 | drv->is_5350 ? ROBO_VLAN_ACCESS_5350 |
| 426 | : ROBO_VLAN_ACCESS, |
| 427 | &vlan, 1); |
| 428 | wpa_driver_roboswitch_read(drv, ROBO_VLAN_PAGE, ROBO_VLAN_READ, _read, |
| 429 | drv->is_5350 ? 2 : 1); |
| 430 | if (!(drv->is_5350 ? _read[1] & (1 << 4) : _read[0] & (1 << 14))) { |
| 431 | wpa_printf(MSG_INFO, "%s: Could not get port information for " |
| 432 | "VLAN %d", __func__, vlan & ~(1 << 13)); |
| 433 | os_free(drv); |
| 434 | return NULL; |
| 435 | } |
| 436 | drv->ports = _read[0] & 0x001F; |
| 437 | /* add the MII port */ |
| 438 | drv->ports |= 1 << 8; |
| 439 | if (wpa_driver_roboswitch_join(drv, drv->ports, pae_group_addr) < 0) { |
| 440 | wpa_printf(MSG_INFO, "%s: Unable to join PAE group", __func__); |
| 441 | os_free(drv); |
| 442 | return NULL; |
| 443 | } else { |
| 444 | wpa_printf(MSG_DEBUG, "%s: Added PAE group address to " |
| 445 | "RoboSwitch ARL", __func__); |
| 446 | } |
| 447 | |
| 448 | return drv; |
| 449 | } |
| 450 | |
| 451 | |
| 452 | static void wpa_driver_roboswitch_deinit(void *priv) |
| 453 | { |
| 454 | struct wpa_driver_roboswitch_data *drv = priv; |
| 455 | |
| 456 | if (drv->l2) { |
| 457 | l2_packet_deinit(drv->l2); |
| 458 | drv->l2 = NULL; |
| 459 | } |
| 460 | if (wpa_driver_roboswitch_leave(drv, drv->ports, pae_group_addr) < 0) { |
| 461 | wpa_printf(MSG_DEBUG, "%s: Unable to leave PAE group", |
| 462 | __func__); |
| 463 | } |
| 464 | |
| 465 | close(drv->fd); |
| 466 | os_free(drv); |
| 467 | } |
| 468 | |
| 469 | |
| 470 | const struct wpa_driver_ops wpa_driver_roboswitch_ops = { |
| 471 | .name = "roboswitch", |
| 472 | .desc = "wpa_supplicant roboswitch driver", |
| 473 | .get_ssid = wpa_driver_roboswitch_get_ssid, |
| 474 | .get_bssid = wpa_driver_roboswitch_get_bssid, |
| 475 | .get_capa = wpa_driver_roboswitch_get_capa, |
| 476 | .init = wpa_driver_roboswitch_init, |
| 477 | .deinit = wpa_driver_roboswitch_deinit, |
| 478 | .set_param = wpa_driver_roboswitch_set_param, |
| 479 | .get_ifname = wpa_driver_roboswitch_get_ifname, |
| 480 | }; |