diff options
author | Masakazu Mokuno <mokuno@sm.sony.co.jp> | 2008-02-07 19:58:57 +0900 |
---|---|---|
committer | Jeff Garzik <jeff@garzik.org> | 2008-02-11 10:30:05 -0500 |
commit | 09dde54c6a69d4f9ea1213923b93aeae7020f8b6 (patch) | |
tree | 5db71afb05e3b69304f795b3ba167870221577e3 /drivers | |
parent | 589866f9f1cb14273b644993d362ec7845007f94 (diff) |
PS3: gelic: Add wireless support for PS3
Signed-off-by: Masakazu Mokuno <mokuno@sm.sony.co.jp>
Acked-by: Dan Williams <dcbw@redhat.com>
Acked-by: John W. Linville <linville@tuxdriver.com>
Signed-off-by: Jeff Garzik <jeff@garzik.org>
Diffstat (limited to 'drivers')
-rw-r--r-- | drivers/net/Kconfig | 10 | ||||
-rw-r--r-- | drivers/net/Makefile | 3 | ||||
-rw-r--r-- | drivers/net/ps3_gelic_net.c | 18 | ||||
-rw-r--r-- | drivers/net/ps3_gelic_net.h | 6 | ||||
-rw-r--r-- | drivers/net/ps3_gelic_wireless.c | 2753 | ||||
-rw-r--r-- | drivers/net/ps3_gelic_wireless.h | 329 |
6 files changed, 3117 insertions, 2 deletions
diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 50c2b60e1fe..37f8e4790b6 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2352,6 +2352,16 @@ config GELIC_NET To compile this driver as a module, choose M here: the module will be called ps3_gelic. +config GELIC_WIRELESS + bool "PS3 Wireless support" + depends on GELIC_NET + help + This option adds the support for the wireless feature of PS3. + If you have the wireless-less model of PS3 or have no plan to + use wireless feature, disabling this option saves memory. As + the driver automatically distinguishes the models, you can + safely enable this option even if you have a wireless-less model. + config GIANFAR tristate "Gianfar Ethernet" depends on FSL_SOC diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 9fc7794e88e..3b1ea321dc0 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -70,7 +70,8 @@ obj-$(CONFIG_BNX2X) += bnx2x.o spidernet-y += spider_net.o spider_net_ethtool.o obj-$(CONFIG_SPIDER_NET) += spidernet.o sungem_phy.o obj-$(CONFIG_GELIC_NET) += ps3_gelic.o -ps3_gelic-objs += ps3_gelic_net.o +gelic_wireless-$(CONFIG_GELIC_WIRELESS) += ps3_gelic_wireless.o +ps3_gelic-objs += ps3_gelic_net.o $(gelic_wireless-y) obj-$(CONFIG_TC35815) += tc35815.o obj-$(CONFIG_SKGE) += skge.o obj-$(CONFIG_SKY2) += sky2.o diff --git a/drivers/net/ps3_gelic_net.c b/drivers/net/ps3_gelic_net.c index c9dd9c0ee22..7eb6e7e848f 100644 --- a/drivers/net/ps3_gelic_net.c +++ b/drivers/net/ps3_gelic_net.c @@ -46,9 +46,10 @@ #include <asm/lv1call.h> #include "ps3_gelic_net.h" +#include "ps3_gelic_wireless.h" #define DRV_NAME "Gelic Network Driver" -#define DRV_VERSION "1.1" +#define DRV_VERSION "2.0" MODULE_AUTHOR("SCE Inc."); MODULE_DESCRIPTION("Gelic Network driver"); @@ -1154,6 +1155,12 @@ static irqreturn_t gelic_card_interrupt(int irq, void *ptr) if (status & GELIC_CARD_PORT_STATUS_CHANGED) gelic_card_get_ether_port_status(card, 1); +#ifdef CONFIG_GELIC_WIRELESS + if (status & (GELIC_CARD_WLAN_EVENT_RECEIVED | + GELIC_CARD_WLAN_COMMAND_COMPLETED)) + gelic_wl_interrupt(card->netdev[GELIC_PORT_WIRELESS], status); +#endif + return IRQ_HANDLED; } @@ -1635,6 +1642,12 @@ static int ps3_gelic_driver_probe(struct ps3_system_bus_device *dev) goto fail_setup_netdev; } +#ifdef CONFIG_GELIC_WIRELESS + if (gelic_wl_driver_probe(card)) { + dev_dbg(&dev->core, "%s: WL init failed\n", __func__); + goto fail_setup_netdev; + } +#endif pr_debug("%s: done\n", __func__); return 0; @@ -1674,6 +1687,9 @@ static int ps3_gelic_driver_remove(struct ps3_system_bus_device *dev) struct net_device *netdev0; pr_debug("%s: called\n", __func__); +#ifdef CONFIG_GELIC_WIRELESS + gelic_wl_driver_remove(card); +#endif /* stop interrupt */ gelic_card_set_irq_mask(card, 0); diff --git a/drivers/net/ps3_gelic_net.h b/drivers/net/ps3_gelic_net.h index 46cfdcdcdbe..1d39d06797e 100644 --- a/drivers/net/ps3_gelic_net.h +++ b/drivers/net/ps3_gelic_net.h @@ -57,6 +57,8 @@ #define GELIC_CARD_RX_PROTECTION_ERR 0x0000000004000000L #define GELIC_CARD_TX_TCP_UDP_CHECKSUM_ERR 0x0000000008000000L #define GELIC_CARD_PORT_STATUS_CHANGED 0x0000000020000000L +#define GELIC_CARD_WLAN_EVENT_RECEIVED 0x0000000040000000L +#define GELIC_CARD_WLAN_COMMAND_COMPLETED 0x0000000080000000L /* INT 0 */ #define GELIC_CARD_TX_FLAGGED_DESCR 0x0004000000000000L #define GELIC_CARD_RX_FLAGGED_DESCR 0x0040000000000000L @@ -180,6 +182,10 @@ enum gelic_lv1_net_control_code { GELIC_LV1_GET_ETH_PORT_STATUS = 2, GELIC_LV1_SET_NEGOTIATION_MODE = 3, GELIC_LV1_GET_VLAN_ID = 4, + GELIC_LV1_GET_CHANNEL = 6, + GELIC_LV1_POST_WLAN_CMD = 9, + GELIC_LV1_GET_WLAN_CMD_RESULT = 10, + GELIC_LV1_GET_WLAN_EVENT = 11 }; /* status returened from GET_ETH_PORT_STATUS */ diff --git a/drivers/net/ps3_gelic_wireless.c b/drivers/net/ps3_gelic_wireless.c new file mode 100644 index 00000000000..750d2a99cb4 --- /dev/null +++ b/drivers/net/ps3_gelic_wireless.c @@ -0,0 +1,2753 @@ +/* + * PS3 gelic network driver. + * + * Copyright (C) 2007 Sony Computer Entertainment Inc. + * Copyright 2007 Sony Corporation + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/module.h> + +#include <linux/etherdevice.h> +#include <linux/ethtool.h> +#include <linux/if_vlan.h> + +#include <linux/in.h> +#include <linux/ip.h> +#include <linux/tcp.h> +#include <linux/wireless.h> +#include <linux/ctype.h> +#include <linux/string.h> +#include <net/iw_handler.h> +#include <net/ieee80211.h> + +#include <linux/dma-mapping.h> +#include <net/checksum.h> +#include <asm/firmware.h> +#include <asm/ps3.h> +#include <asm/lv1call.h> + +#include "ps3_gelic_net.h" +#include "ps3_gelic_wireless.h" + + +static int gelic_wl_start_scan(struct gelic_wl_info *wl, int always_scan); +static int gelic_wl_try_associate(struct net_device *netdev); + +/* + * tables + */ + +/* 802.11b/g channel to freq in MHz */ +static const int channel_freq[] = { + 2412, 2417, 2422, 2427, 2432, + 2437, 2442, 2447, 2452, 2457, + 2462, 2467, 2472, 2484 +}; +#define NUM_CHANNELS ARRAY_SIZE(channel_freq) + +/* in bps */ +static const int bitrate_list[] = { + 1000000, + 2000000, + 5500000, + 11000000, + 6000000, + 9000000, + 12000000, + 18000000, + 24000000, + 36000000, + 48000000, + 54000000 +}; +#define NUM_BITRATES ARRAY_SIZE(bitrate_list) + +/* + * wpa2 support requires the hypervisor version 2.0 or later + */ +static inline int wpa2_capable(void) +{ + return (0 <= ps3_compare_firmware_version(2, 0, 0)); +} + +static inline int precise_ie(void) +{ + return 0; /* FIXME */ +} +/* + * post_eurus_cmd helpers + */ +struct eurus_cmd_arg_info { + int pre_arg; /* command requres arg1, arg2 at POST COMMAND */ + int post_arg; /* command requires arg1, arg2 at GET_RESULT */ +}; + +static const struct eurus_cmd_arg_info cmd_info[GELIC_EURUS_CMD_MAX_INDEX] = { + [GELIC_EURUS_CMD_SET_COMMON_CFG] = { .pre_arg = 1}, + [GELIC_EURUS_CMD_SET_WEP_CFG] = { .pre_arg = 1}, + [GELIC_EURUS_CMD_SET_WPA_CFG] = { .pre_arg = 1}, + [GELIC_EURUS_CMD_GET_COMMON_CFG] = { .post_arg = 1}, + [GELIC_EURUS_CMD_GET_WEP_CFG] = { .post_arg = 1}, + [GELIC_EURUS_CMD_GET_WPA_CFG] = { .post_arg = 1}, + [GELIC_EURUS_CMD_GET_RSSI_CFG] = { .post_arg = 1}, + [GELIC_EURUS_CMD_GET_SCAN] = { .post_arg = 1}, +}; + +#ifdef DEBUG +static const char *cmdstr(enum gelic_eurus_command ix) +{ + switch (ix) { + case GELIC_EURUS_CMD_ASSOC: + return "ASSOC"; + case GELIC_EURUS_CMD_DISASSOC: + return "DISASSOC"; + case GELIC_EURUS_CMD_START_SCAN: + return "SCAN"; + case GELIC_EURUS_CMD_GET_SCAN: + return "GET SCAN"; + case GELIC_EURUS_CMD_SET_COMMON_CFG: + return "SET_COMMON_CFG"; + case GELIC_EURUS_CMD_GET_COMMON_CFG: + return "GET_COMMON_CFG"; + case GELIC_EURUS_CMD_SET_WEP_CFG: + return "SET_WEP_CFG"; + case GELIC_EURUS_CMD_GET_WEP_CFG: + return "GET_WEP_CFG"; + case GELIC_EURUS_CMD_SET_WPA_CFG: + return "SET_WPA_CFG"; + case GELIC_EURUS_CMD_GET_WPA_CFG: + return "GET_WPA_CFG"; + case GELIC_EURUS_CMD_GET_RSSI_CFG: + return "GET_RSSI"; + default: + break; + } + return ""; +}; +#else +static inline const char *cmdstr(enum gelic_eurus_command ix) +{ + return ""; +} +#endif + +/* synchronously do eurus commands */ +static void gelic_eurus_sync_cmd_worker(struct work_struct *work) +{ + struct gelic_eurus_cmd *cmd; + struct gelic_card *card; + struct gelic_wl_info *wl; + + u64 arg1, arg2; + + pr_debug("%s: <-\n", __func__); + cmd = container_of(work, struct gelic_eurus_cmd, work); + BUG_ON(cmd_info[cmd->cmd].pre_arg && + cmd_info[cmd->cmd].post_arg); + wl = cmd->wl; + card = port_to_card(wl_port(wl)); + + if (cmd_info[cmd->cmd].pre_arg) { + arg1 = ps3_mm_phys_to_lpar(__pa(cmd->buffer)); + arg2 = cmd->buf_size; + } else { + arg1 = 0; + arg2 = 0; + } + init_completion(&wl->cmd_done_intr); + pr_debug("%s: cmd='%s' start\n", __func__, cmdstr(cmd->cmd)); + cmd->status = lv1_net_control(bus_id(card), dev_id(card), + GELIC_LV1_POST_WLAN_CMD, + cmd->cmd, arg1, arg2, + &cmd->tag, &cmd->size); + if (cmd->status) { + complete(&cmd->done); + pr_info("%s: cmd issue failed\n", __func__); + return; + } + + wait_for_completion(&wl->cmd_done_intr); + + if (cmd_info[cmd->cmd].post_arg) { + arg1 = ps3_mm_phys_to_lpar(__pa(cmd->buffer)); + arg2 = cmd->buf_size; + } else { + arg1 = 0; + arg2 = 0; + } + + cmd->status = lv1_net_control(bus_id(card), dev_id(card), + GELIC_LV1_GET_WLAN_CMD_RESULT, + cmd->tag, arg1, arg2, + &cmd->cmd_status, &cmd->size); +#ifdef DEBUG + if (cmd->status || cmd->cmd_status) { + pr_debug("%s: cmd done tag=%#lx arg1=%#lx, arg2=%#lx\n", __func__, + cmd->tag, arg1, arg2); + pr_debug("%s: cmd done status=%#x cmd_status=%#lx size=%#lx\n", + __func__, cmd->status, cmd->cmd_status, cmd->size); + } +#endif + complete(&cmd->done); + pr_debug("%s: cmd='%s' done\n", __func__, cmdstr(cmd->cmd)); +} + +static struct gelic_eurus_cmd *gelic_eurus_sync_cmd(struct gelic_wl_info *wl, + unsigned int eurus_cmd, + void *buffer, + unsigned int buf_size) +{ + struct gelic_eurus_cmd *cmd; + + /* allocate cmd */ + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (!cmd) + return NULL; + + /* initialize members */ + cmd->cmd = eurus_cmd; + cmd->buffer = buffer; + cmd->buf_size = buf_size; + cmd->wl = wl; + INIT_WORK(&cmd->work, gelic_eurus_sync_cmd_worker); + init_completion(&cmd->done); + queue_work(wl->eurus_cmd_queue, &cmd->work); + + /* wait for command completion */ + wait_for_completion(&cmd->done); + + return cmd; +} + +static u32 gelic_wl_get_link(struct net_device *netdev) +{ + struct gelic_wl_info *wl = port_wl(netdev_port(netdev)); + u32 ret; + + pr_debug("%s: <-\n", __func__); + down(&wl->assoc_stat_lock); + if (wl->assoc_stat == GELIC_WL_ASSOC_STAT_ASSOCIATED) + ret = 1; + else + ret = 0; + up(&wl->assoc_stat_lock); + pr_debug("%s: ->\n", __func__); + return ret; +} + +static void gelic_wl_send_iwap_event(struct gelic_wl_info *wl, u8 *bssid) +{ + union iwreq_data data; + + memset(&data, 0, sizeof(data)); + if (bssid) + memcpy(data.ap_addr.sa_data, bssid, ETH_ALEN); + data.ap_addr.sa_family = ARPHRD_ETHER; + wireless_send_event(port_to_netdev(wl_port(wl)), SIOCGIWAP, + &data, NULL); +} + +/* + * wireless extension handlers and helpers + */ + +/* SIOGIWNAME */ +static int gelic_wl_get_name(struct net_device *dev, + struct iw_request_info *info, + union iwreq_data *iwreq, char *extra) +{ + strcpy(iwreq->name, "IEEE 802.11bg"); + return 0; +} + +static void gelic_wl_get_ch_info(struct gelic_wl_info *wl) +{ + struct gelic_card *card = port_to_card(wl_port(wl)); + u64 ch_info_raw, tmp; + int status; + + if (!test_and_set_bit(GELIC_WL_STAT_CH_INFO, &wl->stat)) { + status = lv1_net_control(bus_id(card), dev_id(card), + GELIC_LV1_GET_CHANNEL, 0, 0, 0, + &ch_info_raw, + &tmp); + /* some fw versions may return error */ + if (status) { + if (status != LV1_NO_ENTRY) + pr_info("%s: available ch unknown\n", __func__); + wl->ch_info = 0x07ff;/* 11 ch */ + } else + /* 16 bits of MSB has available channels */ + wl->ch_info = ch_info_raw >> 48; + } + return; +} + +/* SIOGIWRANGE */ +static int gelic_wl_get_range(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *iwreq, char *extra) +{ + struct iw_point *point = &iwreq->data; + struct iw_range *range = (struct iw_range *)extra; + struct gelic_wl_info *wl = port_wl(netdev_port(netdev)); + unsigned int i, chs; + + pr_debug("%s: <-\n", __func__); + point->length = sizeof(struct iw_range); + memset(range, 0, sizeof(struct iw_range)); + + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 22; + + /* available channels and frequencies */ + gelic_wl_get_ch_info(wl); + + for (i = 0, chs = 0; + i < NUM_CHANNELS && chs < IW_MAX_FREQUENCIES; i++) + if (wl->ch_info & (1 << i)) { + range->freq[chs].i = i + 1; + range->freq[chs].m = channel_freq[i]; + range->freq[chs].e = 6; + chs++; + } + range->num_frequency = chs; + range->old_num_frequency = chs; + range->num_channels = chs; + range->old_num_channels = chs; + + /* bitrates */ + for (i = 0; i < NUM_BITRATES; i++) + range->bitrate[i] = bitrate_list[i]; + range->num_bitrates = i; + + /* signal levels */ + range->max_qual.qual = 100; /* relative value */ + range->max_qual.level = 100; + range->avg_qual.qual = 50; + range->avg_qual.level = 50; + range->sensitivity = 0; + + /* Event capability */ + IW_EVENT_CAPA_SET_KERNEL(range->event_capa); + IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWAP); + IW_EVENT_CAPA_SET(range->event_capa, SIOCGIWSCAN); + + /* encryption capability */ + range->enc_capa = IW_ENC_CAPA_WPA | + IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP; + if (wpa2_capable()) + range->enc_capa |= IW_ENC_CAPA_WPA2; + range->encoding_size[0] = 5; /* 40bit WEP */ + range->encoding_size[1] = 13; /* 104bit WEP */ + range->encoding_size[2] = 32; /* WPA-PSK */ + range->num_encoding_sizes = 3; + range->max_encoding_tokens = GELIC_WEP_KEYS; + + pr_debug("%s: ->\n", __func__); + return 0; + +} + +/* SIOC{G,S}IWSCAN */ +static int gelic_wl_set_scan(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wl_info *wl = port_wl(netdev_priv(netdev)); + + return gelic_wl_start_scan(wl, 1); +} + +#define OUI_LEN 3 +static const u8 rsn_oui[OUI_LEN] = { 0x00, 0x0f, 0xac }; +static const u8 wpa_oui[OUI_LEN] = { 0x00, 0x50, 0xf2 }; + +/* + * synthesize WPA/RSN IE data + * See WiFi WPA specification and IEEE 802.11-2007 7.3.2.25 + * for the format + */ +static size_t gelic_wl_synthesize_ie(u8 *buf, + struct gelic_eurus_scan_info *scan) +{ + + const u8 *oui_header; + u8 *start = buf; + int rsn; + int ccmp; + + pr_debug("%s: <- sec=%16x\n", __func__, scan->security); + switch (be16_to_cpu(scan->security) & GELIC_EURUS_SCAN_SEC_MASK) { + case GELIC_EURUS_SCAN_SEC_WPA: + rsn = 0; + break; + case GELIC_EURUS_SCAN_SEC_WPA2: + rsn = 1; + break; + default: + /* WEP or none. No IE returned */ + return 0; + } + + switch (be16_to_cpu(scan->security) & GELIC_EURUS_SCAN_SEC_WPA_MASK) { + case GELIC_EURUS_SCAN_SEC_WPA_TKIP: + ccmp = 0; + break; + case GELIC_EURUS_SCAN_SEC_WPA_AES: + ccmp = 1; + break; + default: + if (rsn) { + ccmp = 1; + pr_info("%s: no cipher info. defaulted to CCMP\n", + __func__); + } else { + ccmp = 0; + pr_info("%s: no cipher info. defaulted to TKIP\n", + __func__); + } + } + + if (rsn) + oui_header = rsn_oui; + else + oui_header = wpa_oui; + + /* element id */ + if (rsn) + *buf++ = MFIE_TYPE_RSN; + else + *buf++ = MFIE_TYPE_GENERIC; + + /* length filed; set later */ + buf++; + + /* wpa special header */ + if (!rsn) { + memcpy(buf, wpa_oui, OUI_LEN); + buf += OUI_LEN; + *buf++ = 0x01; + } + + /* version */ + *buf++ = 0x01; /* version 1.0 */ + *buf++ = 0x00; + + /* group cipher */ + memcpy(buf, oui_header, OUI_LEN); + buf += OUI_LEN; + + if (ccmp) + *buf++ = 0x04; /* CCMP */ + else + *buf++ = 0x02; /* TKIP */ + + /* pairwise key count always 1 */ + *buf++ = 0x01; + *buf++ = 0x00; + + /* pairwise key suit */ + memcpy(buf, oui_header, OUI_LEN); + buf += OUI_LEN; + if (ccmp) + *buf++ = 0x04; /* CCMP */ + else + *buf++ = 0x02; /* TKIP */ + + /* AKM count is 1 */ + *buf++ = 0x01; + *buf++ = 0x00; + + /* AKM suite is assumed as PSK*/ + memcpy(buf, oui_header, OUI_LEN); + buf += OUI_LEN; + *buf++ = 0x02; /* PSK */ + + /* RSN capabilities is 0 */ + *buf++ = 0x00; + *buf++ = 0x00; + + /* set length field */ + start[1] = (buf - start - 2); + + pr_debug("%s: ->\n", __func__); + return (buf - start); +} + +struct ie_item { + u8 *data; + u8 len; +}; + +struct ie_info { + struct ie_item wpa; + struct ie_item rsn; +}; + +static void gelic_wl_parse_ie(u8 *data, size_t len, + struct ie_info *ie_info) +{ + size_t data_left = len; + u8 *pos = data; + u8 item_len; + u8 item_id; + + pr_debug("%s: data=%p len=%ld \n", __func__, + data, len); + memset(ie_info, 0, sizeof(struct ie_info)); + + while (0 < data_left) { + item_id = *pos++; + item_len = *pos++; + + switch (item_id) { + case MFIE_TYPE_GENERIC: + if (!memcmp(pos, wpa_oui, OUI_LEN) && + pos[OUI_LEN] == 0x01) { + ie_info->wpa.data = pos - 2; + ie_info->wpa.len = item_len + 2; + } + break; + case MFIE_TYPE_RSN: + ie_info->rsn.data = pos - 2; + /* length includes the header */ + ie_info->rsn.len = item_len + 2; + break; + default: + pr_debug("%s: ignore %#x,%d\n", __func__, + item_id, item_len); + break; + } + pos += item_len; + data_left -= item_len + 2; + } + pr_debug("%s: wpa=%p,%d wpa2=%p,%d\n", __func__, + ie_info->wpa.data, ie_info->wpa.len, + ie_info->rsn.data, ie_info->rsn.len); +} + + +/* + * translate the scan informations from hypervisor to a + * independent format + */ +static char *gelic_wl_translate_scan(struct net_device *netdev, + char *ev, + char *stop, + struct gelic_wl_scan_info *network) +{ + struct iw_event iwe; + struct gelic_eurus_scan_info *scan = network->hwinfo; + char *tmp; + u8 rate; + unsigned int i, j, len; + u8 buf[MAX_WPA_IE_LEN]; + + pr_debug("%s: <-\n", __func__); + + /* first entry should be AP's mac address */ + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, &scan->bssid[2], ETH_ALEN); + ev = iwe_stream_add_event(ev, stop, &iwe, IW_EV_ADDR_LEN); + + /* ESSID */ + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + iwe.u.data.length = strnlen(scan->essid, 32); + ev = iwe_stream_add_point(ev, stop, &iwe, scan->essid); + + /* FREQUENCY */ + iwe.cmd = SIOCGIWFREQ; + iwe.u.freq.m = be16_to_cpu(scan->channel); + iwe.u.freq.e = 0; /* table value in MHz */ + iwe.u.freq.i = 0; + ev = iwe_stream_add_event(ev, stop, &iwe, IW_EV_FREQ_LEN); + + /* RATES */ + iwe.cmd = SIOCGIWRATE; + iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; + /* to stuff multiple values in one event */ + tmp = ev + IW_EV_LCP_LEN; + /* put them in ascendant order (older is first) */ + i = 0; + j = 0; + pr_debug("%s: rates=%d rate=%d\n", __func__, + network->rate_len, network->rate_ext_len); + while (i < network->rate_len) { + if (j < network->rate_ext_len && + ((scan->ext_rate[j] & 0x7f) < (scan->rate[i] & 0x7f))) + rate = scan->ext_rate[j++] & 0x7f; + else + rate = scan->rate[i++] & 0x7f; + iwe.u.bitrate.value = rate * 500000; /* 500kbps unit */ + tmp = iwe_stream_add_value(ev, tmp, stop, &iwe, + IW_EV_PARAM_LEN); + } + while (j < network->rate_ext_len) { + iwe.u.bitrate.value = (scan->ext_rate[j++] & 0x7f) * 500000; + tmp = iwe_stream_add_value(ev, tmp, stop, &iwe, + IW_EV_PARAM_LEN); + } + /* Check if we added any rate */ + if (IW_EV_LCP_LEN < (tmp - ev)) + ev = tmp; + + /* ENCODE */ + iwe.cmd = SIOCGIWENCODE; + if (be16_to_cpu(scan->capability) & WLAN_CAPABILITY_PRIVACY) + iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY; + else + iwe.u.data.flags = IW_ENCODE_DISABLED; + iwe.u.data.length = 0; + ev = iwe_stream_add_point(ev, stop, &iwe, scan->essid); + + /* MODE */ + iwe.cmd = SIOCGIWMODE; + if (be16_to_cpu(scan->capability) & + (WLAN_CAPABILITY_ESS | WLAN_CAPABILITY_IBSS)) { + if (be16_to_cpu(scan->capability) & WLAN_CAPABILITY_ESS) + iwe.u.mode = IW_MODE_MASTER; + else + iwe.u.mode = IW_MODE_ADHOC; + ev = iwe_stream_add_event(ev, stop, &iwe, IW_EV_UINT_LEN); + } + + /* QUAL */ + iwe.cmd = IWEVQUAL; + iwe.u.qual.updated = IW_QUAL_ALL_UPDATED | + IW_QUAL_QUAL_INVALID | IW_QUAL_NOISE_INVALID; + iwe.u.qual.level = be16_to_cpu(scan->rssi); + iwe.u.qual.qual = be16_to_cpu(scan->rssi); + iwe.u.qual.noise = 0; + ev = iwe_stream_add_event(ev, stop, &iwe, IW_EV_QUAL_LEN); + + /* RSN */ + memset(&iwe, 0, sizeof(iwe)); + if (be16_to_cpu(scan->size) <= sizeof(*scan)) { + /* If wpa[2] capable station, synthesize IE and put it */ + len = gelic_wl_synthesize_ie(buf, scan); + if (len) { + iwe.cmd = IWEVGENIE; + iwe.u.data.length = len; + ev = iwe_stream_add_point(ev, stop, &iwe, buf); + } + } else { + /* this scan info has IE data */ + struct ie_info ie_info; + size_t data_len; + + data_len = be16_to_cpu(scan->size) - sizeof(*scan); + + gelic_wl_parse_ie(scan->elements, data_len, &ie_info); + + if (ie_info.wpa.len && (ie_info.wpa.len <= sizeof(buf))) { + memcpy(buf, ie_info.wpa.data, ie_info.wpa.len); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie_info.wpa.len; + ev = iwe_stream_add_point(ev, stop, &iwe, buf); + } + + if (ie_info.rsn.len && (ie_info.rsn.len <= sizeof(buf))) { + memset(&iwe, 0, sizeof(iwe)); + memcpy(buf, ie_info.rsn.data, ie_info.rsn.len); + iwe.cmd = IWEVGENIE; + iwe.u.data.length = ie_info.rsn.len; + ev = iwe_stream_add_point(ev, stop, &iwe, buf); + } + } + + pr_debug("%s: ->\n", __func__); + return ev; +} + + +static int gelic_wl_get_scan(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wl_info *wl = port_wl(netdev_priv(netdev)); + struct gelic_wl_scan_info *scan_info; + char *ev = extra; + char *stop = ev + wrqu->data.length; + int ret = 0; + unsigned long this_time = jiffies; + + pr_debug("%s: <-\n", __func__); + if (down_interruptible(&wl->scan_lock)) + return -EAGAIN; + + switch (wl->scan_stat) { + case GELIC_WL_SCAN_STAT_SCANNING: + /* If a scan in progress, caller should call me again */ + ret = -EAGAIN; + goto out; + break; + + case GELIC_WL_SCAN_STAT_INIT: + /* last scan request failed or never issued */ + ret = -ENODEV; + goto out; + break; + case GELIC_WL_SCAN_STAT_GOT_LIST: + /* ok, use current list */ + break; + } + + list_for_each_entry(scan_info, &wl->network_list, list) { + if (wl->scan_age == 0 || + time_after(scan_info->last_scanned + wl->scan_age, + this_time)) + ev = gelic_wl_translate_scan(netdev, ev, stop, + scan_info); + else + pr_debug("%s:entry too old\n", __func__); + + if (stop - ev <= IW_EV_ADDR_LEN) { + ret = -E2BIG; + goto out; + } + } + + wrqu->data.length = ev - extra; + wrqu->data.flags = 0; +out: + up(&wl->scan_lock); + pr_debug("%s: -> %d %d\n", __func__, ret, wrqu->data.length); + return ret; +} + +#ifdef DEBUG +static void scan_list_dump(struct gelic_wl_info *wl) +{ + struct gelic_wl_scan_info *scan_info; + int i; + DECLARE_MAC_BUF(mac); + + i = 0; + list_for_each_entry(scan_info, &wl->network_list, list) { + pr_debug("%s: item %d\n", __func__, i++); + pr_debug("valid=%d eurusindex=%d last=%lx\n", + scan_info->valid, scan_info->eurus_index, + scan_info->last_scanned); + pr_debug("r_len=%d r_ext_len=%d essid_len=%d\n", + scan_info->rate_len, scan_info->rate_ext_len, + scan_info->essid_len); + /* -- */ + pr_debug("bssid=%s\n", + print_mac(mac, &scan_info->hwinfo->bssid[2])); + pr_debug("essid=%s\n", scan_info->hwinfo->essid); + } +} +#endif + +static int gelic_wl_set_auth(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct iw_param *param = &data->param; + struct gelic_wl_info *wl = port_wl(netdev_port(netdev)); + unsigned long irqflag; + int ret = 0; + + pr_debug("%s: <- %d\n", __func__, param->flags & IW_AUTH_INDEX); + spin_lock_irqsave(&wl->lock, irqflag); + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + if (param->value & IW_AUTH_WPA_VERSION_DISABLED) { + pr_debug("%s: NO WPA selected\n", __func__); + wl->wpa_level = GELIC_WL_WPA_LEVEL_NONE; + wl->group_cipher_method = GELIC_WL_CIPHER_WEP; + wl->pairwise_cipher_method = GELIC_WL_CIPHER_WEP; + } + if (param->value & IW_AUTH_WPA_VERSION_WPA) { + pr_debug("%s: WPA version 1 selected\n", __func__); + wl->wpa_level = GELIC_WL_WPA_LEVEL_WPA; + wl->group_cipher_method = GELIC_WL_CIPHER_TKIP; + wl->pairwise_cipher_method = GELIC_WL_CIPHER_TKIP; + wl->auth_method = GELIC_EURUS_AUTH_OPEN; + } + if (param->value & IW_AUTH_WPA_VERSION_WPA2) { + /* + * As the hypervisor may not tell the cipher + * information of the AP if it is WPA2, + * you will not decide suitable cipher from + * its beacon. + * You should have knowledge about the AP's + * cipher infomation in other method prior to + * the association. + */ + if (!precise_ie()) + pr_info("%s: WPA2 may not work\n", __func__); + if (wpa2_capable()) { + wl->wpa_level = GELIC_WL_WPA_LEVEL_WPA2; + wl->group_cipher_method = GELIC_WL_CIPHER_AES; + wl->pairwise_cipher_method = + GELIC_WL_CIPHER_AES; + wl->auth_method = GELIC_EURUS_AUTH_OPEN; + } else + ret = -EINVAL; + } + break; + + case IW_AUTH_CIPHER_PAIRWISE: + if (param->value & + (IW_AUTH_CIPHER_WEP104 | IW_AUTH_CIPHER_WEP40)) { + pr_debug("%s: WEP selected\n", __func__); + wl->pairwise_cipher_method = GELIC_WL_CIPHER_WEP; + } + if (param->value & IW_AUTH_CIPHER_TKIP) { + pr_debug("%s: TKIP selected\n", __func__); + wl->pairwise_cipher_method = GELIC_WL_CIPHER_TKIP; + } + if (param->value & IW_AUTH_CIPHER_CCMP) { + pr_debug("%s: CCMP selected\n", __func__); + wl->pairwise_cipher_method = GELIC_WL_CIPHER_AES; + } + if (param->value & IW_AUTH_CIPHER_NONE) { + pr_debug("%s: no auth selected\n", __func__); + wl->pairwise_cipher_method = GELIC_WL_CIPHER_NONE; + } + break; + case IW_AUTH_CIPHER_GROUP: + if (param->value & + (IW_AUTH_CIPHER_WEP104 | IW_AUTH_CIPHER_WEP40)) { + pr_debug("%s: WEP selected\n", __func__); + wl->group_cipher_method = GELIC_WL_CIPHER_WEP; + } + if (param->value & IW_AUTH_CIPHER_TKIP) { + pr_debug("%s: TKIP selected\n", __func__); + wl->group_cipher_method = GELIC_WL_CIPHER_TKIP; + } + if (param->value & IW_AUTH_CIPHER_CCMP) { + pr_debug("%s: CCMP selected\n", __func__); + wl->group_cipher_method = GELIC_WL_CIPHER_AES; + } + if (param->value & IW_AUTH_CIPHER_NONE) { + pr_debug("%s: no auth selected\n", __func__); + wl->group_cipher_method = GELIC_WL_CIPHER_NONE; + } + break; + case IW_AUTH_80211_AUTH_ALG: + if (param->value & IW_AUTH_ALG_SHARED_KEY) { + pr_debug("%s: shared key specified\n", __func__); + wl->auth_method = GELIC_EURUS_AUTH_SHARED; + } else if (param->value & IW_AUTH_ALG_OPEN_SYSTEM) { + pr_debug("%s: open system specified\n", __func__); + wl->auth_method = GELIC_EURUS_AUTH_OPEN; + } else + ret = -EINVAL; + break; + + case IW_AUTH_WPA_ENABLED: + if (param->value) { + pr_debug("%s: WPA enabled\n", __func__); + wl->wpa_level = GELIC_WL_WPA_LEVEL_WPA; + } else { + pr_debug("%s: WPA disabled\n", __func__); + wl->wpa_level = GELIC_WL_WPA_LEVEL_NONE; + } + break; + + case IW_AUTH_KEY_MGMT: + if (param->value & IW_AUTH_KEY_MGMT_PSK) + break; + /* intentionally fall through */ + default: + ret = -EOPNOTSUPP; + break; + }; + + if (!ret) + set_bit(GELIC_WL_STAT_CONFIGURED, &wl->stat); + + spin_unlock_irqrestore(&wl->lock, irqflag); + pr_debug("%s: -> %d\n", __func__, ret); + return ret; +} + +static int gelic_wl_get_auth(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *iwreq, char *extra) +{ + struct iw_param *param = &iwreq->param; + struct gelic_wl_info *wl = port_wl(netdev_port(netdev)); + unsigned long irqflag; + int ret = 0; + + pr_debug("%s: <- %d\n", __func__, param->flags & IW_AUTH_INDEX); + spin_lock_irqsave(&wl->lock, irqflag); + switch (param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + switch (wl->wpa_level) { + case GELIC_WL_WPA_LEVEL_WPA: + param->value |= IW_AUTH_WPA_VERSION_WPA; + break; + case GELIC_WL_WPA_LEVEL_WPA2: + param->value |= IW_AUTH_WPA_VERSION_WPA2; + break; + default: + param->value |= IW_AUTH_WPA_VERSION_DISABLED; + } + break; + + case IW_AUTH_80211_AUTH_ALG: + if (wl->auth_method == GELIC_EURUS_AUTH_SHARED) + param->value = IW_AUTH_ALG_SHARED_KEY; + else if (wl->auth_method == GELIC_EURUS_AUTH_OPEN) + param->value = IW_AUTH_ALG_OPEN_SYSTEM; + break; + + case IW_AUTH_WPA_ENABLED: + switch (wl->wpa_level) { + case GELIC_WL_WPA_LEVEL_WPA: + case GELIC_WL_WPA_LEVEL_WPA2: + param->value = 1; + break; + default: + param->value = 0; + break; + } + break; + default: + ret = -EOPNOTSUPP; + } + + spin_unlock_irqrestore(&wl->lock, irqflag); + pr_debug("%s: -> %d\n", __func__, ret); + return ret; +} + +/* SIOC{S,G}IWESSID */ +static int gelic_wl_set_essid(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct gelic_wl_info *wl = port_wl(netdev_priv(netdev)); + unsigned long irqflag; + + pr_debug("%s: <- l=%d f=%d\n", __func__, + data->essid.length, data->essid.flags); + if (IW_ESSID_MAX_SIZE < data->essid.length) + return -EINVAL; + + spin_lock_irqsave(&wl->lock, irqflag); + if (data->essid.flags) { + wl->essid_len = data->essid.length; + memcpy(wl->essid, extra, wl->essid_len); + pr_debug("%s: essid = '%s'\n", __func__, extra); + set_bit(GELIC_WL_STAT_ESSID_SET, &wl->stat); + } else { + pr_debug("%s: ESSID any \n", __func__); + clear_bit(GELIC_WL_STAT_ESSID_SET, &wl->stat); + } + set_bit(GELIC_WL_STAT_CONFIGURED, &wl->stat); + spin_unlock_irqrestore(&wl->lock, irqflag); + + + gelic_wl_try_associate(netdev); /* FIXME */ + pr_debug("%s: -> \n", __func__); + return 0; +} + +static int gelic_wl_get_essid(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *data, char *extra) +{ + struct gelic_wl_info *wl = port_wl(netdev_priv(netdev)); + unsigned long irqflag; + + pr_debug("%s: <- \n", __func__); + down(&wl->assoc_stat_lock); + spin_lock_irqsave(&wl->lock, irqflag); + if (test_bit(GELIC_WL_STAT_ESSID_SET, &wl->stat) || + wl->assoc_stat == GELIC_WL_ASSOC_STAT_ASSOCIATED) { + memcpy(extra, wl->essid, wl->essid_len); + data->essid.length = wl->essid_len; + data->essid.flags = 1; + } else + data->essid.flags = 0; + + up(&wl->assoc_stat_lock); + spi |