lists.openwall.net | lists / announce owl-users owl-dev john-users john-dev passwdqc-users yescrypt popa3d-users / oss-security kernel-hardening musl sabotage tlsify passwords / crypt-dev xvendor / Bugtraq Full-Disclosure linux-kernel linux-netdev linux-ext4 linux-hardening PHC | |
Open Source and information security mailing list archives
| ||
|
Date: Fri, 01 Jun 2007 15:10:35 +0900 From: MOKUNO Masakazu <mokuno@...sony.co.jp> To: netdev@...r.kernel.org Cc: Geoff Levand <geoffrey.levand@...sony.com>, Geert Uytterhoeven <Geert.Uytterhoeven@...ycom.com> Subject: [PATCH 2/2] ps3: add wireless LAN support Add support of the internal wireless LAN of PS3. Signed-off-by: Masakazu Mokuno <mokuno@...sony.co.jp> --- drivers/net/Kconfig | 7 drivers/net/Makefile | 4 drivers/net/gelic_net.c | 30 drivers/net/gelic_net.h | 227 ++++ drivers/net/gelic_wireless.c | 2132 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 2396 insertions(+), 4 deletions(-) --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2274,6 +2274,13 @@ config GELIC_NET To compile this driver as a module, choose M here: the module will be called gelic_net. +config GELIC_WIRELESS + bool "Gelic net Wireless Extension" + depends on GELIC_NET + select WIRELESS_EXT + help + Wireless Extension support for Gelic Gigabit Ethernet driver + config GIANFAR tristate "Gianfar Ethernet" depends on 85xx || 83xx || PPC_86xx --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -60,7 +60,9 @@ obj-$(CONFIG_TIGON3) += tg3.o obj-$(CONFIG_BNX2) += bnx2.o spidernet-y += spider_net.o spider_net_ethtool.o obj-$(CONFIG_SPIDER_NET) += spidernet.o sungem_phy.o -obj-$(CONFIG_GELIC_NET) += gelic_net.o +obj-$(CONFIG_GELIC_NET) += ps3_gelic.o +gelic-$(CONFIG_GELIC_WIRELESS) += gelic_wireless.o +ps3_gelic-objs += gelic_net.o $(gelic-y) obj-$(CONFIG_TC35815) += tc35815.o obj-$(CONFIG_SKGE) += skge.o obj-$(CONFIG_SKY2) += sky2.o --- a/drivers/net/gelic_net.c +++ b/drivers/net/gelic_net.c @@ -550,6 +550,9 @@ static int gelic_net_stop(struct net_dev { struct gelic_net_card *card = netdev_priv(netdev); +#ifdef CONFIG_GELIC_WIRELESS + gelicw_down(netdev); +#endif netif_poll_disable(netdev); netif_stop_queue(netdev); @@ -1024,6 +1027,9 @@ static irqreturn_t gelic_net_interrupt(i /* start pending DMA */ gelic_net_xmit(NULL, netdev); } +#ifdef CONFIG_GELIC_WIRELESS + gelicw_interrupt(netdev, status); +#endif return IRQ_HANDLED; } @@ -1120,13 +1126,18 @@ static int gelic_net_open(struct net_dev card->tx_dma_progress = 0; card->ghiintmask = GELIC_NET_RXINT | GELIC_NET_TXINT; - +#ifdef CONFIG_GELIC_WIRELESS + card->ghiintmask |= GELICW_DEVICE_CMD_COMP | GELICW_DEVICE_EVENT_RECV; +#endif gelic_net_set_irq_mask(card, card->ghiintmask); gelic_net_enable_rxdmac(card); netif_start_queue(netdev); netif_carrier_on(netdev); netif_poll_enable(netdev); +#ifdef CONFIG_GELIC_WIRELESS + gelicw_up(netdev); +#endif return 0; @@ -1205,7 +1216,12 @@ static u32 gelic_net_get_link(struct net link = 1; else link = 0; - +#ifdef CONFIG_GELIC_WIRELESS + /* (v1 & GELIC_NET_LINK_UP) is always 0 in wireless mode */ + if (gelicw_is_associated(netdev)) { + link = 1; + } +#endif return link; } @@ -1396,7 +1412,12 @@ static int gelic_net_setup_netdev(struct } if (card->vlan_id[GELIC_NET_VLAN_WIRED - 1]) card->vlan_index = GELIC_NET_VLAN_WIRED - 1; - +#ifdef CONFIG_GELIC_WIRELESS + card->w.card = card; + /* init wireless extension */ + /* No wireless vlan_index:-1 */ + gelicw_setup_netdev(netdev, card->vlan_index); +#endif status = register_netdev(netdev); if (status) { dev_err(ctodev(card), "%s:Couldn't register net_device: %d\n", @@ -1530,6 +1551,9 @@ static int ps3_gelic_driver_remove (stru card = ps3_system_bus_get_driver_data(dev); +#ifdef CONFIG_GELIC_WIRELESS + gelicw_remove(card->netdev); +#endif wait_event(card->waitq, atomic_read(&card->tx_timeout_task_counter) == 0); --- a/drivers/net/gelic_net.h +++ b/drivers/net/gelic_net.h @@ -30,6 +30,9 @@ #ifndef _GELIC_NET_H #define _GELIC_NET_H +#include <linux/wireless.h> +#include <net/ieee80211.h> + #define GELIC_NET_DRV_NAME "Gelic Network Driver" #define GELIC_NET_DRV_VERSION "1.0" @@ -170,6 +173,220 @@ enum gelic_net_descr_status { #define GELIC_NET_PORT 2 /* for port status */ +/* wireless */ +#define GELICW_WIRELESS_NOT_EXIST 0 +#define GELICW_WIRELESS_SUPPORTED 1 +#define GELICW_WIRELESS_ON 2 +#define GELICW_WIRELESS_SHUTDOWN 3 +/* state */ +#define GELICW_STATE_DOWN 0 +#define GELICW_STATE_UP 1 +#define GELICW_STATE_SCANNING 2 +#define GELICW_STATE_SCAN_DONE 3 +#define GELICW_STATE_ASSOCIATED 4 + +/* cmd_send_flg */ +#define GELICW_CMD_SEND_NONE 0x00 +#define GELICW_CMD_SEND_COMMON 0x01 +#define GELICW_CMD_SEND_ENCODE 0x02 +#define GELICW_CMD_SEND_SCAN 0x04 +#define GELICW_CMD_SEND_ALL (GELICW_CMD_SEND_COMMON \ + | GELICW_CMD_SEND_ENCODE \ + | GELICW_CMD_SEND_SCAN) + +#define GELICW_SCAN_INTERVAL (HZ) + +#ifdef DEBUG +#define CH_INFO_FAIL 0x0600 /* debug */ +#else +#define CH_INFO_FAIL 0 +#endif + + +/* net_control command */ +#define GELICW_SET_PORT 3 /* control Ether port */ +#define GELICW_GET_INFO 6 /* get supported channels */ +#define GELICW_SET_CMD 9 /* set configuration */ +#define GELICW_GET_RES 10 /* get command response */ +#define GELICW_GET_EVENT 11 /* get event from device */ +/* net_control command data buffer */ +#define GELICW_DATA_BUF_SIZE 0x1000 + +/* GELICW_SET_CMD params */ +#define GELICW_CMD_START 1 +#define GELICW_CMD_STOP 2 +#define GELICW_CMD_SCAN 3 +#define GELICW_CMD_GET_SCAN 4 +#define GELICW_CMD_SET_CONFIG 5 +#define GELICW_CMD_GET_CONFIG 6 +#define GELICW_CMD_SET_WEP 7 +#define GELICW_CMD_GET_WEP 8 +#define GELICW_CMD_SET_WPA 9 +#define GELICW_CMD_GET_WPA 10 +#define GELICW_CMD_GET_RSSI 11 + +/* GELICW_SET_PORT params */ +#define GELICW_ETHER_PORT 2 +#define GELICW_PORT_DOWN 0 /* Ether port off */ +#define GELICW_PORT_UP 4 /* Ether port on (auto neg) */ + +/* interrupt status bit */ +#define GELICW_DEVICE_CMD_COMP (1UL << 31) +#define GELICW_DEVICE_EVENT_RECV (1UL << 30) + +/* GELICW_GET_EVENT ID */ +#define GELICW_EVENT_UNKNOWN 0x00 +#define GELICW_EVENT_DEVICE_READY 0x01 +#define GELICW_EVENT_SCAN_COMPLETED 0x02 +#define GELICW_EVENT_DEAUTH 0x04 +#define GELICW_EVENT_BEACON_LOST 0x08 +#define GELICW_EVENT_CONNECTED 0x10 +#define GELICW_EVENT_WPA_CONNECTED 0x20 +#define GELICW_EVENT_WPA_ERROR 0x40 +#define GELICW_EVENT_NO_ENTRY (-6) + +#define MAX_IW_PRIV_SIZE 32 + +/* structure of data buffer for lv1_net_contol */ +/* wep_config: sec */ +#define GELICW_WEP_SEC_NONE 0 +#define GELICW_WEP_SEC_40BIT 1 +#define GELICW_WEP_SEC_104BIT 2 +struct wep_config { + u16 sec; + u8 key[4][16]; +} __attribute__ ((packed)); + +/* wpa_config: sec */ +#define GELICW_WPA_SEC_NONE 0 +#define GELICW_WPA_SEC_TKIP 1 +#define GELICW_WPA_SEC_AES 2 +/* wpa_config: psk_type */ +#define GELICW_PSK_PASSPHRASE 0 +#define GELICW_PSK_64HEX 1 +struct wpa_config { + u16 sec; + u16 psk_type; + u8 psk_material[64]; /* key */ +} __attribute__ ((packed)); + +/* common_config: bss_type */ +#define GELICW_BSS_INFRA 0 +#define GELICW_BSS_ADHOC 1 +/* common_config: auth_method */ +#define GELICW_AUTH_OPEN 0 +#define GELICW_AUTH_SHARED 1 +/* common_config: op_mode */ +#define GELICW_OP_MODE_11BG 0 +#define GELICW_OP_MODE_11B 1 +#define GELICW_OP_MODE_11G 2 +struct common_config { + u16 scan_index; /* index of scan_desc list */ + u16 bss_type; + u16 auth_method; + u16 op_mode; +} __attribute__ ((packed)); + +/* scan_descriptor: security */ +#define GELICW_SEC_TYPE_NONE 0x0000 +#define GELICW_SEC_TYPE_WEP 0x0100 +#define GELICW_SEC_TYPE_WEP40 0x0101 +#define GELICW_SEC_TYPE_WEP104 0x0102 +#define GELICW_SEC_TYPE_TKIP 0x0201 +#define GELICW_SEC_TYPE_AES 0x0202 +#define GELICW_SEC_TYPE_WEP_MASK 0xFF00 +struct scan_desc { + u16 size; + u16 rssi; + u16 channel; + u16 beacon_period; + u16 capability; + u16 security; + u64 bssid; + u8 essid[32]; + u8 rate[16]; + u8 ext_rate[16]; + u32 reserved1; + u32 reserved2; + u32 reserved3; + u32 reserved4; +} __attribute__ ((packed)); + +/* rssi_descriptor */ +struct rssi_desc { + u16 rssi; /* max rssi = 100 */ +} __attribute__ ((packed)); + + +struct gelicw_bss { + u8 bssid[ETH_ALEN]; + u8 channel; + u8 mode; + u8 essid_len; + u8 essid[IW_ESSID_MAX_SIZE + 1]; /* null terminated for debug msg */ + + u16 capability; + u16 beacon_interval; + + u8 rates_len; + u8 rates[MAX_RATES_LENGTH]; + u8 rates_ex_len; + u8 rates_ex[MAX_RATES_EX_LENGTH]; + u8 rssi; + + /* scan results have sec_info instead of rsn_ie or wpa_ie */ + u16 sec_info; +}; + +/* max station count of station list which hvc returns */ +#define MAX_SCAN_BSS (16) + +struct gelic_wireless { + struct gelic_net_card *card; + struct completion cmd_done, rssi_done; + struct work_struct work_event, work_start_done; + struct delayed_work work_rssi, work_scan_all, work_scan_essid; + struct delayed_work work_common, work_encode; + struct delayed_work work_start, work_stop, work_roam; + wait_queue_head_t waitq_cmd, waitq_scan; + + u64 cmd_tag, cmd_id; + u8 cmd_send_flg; + + struct iw_public_data wireless_data; + u8 *data_buf; /* data buffer for lv1_net_control */ + + u8 wireless; /* wireless support */ + u8 state; + u8 scan_all; /* essid scan or all scan */ + u8 essid_search; /* essid background scan */ + u8 is_assoc; + + u16 ch_info; /* supoprted channels */ + u8 wireless_mode; /* 11b/g */ + u8 channel; /* current ch */ + u8 iw_mode; /* INFRA or Ad-hoc */ + u8 rssi; + u8 essid_len; + u8 essid[IW_ESSID_MAX_SIZE + 1]; /* null terminated for debug msg */ + u8 nick[IW_ESSID_MAX_SIZE + 1]; + u8 bssid[ETH_ALEN]; + + u8 key_index; + u8 key[WEP_KEYS][IW_ENCODING_TOKEN_MAX]; /* 4 * 64byte */ + u8 key_len[WEP_KEYS]; + u8 key_alg; /* key algorithm */ + u8 auth_mode; /* authenticaton mode */ + + u8 bss_index; /* current bss in bss_list */ + u8 num_bss_list; + u8 bss_key_alg; /* key alg of bss */ + u8 wap_bssid[ETH_ALEN]; + unsigned long last_scan; /* last scan time */ + struct gelicw_bss current_bss; + struct gelicw_bss bss_list[MAX_SCAN_BSS]; +}; + /* size of hardware part of gelic descriptor */ #define GELIC_NET_DESCR_SIZE (32) struct gelic_net_descr { @@ -225,9 +442,19 @@ struct gelic_net_card { wait_queue_head_t waitq; struct gelic_net_descr *tx_top, *rx_top; +#ifdef CONFIG_GELIC_WIRELESS + struct gelic_wireless w; +#endif struct gelic_net_descr descr[0]; }; extern unsigned long p_to_lp(long pa); +extern int gelicw_setup_netdev(struct net_device *netdev, int wi); +extern void gelicw_up(struct net_device *netdev); +extern int gelicw_down(struct net_device *netdev); +extern void gelicw_remove(struct net_device *netdev); +extern void gelicw_interrupt(struct net_device *netdev, u64 status); +extern int gelicw_is_associated(struct net_device *netdev); + #endif //_GELIC_NET_H --- /dev/null +++ b/drivers/net/gelic_wireless.c @@ -0,0 +1,2132 @@ +/* + * gelic_wireless.c: wireless extension for gelic_net + * + * 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 as published + * by the Free Software Foundation; version 2 of the License. + * + * 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. + */ + +#undef DEBUG + +#include <linux/kernel.h> +#include <linux/netdevice.h> +#include <linux/if_vlan.h> +#include <linux/completion.h> +#include <linux/ctype.h> +#include <asm/ps3.h> +#include <asm/lv1call.h> + +#include "gelic_net.h" + +static struct iw_handler_def gelicw_handler_def; + +/* + * Data tables + */ +static const u32 freq_list[] = { + 2412, 2417, 2422, 2427, 2432, + 2437, 2442, 2447, 2452, 2457, + 2462, 2467, 2472, 2484 }; + +static const u32 bitrate_list[] = { + 1000000, 2000000, 5500000, 11000000, /* 11b */ + 6000000, 9000000, 12000000, 18000000, + 24000000, 36000000, 48000000, 54000000 +}; +#define GELICW_NUM_11B_BITRATES 4 /* 802.11b: 1 ~ 11 Mb/s */ + +static inline struct device * ntodev(struct net_device *netdev) +{ + return &((struct gelic_net_card *)netdev_priv(netdev))->dev->core; +} + +static inline struct device * wtodev(struct gelic_wireless *w) +{ + return &w->card->dev->core; +} +static inline struct gelic_wireless *gelicw_priv(struct net_device *netdev) +{ + struct gelic_net_card *card = netdev_priv(netdev); + return &card->w; +} +static inline unsigned int bus_id(struct gelic_wireless *w) +{ + return w->card->dev->did.bus_id; +} +static inline unsigned int dev_id(struct gelic_wireless *w) +{ + return w->card->dev->did.dev_id; +} + +/* control wired or wireless */ +static void gelicw_vlan_mode(struct net_device *netdev, int mode) +{ + struct gelic_net_card *card = netdev_priv(netdev); + + if ((mode < GELIC_NET_VLAN_WIRED) || + (mode > GELIC_NET_VLAN_WIRELESS)) + return; + + card->vlan_index = mode - 1; +} + +/* wireless_send_event */ +static void notify_assoc_event(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + union iwreq_data wrqu; + + cancel_delayed_work(&w->work_scan_all); + cancel_delayed_work(&w->work_scan_essid); + + wrqu.ap_addr.sa_family = ARPHRD_ETHER; + if (w->state < GELICW_STATE_ASSOCIATED) { + dev_dbg(ntodev(netdev), "notify disassociated\n"); + w->is_assoc = 0; + memset(w->bssid, 0, ETH_ALEN); + } else { + dev_dbg(ntodev(netdev), "notify associated\n"); + w->channel = w->current_bss.channel; + w->is_assoc = 1; + memcpy(w->bssid, w->current_bss.bssid, ETH_ALEN); + } + memcpy(wrqu.ap_addr.sa_data, w->bssid, ETH_ALEN); + wireless_send_event(netdev, SIOCGIWAP, &wrqu, NULL); +} + +/* association/disassociation */ +static void gelicw_assoc(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (w->state == GELICW_STATE_ASSOCIATED) + return; + schedule_delayed_work(&w->work_start, 0); +} + +static int gelicw_disassoc(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + memset(w->bssid, 0, ETH_ALEN); /* clear current bssid */ + if (w->state < GELICW_STATE_ASSOCIATED) + return 0; + + schedule_delayed_work(&w->work_stop, 0); + w->state = GELICW_STATE_SCAN_DONE; + /* notify disassociation */ + notify_assoc_event(netdev); + + return 0; +} + +static void gelicw_reassoc(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (w->cmd_send_flg != GELICW_CMD_SEND_ALL) + return; + + if (!gelicw_disassoc(netdev)) + gelicw_assoc(netdev); +} + + +/* + * lv1_net_control command + */ +/* control Ether port */ +static int gelicw_cmd_set_port(struct net_device *netdev, int mode) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 tag, val; + int status; + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_PORT, GELICW_ETHER_PORT, mode, 0, + &tag, &val); + if (status) + dev_dbg(ntodev(netdev), "GELICW_SET_PORT failed:%d\n", status); + return status; +} + +/* check support channels */ +static int gelicw_cmd_get_ch_info(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 ch_info, val; + int status; + + if (w->state < GELICW_STATE_UP) + return -EIO; + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_INFO, 0, 0 , 0, + &ch_info, &val); + if (status) { + dev_dbg(ntodev(netdev), "GELICW_GET_INFO failed:%d\n", status); + w->ch_info = CH_INFO_FAIL; + } else { + dev_dbg(ntodev(netdev), "ch_info:%lx val:%lx\n", ch_info, val); + w->ch_info = ch_info >> 48; /* MSB 16bit shows supported channnels */ + } + return status; +} + + +/* + * lv1_net_control GELICW_SET_CMD command + * queued using schedule_work() + */ +/* association */ +static void gelicw_cmd_start(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 val; + int status; + + if (w->state < GELICW_STATE_SCAN_DONE) + return; + + dev_dbg(ntodev(netdev), "GELICW_CMD_START\n"); + w->cmd_id = GELICW_CMD_START; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, 0, 0, + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_START failed:%d\n", status); + } +} + +/* association done */ +static void gelicw_cmd_start_done(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 res, val; + int status; + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, 0, 0, + &res, &val); + w->cmd_tag = 0; + wake_up_interruptible(&w->waitq_cmd); + + if (status || res) + dev_dbg(ntodev(netdev), "GELICW_CMD_START res:%d,%ld\n", status, res); +} + +/* disassociation */ +static void gelicw_cmd_stop(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 res, val; + int status; + + if (w->state < GELICW_STATE_SCAN_DONE) + return; + + dev_dbg(ntodev(netdev), "GELICW_CMD_STOP\n"); + init_completion(&w->cmd_done); + w->cmd_id = GELICW_CMD_STOP; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, 0, 0, + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_STOP failed:%d\n", status); + return; + } + wait_for_completion_interruptible(&w->cmd_done); + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, 0, 0, + &res, &val); + w->cmd_tag = 0; + if (status || res) { + dev_dbg(ntodev(netdev), "GELICW_CMD_STOP res:%d,%ld\n", status, res); + return; + } +} + +/* get rssi */ +static void gelicw_cmd_rssi(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 lpar, res, val; + int status; + struct rssi_desc *rssi; + + if (w->state < GELICW_STATE_ASSOCIATED) { + w->rssi = 0; + complete(&w->rssi_done); + return; + } + + lpar = ps3_mm_phys_to_lpar(__pa(w->data_buf)); + init_completion(&w->cmd_done); + w->cmd_id = GELICW_CMD_GET_RSSI; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, 0, 0, + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + w->rssi = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_GET_RSSI failed:%d\n", status); + } else { + wait_for_completion_interruptible(&w->cmd_done); + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, lpar, sizeof(*rssi), + &res, &val); + w->cmd_tag = 0; + if (status || res) { + w->rssi = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_GET_RSSI res:%d,%ld\n", status, res); + } + rssi = (struct rssi_desc *)w->data_buf; + w->rssi = rssi->rssi; + dev_dbg(ntodev(netdev), "GELICW_CMD_GET_RSSI:%d\n", rssi->rssi); + } + + complete(&w->rssi_done); +} + +/* set common configuration */ +static int gelicw_cmd_common(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 lpar, res, val; + int status; + struct common_config *config; + + if (w->state < GELICW_STATE_SCAN_DONE) + return -EIO; + + lpar = ps3_mm_phys_to_lpar(__pa(w->data_buf)); + config = (struct common_config *)w->data_buf; + config->scan_index = w->bss_index; + config->bss_type = (w->iw_mode == IW_MODE_ADHOC) ? + GELICW_BSS_ADHOC : GELICW_BSS_INFRA; + config->auth_method = (w->auth_mode == IW_AUTH_ALG_SHARED_KEY) ? + GELICW_AUTH_SHARED : GELICW_AUTH_OPEN; + switch (w->wireless_mode) { + case IEEE_B: + config->op_mode = GELICW_OP_MODE_11B; + break; + case IEEE_G: + config->op_mode = GELICW_OP_MODE_11G; + break; + case IEEE_B | IEEE_G: + default: + /* default 11bg mode */ + config->op_mode = GELICW_OP_MODE_11BG; + break; + } + + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_CONFIG: index:%d type:%d auth:%d mode:%d\n",\ + config->scan_index, config->bss_type,\ + config->auth_method,config->op_mode); + init_completion(&w->cmd_done); + w->cmd_id = GELICW_CMD_SET_CONFIG; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, + lpar, sizeof(struct common_config), + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_CONFIG failed:%d\n", status); + return status; + } + wait_for_completion_interruptible(&w->cmd_done); + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, 0, 0, + &res, &val); + w->cmd_tag = 0; + if (status || res) { + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_CONFIG res:%d,%ld\n", status, res); + return -EFAULT; + } + + w->cmd_send_flg |= GELICW_CMD_SEND_COMMON; + + return 0; +} + +#define h2i(c) (isdigit(c) ? c - '0' : toupper(c) - 'A' + 10) +/* send WEP/WPA configuration */ +static int gelicw_cmd_encode(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 res, val, lpar; + int status; + struct wep_config *config; + struct wpa_config *wpa_config; + + u8 *key, key_len; + + if (w->state < GELICW_STATE_SCAN_DONE) + return -EIO; + + lpar = ps3_mm_phys_to_lpar(__pa(w->data_buf)); + + if (w->key_alg == IW_ENCODE_ALG_WEP || + w->key_alg == IW_ENCODE_ALG_NONE) { + /* WEP */ + config = (struct wep_config *)w->data_buf; + memset(config, 0, sizeof(struct wep_config)); + + /* check key len */ + key_len = w->key_len[w->key_index]; + key = w->key[w->key_index]; + if (w->key_alg == IW_ENCODE_ALG_NONE) + config->sec = GELICW_WEP_SEC_NONE; + else + config->sec = (key_len == 5) ? GELICW_WEP_SEC_40BIT : + GELICW_WEP_SEC_104BIT; + /* copy key */ + memcpy(config->key[w->key_index], key, key_len); + + /* send wep config */ + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_WEP\n"); + init_completion(&w->cmd_done); + w->cmd_id = GELICW_CMD_SET_WEP; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, + lpar, sizeof(struct wep_config), + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_WEP failed:%d\n", status); + goto err; + } + wait_for_completion_interruptible(&w->cmd_done); + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, 0, 0, + &res, &val); + w->cmd_tag = 0; + if (status || res) { + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_WEP res:%d,%ld\n", status, res); + status = -EFAULT; + goto err; + } + } else { + /* WPA */ + wpa_config = (struct wpa_config *)w->data_buf; + memset(wpa_config, 0, sizeof(struct wpa_config)); + + switch (w->key_alg) { + case IW_ENCODE_ALG_TKIP: + wpa_config->sec = GELICW_WPA_SEC_TKIP; + break; + default: + case IW_ENCODE_ALG_CCMP: + wpa_config->sec = GELICW_WPA_SEC_AES; + break; + } + /* check key len */ + key = w->key[w->key_index]; + key_len = w->key_len[w->key_index]; + + if (key_len > 64) key_len = 64; + if (key_len != 64) { + /* if key_len isn't 64byte ,it should be passphrase */ + /* pass phrase */ + memcpy(wpa_config->psk_material, key, key_len); + wpa_config->psk_type = GELICW_PSK_PASSPHRASE; + } else { + int i; + /* 64 hex */ + for (i = 0; i < 32; i++) + wpa_config->psk_material[i] = + h2i(key[2 * i]) * 16 + + h2i(key[2 * i + 1]); + wpa_config->psk_type = GELICW_PSK_64HEX; + } + + /* send wpa config */ + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_WPA:type:%d\n", wpa_config->psk_type); + init_completion(&w->cmd_done); + w->cmd_id = GELICW_CMD_SET_WPA; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, + lpar, sizeof(struct wpa_config), + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_WPA failed:%d\n", status); + goto err; + } + wait_for_completion_interruptible(&w->cmd_done); + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, 0, 0, &res, &val); + w->cmd_tag = 0; + if (status || res) { + dev_dbg(ntodev(netdev), "GELICW_CMD_SET_WPA res:%d,%ld\n", status, res); + status = -EFAULT; + goto err; + } + } + + w->cmd_send_flg |= GELICW_CMD_SEND_ENCODE; + /* (re)associate */ + gelicw_reassoc(netdev); + + return 0; +err: + gelicw_disassoc(netdev); + return status; +} + +static int gelicw_is_ap_11b(struct gelicw_bss *list) +{ + if (list->rates_len + list->rates_ex_len == GELICW_NUM_11B_BITRATES) + return 1; + else + return 0; +} + +/* get scan results */ +static int gelicw_cmd_get_scan(struct gelic_wireless *w) +{ + u64 lpar, res, val; + int status; + struct scan_desc *desc; + int i, j; + u8 *p; + + + /* get scan */ + dev_dbg(wtodev(w), "GELICW_CMD_GET_SCAN\n"); + init_completion(&w->cmd_done); + w->cmd_id = GELICW_CMD_GET_SCAN; + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, 0, 0, + &w->cmd_tag, &val); + if (status) { + w->cmd_tag = 0; + dev_dbg(wtodev(w), "GELICW_CMD_GET_SCAN failed:%d\n", status); + return status; + } + wait_for_completion_interruptible(&w->cmd_done); + + lpar = ps3_mm_phys_to_lpar(__pa(w->data_buf)); + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, lpar, PAGE_SIZE, + &res, &val); + w->cmd_tag = 0; + if (status || res) { + dev_dbg(wtodev(w), "GELICW_CMD_GET_SCAN res:%d,%ld\n", + status, res); + return -EFAULT; + } + + desc = (struct scan_desc *)w->data_buf; + for (i = 0; + i < val / sizeof(struct scan_desc) && i < MAX_SCAN_BSS; + i++) { + struct gelicw_bss *bss = &w->bss_list[i]; + + bss->rates_len = 0; + for (j = 0; j < MAX_RATES_LENGTH; j++) + if (desc[i].rate[j]) + bss->rates[bss->rates_len++] = desc[i].rate[j]; + bss->rates_ex_len = 0; + for (j = 0; j < MAX_RATES_EX_LENGTH; j++) + if (desc[i].ext_rate[j]) + bss->rates_ex[bss->rates_ex_len++] + = desc[i].ext_rate[j]; + + if (desc[i].capability & 0x3) { + if (desc[i].capability & 0x1) + bss->mode = IW_MODE_INFRA; + else + bss->mode = IW_MODE_ADHOC; + } + bss->channel = desc[i].channel; + bss->essid_len = strnlen(desc[i].essid, IW_ESSID_MAX_SIZE); + bss->rssi = (u8)desc[i].rssi; + bss->capability = desc[i].capability; + bss->beacon_interval = desc[i].beacon_period; + memset(bss->essid, 0, sizeof(bss->essid)); + memcpy(bss->essid, desc[i].essid, bss->essid_len); + p = (u8 *)&desc[i].bssid; + memcpy(bss->bssid, &p[2], ETH_ALEN);/* bssid:64bit in desc */ + bss->sec_info = desc[i].security; + } + w->num_bss_list = i; + + if (w->num_bss_list) + return 0; /* ap found */ + else + return -1; /* no ap found */ +} + +/* search bssid in bss list */ +static int gelicw_search_bss_list(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + static const u8 off[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + int i; + int check_bss = 0, check_11g = 0, found_bss, found_11g; + + if (!w->num_bss_list) + return -1; /* no bss list */ + + if (memcmp(off, w->wap_bssid, ETH_ALEN)) + check_bss = 1; + + /* wireless op_mode seems not working with CMD_SET_CONFIG */ + if (w->wireless_mode == IEEE_G) + check_11g = 1; + + if (!check_bss && !check_11g) + return 0; /* no check bssid, wmode */ + + for (i = 0; i < w->num_bss_list; i++) { + found_bss = found_11g = 1; + if (check_bss && + memcmp(w->bss_list[i].bssid, w->wap_bssid, ETH_ALEN)) + found_bss = 0; /* not found */ + + if (check_11g && + gelicw_is_ap_11b(&w->bss_list[i])) + found_11g = 0; /* not found */ + + if (found_bss && found_11g) + break; + } + + if (i == w->num_bss_list) + return -1; /* not found */ + else + return i; +} + +/* scan done */ +static void gelicw_scan_complete(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + int res; + int bss_index; + + /* get scan results */ + res = gelicw_cmd_get_scan(w); + if (w->is_assoc) + w->state = GELICW_STATE_ASSOCIATED; + else + w->state = GELICW_STATE_SCAN_DONE; + + if (res) { + /* No AP found */ + if (!w->scan_all) { + /* no specified AP */ + gelicw_disassoc(netdev); + /* rescan */ + if (w->essid_search && w->essid_len) + schedule_delayed_work(&w->work_scan_essid, + GELICW_SCAN_INTERVAL); + return; + } + } + + if (w->scan_all) { + /* all params should be set again after scan */ + w->cmd_send_flg = 0; + return; + } + + bss_index = gelicw_search_bss_list(netdev); + if (bss_index < 0) { + /* no wap_bssid in bss_list */ + if (w->essid_search && w->essid_len) + schedule_delayed_work(&w->work_scan_essid, + GELICW_SCAN_INTERVAL); + return; + } + w->bss_index = (u8)bss_index; + w->current_bss = w->bss_list[w->bss_index]; + + /* essid search complete */ + w->essid_search = 0; + w->cmd_send_flg |= GELICW_CMD_SEND_SCAN; + + /* (re)connect to AP */ + if (w->is_assoc) { + /* notify disassociation */ + w->state = GELICW_STATE_SCAN_DONE; + notify_assoc_event(netdev); + } + schedule_delayed_work(&w->work_common, 0); + schedule_delayed_work(&w->work_encode, 0); +} + +/* start scan */ +static int gelicw_cmd_set_scan(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 res, val, lpar; + int status; + u8 *p; + + if (w->state < GELICW_STATE_UP) { + w->scan_all = 0; + return -EIO; + } + if (!w->scan_all && !w->essid_len) + return -EINVAL; /* unsupported essid ANY */ + + /* set device wired to wireless when essid is set */ + if (!w->scan_all && w->wireless < GELICW_WIRELESS_ON) { + gelicw_vlan_mode(netdev, GELIC_NET_VLAN_WIRELESS); + gelicw_cmd_set_port(netdev, GELICW_PORT_DOWN); + w->wireless = GELICW_WIRELESS_ON; + } + + p = (u8 *)w->data_buf; + lpar = ps3_mm_phys_to_lpar(__pa(w->data_buf)); + + /* avoid frequent scanning */ + if (!w->essid_search && /* background scan off */ + w->scan_all && + (time_before64(get_jiffies_64(), w->last_scan + 5 * HZ))) + return 0; + + w->bss_key_alg = IW_ENCODE_ALG_NONE; + + init_completion(&w->cmd_done); + w->state = GELICW_STATE_SCANNING; + w->cmd_id = GELICW_CMD_SCAN; + + if (w->scan_all) { + /* scan all ch */ + dev_dbg(ntodev(netdev), "GELICW_CMD_SCAN all\n"); + w->last_scan = get_jiffies_64(); /* last scan time */ + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, 0, 0, + &w->cmd_tag, &val); + } else { + /* scan essid */ + memset(p, 0, 32); + memcpy(p, w->essid, w->essid_len); + dev_dbg(ntodev(netdev), "GELICW_CMD_SCAN essid\n"); + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_SET_CMD, w->cmd_id, lpar, 32, + &w->cmd_tag, &val); + } + + if (status) { + w->cmd_tag = 0; + dev_dbg(ntodev(netdev), "GELICW_CMD_SCAN failed:%d\n", status); + return status; + } + wait_for_completion_interruptible(&w->cmd_done); + + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_RES, w->cmd_tag, 0, 0, + &res, &val); + w->cmd_tag = 0; + if (status || res) { + dev_dbg(ntodev(netdev), "GELICW_CMD_SCAN res:%d,%ld\n", status, res); + return -EFAULT; + } + + return 0; +} + +static void gelicw_send_common_config(struct net_device *netdev, + u8 *cur, u8 mode) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (*cur != mode) { + *cur = mode; + if (w->state < GELICW_STATE_SCAN_DONE) + return; + + if (!(w->cmd_send_flg & GELICW_CMD_SEND_SCAN) && + w->essid_len) + /* scan essid and set other params */ + schedule_delayed_work(&w->work_scan_essid, 0); + else { + schedule_delayed_work(&w->work_common, 0); + if (w->cmd_send_flg + & GELICW_CMD_SEND_ENCODE) + /* (re)send encode key */ + schedule_delayed_work(&w->work_encode, 0); + } + } +} + + +/* + * work queue + */ +static void gelicw_work_rssi(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_rssi.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag) { + schedule_delayed_work(&w->work_rssi, HZ / 5); + return; + } + + gelicw_cmd_rssi(netdev); +} + +static void gelicw_work_scan_all(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_scan_all.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag || w->state == GELICW_STATE_SCANNING) { + schedule_delayed_work(&w->work_scan_all, HZ / 5); + return; + } + + w->scan_all = 1; + gelicw_cmd_set_scan(netdev); +} + +static void gelicw_work_scan_essid(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_scan_essid.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag || w->scan_all || w->state == GELICW_STATE_SCANNING) { + schedule_delayed_work(&w->work_scan_essid, HZ / 5); + return; + } + w->bss_index = 0; + w->scan_all = 0; + gelicw_cmd_set_scan(netdev); +} + +static void gelicw_work_common(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_common.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag) { + schedule_delayed_work(&w->work_common, HZ / 5); + return; + } + gelicw_cmd_common(netdev); +} + +static void gelicw_work_encode(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_encode.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag) { + schedule_delayed_work(&w->work_encode, HZ / 5); + return; + } + gelicw_cmd_encode(netdev); +} + +static void gelicw_work_start(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_start.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag) { + schedule_delayed_work(&w->work_start, HZ / 5); + return; + } + gelicw_cmd_start(netdev); +} + +static void gelicw_work_start_done(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_start_done); + struct net_device *netdev = w->card->netdev; + + gelicw_cmd_start_done(netdev); +} + +static void gelicw_work_stop(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_stop.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag) { + schedule_delayed_work(&w->work_stop, HZ / 5); + return; + } + gelicw_cmd_stop(netdev); +} + +static void gelicw_work_roam(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_roam.work); + struct net_device *netdev = w->card->netdev; + + if (w->cmd_tag || w->scan_all || w->state == GELICW_STATE_SCANNING) { + schedule_delayed_work(&w->work_roam, HZ / 5); + return; + } + gelicw_cmd_stop(netdev); + w->bss_index = 0; + w->scan_all = 0; + gelicw_cmd_set_scan(netdev); +} + +/* + * Event handler + */ +#define GELICW_EVENT_LOOP_MAX 16 +static void gelicw_event(struct work_struct *work) +{ + struct gelic_wireless *w = + container_of(work, struct gelic_wireless, work_event); + struct net_device *netdev = w->card->netdev; + u64 event_type, val; + int i, status; + + for (i = 0; i < GELICW_EVENT_LOOP_MAX; i++) { + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_EVENT, 0, 0 , 0, + &event_type, &val); + if (status == GELICW_EVENT_NO_ENTRY) + /* got all events */ + break; + else if (status){ + dev_dbg(ntodev(netdev), "GELICW_GET_EVENT failed:%d\n", status); + return; + } + switch(event_type) { + case GELICW_EVENT_DEVICE_READY: + dev_dbg(ntodev(netdev), " GELICW_EVENT_DEVICE_READY\n"); + break; + case GELICW_EVENT_SCAN_COMPLETED: + dev_dbg(ntodev(netdev), " GELICW_EVENT_SCAN_COMPLETED\n"); + gelicw_scan_complete(netdev); + break; + case GELICW_EVENT_BEACON_LOST: + dev_dbg(ntodev(netdev), " GELICW_EVENT_BEACON_LOST\n"); + w->state = GELICW_STATE_SCAN_DONE; + notify_assoc_event(netdev); + /* roaming */ + w->essid_search = 1; + schedule_delayed_work(&w->work_roam, 0); + break; + case GELICW_EVENT_CONNECTED: + { + u16 ap_sec; + dev_dbg(ntodev(netdev), " GELICW_EVENT_CONNECTED\n"); + /* this event ocuured with any key_alg */ + ap_sec = w->current_bss.sec_info; + if (w->key_alg == IW_ENCODE_ALG_NONE) { + /* no encryption */ + if (ap_sec == 0) { + w->state = GELICW_STATE_ASSOCIATED; + notify_assoc_event(netdev); + } + } else if (w->key_alg == IW_ENCODE_ALG_WEP){ + if ((ap_sec & GELICW_SEC_TYPE_WEP_MASK) + == GELICW_SEC_TYPE_WEP) { + /* wep */ + w->state = GELICW_STATE_ASSOCIATED; + notify_assoc_event(netdev); + } + } + break; + } + case GELICW_EVENT_WPA_CONNECTED: + dev_dbg(ntodev(netdev), " GELICW_EVENT_WPA_CONNECTED\n"); + w->state = GELICW_STATE_ASSOCIATED; + notify_assoc_event(netdev); + break; + case GELICW_EVENT_WPA_ERROR: + dev_dbg(ntodev(netdev), " GELICW_EVENT_WPA_ERROR\n"); + break; + default: + dev_dbg(ntodev(netdev), " GELICW_EVENT_UNKNOWN\n"); + break; + } + } +} + +static void gelicw_clear_event(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u64 event_type, val; + int i, status; + + for (i = 0; i < GELICW_EVENT_LOOP_MAX; i++) { + status = lv1_net_control(bus_id(w), dev_id(w), + GELICW_GET_EVENT, 0, 0 , 0, + &event_type, &val); + if (status) + return;/* got all events */ + + switch(event_type) { + case GELICW_EVENT_SCAN_COMPLETED: + w->state = GELICW_STATE_SCAN_DONE; + wake_up_interruptible(&w->waitq_scan); + break; + default: + break; + } + } +} + +/* + * gelic_net support function + */ +static void gelicw_clear_params(struct gelic_wireless *w) +{ + int i; + + /* clear status */ + w->state = GELICW_STATE_DOWN; + w->cmd_send_flg = 0; + w->scan_all = 0; + w->is_assoc = 0; + w->essid_search = 0; + w->cmd_tag = 0; + w->cmd_id = 0; + w->last_scan = 0; + + /* default mode and settings */ + w->essid_len = 0; + w->essid[0] = '\0'; + w->nick[0] = '\0'; + w->iw_mode = IW_MODE_INFRA; + w->auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + w->wireless_mode = IEEE_B | IEEE_G; + w->bss_index = 0; + memset(w->bssid, 0, ETH_ALEN); + memset(w->wap_bssid, 0, ETH_ALEN); + + /* init key */ + w->key_index = 0; + for (i = 0; i < WEP_KEYS; i++) { + w->key[i][0] = '\0'; + w->key_len[i] = 0; + } + w->key_alg = IW_ENCODE_ALG_NONE; + w->bss_key_alg = IW_ENCODE_ALG_NONE; +} + +int gelicw_setup_netdev(struct net_device *netdev, int wi) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + union ps3_firmware_version ver; + union ps3_firmware_version initial_ver; + + if (wi < 0) { + /* PS3 low model has no wireless */ + dev_info(ntodev(netdev), "No wireless dvice in this system\n"); + w->wireless = 0; + return 0; + } + /* version check */ + initial_ver.raw = 0; + initial_ver.major = 1; + initial_ver.minor = 6; + if (ps3_get_firmware_version(&ver) || + (ver.raw < initial_ver.raw)) { + dev_info(ntodev(netdev), + "firmware %d.%d is too old for wireless.\n", + ver.major, ver.minor); + w->wireless = 0; + return 0; + } + /* we need 4K aligned, 16 units of scan_desc sized */ + BUILD_BUG_ON(PAGE_SIZE < sizeof(struct scan_desc) * MAX_SCAN_BSS); + w->data_buf = (u8*)get_zeroed_page(GFP_KERNEL); + if (!w->data_buf) { + w->wireless = 0; + dev_info(ntodev(netdev), "%s:get_page failed\n", __func__); + return -ENOMEM; + } + + w->wireless = GELICW_WIRELESS_SUPPORTED; + + w->ch_info = 0; + w->channel = 0; + netdev->wireless_data = &w->wireless_data; + netdev->wireless_handlers = &gelicw_handler_def; + INIT_WORK(&w->work_event, gelicw_event); + INIT_WORK(&w->work_start_done, gelicw_work_start_done); + INIT_DELAYED_WORK(&w->work_rssi, gelicw_work_rssi); + INIT_DELAYED_WORK(&w->work_scan_all, gelicw_work_scan_all); + INIT_DELAYED_WORK(&w->work_scan_essid, gelicw_work_scan_essid); + INIT_DELAYED_WORK(&w->work_common, gelicw_work_common); + INIT_DELAYED_WORK(&w->work_encode, gelicw_work_encode); + INIT_DELAYED_WORK(&w->work_start, gelicw_work_start); + INIT_DELAYED_WORK(&w->work_stop, gelicw_work_stop); + INIT_DELAYED_WORK(&w->work_roam, gelicw_work_roam); + init_waitqueue_head(&w->waitq_cmd); + init_waitqueue_head(&w->waitq_scan); + + gelicw_clear_params(w); + + return 0; +} + +void gelicw_up(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (!w->wireless) + return; + + dev_dbg(ntodev(netdev), "gelicw_up\n"); + if (w->state < GELICW_STATE_UP) + w->state = GELICW_STATE_UP; + + /* start essid scanning */ + if (w->essid_len) + schedule_delayed_work(&w->work_scan_essid, 0); +} + +int gelicw_down(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (!w->wireless || w->state == GELICW_STATE_DOWN) + return 0; + + dev_dbg(ntodev(netdev), "gelicw_down\n"); + w->wireless = GELICW_WIRELESS_SHUTDOWN; + flush_scheduled_work(); + + /* check cmd_tag of CMD_START */ + if (w->cmd_id == GELICW_CMD_START) + wait_event_interruptible(w->waitq_cmd, !w->cmd_tag); + /* wait scan done */ + if (w->state == GELICW_STATE_SCANNING) { + wait_event_interruptible(w->waitq_scan, + w->state != GELICW_STATE_SCANNING); + gelicw_cmd_get_scan(w); + } + + gelicw_cmd_stop(netdev); + if (w->is_assoc) { + w->state = GELICW_STATE_DOWN; + notify_assoc_event(netdev); + } + gelicw_clear_params(w); + + /* set device wireless to wired */ + gelicw_vlan_mode(netdev, GELIC_NET_VLAN_WIRED); + gelicw_cmd_set_port(netdev, GELICW_PORT_UP); + w->wireless = GELICW_WIRELESS_SUPPORTED; + + return 0; +} + +void gelicw_remove(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (!w->wireless) + return; + + dev_dbg(ntodev(netdev), "gelicw_remove\n"); + gelicw_down(netdev); + w->wireless = 0; + netdev->wireless_handlers = NULL; + free_page((unsigned long)w->data_buf); +} + +void gelicw_interrupt(struct net_device *netdev, u64 status) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (!w->wireless) + return; + + if (status & GELICW_DEVICE_CMD_COMP) { + dev_dbg(ntodev(netdev), "GELICW_DEVICE_CMD_COMP\n"); + if (w->cmd_id == GELICW_CMD_START) + schedule_work(&w->work_start_done); + else + complete(&w->cmd_done); + } + if (status & GELICW_DEVICE_EVENT_RECV) { + dev_dbg(ntodev(netdev), "GELICW_DEVICE_EVENT_RECV\n"); + if (w->wireless == GELICW_WIRELESS_SHUTDOWN) + gelicw_clear_event(netdev); + else + schedule_work(&w->work_event); + } +} + +int gelicw_is_associated(struct net_device *netdev) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + if (!w->wireless) + return 0; + + return w->is_assoc; +} + + +/* + * Wireless externsions + */ +static int gelicw_get_name(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx: get_name\n"); + if (w->state < GELICW_STATE_UP) { + strcpy(wrqu->name, "radio off"); + return 0; + } + + if (w->wireless_mode == IEEE_B || + (w->is_assoc && gelicw_is_ap_11b(&w->current_bss))) + strcpy(wrqu->name, "IEEE 802.11b"); + else { + switch (w->wireless_mode) { + case IEEE_G: + strcpy(wrqu->name, "IEEE 802.11g"); + break; + case IEEE_B | IEEE_G: + default: + strcpy(wrqu->name, "IEEE 802.11bg"); + break; + } + } + + return 0; +} + +static int gelicw_set_freq(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_freq *fwrq = &wrqu->freq; + int ch; + + dev_dbg(ntodev(netdev), "wx: set_freq e:%d m:%d\n", fwrq->e, fwrq->m); + if (w->is_assoc || w->state < GELICW_STATE_UP) + return 0; + + /* this setting has no effect for INFRA mode */ + if (fwrq->e == 1) { + u32 f = fwrq->m / 100000; + int i; + for (i = 0; i < ARRAY_SIZE(freq_list); i++) + if (freq_list[i] == f) + break; + if (i == ARRAY_SIZE(freq_list)) + return -EINVAL; + fwrq->m = i + 1; /* ch number */ + fwrq->e = 0; + } + if (fwrq->e > 0) + return -EINVAL; + + ch = fwrq->m; + if (ch < 1) + w->channel = 0; /* auto */ + else if (ch > ARRAY_SIZE(freq_list)) + return -EINVAL; + else { + /* check supported channnel */ + if (!w->ch_info) + gelicw_cmd_get_ch_info(netdev); + if (w->ch_info & (1 << (ch - 1))) + w->channel = ch; + else + return -EINVAL; + } + dev_dbg(ntodev(netdev), " set cnannel: %d\n", w->channel); + + return 0; +} + +static int gelicw_get_freq(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx: get_freq:%d\n", w->channel); + if (w->channel == 0) + wrqu->freq.m = 0; + else + wrqu->freq.m = freq_list[w->channel - 1] * 100000; + wrqu->freq.e = 1; + + return 0; +} + +static int gelicw_set_mode(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + int mode = wrqu->mode; + u8 iw_mode = IW_MODE_INFRA; + + dev_dbg(ntodev(netdev), "wx: set_mode:%x\n",mode); + switch (mode) { + case IW_MODE_ADHOC: + dev_dbg(ntodev(netdev), "IW_MODE_ADHOC\n"); + iw_mode = mode; + return -EOPNOTSUPP; /* adhoc not supported */ + case IW_MODE_INFRA: + default: + dev_dbg(ntodev(netdev), "IW_MODE_INFRA\n"); + iw_mode = IW_MODE_INFRA; + break; + } + + /* send common config */ + gelicw_send_common_config(netdev, &w->iw_mode, iw_mode); + + return 0; +} + +static int gelicw_get_mode(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx: get_mode\n"); + wrqu->mode = w->iw_mode; + + return 0; +} + +static inline int gelicw_qual2level(int qual) +{ + return (qual * 4 - 820)/10; /* FIXME: dummy */ +} + +static int gelicw_get_range(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_range *range = (struct iw_range *)extra; + int num_ch, i; + + dev_dbg(ntodev(netdev), "wx: get_range\n"); + wrqu->data.length = sizeof(*range); + memset(range, 0, sizeof(*range)); + + /* wireless extension */ + range->we_version_compiled = WIRELESS_EXT; + range->we_version_source = 19; + + /* supported bitrates */ + if (w->wireless_mode == IEEE_B) + range->num_bitrates = GELICW_NUM_11B_BITRATES; + else + range->num_bitrates = ARRAY_SIZE(bitrate_list); + range->throughput = bitrate_list[range->num_bitrates -1] / 2; /* half */ + for (i = 0; i < range->num_bitrates; i++) + range->bitrate[i] = bitrate_list[i]; + + range->max_qual.qual = 100; /* relative value */ + range->max_qual.level = 0; + range->avg_qual.qual = 50; + range->avg_qual.level = 0; + range->sensitivity = 0; + + /* encryption capabilities */ + range->encoding_size[0] = 5; /* 40bit WEP */ + range->encoding_size[1] = 13; /* 104bit WEP */ + range->encoding_size[2] = 64; /* WPA-PSK */ + range->num_encoding_sizes = 3; + range->max_encoding_tokens = WEP_KEYS; + range->enc_capa = IW_ENC_CAPA_WPA | IW_ENC_CAPA_WPA2 | + IW_ENC_CAPA_CIPHER_TKIP | IW_ENC_CAPA_CIPHER_CCMP; + + /* freq */ + if (!w->ch_info) + gelicw_cmd_get_ch_info(netdev); /* get supported freq */ + + num_ch = 0; + for (i = 0; i < ARRAY_SIZE(freq_list); i++) + if (w->ch_info & (1 << i)) { + range->freq[num_ch].i = i + 1; + range->freq[num_ch].m = freq_list[i] * 100000; + range->freq[num_ch].e = 1; + if (++num_ch == IW_MAX_FREQUENCIES) + break; + } + + range->num_channels = num_ch; + range->num_frequency = num_ch; + + /* event capabilities */ + range->event_capa[0] = (IW_EVENT_CAPA_K_0 | + IW_EVENT_CAPA_MASK(SIOCGIWAP)); + range->event_capa[1] = IW_EVENT_CAPA_K_1; + + return 0; +} + +static int gelicw_set_wap(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + static const u8 any[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + static const u8 off[] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + + dev_dbg(ntodev(netdev), "wx: set_wap\n"); + if (wrqu->ap_addr.sa_family != ARPHRD_ETHER) + return -EINVAL; + + if (!memcmp(any, wrqu->ap_addr.sa_data, ETH_ALEN) || + !memcmp(off, wrqu->ap_addr.sa_data, ETH_ALEN)) { + if (!memcmp(off, w->wap_bssid, ETH_ALEN)) + return 0; /* ap off, no change */ + else { + memset(w->wap_bssid, 0, ETH_ALEN); + /* start scan */ + } + } else if (!memcmp(w->wap_bssid, wrqu->ap_addr.sa_data, ETH_ALEN)) + /* no change */ + return 0; + else if (!memcmp(w->bssid, wrqu->ap_addr.sa_data, ETH_ALEN)) { + /* current bss */ + memcpy(w->wap_bssid, wrqu->ap_addr.sa_data, ETH_ALEN); + return 0; + } else + memcpy(w->wap_bssid, wrqu->ap_addr.sa_data, ETH_ALEN); + + /* start scan */ + if (w->essid_len && w->state >= GELICW_STATE_SCAN_DONE) { + gelicw_disassoc(netdev); + /* scan essid */ + cancel_delayed_work(&w->work_scan_all); + cancel_delayed_work(&w->work_scan_essid); + w->essid_search = 1; + schedule_delayed_work(&w->work_scan_essid, 0); + } + + return 0; +} + +static int gelicw_get_wap(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx: get_wap\n"); + wrqu->ap_addr.sa_family = ARPHRD_ETHER; + memcpy(wrqu->ap_addr.sa_data, w->bssid, ETH_ALEN); + + return 0; +} + +static int gelicw_set_scan(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx: set_scan\n"); + if (w->state < GELICW_STATE_UP) + return -EIO; + + /* cancel scan */ + cancel_delayed_work(&w->work_scan_all); + cancel_delayed_work(&w->work_scan_essid); + + schedule_delayed_work(&w->work_scan_all, 0); + + return 0; +} + +#define MAX_CUSTOM_LEN 64 +static char *gelicw_translate_scan(struct net_device *netdev, + char *start, char *stop, + struct gelicw_bss *list) +{ + char custom[MAX_CUSTOM_LEN]; + struct iw_event iwe; + int i; + char *p, *current_val; + + /* BSSID */ + iwe.cmd = SIOCGIWAP; + iwe.u.ap_addr.sa_family = ARPHRD_ETHER; + memcpy(iwe.u.ap_addr.sa_data, list->bssid, ETH_ALEN); + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_ADDR_LEN); + + /* ESSID */ + iwe.cmd = SIOCGIWESSID; + iwe.u.data.flags = 1; + iwe.u.data.length = list->essid_len; + start = iwe_stream_add_point(start, stop, &iwe, list->essid); + + /* protocol name */ + iwe.cmd = SIOCGIWNAME; + if (gelicw_is_ap_11b(list)) + snprintf(iwe.u.name, IFNAMSIZ, "IEEE 802.11b"); + else + snprintf(iwe.u.name, IFNAMSIZ, "IEEE 802.11bg"); + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_CHAR_LEN); + + /* MODE */ + iwe.cmd = SIOCGIWMODE; + iwe.u.mode = list->mode; + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_UINT_LEN); + + /* FREQ */ + iwe.cmd = SIOCGIWFREQ; + iwe.u.freq.m = list->channel; + iwe.u.freq.e = 0; + iwe.u.freq.i = 0; + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_FREQ_LEN); + + /* ENCODE */ + iwe.cmd = SIOCGIWENCODE; + if (list->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; + start = iwe_stream_add_point(start, stop, &iwe, list->essid); + + /* QUAL */ + iwe.cmd = IWEVQUAL; + iwe.u.qual.updated = IW_QUAL_QUAL_UPDATED | + IW_QUAL_LEVEL_UPDATED | IW_QUAL_NOISE_INVALID; + iwe.u.qual.qual = list->rssi; + iwe.u.qual.level = gelicw_qual2level(list->rssi); + start = iwe_stream_add_event(start, stop, &iwe, IW_EV_QUAL_LEN); + + /* RATE */ + current_val = start + IW_EV_LCP_LEN; + iwe.cmd = SIOCGIWRATE; + iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0; + for (i = 0; i < list->rates_len; i++) { + iwe.u.bitrate.value = ((list->rates[i] & 0x7f) * 500000); + current_val = iwe_stream_add_value(start, current_val, stop, + &iwe, IW_EV_PARAM_LEN); + } + for (i = 0; i < list->rates_ex_len; i++) { + iwe.u.bitrate.value = ((list->rates_ex[i] & 0x7f) * 500000); + current_val = iwe_stream_add_value(start, current_val, stop, + &iwe, IW_EV_PARAM_LEN); + } + if ((current_val - start) > IW_EV_LCP_LEN) + start = current_val; + + /* Extra */ + /* BEACON */ + memset(&iwe, 0, sizeof(iwe)); + iwe.cmd = IWEVCUSTOM; + p = custom; + p += snprintf(p, MAX_CUSTOM_LEN, "bcn_int=%d", list->beacon_interval); + iwe.u.data.length = p - custom; + start = iwe_stream_add_point(start, stop, &iwe, custom); + + /* AP security */ + memset(&iwe, 0, sizeof(iwe)); + iwe.cmd = IWEVCUSTOM; + p = custom; + p += snprintf(p, MAX_CUSTOM_LEN, "ap_sec=%04X", list->sec_info); + iwe.u.data.length = p - custom; + start = iwe_stream_add_point(start, stop, &iwe, custom); + + return start; +} + +static int gelicw_get_scan(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + int i; + char *ev = extra; + char *stop = ev + wrqu->data.length; + + dev_dbg(ntodev(netdev), "wx: get_scan \n"); + switch (w->state) { + case GELICW_STATE_DOWN: + case GELICW_STATE_UP: + return 0; /* no scan results */ + case GELICW_STATE_SCANNING: + return -EAGAIN; /* now scanning */ + case GELICW_STATE_SCAN_DONE: + if (!w->scan_all) /* essid scan */ + return -EAGAIN; + break; + default: + break; + } + + w->scan_all = 0; + for (i = 0; i < w->num_bss_list; i++) + ev = gelicw_translate_scan(netdev, ev, stop, &w->bss_list[i]); + wrqu->data.length = ev - extra; + wrqu->data.flags = 0; + + /* start background scan */ + if (w->essid_search) + schedule_delayed_work(&w->work_scan_essid, + GELICW_SCAN_INTERVAL); + + return 0; +} + +static int gelicw_set_essid(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u16 length = 0; + + dev_dbg(ntodev(netdev), "wx:set_essid\n"); + /* cancel scan */ + w->essid_search = 0; + cancel_delayed_work(&w->work_scan_all); + cancel_delayed_work(&w->work_scan_essid); + + if (wrqu->essid.flags && wrqu->essid.length) + length = wrqu->essid.length; + + if (length == 0) { + /* essid ANY scan not supported */ + dev_dbg(ntodev(netdev), "ESSID ANY\n"); + w->essid_len = 0; /* clear essid */ + w->essid[0] = '\0'; + return 0; + } else { + /* check essid */ + if (length > IW_ESSID_MAX_SIZE) + return -EINVAL; + if (w->essid_len == length && + !strncmp(w->essid, extra, length)) { + /* same essid */ + if (w->is_assoc) + return 0; + } else { + /* set new essid */ + w->essid_len = length; + memcpy(w->essid, extra, length); + } + } + /* start essid scan */ + w->essid_search = 1; + schedule_delayed_work(&w->work_scan_essid, 0); + + return 0; +} + +static int gelicw_get_essid(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx:get_essid\n"); + if (w->essid_len) { + memcpy(extra, w->essid, w->essid_len); + wrqu->essid.length = w->essid_len; + wrqu->essid.flags = 1; + } else { + wrqu->essid.length = 0; + wrqu->essid.flags = 0; + } + + return 0; +} + +static int gelicw_set_nick(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + u32 len = wrqu->data.length; + + dev_dbg(ntodev(netdev), "wx:set_nick\n"); + if (len > IW_ESSID_MAX_SIZE) + return -EINVAL; + + memset(w->nick, 0, sizeof(w->nick)); + memcpy(w->nick, extra, len); + + return 0; +} + +static int gelicw_get_nick(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx:get_nick\n"); + wrqu->data.length = strlen(w->nick); + memcpy(extra, w->nick, wrqu->data.length); + wrqu->data.flags = 1; + + return 0; +} + +static int gelicw_set_rate(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + dev_dbg(ntodev(netdev), "wx:set_rate:%d\n", wrqu->bitrate.value); + if (wrqu->bitrate.value == -1) + return 0; /* auto rate only */ + + return -EOPNOTSUPP; +} + +static int gelicw_get_rate(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx:get_rate\n"); + + if (w->wireless_mode == IEEE_B || + (w->is_assoc && gelicw_is_ap_11b(&w->current_bss))) + wrqu->bitrate.value = bitrate_list[GELICW_NUM_11B_BITRATES -1]; + else + wrqu->bitrate.value = bitrate_list[ARRAY_SIZE(bitrate_list) -1]; + + wrqu->bitrate.fixed = 0; + + return 0; +} + +static int gelicw_set_encode(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_point *enc = &wrqu->encoding; + int i, index, key_index; + + dev_dbg(ntodev(netdev), "wx:set_encode: flags:%x\n", enc->flags ); + index = enc->flags & IW_ENCODE_INDEX; + if (index < 0 || index > WEP_KEYS) + return -EINVAL; + index--; + + if (enc->length > IW_ENCODING_TOKEN_MAX) + return -EINVAL; + + if (index != -1) + w->key_index = index; + key_index = w->key_index; + + if (enc->flags & IW_ENCODE_DISABLED) { + /* disable encryption */ + if (index == -1) { + /* disable all */ + w->key_alg = IW_ENCODE_ALG_NONE; + for (i = 0; i < WEP_KEYS; i++) + w->key_len[i] = 0; + } else + w->key_len[key_index] = 0; + } else if (enc->flags & IW_ENCODE_NOKEY) { + /* key not changed */ + if (w->key_alg == IW_ENCODE_ALG_NONE) + w->key_alg = IW_ENCODE_ALG_WEP; /* default wep */ + } else { + /* enable encryption */ + w->key_len[key_index] = enc->length; + if (w->key_alg == IW_ENCODE_ALG_NONE) + w->key_alg = IW_ENCODE_ALG_WEP; /* default wep */ + memcpy(w->key[key_index], extra, w->key_len[key_index]); + } + dev_dbg(ntodev(netdev), "key %d len:%d alg:%x\n",\ + key_index, w->key_len[key_index], w->key_alg); + + if (w->state >= GELICW_STATE_SCAN_DONE && + w->cmd_send_flg == 0 && w->essid_len) + /* scan essid and set other params */ + schedule_delayed_work(&w->work_scan_essid, 0); + else + schedule_delayed_work(&w->work_encode, 0); + + return 0; +} + +static int gelicw_get_encode(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_point *enc = &wrqu->encoding; + int index, key_index; + + dev_dbg(ntodev(netdev), "wx:get_encode\n"); + index = enc->flags & IW_ENCODE_INDEX; + if (index < 0 || index > WEP_KEYS) + return -EINVAL; + + index--; + key_index = (index == -1 ? w->key_index : index); + enc->flags = key_index + 1; + + if (w->key_alg == IW_ENCODE_ALG_NONE || !w->key_len[key_index]) { + /* no encryption */ + enc->flags |= IW_ENCODE_DISABLED; + enc->length = 0; + } else { + enc->flags |= IW_ENCODE_NOKEY; + enc->length = w->key_len[key_index]; + memset(extra, 0, w->key_len[key_index]); + } + + return 0; +} + +static int gelicw_set_auth(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_param *param = &wrqu->param; + int value = param->value; + int ret = 0; + + dev_dbg(ntodev(netdev), "wx:set_auth:%x\n", param->flags & IW_AUTH_INDEX); + switch(param->flags & IW_AUTH_INDEX) { + case IW_AUTH_WPA_VERSION: + case IW_AUTH_CIPHER_PAIRWISE: + case IW_AUTH_CIPHER_GROUP: + case IW_AUTH_KEY_MGMT: + case IW_AUTH_TKIP_COUNTERMEASURES: + case IW_AUTH_DROP_UNENCRYPTED: + case IW_AUTH_WPA_ENABLED: + case IW_AUTH_RX_UNENCRYPTED_EAPOL: + case IW_AUTH_ROAMING_CONTROL: + case IW_AUTH_PRIVACY_INVOKED: + /* ignore */ + dev_dbg(ntodev(netdev), "IW_AUTH(%x)\n", param->flags & IW_AUTH_INDEX); + break; + case IW_AUTH_80211_AUTH_ALG: + dev_dbg(ntodev(netdev), "IW_AUTH_80211_AUTH_ALG:\n"); + if (value & IW_AUTH_ALG_SHARED_KEY) + w->auth_mode = IW_AUTH_ALG_SHARED_KEY; + else if (value & IW_AUTH_ALG_OPEN_SYSTEM) + w->auth_mode = IW_AUTH_ALG_OPEN_SYSTEM; + else + ret = -EINVAL; + break; + default: + dev_dbg(ntodev(netdev), "IW_AUTH_UNKNOWN flags:%x\n", param->flags); + ret = -EOPNOTSUPP; + } + + return ret; +} + +static int gelicw_get_auth(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_param *param = &wrqu->param; + + dev_dbg(ntodev(netdev), "wx:get_auth\n"); + switch(param->flags & IW_AUTH_INDEX) { + case IW_AUTH_80211_AUTH_ALG: + param->value = w->auth_mode; + break; + case IW_AUTH_WPA_ENABLED: + if ((w->key_alg & IW_ENCODE_ALG_TKIP) || + (w->key_alg & IW_ENCODE_ALG_CCMP)) + param->value = 1; + else + param->value = 0; + break; + default: + return -EOPNOTSUPP; + } + + return 0; +} + +static int gelicw_set_encodeext(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_point *enc = &wrqu->encoding; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int i, index, key_index; + + dev_dbg(ntodev(netdev), "wx:set_encodeext\n"); + index = enc->flags & IW_ENCODE_INDEX; + if (index < 0 || index > WEP_KEYS) + return -EINVAL; + + index--; + if (ext->key_len > IW_ENCODING_TOKEN_MAX) + return -EINVAL; + + if (index != -1) + w->key_index = index; + key_index = w->key_index; + + if (enc->flags & IW_ENCODE_DISABLED) { + /* disable encryption */ + if (index == -1) { + /* disable all */ + w->key_alg = IW_ENCODE_ALG_NONE; + for (i = 0; i < WEP_KEYS; i++) + w->key_len[i] = 0; + } else + w->key_len[key_index] = 0; + } else if (enc->flags & IW_ENCODE_NOKEY) + /* key not changed */ + w->key_alg = ext->alg; + else { + w->key_len[key_index] = ext->key_len; + w->key_alg = ext->alg; + if (w->key_alg != IW_ENCODE_ALG_NONE && w->key_len[key_index]) + memcpy(w->key[key_index], ext->key, w->key_len[key_index]); + } + dev_dbg(ntodev(netdev), "key %d len:%d alg:%x\n",\ + key_index, w->key_len[key_index], w->key_alg); + + if (w->state >= GELICW_STATE_SCAN_DONE && + w->cmd_send_flg == 0 && w->essid_len) + /* scan essid and set other params */ + schedule_delayed_work(&w->work_scan_essid, 0); + else + schedule_delayed_work(&w->work_encode, 0); + + return 0; +} + +static int gelicw_get_encodeext(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + struct iw_point *enc = &wrqu->encoding; + struct iw_encode_ext *ext = (struct iw_encode_ext *)extra; + int index, key_index, key_len; + + dev_dbg(ntodev(netdev), "wx:get_encodeext\n"); + key_len = enc->length - sizeof(*ext); + if (key_len < 0) + return -EINVAL; + + index = enc->flags & IW_ENCODE_INDEX; + if (index < 0 || index > WEP_KEYS) + return -EINVAL; + + index--; + key_index = (index == -1 ? w->key_index : index); + + memset(ext, 0, sizeof(*ext)); + enc->flags = key_index + 1; + + if (w->key_alg == IW_ENCODE_ALG_NONE || !w->key_len[key_index]) { + /* no encryption */ + enc->flags |= IW_ENCODE_DISABLED; + ext->alg = IW_ENCODE_ALG_NONE; + ext->key_len = 0; + } else { + enc->flags |= IW_ENCODE_NOKEY; + ext->alg = w->key_alg; + ext->key_len = w->key_len[key_index]; + } + + return 0; +} + +/* + * wireless stats + */ +static struct iw_statistics *gelicw_get_wireless_stats(struct net_device *netdev) +{ + static struct iw_statistics wstats; + struct gelic_wireless *w = gelicw_priv(netdev); + + dev_dbg(ntodev(netdev), "wx:wireless_stats\n"); + if (w->state < GELICW_STATE_ASSOCIATED) { + wstats.qual.updated = IW_QUAL_QUAL_UPDATED | + IW_QUAL_LEVEL_UPDATED | IW_QUAL_NOISE_INVALID; + wstats.qual.qual = 0; + wstats.qual.level = 0; + return &wstats; + } + init_completion(&w->rssi_done); + schedule_delayed_work(&w->work_rssi, 0); + + wait_for_completion_interruptible(&w->rssi_done); + wstats.qual.updated = IW_QUAL_QUAL_UPDATED | + IW_QUAL_LEVEL_UPDATED | IW_QUAL_NOISE_INVALID; + wstats.qual.qual = w->rssi; + wstats.qual.level = gelicw_qual2level(w->rssi); + + return &wstats; +} + +/* + * private handler + */ +static int gelicw_priv_set_alg_mode(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + int mode = *(int *)extra; + + dev_dbg(ntodev(netdev), "wx:priv_set_alg\n"); + switch (mode) { + case IW_ENCODE_ALG_NONE: + case IW_ENCODE_ALG_WEP: + case IW_ENCODE_ALG_TKIP: + case IW_ENCODE_ALG_CCMP: + break; + default: + return -EINVAL; + } + /* send common config */ + gelicw_send_common_config(netdev, &w->key_alg, (u8)mode); + + return 0; +} + +static int gelicw_priv_get_alg_mode(struct net_device *netdev, + struct iw_request_info *info, + union iwreq_data *wrqu, char *extra) +{ + struct gelic_wireless *w = gelicw_priv(netdev); + char *p; + + dev_dbg(ntodev(netdev), "wx:priv_get_alg\n"); + switch (w->key_alg) { + case IW_ENCODE_ALG_NONE: + strncpy(extra, "OFF", MAX_IW_PRIV_SIZE); + break; + case IW_ENCODE_ALG_WEP: + strncpy(extra, "WEP", MAX_IW_PRIV_SIZE); + break; + case IW_ENCODE_ALG_TKIP: + strncpy(extra, "TKIP", MAX_IW_PRIV_SIZE); + break; + case IW_ENCODE_ALG_CCMP: + strncpy(extra, "AES-CCMP", MAX_IW_PRIV_SIZE); + break; + default: + break; + } + p = extra + strlen(extra); + + if (w->key_alg == IW_ENCODE_ALG_TKIP || + w->key_alg == IW_ENCODE_ALG_CCMP) { + if (w->key_len[w->key_index] == 64) /* current key index */ + strncpy(p, " hex", MAX_IW_PRIV_SIZE); + else + strncpy(p, " passphrase", MAX_IW_PRIV_SIZE); + } + wrqu->data.length = strlen(extra); + + return 0; +} + + +/* + * Wireless handlers + */ +static const iw_handler gelicw_handler[] = +{ + [IW_IOCTL_IDX(SIOCGIWNAME)] = gelicw_get_name, + [IW_IOCTL_IDX(SIOCSIWFREQ)] = gelicw_set_freq, + [IW_IOCTL_IDX(SIOCGIWFREQ)] = gelicw_get_freq, + [IW_IOCTL_IDX(SIOCSIWMODE)] = gelicw_set_mode, + [IW_IOCTL_IDX(SIOCGIWMODE)] = gelicw_get_mode, + [IW_IOCTL_IDX(SIOCGIWRANGE)] = gelicw_get_range, + [IW_IOCTL_IDX(SIOCSIWAP)] = gelicw_set_wap, + [IW_IOCTL_IDX(SIOCGIWAP)] = gelicw_get_wap, + [IW_IOCTL_IDX(SIOCSIWSCAN)] = gelicw_set_scan, + [IW_IOCTL_IDX(SIOCGIWSCAN)] = gelicw_get_scan, + [IW_IOCTL_IDX(SIOCSIWESSID)] = gelicw_set_essid, + [IW_IOCTL_IDX(SIOCGIWESSID)] = gelicw_get_essid, + [IW_IOCTL_IDX(SIOCSIWNICKN)] = gelicw_set_nick, + [IW_IOCTL_IDX(SIOCGIWNICKN)] = gelicw_get_nick, + [IW_IOCTL_IDX(SIOCSIWRATE)] = gelicw_set_rate, + [IW_IOCTL_IDX(SIOCGIWRATE)] = gelicw_get_rate, + [IW_IOCTL_IDX(SIOCSIWENCODE)] = gelicw_set_encode, + [IW_IOCTL_IDX(SIOCGIWENCODE)] = gelicw_get_encode, + [IW_IOCTL_IDX(SIOCSIWAUTH)] = gelicw_set_auth, + [IW_IOCTL_IDX(SIOCGIWAUTH)] = gelicw_get_auth, + [IW_IOCTL_IDX(SIOCSIWENCODEEXT)] = gelicw_set_encodeext, + [IW_IOCTL_IDX(SIOCGIWENCODEEXT)] = gelicw_get_encodeext, +}; + +/* + * Private wireless handlers + */ +enum { + GELICW_PRIV_SET_AUTH = SIOCIWFIRSTPRIV, + GELICW_PRIV_GET_AUTH +}; + +static struct iw_priv_args gelicw_private_args[] = { + { + .cmd = GELICW_PRIV_SET_AUTH, + .set_args = IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1, + .name = "set_alg" + }, + { + .cmd = GELICW_PRIV_GET_AUTH, + .get_args = IW_PRIV_TYPE_CHAR | IW_PRIV_SIZE_FIXED | MAX_IW_PRIV_SIZE, + .name = "get_alg" + }, +}; + +static const iw_handler gelicw_private_handler[] = +{ + gelicw_priv_set_alg_mode, + gelicw_priv_get_alg_mode, +}; + +static struct iw_handler_def gelicw_handler_def = +{ + .num_standard = ARRAY_SIZE(gelicw_handler), + .num_private = ARRAY_SIZE(gelicw_private_handler), + .num_private_args = ARRAY_SIZE(gelicw_private_args), + .standard = (iw_handler *)gelicw_handler, + .private = (iw_handler *)gelicw_private_handler, + .private_args = (struct iw_priv_args *)gelicw_private_args, + .get_wireless_stats = gelicw_get_wireless_stats +}; -- Masakazu MOKUNO - To unsubscribe from this list: send the line "unsubscribe netdev" in the body of a message to majordomo@...r.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists