2722168ada
git-svn-id: https://svn.o-hand.com/repos/poky/trunk@3595 311d38ba-8fff-0310-9ca6-ca027cbcb966
33528 lines
944 KiB
Diff
33528 lines
944 KiB
Diff
---
|
|
drivers/net/wireless/Kconfig | 31
|
|
drivers/net/wireless/Makefile | 2
|
|
drivers/net/wireless/acx/Kconfig | 113
|
|
drivers/net/wireless/acx/Makefile | 21
|
|
drivers/net/wireless/acx/acx.h | 14
|
|
drivers/net/wireless/acx/acx_config.h | 50
|
|
drivers/net/wireless/acx/acx_func.h | 710 ++
|
|
drivers/net/wireless/acx/acx_hw.h | 18
|
|
drivers/net/wireless/acx/acx_struct.h | 2114 ++++++++
|
|
drivers/net/wireless/acx/common.c | 7388 ++++++++++++++++++++++++++++
|
|
drivers/net/wireless/acx/conv.c | 504 +
|
|
drivers/net/wireless/acx/cs.c | 5703 +++++++++++++++++++++
|
|
drivers/net/wireless/acx/htcsable_acx.c | 118
|
|
drivers/net/wireless/acx/htcuniversal_acx.c | 108
|
|
drivers/net/wireless/acx/hx4700_acx.c | 108
|
|
drivers/net/wireless/acx/ioctl.c | 2748 ++++++++++
|
|
drivers/net/wireless/acx/mem.c | 5363 ++++++++++++++++++++
|
|
drivers/net/wireless/acx/pci.c | 4234 ++++++++++++++++
|
|
drivers/net/wireless/acx/rx3000_acx.c | 110
|
|
drivers/net/wireless/acx/setrate.c | 213
|
|
drivers/net/wireless/acx/usb.c | 1922 +++++++
|
|
drivers/net/wireless/acx/wlan.c | 424 +
|
|
drivers/net/wireless/acx/wlan_compat.h | 260
|
|
drivers/net/wireless/acx/wlan_hdr.h | 497 +
|
|
drivers/net/wireless/acx/wlan_mgmt.h | 582 ++
|
|
25 files changed, 33355 insertions(+)
|
|
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/acx_config.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/acx_config.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,50 @@
|
|
+#define ACX_RELEASE "v0.3.36"
|
|
+
|
|
+/*
|
|
+ * Test out all the channels in reg domain 0x10
|
|
+ */
|
|
+#define ACX_ALLOW_ALLCHANNELS
|
|
+
|
|
+/* set to 0 if you don't want any debugging code to be compiled in */
|
|
+/* set to 1 if you want some debugging */
|
|
+/* set to 2 if you want extensive debug log */
|
|
+#define ACX_DEBUG 0
|
|
+
|
|
+/*
|
|
+ * Since we'll be changing channels a lot
|
|
+#define ACX_DEFAULT_MSG (L_ASSOC|L_INIT)
|
|
+*/
|
|
+#define ACX_DEFAULT_MSG (L_ASSOC|L_INIT)
|
|
+
|
|
+/* assume 32bit I/O width
|
|
+ * (16bit is also compatible with Compact Flash) */
|
|
+#define ACX_IO_WIDTH 32
|
|
+
|
|
+/* Set this to 1 if you want monitor mode to use
|
|
+ * phy header. Currently it is not useful anyway since we
|
|
+ * don't know what useful info (if any) is in phy header.
|
|
+ * If you want faster/smaller code, say 0 here */
|
|
+#define WANT_PHY_HDR 0
|
|
+
|
|
+/* whether to do Tx descriptor cleanup in softirq (i.e. not in IRQ
|
|
+ * handler) or not. Note that doing it later does slightly increase
|
|
+ * system load, so still do that stuff in the IRQ handler for now,
|
|
+ * even if that probably means worse latency */
|
|
+#define TX_CLEANUP_IN_SOFTIRQ 0
|
|
+
|
|
+/* if you want very experimental 802.11 power save mode features */
|
|
+#define POWER_SAVE_80211 0
|
|
+
|
|
+/* if you want very early packet fragmentation bits and pieces */
|
|
+#define ACX_FRAGMENTATION 0
|
|
+
|
|
+/* Locking: */
|
|
+/* very talkative */
|
|
+/* #define PARANOID_LOCKING 1 */
|
|
+/* normal (use when bug-free) */
|
|
+#define DO_LOCKING 1
|
|
+/* else locking is disabled! */
|
|
+
|
|
+/* 0 - normal mode */
|
|
+/* 1 - development/debug: probe for IEs on modprobe */
|
|
+#define CMD_DISCOVERY 0
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/acx_func.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/acx_func.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,710 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** LOGGING
|
|
+**
|
|
+** - Avoid SHOUTING needlessly. Avoid excessive verbosity.
|
|
+** Gradually remove messages which are old debugging aids.
|
|
+**
|
|
+** - Use printk() for messages which are to be always logged.
|
|
+** Supply either 'acx:' or '<devname>:' prefix so that user
|
|
+** can figure out who's speaking among other kernel chatter.
|
|
+** acx: is for general issues (e.g. "acx: no firmware image!")
|
|
+** while <devname>: is related to a particular device
|
|
+** (think about multi-card setup). Double check that message
|
|
+** is not confusing to the average user.
|
|
+**
|
|
+** - use printk KERN_xxx level only if message is not a WARNING
|
|
+** but is INFO, ERR etc.
|
|
+**
|
|
+** - Use printk_ratelimited() for messages which may flood
|
|
+** (e.g. "rx DUP pkt!").
|
|
+**
|
|
+** - Use log() for messages which may be omitted (and they
|
|
+** _will_ be omitted in non-debug builds). Note that
|
|
+** message levels may be disabled at compile-time selectively,
|
|
+** thus select them wisely. Example: L_DEBUG is the lowest
|
|
+** (most likely to be compiled out) -> use for less important stuff.
|
|
+**
|
|
+** - Do not print important stuff with log(), or else people
|
|
+** will never build non-debug driver.
|
|
+**
|
|
+** Style:
|
|
+** hex: capital letters, zero filled (e.g. 0x02AC)
|
|
+** str: dont start from capitals, no trailing periods ("tx: queue is stopped")
|
|
+*/
|
|
+#if ACX_DEBUG > 1
|
|
+
|
|
+void log_fn_enter(const char *funcname);
|
|
+void log_fn_exit(const char *funcname);
|
|
+void log_fn_exit_v(const char *funcname, int v);
|
|
+
|
|
+#define FN_ENTER \
|
|
+ do { \
|
|
+ if (unlikely(acx_debug & L_FUNC)) { \
|
|
+ log_fn_enter(__func__); \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
+#define FN_EXIT1(v) \
|
|
+ do { \
|
|
+ if (unlikely(acx_debug & L_FUNC)) { \
|
|
+ log_fn_exit_v(__func__, v); \
|
|
+ } \
|
|
+ } while (0)
|
|
+#define FN_EXIT0 \
|
|
+ do { \
|
|
+ if (unlikely(acx_debug & L_FUNC)) { \
|
|
+ log_fn_exit(__func__); \
|
|
+ } \
|
|
+ } while (0)
|
|
+
|
|
+#else
|
|
+
|
|
+#define FN_ENTER
|
|
+#define FN_EXIT1(v)
|
|
+#define FN_EXIT0
|
|
+
|
|
+#endif /* ACX_DEBUG > 1 */
|
|
+
|
|
+
|
|
+#if ACX_DEBUG
|
|
+
|
|
+#define log(chan, args...) \
|
|
+ do { \
|
|
+ if (acx_debug & (chan)) \
|
|
+ printk(KERN_DEBUG args); \
|
|
+ } while (0)
|
|
+#define printk_ratelimited(args...) printk(args)
|
|
+
|
|
+#else /* Non-debug build: */
|
|
+
|
|
+#define log(chan, args...)
|
|
+/* Standard way of log flood prevention */
|
|
+#define printk_ratelimited(args...) \
|
|
+do { \
|
|
+ if (printk_ratelimit()) \
|
|
+ printk(args); \
|
|
+} while (0)
|
|
+
|
|
+#endif /* ACX_DEBUG */
|
|
+
|
|
+void acx_print_mac(const char *head, const u8 *mac, const char *tail);
|
|
+
|
|
+/* Optimized out to nothing in non-debug build */
|
|
+static inline void
|
|
+acxlog_mac(int level, const char *head, const u8 *mac, const char *tail)
|
|
+{
|
|
+ if (acx_debug & level) {
|
|
+ acx_print_mac(head, mac, tail);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** MAC address helpers
|
|
+*/
|
|
+static inline void
|
|
+MAC_COPY(u8 *mac, const u8 *src)
|
|
+{
|
|
+ *(u32*)mac = *(u32*)src;
|
|
+ ((u16*)mac)[2] = ((u16*)src)[2];
|
|
+ /* kernel's memcpy will do the same: memcpy(dst, src, ETH_ALEN); */
|
|
+}
|
|
+
|
|
+static inline void
|
|
+MAC_FILL(u8 *mac, u8 val)
|
|
+{
|
|
+ memset(mac, val, ETH_ALEN);
|
|
+}
|
|
+
|
|
+static inline void
|
|
+MAC_BCAST(u8 *mac)
|
|
+{
|
|
+ ((u16*)mac)[2] = *(u32*)mac = -1;
|
|
+}
|
|
+
|
|
+static inline void
|
|
+MAC_ZERO(u8 *mac)
|
|
+{
|
|
+ ((u16*)mac)[2] = *(u32*)mac = 0;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+mac_is_equal(const u8 *a, const u8 *b)
|
|
+{
|
|
+ /* can't beat this */
|
|
+ return memcmp(a, b, ETH_ALEN) == 0;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+mac_is_bcast(const u8 *mac)
|
|
+{
|
|
+ /* AND together 4 first bytes with sign-extended 2 last bytes
|
|
+ ** Only bcast address gives 0xffffffff. +1 gives 0 */
|
|
+ return ( *(s32*)mac & ((s16*)mac)[2] ) + 1 == 0;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+mac_is_zero(const u8 *mac)
|
|
+{
|
|
+ return ( *(u32*)mac | ((u16*)mac)[2] ) == 0;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+mac_is_directed(const u8 *mac)
|
|
+{
|
|
+ return (mac[0] & 1)==0;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+mac_is_mcast(const u8 *mac)
|
|
+{
|
|
+ return (mac[0] & 1) && !mac_is_bcast(mac);
|
|
+}
|
|
+
|
|
+#define MACSTR "%02X:%02X:%02X:%02X:%02X:%02X"
|
|
+#define MAC(bytevector) \
|
|
+ ((unsigned char *)bytevector)[0], \
|
|
+ ((unsigned char *)bytevector)[1], \
|
|
+ ((unsigned char *)bytevector)[2], \
|
|
+ ((unsigned char *)bytevector)[3], \
|
|
+ ((unsigned char *)bytevector)[4], \
|
|
+ ((unsigned char *)bytevector)[5]
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Random helpers
|
|
+*/
|
|
+#define TO_STRING(x) #x
|
|
+#define STRING(x) TO_STRING(x)
|
|
+
|
|
+#define CLEAR_BIT(val, mask) ((val) &= ~(mask))
|
|
+#define SET_BIT(val, mask) ((val) |= (mask))
|
|
+
|
|
+/* undefined if v==0 */
|
|
+static inline unsigned int
|
|
+lowest_bit(u16 v)
|
|
+{
|
|
+ unsigned int n = 0;
|
|
+ while (!(v & 0xf)) { v>>=4; n+=4; }
|
|
+ while (!(v & 1)) { v>>=1; n++; }
|
|
+ return n;
|
|
+}
|
|
+
|
|
+/* undefined if v==0 */
|
|
+static inline unsigned int
|
|
+highest_bit(u16 v)
|
|
+{
|
|
+ unsigned int n = 0;
|
|
+ while (v>0xf) { v>>=4; n+=4; }
|
|
+ while (v>1) { v>>=1; n++; }
|
|
+ return n;
|
|
+}
|
|
+
|
|
+/* undefined if v==0 */
|
|
+static inline int
|
|
+has_only_one_bit(u16 v)
|
|
+{
|
|
+ return ((v-1) ^ v) >= v;
|
|
+}
|
|
+
|
|
+
|
|
+static inline int
|
|
+is_hidden_essid(char *essid)
|
|
+{
|
|
+ return (('\0' == essid[0]) ||
|
|
+ ((' ' == essid[0]) && ('\0' == essid[1])));
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+** LOCKING
|
|
+** We have adev->sem and adev->lock.
|
|
+**
|
|
+** We employ following naming convention in order to get locking right:
|
|
+**
|
|
+** acx_e_xxxx - external entry points called from process context.
|
|
+** It is okay to sleep. adev->sem is to be taken on entry.
|
|
+** acx_i_xxxx - external entry points possibly called from atomic context.
|
|
+** Sleeping is not allowed (and thus down(sem) is not legal!)
|
|
+** acx_s_xxxx - potentially sleeping functions. Do not ever call under lock!
|
|
+** acx_l_xxxx - functions which expect lock to be already taken.
|
|
+** rest - non-sleeping functions which do not require locking
|
|
+** but may be run under lock
|
|
+**
|
|
+** A small number of local helpers do not have acx_[eisl]_ prefix.
|
|
+** They are always close to caller and are to be reviewed locally.
|
|
+**
|
|
+** Theory of operation:
|
|
+**
|
|
+** All process-context entry points (_e_ functions) take sem
|
|
+** immediately. IRQ handler and other 'atomic-context' entry points
|
|
+** (_i_ functions) take lock immediately on entry, but dont take sem
|
|
+** because that might sleep.
|
|
+**
|
|
+** Thus *all* code is either protected by sem or lock, or both.
|
|
+**
|
|
+** Code which must not run concurrently with IRQ takes lock.
|
|
+** Such code is marked with _l_.
|
|
+**
|
|
+** This results in the following rules of thumb useful in code review:
|
|
+**
|
|
+** + If a function calls _s_ fn, it must be an _s_ itself.
|
|
+** + You can call _l_ fn only (a) from another _l_ fn
|
|
+** or (b) from _s_, _e_ or _i_ fn by taking lock, calling _l_,
|
|
+** and dropping lock.
|
|
+** + All IRQ code runs under lock.
|
|
+** + Any _s_ fn is running under sem.
|
|
+** + Code under sem can race only with IRQ code.
|
|
+** + Code under sem+lock cannot race with anything.
|
|
+*/
|
|
+
|
|
+/* These functions *must* be inline or they will break horribly on SPARC, due
|
|
+ * to its weird semantics for save/restore flags */
|
|
+
|
|
+#if defined(PARANOID_LOCKING) /* Lock debugging */
|
|
+
|
|
+void acx_lock_debug(acx_device_t *adev, const char* where);
|
|
+void acx_unlock_debug(acx_device_t *adev, const char* where);
|
|
+void acx_down_debug(acx_device_t *adev, const char* where);
|
|
+void acx_up_debug(acx_device_t *adev, const char* where);
|
|
+void acx_lock_unhold(void);
|
|
+void acx_sem_unhold(void);
|
|
+
|
|
+static inline void
|
|
+acx_lock_helper(acx_device_t *adev, unsigned long *fp, const char* where)
|
|
+{
|
|
+ acx_lock_debug(adev, where);
|
|
+ spin_lock_irqsave(&adev->lock, *fp);
|
|
+}
|
|
+static inline void
|
|
+acx_unlock_helper(acx_device_t *adev, unsigned long *fp, const char* where)
|
|
+{
|
|
+ acx_unlock_debug(adev, where);
|
|
+ spin_unlock_irqrestore(&adev->lock, *fp);
|
|
+}
|
|
+static inline void
|
|
+acx_down_helper(acx_device_t *adev, const char* where)
|
|
+{
|
|
+ acx_down_debug(adev, where);
|
|
+}
|
|
+static inline void
|
|
+acx_up_helper(acx_device_t *adev, const char* where)
|
|
+{
|
|
+ acx_up_debug(adev, where);
|
|
+}
|
|
+#define acx_lock(adev, flags) acx_lock_helper(adev, &(flags), __FILE__ ":" STRING(__LINE__))
|
|
+#define acx_unlock(adev, flags) acx_unlock_helper(adev, &(flags), __FILE__ ":" STRING(__LINE__))
|
|
+#define acx_sem_lock(adev) acx_down_helper(adev, __FILE__ ":" STRING(__LINE__))
|
|
+#define acx_sem_unlock(adev) acx_up_helper(adev, __FILE__ ":" STRING(__LINE__))
|
|
+
|
|
+#elif defined(DO_LOCKING)
|
|
+
|
|
+#define acx_lock(adev, flags) spin_lock_irqsave(&adev->lock, flags)
|
|
+#define acx_unlock(adev, flags) spin_unlock_irqrestore(&adev->lock, flags)
|
|
+#define acx_sem_lock(adev) down(&adev->sem)
|
|
+#define acx_sem_unlock(adev) up(&adev->sem)
|
|
+#define acx_lock_unhold() ((void)0)
|
|
+#define acx_sem_unhold() ((void)0)
|
|
+
|
|
+#else /* no locking! :( */
|
|
+
|
|
+#define acx_lock(adev, flags) ((void)0)
|
|
+#define acx_unlock(adev, flags) ((void)0)
|
|
+#define acx_sem_lock(adev) ((void)0)
|
|
+#define acx_sem_unlock(adev) ((void)0)
|
|
+#define acx_lock_unhold() ((void)0)
|
|
+#define acx_sem_unhold() ((void)0)
|
|
+
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+
|
|
+/* Can race with rx path (which is not protected by sem):
|
|
+** rx -> process_[re]assocresp() -> set_status(ASSOCIATED) -> wake_queue()
|
|
+** Can race with tx_complete IRQ:
|
|
+** IRQ -> acxpci_l_clean_txdesc -> acx_wake_queue
|
|
+** Review carefully all callsites */
|
|
+static inline void
|
|
+acx_stop_queue(struct net_device *ndev, const char *msg)
|
|
+{
|
|
+ if (netif_queue_stopped(ndev))
|
|
+ return;
|
|
+
|
|
+ netif_stop_queue(ndev);
|
|
+ if (msg)
|
|
+ log(L_BUFT, "tx: stop queue %s\n", msg);
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_queue_stopped(struct net_device *ndev)
|
|
+{
|
|
+ return netif_queue_stopped(ndev);
|
|
+}
|
|
+
|
|
+/*
|
|
+static inline void
|
|
+acx_start_queue(struct net_device *ndev, const char *msg)
|
|
+{
|
|
+ netif_start_queue(ndev);
|
|
+ if (msg)
|
|
+ log(L_BUFT, "tx: start queue %s\n", msg);
|
|
+}
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+acx_wake_queue(struct net_device *ndev, const char *msg)
|
|
+{
|
|
+ netif_wake_queue(ndev);
|
|
+ if (msg)
|
|
+ log(L_BUFT, "tx: wake queue %s\n", msg);
|
|
+}
|
|
+
|
|
+static inline void
|
|
+acx_carrier_off(struct net_device *ndev, const char *msg)
|
|
+{
|
|
+ netif_carrier_off(ndev);
|
|
+ if (msg)
|
|
+ log(L_BUFT, "tx: carrier off %s\n", msg);
|
|
+}
|
|
+
|
|
+static inline void
|
|
+acx_carrier_on(struct net_device *ndev, const char *msg)
|
|
+{
|
|
+ netif_carrier_on(ndev);
|
|
+ if (msg)
|
|
+ log(L_BUFT, "tx: carrier on %s\n", msg);
|
|
+}
|
|
+
|
|
+/* This function does not need locking UNLESS you call it
|
|
+** as acx_set_status(ACX_STATUS_4_ASSOCIATED), bacause this can
|
|
+** wake queue. This can race with stop_queue elsewhere. */
|
|
+void acx_set_status(acx_device_t *adev, u16 status);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Communication with firmware
|
|
+*/
|
|
+#define CMD_TIMEOUT_MS(n) (n)
|
|
+#define ACX_CMD_TIMEOUT_DEFAULT CMD_TIMEOUT_MS(50)
|
|
+
|
|
+#if ACX_DEBUG
|
|
+
|
|
+/* We want to log cmd names */
|
|
+int acxpci_s_issue_cmd_timeo_debug(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout, const char* cmdstr);
|
|
+int acxmem_s_issue_cmd_timeo_debug(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout, const char* cmdstr);
|
|
+int acxusb_s_issue_cmd_timeo_debug(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout, const char* cmdstr);
|
|
+static inline int
|
|
+acx_s_issue_cmd_timeo_debug(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout, const char* cmdstr)
|
|
+{
|
|
+ if (IS_MEM(adev))
|
|
+ return acxmem_s_issue_cmd_timeo_debug(adev, cmd, param, len, timeout, cmdstr);
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_s_issue_cmd_timeo_debug(adev, cmd, param, len, timeout, cmdstr);
|
|
+ return acxusb_s_issue_cmd_timeo_debug(adev, cmd, param, len, timeout, cmdstr);
|
|
+}
|
|
+#define acx_s_issue_cmd(adev,cmd,param,len) \
|
|
+ acx_s_issue_cmd_timeo_debug(adev,cmd,param,len,ACX_CMD_TIMEOUT_DEFAULT,#cmd)
|
|
+#define acx_s_issue_cmd_timeo(adev,cmd,param,len,timeo) \
|
|
+ acx_s_issue_cmd_timeo_debug(adev,cmd,param,len,timeo,#cmd)
|
|
+int acx_s_configure_debug(acx_device_t *adev, void *pdr, int type, const char* str);
|
|
+#define acx_s_configure(adev,pdr,type) \
|
|
+ acx_s_configure_debug(adev,pdr,type,#type)
|
|
+int acx_s_interrogate_debug(acx_device_t *adev, void *pdr, int type, const char* str);
|
|
+#define acx_s_interrogate(adev,pdr,type) \
|
|
+ acx_s_interrogate_debug(adev,pdr,type,#type)
|
|
+
|
|
+#else
|
|
+
|
|
+int acxpci_s_issue_cmd_timeo(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout);
|
|
+int acxmem_s_issue_cmd_timeo(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout);
|
|
+int acxusb_s_issue_cmd_timeo(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout);
|
|
+static inline int
|
|
+acx_s_issue_cmd_timeo(acx_device_t *adev, unsigned cmd, void *param, unsigned len, unsigned timeout)
|
|
+{
|
|
+ if (IS_MEM(adev))
|
|
+ return acxmem_s_issue_cmd_timeo(adev, cmd, param, len, timeout);
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_s_issue_cmd_timeo(adev, cmd, param, len, timeout);
|
|
+ return acxusb_s_issue_cmd_timeo(adev, cmd, param, len, timeout);
|
|
+}
|
|
+static inline int
|
|
+acx_s_issue_cmd(acx_device_t *adev, unsigned cmd, void *param, unsigned len)
|
|
+{
|
|
+ if (IS_MEM(adev))
|
|
+ return acxmem_s_issue_cmd_timeo(adev, cmd, param, len, ACX_CMD_TIMEOUT_DEFAULT);
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_s_issue_cmd_timeo(adev, cmd, param, len, ACX_CMD_TIMEOUT_DEFAULT);
|
|
+ return acxusb_s_issue_cmd_timeo(adev, cmd, param, len, ACX_CMD_TIMEOUT_DEFAULT);
|
|
+}
|
|
+int acx_s_configure(acx_device_t *adev, void *pdr, int type);
|
|
+int acx_s_interrogate(acx_device_t *adev, void *pdr, int type);
|
|
+
|
|
+#endif
|
|
+
|
|
+void acx_s_cmd_start_scan(acx_device_t *adev);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Ioctls
|
|
+*/
|
|
+int
|
|
+acx111pci_ioctl_info(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra);
|
|
+int
|
|
+acx100pci_ioctl_set_phy_amp_bias(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra);
|
|
+int
|
|
+acx100mem_ioctl_set_phy_amp_bias(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** /proc
|
|
+*/
|
|
+#ifdef CONFIG_PROC_FS
|
|
+int acx_proc_register_entries(const struct net_device *ndev);
|
|
+int acx_proc_unregister_entries(const struct net_device *ndev);
|
|
+#else
|
|
+static inline int
|
|
+acx_proc_register_entries(const struct net_device *ndev) { return OK; }
|
|
+static inline int
|
|
+acx_proc_unregister_entries(const struct net_device *ndev) { return OK; }
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+firmware_image_t *acx_s_read_fw(struct device *dev, const char *file, u32 *size);
|
|
+int acxpci_s_upload_radio(acx_device_t *adev);
|
|
+int acxmem_s_upload_radio(acx_device_t *adev);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Unsorted yet :)
|
|
+*/
|
|
+int acxpci_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf);
|
|
+int acxmem_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf);
|
|
+int acxusb_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf);
|
|
+static inline int
|
|
+acx_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf)
|
|
+{
|
|
+ if (IS_MEM(adev))
|
|
+ return acxmem_s_read_phy_reg(adev, reg, charbuf);
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_s_read_phy_reg(adev, reg, charbuf);
|
|
+ return acxusb_s_read_phy_reg(adev, reg, charbuf);
|
|
+}
|
|
+
|
|
+int acxpci_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value);
|
|
+int acxmem_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value);
|
|
+int acxusb_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value);
|
|
+static inline int
|
|
+acx_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value)
|
|
+{
|
|
+ if (IS_MEM(adev))
|
|
+ return acxmem_s_write_phy_reg(adev, reg, value);
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_s_write_phy_reg(adev, reg, value);
|
|
+ return acxusb_s_write_phy_reg(adev, reg, value);
|
|
+}
|
|
+
|
|
+tx_t* acxpci_l_alloc_tx(acx_device_t *adev);
|
|
+tx_t* acxmem_l_alloc_tx(acx_device_t *adev);
|
|
+tx_t* acxusb_l_alloc_tx(acx_device_t *adev);
|
|
+static inline tx_t*
|
|
+acx_l_alloc_tx(acx_device_t *adev)
|
|
+{
|
|
+ if (IS_MEM(adev))
|
|
+ return acxmem_l_alloc_tx(adev);
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_l_alloc_tx(adev);
|
|
+ return acxusb_l_alloc_tx(adev);
|
|
+}
|
|
+
|
|
+void acxusb_l_dealloc_tx(tx_t *tx_opaque);
|
|
+void acxmem_l_dealloc_tx(acx_device_t *adev, tx_t *tx_opaque);
|
|
+static inline void
|
|
+acx_l_dealloc_tx(acx_device_t *adev, tx_t *tx_opaque)
|
|
+{
|
|
+#ifdef ACX_MEM
|
|
+ acxmem_l_dealloc_tx (adev, tx_opaque);
|
|
+#else
|
|
+ if (IS_USB(adev))
|
|
+ acxusb_l_dealloc_tx(tx_opaque);
|
|
+#endif
|
|
+}
|
|
+
|
|
+void* acxpci_l_get_txbuf(acx_device_t *adev, tx_t *tx_opaque);
|
|
+void* acxmem_l_get_txbuf(acx_device_t *adev, tx_t *tx_opaque);
|
|
+void* acxusb_l_get_txbuf(acx_device_t *adev, tx_t *tx_opaque);
|
|
+static inline void*
|
|
+acx_l_get_txbuf(acx_device_t *adev, tx_t *tx_opaque)
|
|
+{
|
|
+#if defined (ACX_MEM)
|
|
+ return acxmem_l_get_txbuf(adev, tx_opaque);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ return acxpci_l_get_txbuf(adev, tx_opaque);
|
|
+ return acxusb_l_get_txbuf(adev, tx_opaque);
|
|
+#endif
|
|
+}
|
|
+
|
|
+void acxpci_l_tx_data(acx_device_t *adev, tx_t *tx_opaque, int len);
|
|
+void acxmem_l_tx_data(acx_device_t *adev, tx_t *tx_opaque, int len);
|
|
+void acxusb_l_tx_data(acx_device_t *adev, tx_t *tx_opaque, int len);
|
|
+static inline void
|
|
+acx_l_tx_data(acx_device_t *adev, tx_t *tx_opaque, int len)
|
|
+{
|
|
+#if defined (ACX_MEM)
|
|
+ acxmem_l_tx_data(adev, tx_opaque, len);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ acxpci_l_tx_data(adev, tx_opaque, len);
|
|
+ else
|
|
+ acxusb_l_tx_data(adev, tx_opaque, len);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static inline wlan_hdr_t*
|
|
+acx_get_wlan_hdr(acx_device_t *adev, const rxbuffer_t *rxbuf)
|
|
+{
|
|
+ return (wlan_hdr_t*)((u8*)&rxbuf->hdr_a3 + adev->phy_header_len);
|
|
+}
|
|
+
|
|
+void acxpci_l_power_led(acx_device_t *adev, int enable);
|
|
+int acxpci_read_eeprom_byte(acx_device_t *adev, u32 addr, u8 *charbuf);
|
|
+unsigned int acxpci_l_clean_txdesc(acx_device_t *adev);
|
|
+void acxpci_l_clean_txdesc_emergency(acx_device_t *adev);
|
|
+int acxpci_s_create_hostdesc_queues(acx_device_t *adev);
|
|
+void acxpci_create_desc_queues(acx_device_t *adev, u32 tx_queue_start, u32 rx_queue_start);
|
|
+void acxpci_free_desc_queues(acx_device_t *adev);
|
|
+char* acxpci_s_proc_diag_output(char *p, acx_device_t *adev);
|
|
+int acxpci_proc_eeprom_output(char *p, acx_device_t *adev);
|
|
+void acxpci_set_interrupt_mask(acx_device_t *adev);
|
|
+int acx100pci_s_set_tx_level(acx_device_t *adev, u8 level_dbm);
|
|
+
|
|
+void acxmem_l_power_led(acx_device_t *adev, int enable);
|
|
+int acxmem_read_eeprom_byte(acx_device_t *adev, u32 addr, u8 *charbuf);
|
|
+unsigned int acxmem_l_clean_txdesc(acx_device_t *adev);
|
|
+void acxmem_l_clean_txdesc_emergency(acx_device_t *adev);
|
|
+int acxmem_s_create_hostdesc_queues(acx_device_t *adev);
|
|
+void acxmem_create_desc_queues(acx_device_t *adev, u32 tx_queue_start, u32 rx_queue_start);
|
|
+void acxmem_free_desc_queues(acx_device_t *adev);
|
|
+char* acxmem_s_proc_diag_output(char *p, acx_device_t *adev);
|
|
+int acxmem_proc_eeprom_output(char *p, acx_device_t *adev);
|
|
+void acxmem_set_interrupt_mask(acx_device_t *adev);
|
|
+int acx100mem_s_set_tx_level(acx_device_t *adev, u8 level_dbm);
|
|
+
|
|
+void acx_s_msleep(int ms);
|
|
+int acx_s_init_mac(acx_device_t *adev);
|
|
+void acx_set_reg_domain(acx_device_t *adev, unsigned char reg_dom_id);
|
|
+void acx_set_timer(acx_device_t *adev, int timeout_us);
|
|
+void acx_update_capabilities(acx_device_t *adev);
|
|
+void acx_s_start(acx_device_t *adev);
|
|
+
|
|
+void acx_s_update_card_settings(acx_device_t *adev);
|
|
+void acx_s_parse_configoption(acx_device_t *adev, const acx111_ie_configoption_t *pcfg);
|
|
+void acx_l_update_ratevector(acx_device_t *adev);
|
|
+
|
|
+void acx_init_task_scheduler(acx_device_t *adev);
|
|
+void acx_schedule_task(acx_device_t *adev, unsigned int set_flag);
|
|
+
|
|
+int acx_e_ioctl_old(struct net_device *ndev, struct ifreq *ifr, int cmd);
|
|
+
|
|
+client_t *acx_l_sta_list_get(acx_device_t *adev, const u8 *address);
|
|
+void acx_l_sta_list_del(acx_device_t *adev, client_t *clt);
|
|
+
|
|
+int acx_l_transmit_disassoc(acx_device_t *adev, client_t *clt);
|
|
+void acx_i_timer(unsigned long a);
|
|
+int acx_s_complete_scan(acx_device_t *adev);
|
|
+
|
|
+struct sk_buff *acx_rxbuf_to_ether(acx_device_t *adev, rxbuffer_t *rxbuf);
|
|
+int acx_ether_to_txbuf(acx_device_t *adev, void *txbuf, const struct sk_buff *skb);
|
|
+
|
|
+u8 acx_signal_determine_quality(u8 signal, u8 noise);
|
|
+
|
|
+void acx_l_process_rxbuf(acx_device_t *adev, rxbuffer_t *rxbuf);
|
|
+void acx_l_handle_txrate_auto(acx_device_t *adev, struct client *txc,
|
|
+ u16 intended_rate, u8 rate100, u16 rate111, u8 error,
|
|
+ int pkts_to_ignore);
|
|
+
|
|
+void acx_dump_bytes(const void *, int);
|
|
+void acx_log_bad_eid(wlan_hdr_t* hdr, int len, wlan_ie_t* ie_ptr);
|
|
+
|
|
+u8 acx_rate111to100(u16);
|
|
+
|
|
+void acx_s_set_defaults(acx_device_t *adev);
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline const char* acx_get_packet_type_string(u16 fc) { return ""; }
|
|
+#else
|
|
+const char* acx_get_packet_type_string(u16 fc);
|
|
+#endif
|
|
+const char* acx_cmd_status_str(unsigned int state);
|
|
+
|
|
+int acx_i_start_xmit(struct sk_buff *skb, struct net_device *ndev);
|
|
+
|
|
+void great_inquisitor(acx_device_t *adev);
|
|
+
|
|
+void acx_s_get_firmware_version(acx_device_t *adev);
|
|
+void acx_display_hardware_details(acx_device_t *adev);
|
|
+
|
|
+int acx_e_change_mtu(struct net_device *ndev, int mtu);
|
|
+struct net_device_stats* acx_e_get_stats(struct net_device *ndev);
|
|
+struct iw_statistics* acx_e_get_wireless_stats(struct net_device *ndev);
|
|
+
|
|
+#ifdef ACX_MEM
|
|
+int __init acxmem_e_init_module(void);
|
|
+void __exit acxmem_e_cleanup_module(void);
|
|
+void acxmem_e_release(struct device *dev);
|
|
+#else
|
|
+int __init acxpci_e_init_module(void);
|
|
+int __init acxusb_e_init_module(void);
|
|
+void __exit acxpci_e_cleanup_module(void);
|
|
+void __exit acxusb_e_cleanup_module(void);
|
|
+#endif
|
|
+int __init acx_cs_init(void);
|
|
+void __exit acx_cs_cleanup(void);
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/acx.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/acx.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,14 @@
|
|
+#if defined(CONFIG_ACX_MEM) && !defined(ACX_MEM)
|
|
+#define ACX_MEM
|
|
+#endif
|
|
+
|
|
+#if defined(CONFIG_ACX_CS) && !defined(ACX_MEM)
|
|
+#define ACX_MEM
|
|
+#endif
|
|
+
|
|
+#include "acx_config.h"
|
|
+#include "wlan_compat.h"
|
|
+#include "wlan_hdr.h"
|
|
+#include "wlan_mgmt.h"
|
|
+#include "acx_struct.h"
|
|
+#include "acx_func.h"
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/acx_hw.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/acx_hw.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,18 @@
|
|
+/*
|
|
+ * Interface for ACX slave memory driver
|
|
+ *
|
|
+ * Copyright (c) 2006 SDG Systems, LLC
|
|
+ *
|
|
+ * GPL
|
|
+ *
|
|
+ */
|
|
+
|
|
+#ifndef _ACX_HW_H
|
|
+#define _ACX_HW_H
|
|
+
|
|
+struct acx_hardware_data {
|
|
+ int (*start_hw)( void );
|
|
+ int (*stop_hw)( void );
|
|
+};
|
|
+
|
|
+#endif /* _ACX_HW_H */
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/acx_struct.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/acx_struct.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,2114 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** Forward declarations of types
|
|
+*/
|
|
+typedef struct tx tx_t;
|
|
+typedef struct acx_device acx_device_t;
|
|
+typedef struct client client_t;
|
|
+typedef struct rxdesc rxdesc_t;
|
|
+typedef struct txdesc txdesc_t;
|
|
+typedef struct rxhostdesc rxhostdesc_t;
|
|
+typedef struct txhostdesc txhostdesc_t;
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Debug / log functionality
|
|
+*/
|
|
+enum {
|
|
+ L_LOCK = (ACX_DEBUG>1)*0x0001, /* locking debug log */
|
|
+ L_INIT = (ACX_DEBUG>0)*0x0002, /* special card initialization logging */
|
|
+ L_IRQ = (ACX_DEBUG>0)*0x0004, /* interrupt stuff */
|
|
+ L_ASSOC = (ACX_DEBUG>0)*0x0008, /* assocation (network join) and station log */
|
|
+ L_FUNC = (ACX_DEBUG>1)*0x0020, /* logging of function enter / leave */
|
|
+ L_XFER = (ACX_DEBUG>1)*0x0080, /* logging of transfers and mgmt */
|
|
+ L_DATA = (ACX_DEBUG>1)*0x0100, /* logging of transfer data */
|
|
+ L_DEBUG = (ACX_DEBUG>1)*0x0200, /* log of debug info */
|
|
+ L_IOCTL = (ACX_DEBUG>0)*0x0400, /* log ioctl calls */
|
|
+ L_CTL = (ACX_DEBUG>1)*0x0800, /* log of low-level ctl commands */
|
|
+ L_BUFR = (ACX_DEBUG>1)*0x1000, /* debug rx buffer mgmt (ring buffer etc.) */
|
|
+ L_XFER_BEACON = (ACX_DEBUG>1)*0x2000, /* also log beacon packets */
|
|
+ L_BUFT = (ACX_DEBUG>1)*0x4000, /* debug tx buffer mgmt (ring buffer etc.) */
|
|
+ L_USBRXTX = (ACX_DEBUG>0)*0x8000, /* debug USB rx/tx operations */
|
|
+ L_BUF = L_BUFR + L_BUFT,
|
|
+ L_ANY = 0xffff
|
|
+};
|
|
+
|
|
+#if ACX_DEBUG
|
|
+extern unsigned int acx_debug;
|
|
+#else
|
|
+enum { acx_debug = 0 };
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Random helpers
|
|
+*/
|
|
+#define ACX_PACKED __attribute__ ((packed))
|
|
+
|
|
+#define VEC_SIZE(a) (sizeof(a)/sizeof(a[0]))
|
|
+
|
|
+/* Use worker_queues for 2.5/2.6 kernels and queue tasks for 2.4 kernels
|
|
+ (used for the 'bottom half' of the interrupt routine) */
|
|
+
|
|
+#include <linux/workqueue.h>
|
|
+#define USE_WORKER_TASKS
|
|
+#define WORK_STRUCT struct work_struct
|
|
+#define SCHEDULE_WORK schedule_work
|
|
+#define FLUSH_SCHEDULED_WORK flush_scheduled_work
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Constants
|
|
+*/
|
|
+#define OK 0
|
|
+#define NOT_OK 1
|
|
+
|
|
+/* The supported chip models */
|
|
+#define CHIPTYPE_ACX100 1
|
|
+#define CHIPTYPE_ACX111 2
|
|
+
|
|
+#define IS_ACX100(adev) ((adev)->chip_type == CHIPTYPE_ACX100)
|
|
+#define IS_ACX111(adev) ((adev)->chip_type == CHIPTYPE_ACX111)
|
|
+
|
|
+/* Supported interfaces */
|
|
+#define DEVTYPE_PCI 0
|
|
+#define DEVTYPE_USB 1
|
|
+#define DEVTYPE_MEM 2
|
|
+
|
|
+#if !defined(CONFIG_ACX_PCI) && !defined(CONFIG_ACX_USB) && !defined(CONFIG_ACX_MEM) && !defined(CONFIG_ACX_CS)
|
|
+#error Driver must include PCI, USB, PCMCIA or memory mapped interface support. You selected none of them.
|
|
+#endif
|
|
+
|
|
+#if defined(CONFIG_ACX_PCI)
|
|
+ #if !defined(CONFIG_ACX_USB)
|
|
+ #define IS_PCI(adev) 1
|
|
+ #else
|
|
+ #define IS_PCI(adev) ((adev)->dev_type == DEVTYPE_PCI)
|
|
+ #endif
|
|
+#else
|
|
+ #define IS_PCI(adev) 0
|
|
+#endif
|
|
+
|
|
+#if defined(CONFIG_ACX_USB)
|
|
+ #if !defined(CONFIG_ACX_PCI)
|
|
+ #define IS_USB(adev) 1
|
|
+ #else
|
|
+ #define IS_USB(adev) ((adev)->dev_type == DEVTYPE_USB)
|
|
+ #endif
|
|
+#else
|
|
+ #define IS_USB(adev) 0
|
|
+#endif
|
|
+
|
|
+#if defined(CONFIG_ACX_MEM) || defined(CONFIG_ACX_CS)
|
|
+ #define IS_MEM(adev) 1
|
|
+#else
|
|
+ #define IS_MEM(adev) 0
|
|
+#endif
|
|
+
|
|
+/* Driver defaults */
|
|
+#define DEFAULT_DTIM_INTERVAL 10
|
|
+/* used to be 2048, but FreeBSD driver changed it to 4096 to work properly
|
|
+** in noisy wlans */
|
|
+#define DEFAULT_MSDU_LIFETIME 4096
|
|
+#define DEFAULT_RTS_THRESHOLD 2312 /* max. size: disable RTS mechanism */
|
|
+#define DEFAULT_BEACON_INTERVAL 100
|
|
+
|
|
+#define ACX100_BAP_DATALEN_MAX 4096
|
|
+#define ACX100_RID_GUESSING_MAXLEN 2048 /* I'm not really sure */
|
|
+#define ACX100_RIDDATA_MAXLEN ACX100_RID_GUESSING_MAXLEN
|
|
+
|
|
+/* Support Constants */
|
|
+/* Radio type names, found in Win98 driver's TIACXLN.INF */
|
|
+#define RADIO_MAXIM_0D 0x0d
|
|
+#define RADIO_RFMD_11 0x11
|
|
+#define RADIO_RALINK_15 0x15
|
|
+/* used in ACX111 cards (WG311v2, WL-121, ...): */
|
|
+#define RADIO_RADIA_16 0x16
|
|
+/* most likely *sometimes* used in ACX111 cards: */
|
|
+#define RADIO_UNKNOWN_17 0x17
|
|
+/* FwRad19.bin was found in a Safecom driver; must be an ACX111 radio: */
|
|
+#define RADIO_UNKNOWN_19 0x19
|
|
+#define RADIO_UNKNOWN_1B 0x1b /* radio in SafeCom SWLUT-54125 USB adapter; entirely unknown!! */
|
|
+
|
|
+/* Controller Commands */
|
|
+/* can be found in table cmdTable in firmware "Rev. 1.5.0" (FW150) */
|
|
+#define ACX1xx_CMD_RESET 0x00
|
|
+#define ACX1xx_CMD_INTERROGATE 0x01
|
|
+#define ACX1xx_CMD_CONFIGURE 0x02
|
|
+#define ACX1xx_CMD_ENABLE_RX 0x03
|
|
+#define ACX1xx_CMD_ENABLE_TX 0x04
|
|
+#define ACX1xx_CMD_DISABLE_RX 0x05
|
|
+#define ACX1xx_CMD_DISABLE_TX 0x06
|
|
+#define ACX1xx_CMD_FLUSH_QUEUE 0x07
|
|
+#define ACX1xx_CMD_SCAN 0x08
|
|
+#define ACX1xx_CMD_STOP_SCAN 0x09
|
|
+#define ACX1xx_CMD_CONFIG_TIM 0x0a
|
|
+#define ACX1xx_CMD_JOIN 0x0b
|
|
+#define ACX1xx_CMD_WEP_MGMT 0x0c
|
|
+#ifdef OLD_FIRMWARE_VERSIONS
|
|
+#define ACX100_CMD_HALT 0x0e /* mapped to unknownCMD in FW150 */
|
|
+#else
|
|
+#define ACX1xx_CMD_MEM_READ 0x0d
|
|
+#define ACX1xx_CMD_MEM_WRITE 0x0e
|
|
+#endif
|
|
+#define ACX1xx_CMD_SLEEP 0x0f
|
|
+#define ACX1xx_CMD_WAKE 0x10
|
|
+#define ACX1xx_CMD_UNKNOWN_11 0x11 /* mapped to unknownCMD in FW150 */
|
|
+#define ACX100_CMD_INIT_MEMORY 0x12
|
|
+#define ACX1FF_CMD_DISABLE_RADIO 0x12 /* new firmware? TNETW1450? */
|
|
+#define ACX1xx_CMD_CONFIG_BEACON 0x13
|
|
+#define ACX1xx_CMD_CONFIG_PROBE_RESPONSE 0x14
|
|
+#define ACX1xx_CMD_CONFIG_NULL_DATA 0x15
|
|
+#define ACX1xx_CMD_CONFIG_PROBE_REQUEST 0x16
|
|
+#define ACX1xx_CMD_FCC_TEST 0x17
|
|
+#define ACX1xx_CMD_RADIOINIT 0x18
|
|
+#define ACX111_CMD_RADIOCALIB 0x19
|
|
+#define ACX1FF_CMD_NOISE_HISTOGRAM 0x1c /* new firmware? TNETW1450? */
|
|
+#define ACX1FF_CMD_RX_RESET 0x1d /* new firmware? TNETW1450? */
|
|
+#define ACX1FF_CMD_LNA_CONTROL 0x20 /* new firmware? TNETW1450? */
|
|
+#define ACX1FF_CMD_CONTROL_DBG_TRACE 0x21 /* new firmware? TNETW1450? */
|
|
+
|
|
+/* 'After Interrupt' Commands */
|
|
+#define ACX_AFTER_IRQ_CMD_STOP_SCAN 0x01
|
|
+#define ACX_AFTER_IRQ_CMD_ASSOCIATE 0x02
|
|
+#define ACX_AFTER_IRQ_CMD_RADIO_RECALIB 0x04
|
|
+#define ACX_AFTER_IRQ_UPDATE_CARD_CFG 0x08
|
|
+#define ACX_AFTER_IRQ_TX_CLEANUP 0x10
|
|
+#define ACX_AFTER_IRQ_COMPLETE_SCAN 0x20
|
|
+#define ACX_AFTER_IRQ_RESTART_SCAN 0x40
|
|
+
|
|
+/***********************************************************************
|
|
+** Tx/Rx buffer sizes and watermarks
|
|
+**
|
|
+** This will alloc and use DMAable buffers of
|
|
+** WLAN_A4FR_MAXLEN_WEP_FCS * (RX_CNT + TX_CNT) bytes
|
|
+** RX/TX_CNT=32 -> ~150k DMA buffers
|
|
+** RX/TX_CNT=16 -> ~75k DMA buffers
|
|
+**
|
|
+** 2005-10-10: reduced memory usage by lowering both to 16
|
|
+*/
|
|
+#define RX_CNT 16
|
|
+#define TX_CNT 16
|
|
+
|
|
+/* we clean up txdescs when we have N free txdesc: */
|
|
+#define TX_CLEAN_BACKLOG (TX_CNT/4)
|
|
+#define TX_START_CLEAN (TX_CNT - TX_CLEAN_BACKLOG)
|
|
+#define TX_EMERG_CLEAN 2
|
|
+/* we stop queue if we have < N free txbufs: */
|
|
+#define TX_STOP_QUEUE 3
|
|
+/* we start queue if we have >= N free txbufs: */
|
|
+#define TX_START_QUEUE 5
|
|
+
|
|
+/***********************************************************************
|
|
+** Interrogate/Configure cmd constants
|
|
+**
|
|
+** NB: length includes JUST the data part of the IE
|
|
+** (does not include size of the (type,len) pair)
|
|
+**
|
|
+** TODO: seems that acx100, acx100usb, acx111 have some differences,
|
|
+** fix code with regard to this!
|
|
+*/
|
|
+
|
|
+#define DEF_IE(name, val, len) enum { ACX##name=val, ACX##name##_LEN=len }
|
|
+
|
|
+/* Information Elements: Network Parameters, Static Configuration Entities */
|
|
+/* these are handled by real_cfgtable in firmware "Rev 1.5.0" (FW150) */
|
|
+DEF_IE(1xx_IE_UNKNOWN_00 ,0x0000, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(100_IE_ACX_TIMER ,0x0001, 0x10);
|
|
+DEF_IE(1xx_IE_POWER_MGMT ,0x0002, 0x06); /* TNETW1450: length 0x18!! */
|
|
+DEF_IE(1xx_IE_QUEUE_CONFIG ,0x0003, 0x1c);
|
|
+DEF_IE(100_IE_BLOCK_SIZE ,0x0004, 0x02);
|
|
+DEF_IE(1FF_IE_SLOT_TIME ,0x0004, 0x08); /* later firmware versions only? */
|
|
+DEF_IE(1xx_IE_MEMORY_CONFIG_OPTIONS ,0x0005, 0x14);
|
|
+DEF_IE(1FF_IE_QUEUE_HEAD ,0x0005, 0x14 /* FIXME: length? */);
|
|
+DEF_IE(1xx_IE_RATE_FALLBACK ,0x0006, 0x01); /* TNETW1450: length 2 */
|
|
+DEF_IE(100_IE_WEP_OPTIONS ,0x0007, 0x03);
|
|
+DEF_IE(111_IE_RADIO_BAND ,0x0007, -1);
|
|
+DEF_IE(1FF_IE_TIMING_CFG ,0x0007, -1); /* later firmware versions; TNETW1450 only? */
|
|
+DEF_IE(100_IE_SSID ,0x0008, 0x20); /* huh? */
|
|
+DEF_IE(1xx_IE_MEMORY_MAP ,0x0008, 0x28); /* huh? TNETW1450 has length 0x40!! */
|
|
+DEF_IE(1xx_IE_SCAN_STATUS ,0x0009, 0x04); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(1xx_IE_ASSOC_ID ,0x000a, 0x02);
|
|
+DEF_IE(1xx_IE_UNKNOWN_0B ,0x000b, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(1FF_IE_TX_POWER_LEVEL_TABLE ,0x000b, 0x18); /* later firmware versions; TNETW1450 only? */
|
|
+DEF_IE(100_IE_UNKNOWN_0C ,0x000c, -1); /* very small implementation in FW150! */
|
|
+/* ACX100 has an equivalent struct in the cmd mailbox directly after reset.
|
|
+ * 0x14c seems extremely large, will trash stack on failure (memset!)
|
|
+ * in case of small input struct --> OOPS! */
|
|
+DEF_IE(111_IE_CONFIG_OPTIONS ,0x000c, 0x14c);
|
|
+DEF_IE(1xx_IE_FWREV ,0x000d, 0x18);
|
|
+DEF_IE(1xx_IE_FCS_ERROR_COUNT ,0x000e, 0x04);
|
|
+DEF_IE(1xx_IE_MEDIUM_USAGE ,0x000f, 0x08);
|
|
+DEF_IE(1xx_IE_RXCONFIG ,0x0010, 0x04);
|
|
+DEF_IE(100_IE_UNKNOWN_11 ,0x0011, -1); /* NONBINARY: large implementation in FW150! link quality readings or so? */
|
|
+DEF_IE(111_IE_QUEUE_THRESH ,0x0011, -1);
|
|
+DEF_IE(100_IE_UNKNOWN_12 ,0x0012, -1); /* NONBINARY: VERY large implementation in FW150!! */
|
|
+DEF_IE(111_IE_BSS_POWER_SAVE ,0x0012, /* -1 */ 2);
|
|
+DEF_IE(1xx_IE_FIRMWARE_STATISTICS ,0x0013, 0x9c); /* TNETW1450: length 0x134!! */
|
|
+DEF_IE(1FF_IE_RX_INTR_CONFIG ,0x0014, 0x14); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1xx_IE_FEATURE_CONFIG ,0x0015, 0x08);
|
|
+DEF_IE(111_IE_KEY_CHOOSE ,0x0016, 0x04); /* for rekeying. really len=4?? */
|
|
+DEF_IE(1FF_IE_MISC_CONFIG_TABLE ,0x0017, 0x04); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_WONE_CONFIG ,0x0018, -1); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_TID_CONFIG ,0x001a, 0x2c); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_CALIB_ASSESSMENT ,0x001e, 0x04); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_BEACON_FILTER_OPTIONS ,0x001f, 0x02); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_LOW_RSSI_THRESH_OPT ,0x0020, 0x04); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_NOISE_HISTOGRAM_RESULTS ,0x0021, 0x30); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_PACKET_DETECT_THRESH ,0x0023, 0x04); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_TX_CONFIG_OPTIONS ,0x0024, 0x04); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_CCA_THRESHOLD ,0x0025, 0x02); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_EVENT_MASK ,0x0026, 0x08); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_DTIM_PERIOD ,0x0027, 0x02); /* later firmware versions, TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_ACI_CONFIG_SET ,0x0029, 0x06); /* later firmware versions; maybe TNETW1450 only? */
|
|
+DEF_IE(1FF_IE_EEPROM_VER ,0x0030, 0x04); /* later firmware versions; maybe TNETW1450 only? */
|
|
+DEF_IE(1xx_IE_DOT11_STATION_ID ,0x1001, 0x06);
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1002 ,0x1002, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(111_IE_DOT11_FRAG_THRESH ,0x1002, -1); /* mapped to cfgInvalid in FW150; TNETW1450 has length 2!! */
|
|
+DEF_IE(100_IE_DOT11_BEACON_PERIOD ,0x1003, 0x02); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(1xx_IE_DOT11_DTIM_PERIOD ,0x1004, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(1FF_IE_DOT11_MAX_RX_LIFETIME ,0x1004, -1); /* later firmware versions; maybe TNETW1450 only? */
|
|
+DEF_IE(1xx_IE_DOT11_SHORT_RETRY_LIMIT ,0x1005, 0x01); /* TNETW1450: length 2 */
|
|
+DEF_IE(1xx_IE_DOT11_LONG_RETRY_LIMIT ,0x1006, 0x01); /* TNETW1450: length 2 */
|
|
+DEF_IE(100_IE_DOT11_WEP_DEFAULT_KEY_WRITE ,0x1007, 0x20); /* configure default keys; TNETW1450 has length 0x24!! */
|
|
+DEF_IE(1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME ,0x1008, 0x04);
|
|
+DEF_IE(1xx_IE_DOT11_GROUP_ADDR ,0x1009, -1);
|
|
+DEF_IE(1xx_IE_DOT11_CURRENT_REG_DOMAIN ,0x100a, 0x02);
|
|
+/* It's harmless to have larger struct. Use USB case always. */
|
|
+DEF_IE(1xx_IE_DOT11_CURRENT_ANTENNA ,0x100b, 0x02); /* in fact len=1 for PCI */
|
|
+DEF_IE(1xx_IE_DOT11_UNKNOWN_100C ,0x100c, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(1xx_IE_DOT11_TX_POWER_LEVEL ,0x100d, 0x01); /* TNETW1450 has length 2!! */
|
|
+DEF_IE(1xx_IE_DOT11_CURRENT_CCA_MODE ,0x100e, 0x02); /* in fact len=1 for PCI */
|
|
+/* USB doesn't return anything - len==0?! */
|
|
+DEF_IE(100_IE_DOT11_ED_THRESHOLD ,0x100f, 0x04);
|
|
+DEF_IE(1xx_IE_DOT11_WEP_DEFAULT_KEY_SET ,0x1010, 0x01); /* set default key ID; TNETW1450: length 2 */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1011 ,0x1011, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(1FF_IE_DOT11_CURR_5GHZ_REGDOM ,0x1011, -1); /* later firmware versions; maybe TNETW1450 only? */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1012 ,0x1012, -1); /* mapped to cfgInvalid in FW150 */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1013 ,0x1013, -1); /* mapped to cfgInvalid in FW150 */
|
|
+
|
|
+#if 0
|
|
+/* Experimentally obtained on acx100, fw 1.9.8.b
|
|
+** -1 means that fw returned 'invalid IE'
|
|
+** 0200 FC00 nnnn... are test read contents: u16 type, u16 len, data
|
|
+** (AA are poison bytes marking bytes not written by fw)
|
|
+**
|
|
+** Looks like acx100 fw does not update len field (thus len=256-4=FC here)
|
|
+** A number of IEs seem to trash type,len fields
|
|
+** IEs marked 'huge' return gobs of data (no poison bytes remain)
|
|
+*/
|
|
+DEF_IE(100_IE_INVAL_00, 0x0000, -1);
|
|
+DEF_IE(100_IE_INVAL_01, 0x0001, -1); /* IE_ACX_TIMER, len=16 on older fw */
|
|
+DEF_IE(100_IE_POWER_MGMT, 0x0002, 4); /* 0200FC00 00040000 AAAAAAAA */
|
|
+DEF_IE(100_IE_QUEUE_CONFIG, 0x0003, 28); /* 0300FC00 48060000 9CAD0000 0101AAAA DCB00000 E4B00000 9CAA0000 00AAAAAA */
|
|
+DEF_IE(100_IE_BLOCK_SIZE, 0x0004, 2); /* 0400FC00 0001AAAA AAAAAAAA AAAAAAAA */
|
|
+/* write only: */
|
|
+DEF_IE(100_IE_MEMORY_CONFIG_OPTIONS, 0x0005, 20);
|
|
+DEF_IE(100_IE_RATE_FALLBACK, 0x0006, 1); /* 0600FC00 00AAAAAA AAAAAAAA AAAAAAAA */
|
|
+/* write only: */
|
|
+DEF_IE(100_IE_WEP_OPTIONS, 0x0007, 3);
|
|
+DEF_IE(100_IE_MEMORY_MAP, 0x0008, 40); /* huge: 0800FC00 30000000 6CA20000 70A20000... */
|
|
+/* gives INVAL on read: */
|
|
+DEF_IE(100_IE_SCAN_STATUS, 0x0009, -1);
|
|
+DEF_IE(100_IE_ASSOC_ID, 0x000a, 2); /* huge: 0A00FC00 00000000 01040800 00000000... */
|
|
+DEF_IE(100_IE_INVAL_0B, 0x000b, -1);
|
|
+/* 'command rejected': */
|
|
+DEF_IE(100_IE_CONFIG_OPTIONS, 0x000c, -3);
|
|
+DEF_IE(100_IE_FWREV, 0x000d, 24); /* 0D00FC00 52657620 312E392E 382E6200 AAAAAAAA AAAAAAAA 05050201 AAAAAAAA */
|
|
+DEF_IE(100_IE_FCS_ERROR_COUNT, 0x000e, 4);
|
|
+DEF_IE(100_IE_MEDIUM_USAGE, 0x000f, 8); /* E41F0000 2D780300 FCC91300 AAAAAAAA */
|
|
+DEF_IE(100_IE_RXCONFIG, 0x0010, 4); /* 1000FC00 00280000 AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_QUEUE_THRESH, 0x0011, 12); /* 1100FC00 AAAAAAAA 00000000 00000000 */
|
|
+DEF_IE(100_IE_BSS_POWER_SAVE, 0x0012, 1); /* 1200FC00 00AAAAAA AAAAAAAA AAAAAAAA */
|
|
+/* read only, variable len */
|
|
+DEF_IE(100_IE_FIRMWARE_STATISTICS, 0x0013, 256); /* 0000AC00 00000000 ... */
|
|
+DEF_IE(100_IE_INT_CONFIG, 0x0014, 20); /* 00000000 00000000 00000000 00000000 5D74D105 00000000 AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_FEATURE_CONFIG, 0x0015, 8); /* 1500FC00 16000000 AAAAAAAA AAAAAAAA */
|
|
+/* returns 'invalid MAC': */
|
|
+DEF_IE(100_IE_KEY_CHOOSE, 0x0016, -4);
|
|
+DEF_IE(100_IE_INVAL_17, 0x0017, -1);
|
|
+DEF_IE(100_IE_UNKNOWN_18, 0x0018, 0); /* null len?! 1800FC00 AAAAAAAA AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_UNKNOWN_19, 0x0019, 256); /* huge: 1900FC00 9C1F00EA FEFFFFEA FEFFFFEA... */
|
|
+DEF_IE(100_IE_INVAL_1A, 0x001A, -1);
|
|
+
|
|
+DEF_IE(100_IE_DOT11_INVAL_1000, 0x1000, -1);
|
|
+DEF_IE(100_IE_DOT11_STATION_ID, 0x1001, 6); /* huge: 0110FC00 58B10E2F 03000000 00000000... */
|
|
+DEF_IE(100_IE_DOT11_INVAL_1002, 0x1002, -1);
|
|
+DEF_IE(100_IE_DOT11_INVAL_1003, 0x1003, -1);
|
|
+DEF_IE(100_IE_DOT11_INVAL_1004, 0x1004, -1);
|
|
+DEF_IE(100_IE_DOT11_SHORT_RETRY_LIMIT, 0x1005, 1);
|
|
+DEF_IE(100_IE_DOT11_LONG_RETRY_LIMIT, 0x1006, 1);
|
|
+/* write only: */
|
|
+DEF_IE(100_IE_DOT11_WEP_DEFAULT_KEY_WRITE, 0x1007, 32);
|
|
+DEF_IE(100_IE_DOT11_MAX_XMIT_MSDU_LIFETIME, 0x1008, 4); /* huge: 0810FC00 00020000 F4010000 00000000... */
|
|
+/* undoc but returns something */
|
|
+DEF_IE(100_IE_DOT11_GROUP_ADDR, 0x1009, 12); /* huge: 0910FC00 00000000 00000000 00000000... */
|
|
+DEF_IE(100_IE_DOT11_CURRENT_REG_DOMAIN, 0x100a, 1); /* 0A10FC00 30AAAAAA AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_DOT11_CURRENT_ANTENNA, 0x100b, 1); /* 0B10FC00 8FAAAAAA AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_DOT11_INVAL_100C, 0x100c, -1);
|
|
+DEF_IE(100_IE_DOT11_TX_POWER_LEVEL, 0x100d, 2); /* 00000000 0100AAAA AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_DOT11_CURRENT_CCA_MODE, 0x100e, 1); /* 0E10FC00 0DAAAAAA AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_DOT11_ED_THRESHOLD, 0x100f, 4); /* 0F10FC00 70000000 AAAAAAAA AAAAAAAA */
|
|
+/* set default key ID */
|
|
+DEF_IE(100_IE_DOT11_WEP_DEFAULT_KEY_SET, 0x1010, 1); /* 1010FC00 00AAAAAA AAAAAAAA AAAAAAAA */
|
|
+DEF_IE(100_IE_DOT11_INVAL_1011, 0x1011, -1);
|
|
+DEF_IE(100_IE_DOT11_INVAL_1012, 0x1012, -1);
|
|
+DEF_IE(100_IE_DOT11_INVAL_1013, 0x1013, -1);
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1014, 0x1014, 256); /* huge */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1015, 0x1015, 256); /* huge */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1016, 0x1016, 256); /* huge */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1017, 0x1017, 256); /* huge */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1018, 0x1018, 256); /* huge */
|
|
+DEF_IE(100_IE_DOT11_UNKNOWN_1019, 0x1019, 256); /* huge */
|
|
+#endif
|
|
+
|
|
+#if 0
|
|
+/* Experimentally obtained on PCI acx111 Xterasys XN-2522g, fw 1.2.1.34
|
|
+** -1 means that fw returned 'invalid IE'
|
|
+** 0400 0800 nnnn... are test read contents: u16 type, u16 len, data
|
|
+** (AA are poison bytes marking bytes not written by fw)
|
|
+**
|
|
+** Looks like acx111 fw reports real len!
|
|
+*/
|
|
+DEF_IE(111_IE_INVAL_00, 0x0000, -1);
|
|
+DEF_IE(111_IE_INVAL_01, 0x0001, -1);
|
|
+DEF_IE(111_IE_POWER_MGMT, 0x0002, 12);
|
|
+/* write only, variable len: 12 + rxqueue_cnt*8 + txqueue_cnt*4: */
|
|
+DEF_IE(111_IE_MEMORY_CONFIG, 0x0003, 24);
|
|
+DEF_IE(111_IE_BLOCK_SIZE, 0x0004, 8); /* 04000800 AA00AAAA AAAAAAAA */
|
|
+/* variable len: 8 + rxqueue_cnt*8 + txqueue_cnt*8: */
|
|
+DEF_IE(111_IE_QUEUE_HEAD, 0x0005, 24);
|
|
+DEF_IE(111_IE_RATE_FALLBACK, 0x0006, 1);
|
|
+/* acx100 name:WEP_OPTIONS */
|
|
+/* said to have len:1 (not true, actually returns 12 bytes): */
|
|
+DEF_IE(111_IE_RADIO_BAND, 0x0007, 12); /* 07000C00 AAAA1F00 FF03AAAA AAAAAAAA */
|
|
+DEF_IE(111_IE_MEMORY_MAP, 0x0008, 48);
|
|
+/* said to have len:4, but gives INVAL on read: */
|
|
+DEF_IE(111_IE_SCAN_STATUS, 0x0009, -1);
|
|
+DEF_IE(111_IE_ASSOC_ID, 0x000a, 2);
|
|
+/* write only, len is not known: */
|
|
+DEF_IE(111_IE_UNKNOWN_0B, 0x000b, 0);
|
|
+/* read only, variable len. I see 67 byte reads: */
|
|
+DEF_IE(111_IE_CONFIG_OPTIONS, 0x000c, 67); /* 0C004300 01160500 ... */
|
|
+DEF_IE(111_IE_FWREV, 0x000d, 24);
|
|
+DEF_IE(111_IE_FCS_ERROR_COUNT, 0x000e, 4);
|
|
+DEF_IE(111_IE_MEDIUM_USAGE, 0x000f, 8);
|
|
+DEF_IE(111_IE_RXCONFIG, 0x0010, 4);
|
|
+DEF_IE(111_IE_QUEUE_THRESH, 0x0011, 12);
|
|
+DEF_IE(111_IE_BSS_POWER_SAVE, 0x0012, 1);
|
|
+/* read only, variable len. I see 240 byte reads: */
|
|
+DEF_IE(111_IE_FIRMWARE_STATISTICS, 0x0013, 240); /* 1300F000 00000000 ... */
|
|
+/* said to have len=17. looks like fw pads it to 20: */
|
|
+DEF_IE(111_IE_INT_CONFIG, 0x0014, 20); /* 14001400 00000000 00000000 00000000 00000000 00000000 */
|
|
+DEF_IE(111_IE_FEATURE_CONFIG, 0x0015, 8);
|
|
+/* said to be name:KEY_INDICATOR, len:4, but gives INVAL on read: */
|
|
+DEF_IE(111_IE_KEY_CHOOSE, 0x0016, -1);
|
|
+/* said to have len:4, but in fact returns 8: */
|
|
+DEF_IE(111_IE_MAX_USB_XFR, 0x0017, 8); /* 17000800 00014000 00000000 */
|
|
+DEF_IE(111_IE_INVAL_18, 0x0018, -1);
|
|
+DEF_IE(111_IE_INVAL_19, 0x0019, -1);
|
|
+/* undoc but returns something: */
|
|
+/* huh, fw indicates len=20 but uses 4 more bytes in buffer??? */
|
|
+DEF_IE(111_IE_UNKNOWN_1A, 0x001A, 20); /* 1A001400 AA00AAAA 0000020F FF030000 00020000 00000007 04000000 */
|
|
+
|
|
+DEF_IE(111_IE_DOT11_INVAL_1000, 0x1000, -1);
|
|
+DEF_IE(111_IE_DOT11_STATION_ID, 0x1001, 6);
|
|
+DEF_IE(111_IE_DOT11_FRAG_THRESH, 0x1002, 2);
|
|
+/* acx100 only? gives INVAL on read: */
|
|
+DEF_IE(111_IE_DOT11_BEACON_PERIOD, 0x1003, -1);
|
|
+/* said to be MAX_RECV_MSDU_LIFETIME: */
|
|
+DEF_IE(111_IE_DOT11_DTIM_PERIOD, 0x1004, 4);
|
|
+DEF_IE(111_IE_DOT11_SHORT_RETRY_LIMIT, 0x1005, 1);
|
|
+DEF_IE(111_IE_DOT11_LONG_RETRY_LIMIT, 0x1006, 1);
|
|
+/* acx100 only? gives INVAL on read: */
|
|
+DEF_IE(111_IE_DOT11_WEP_DEFAULT_KEY_WRITE, 0x1007, -1);
|
|
+DEF_IE(111_IE_DOT11_MAX_XMIT_MSDU_LIFETIME, 0x1008, 4);
|
|
+/* undoc but returns something. maybe it's 2 multicast MACs to listen to? */
|
|
+DEF_IE(111_IE_DOT11_GROUP_ADDR, 0x1009, 12); /* 09100C00 00000000 00000000 00000000 */
|
|
+DEF_IE(111_IE_DOT11_CURRENT_REG_DOMAIN, 0x100a, 1);
|
|
+DEF_IE(111_IE_DOT11_CURRENT_ANTENNA, 0x100b, 2);
|
|
+DEF_IE(111_IE_DOT11_INVAL_100C, 0x100c, -1);
|
|
+DEF_IE(111_IE_DOT11_TX_POWER_LEVEL, 0x100d, 1);
|
|
+/* said to have len=1 but gives INVAL on read: */
|
|
+DEF_IE(111_IE_DOT11_CURRENT_CCA_MODE, 0x100e, -1);
|
|
+/* said to have len=4 but gives INVAL on read: */
|
|
+DEF_IE(111_IE_DOT11_ED_THRESHOLD, 0x100f, -1);
|
|
+/* set default key ID. write only: */
|
|
+DEF_IE(111_IE_DOT11_WEP_DEFAULT_KEY_SET, 0x1010, 1);
|
|
+/* undoc but returns something: */
|
|
+DEF_IE(111_IE_DOT11_UNKNOWN_1011, 0x1011, 1); /* 11100100 20 */
|
|
+DEF_IE(111_IE_DOT11_INVAL_1012, 0x1012, -1);
|
|
+DEF_IE(111_IE_DOT11_INVAL_1013, 0x1013, -1);
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+**Information Frames Structures
|
|
+*/
|
|
+
|
|
+/* Used in beacon frames and the like */
|
|
+#define DOT11RATEBYTE_1 (1*2)
|
|
+#define DOT11RATEBYTE_2 (2*2)
|
|
+#define DOT11RATEBYTE_5_5 (5*2+1)
|
|
+#define DOT11RATEBYTE_11 (11*2)
|
|
+#define DOT11RATEBYTE_22 (22*2)
|
|
+#define DOT11RATEBYTE_6_G (6*2)
|
|
+#define DOT11RATEBYTE_9_G (9*2)
|
|
+#define DOT11RATEBYTE_12_G (12*2)
|
|
+#define DOT11RATEBYTE_18_G (18*2)
|
|
+#define DOT11RATEBYTE_24_G (24*2)
|
|
+#define DOT11RATEBYTE_36_G (36*2)
|
|
+#define DOT11RATEBYTE_48_G (48*2)
|
|
+#define DOT11RATEBYTE_54_G (54*2)
|
|
+#define DOT11RATEBYTE_BASIC 0x80 /* flags rates included in basic rate set */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** rxbuffer_t
|
|
+**
|
|
+** This is the format of rx data returned by acx
|
|
+*/
|
|
+
|
|
+/* I've hoped it's a 802.11 PHY header, but no...
|
|
+ * so far, I've seen on acx111:
|
|
+ * 0000 3a00 0000 0000 IBSS Beacons
|
|
+ * 0000 3c00 0000 0000 ESS Beacons
|
|
+ * 0000 2700 0000 0000 Probe requests
|
|
+ * --vda
|
|
+ */
|
|
+typedef struct phy_hdr {
|
|
+ u8 unknown[4];
|
|
+ u8 acx111_unknown[4];
|
|
+} ACX_PACKED phy_hdr_t;
|
|
+
|
|
+/* seems to be a bit similar to hfa384x_rx_frame.
|
|
+ * These fields are still not quite obvious, though.
|
|
+ * Some seem to have different meanings... */
|
|
+
|
|
+#define RXBUF_HDRSIZE 12
|
|
+#define RXBUF_BYTES_RCVD(adev, rxbuf) \
|
|
+ ((le16_to_cpu((rxbuf)->mac_cnt_rcvd) & 0xfff) - (adev)->phy_header_len)
|
|
+#define RXBUF_BYTES_USED(rxbuf) \
|
|
+ ((le16_to_cpu((rxbuf)->mac_cnt_rcvd) & 0xfff) + RXBUF_HDRSIZE)
|
|
+/* USBism */
|
|
+#define RXBUF_IS_TXSTAT(rxbuf) (le16_to_cpu((rxbuf)->mac_cnt_rcvd) & 0x8000)
|
|
+/*
|
|
+mac_cnt_rcvd:
|
|
+ 12 bits: length of frame from control field to first byte of FCS
|
|
+ 3 bits: reserved
|
|
+ 1 bit: 1 = it's a tx status info, not a rx packet (USB only)
|
|
+
|
|
+mac_cnt_mblks:
|
|
+ 6 bits: number of memory block used to store frame in adapter memory
|
|
+ 1 bit: Traffic Indicator bit in TIM of received Beacon was set
|
|
+
|
|
+mac_status: 1 byte (bitmap):
|
|
+ 7 Matching BSSID
|
|
+ 6 Matching SSID
|
|
+ 5 BDCST Address 1 field is a broadcast
|
|
+ 4 VBM received beacon frame has more than one set bit (?!)
|
|
+ 3 TIM Set bit representing this station is set in TIM of received beacon
|
|
+ 2 GROUP Address 1 is a multicast
|
|
+ 1 ADDR1 Address 1 matches our MAC
|
|
+ 0 FCSGD FSC is good
|
|
+
|
|
+phy_stat_baseband: 1 byte (bitmap):
|
|
+ 7 Preamble frame had a long preamble
|
|
+ 6 PLCP Error CRC16 error in PLCP header
|
|
+ 5 Unsup_Mod unsupported modulation
|
|
+ 4 Selected Antenna antenna 1 was used to receive this frame
|
|
+ 3 PBCC/CCK frame used: 1=PBCC, 0=CCK modulation
|
|
+ 2 OFDM frame used OFDM modulation
|
|
+ 1 TI Protection protection frame was detected
|
|
+ 0 Reserved
|
|
+
|
|
+phy_plcp_signal: 1 byte:
|
|
+ Receive PLCP Signal field from the Baseband Processor
|
|
+
|
|
+phy_level: 1 byte:
|
|
+ receive AGC gain level (can be used to measure receive signal strength)
|
|
+
|
|
+phy_snr: 1 byte:
|
|
+ estimated noise power of equalized receive signal
|
|
+ at input of FEC decoder (can be used to measure receive signal quality)
|
|
+
|
|
+time: 4 bytes:
|
|
+ timestamp sampled from either the Access Manager TSF counter
|
|
+ or free-running microsecond counter when the MAC receives
|
|
+ first byte of PLCP header.
|
|
+*/
|
|
+
|
|
+typedef struct rxbuffer {
|
|
+ u16 mac_cnt_rcvd; /* only 12 bits are len! (0xfff) */
|
|
+ u8 mac_cnt_mblks;
|
|
+ u8 mac_status;
|
|
+ u8 phy_stat_baseband; /* bit 0x80: used LNA (Low-Noise Amplifier) */
|
|
+ u8 phy_plcp_signal;
|
|
+ u8 phy_level; /* PHY stat */
|
|
+ u8 phy_snr; /* PHY stat */
|
|
+ u32 time; /* timestamp upon MAC rcv first byte */
|
|
+/* 4-byte (acx100) or 8-byte (acx111) phy header will be here
|
|
+** if RX_CFG1_INCLUDE_PHY_HDR is in effect:
|
|
+** phy_hdr_t phy */
|
|
+ wlan_hdr_a3_t hdr_a3;
|
|
+ /* maximally sized data part of wlan packet */
|
|
+ u8 data_a3[WLAN_A4FR_MAXLEN_WEP_FCS - WLAN_HDR_A3_LEN];
|
|
+ /* can add hdr/data_a4 if needed */
|
|
+} ACX_PACKED rxbuffer_t;
|
|
+
|
|
+
|
|
+/*--- Firmware statistics ----------------------------------------------------*/
|
|
+
|
|
+/* define a random 100 bytes more to catch firmware versions which
|
|
+ * provide a bigger struct */
|
|
+#define FW_STATS_FUTURE_EXTENSION 100
|
|
+
|
|
+typedef struct fw_stats_tx {
|
|
+ u32 tx_desc_of;
|
|
+} ACX_PACKED fw_stats_tx_t;
|
|
+
|
|
+typedef struct fw_stats_rx {
|
|
+ u32 rx_oom;
|
|
+ u32 rx_hdr_of;
|
|
+ u32 rx_hw_stuck; /* old: u32 rx_hdr_use_next */
|
|
+ u32 rx_dropped_frame;
|
|
+ u32 rx_frame_ptr_err;
|
|
+ u32 rx_xfr_hint_trig;
|
|
+ u32 rx_aci_events; /* later versions only */
|
|
+ u32 rx_aci_resets; /* later versions only */
|
|
+} ACX_PACKED fw_stats_rx_t;
|
|
+
|
|
+typedef struct fw_stats_dma {
|
|
+ u32 rx_dma_req;
|
|
+ u32 rx_dma_err;
|
|
+ u32 tx_dma_req;
|
|
+ u32 tx_dma_err;
|
|
+} ACX_PACKED fw_stats_dma_t;
|
|
+
|
|
+typedef struct fw_stats_irq {
|
|
+ u32 cmd_cplt;
|
|
+ u32 fiq;
|
|
+ u32 rx_hdrs;
|
|
+ u32 rx_cmplt;
|
|
+ u32 rx_mem_of;
|
|
+ u32 rx_rdys;
|
|
+ u32 irqs;
|
|
+ u32 tx_procs;
|
|
+ u32 decrypt_done;
|
|
+ u32 dma_0_done;
|
|
+ u32 dma_1_done;
|
|
+ u32 tx_exch_complet;
|
|
+ u32 commands;
|
|
+ u32 rx_procs;
|
|
+ u32 hw_pm_mode_changes;
|
|
+ u32 host_acks;
|
|
+ u32 pci_pm;
|
|
+ u32 acm_wakeups;
|
|
+} ACX_PACKED fw_stats_irq_t;
|
|
+
|
|
+typedef struct fw_stats_wep {
|
|
+ u32 wep_key_count;
|
|
+ u32 wep_default_key_count;
|
|
+ u32 dot11_def_key_mib;
|
|
+ u32 wep_key_not_found;
|
|
+ u32 wep_decrypt_fail;
|
|
+ u32 wep_pkt_decrypt;
|
|
+ u32 wep_decrypt_irqs;
|
|
+} ACX_PACKED fw_stats_wep_t;
|
|
+
|
|
+typedef struct fw_stats_pwr {
|
|
+ u32 tx_start_ctr;
|
|
+ u32 no_ps_tx_too_short;
|
|
+ u32 rx_start_ctr;
|
|
+ u32 no_ps_rx_too_short;
|
|
+ u32 lppd_started;
|
|
+ u32 no_lppd_too_noisy;
|
|
+ u32 no_lppd_too_short;
|
|
+ u32 no_lppd_matching_frame;
|
|
+} ACX_PACKED fw_stats_pwr_t;
|
|
+
|
|
+typedef struct fw_stats_mic {
|
|
+ u32 mic_rx_pkts;
|
|
+ u32 mic_calc_fail;
|
|
+} ACX_PACKED fw_stats_mic_t;
|
|
+
|
|
+typedef struct fw_stats_aes {
|
|
+ u32 aes_enc_fail;
|
|
+ u32 aes_dec_fail;
|
|
+ u32 aes_enc_pkts;
|
|
+ u32 aes_dec_pkts;
|
|
+ u32 aes_enc_irq;
|
|
+ u32 aes_dec_irq;
|
|
+} ACX_PACKED fw_stats_aes_t;
|
|
+
|
|
+typedef struct fw_stats_event {
|
|
+ u32 heartbeat;
|
|
+ u32 calibration;
|
|
+ u32 rx_mismatch;
|
|
+ u32 rx_mem_empty;
|
|
+ u32 rx_pool;
|
|
+ u32 oom_late;
|
|
+ u32 phy_tx_err;
|
|
+ u32 tx_stuck;
|
|
+} ACX_PACKED fw_stats_event_t;
|
|
+
|
|
+/* mainly for size calculation only */
|
|
+typedef struct fw_stats {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ fw_stats_tx_t tx;
|
|
+ fw_stats_rx_t rx;
|
|
+ fw_stats_dma_t dma;
|
|
+ fw_stats_irq_t irq;
|
|
+ fw_stats_wep_t wep;
|
|
+ fw_stats_pwr_t pwr;
|
|
+ fw_stats_mic_t mic;
|
|
+ fw_stats_aes_t aes;
|
|
+ fw_stats_event_t evt;
|
|
+ u8 _padding[FW_STATS_FUTURE_EXTENSION];
|
|
+} fw_stats_t;
|
|
+
|
|
+/* Firmware version struct */
|
|
+
|
|
+typedef struct fw_ver {
|
|
+ u16 cmd;
|
|
+ u16 size;
|
|
+ char fw_id[20];
|
|
+ u32 hw_id;
|
|
+} ACX_PACKED fw_ver_t;
|
|
+
|
|
+#define FW_ID_SIZE 20
|
|
+
|
|
+typedef struct shared_queueindicator {
|
|
+ u32 indicator;
|
|
+ u16 host_lock;
|
|
+ u16 fw_lock;
|
|
+} ACX_PACKED queueindicator_t;
|
|
+
|
|
+/*--- WEP stuff --------------------------------------------------------------*/
|
|
+#define DOT11_MAX_DEFAULT_WEP_KEYS 4
|
|
+
|
|
+/* non-firmware struct, no packing necessary */
|
|
+typedef struct wep_key {
|
|
+ size_t size; /* most often used member first */
|
|
+ u8 index;
|
|
+ u8 key[29];
|
|
+ u16 strange_filler;
|
|
+} wep_key_t; /* size = 264 bytes (33*8) */
|
|
+/* FIXME: We don't have size 264! Or is there 2 bytes beyond the key
|
|
+ * (strange_filler)? */
|
|
+
|
|
+/* non-firmware struct, no packing necessary */
|
|
+typedef struct key_struct {
|
|
+ u8 addr[ETH_ALEN]; /* 0x00 */
|
|
+ u16 filler1; /* 0x06 */
|
|
+ u32 filler2; /* 0x08 */
|
|
+ u32 index; /* 0x0c */
|
|
+ u16 len; /* 0x10 */
|
|
+ u8 key[29]; /* 0x12; is this long enough??? */
|
|
+} key_struct_t; /* size = 276. FIXME: where is the remaining space?? */
|
|
+
|
|
+
|
|
+/*--- Client (peer) info -----------------------------------------------------*/
|
|
+/* adev->sta_list[] is used for:
|
|
+** accumulating and processing of scan results
|
|
+** keeping client info in AP mode
|
|
+** keeping AP info in STA mode (AP is the only one 'client')
|
|
+** keeping peer info in ad-hoc mode
|
|
+** non-firmware struct --> no packing necessary */
|
|
+enum {
|
|
+ CLIENT_EMPTY_SLOT_0 = 0,
|
|
+ CLIENT_EXIST_1 = 1,
|
|
+ CLIENT_AUTHENTICATED_2 = 2,
|
|
+ CLIENT_ASSOCIATED_3 = 3,
|
|
+ CLIENT_JOIN_CANDIDATE = 4
|
|
+};
|
|
+struct client {
|
|
+ /* most frequent access first */
|
|
+ u8 used; /* misnamed, more like 'status' */
|
|
+ struct client* next;
|
|
+ unsigned long mtime; /* last time we heard it, in jiffies */
|
|
+ size_t essid_len; /* length of ESSID (without '\0') */
|
|
+ u32 sir; /* Standard IR */
|
|
+ u32 snr; /* Signal to Noise Ratio */
|
|
+ u16 aid; /* association ID */
|
|
+ u16 seq; /* from client's auth req */
|
|
+ u16 auth_alg; /* from client's auth req */
|
|
+ u16 cap_info; /* from client's assoc req */
|
|
+ u16 rate_cap; /* what client supports (all rates) */
|
|
+ u16 rate_bas; /* what client supports (basic rates) */
|
|
+ u16 rate_cfg; /* what is allowed (by iwconfig etc) */
|
|
+ u16 rate_cur; /* currently used rate mask */
|
|
+ u8 rate_100; /* currently used rate byte (acx100 only) */
|
|
+ u8 address[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN]; /* ad-hoc hosts can have bssid != mac */
|
|
+ u8 channel;
|
|
+ u8 auth_step;
|
|
+ u8 ignore_count;
|
|
+ u8 fallback_count;
|
|
+ u8 stepup_count;
|
|
+ char essid[IW_ESSID_MAX_SIZE + 1]; /* ESSID and trailing '\0' */
|
|
+/* FIXME: this one is too damn big */
|
|
+ char challenge_text[WLAN_CHALLENGE_LEN];
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Hardware structures
|
|
+*/
|
|
+
|
|
+/* An opaque typesafe helper type
|
|
+ *
|
|
+ * Some hardware fields are actually pointers,
|
|
+ * but they have to remain u32, since using ptr instead
|
|
+ * (8 bytes on 64bit systems!) would disrupt the fixed descriptor
|
|
+ * format the acx firmware expects in the non-user area.
|
|
+ * Since we cannot cram an 8 byte ptr into 4 bytes, we need to
|
|
+ * enforce that pointed to data remains in low memory
|
|
+ * (address value needs to fit in 4 bytes) on 64bit systems.
|
|
+ *
|
|
+ * This is easy to get wrong, thus we are using a small struct
|
|
+ * and special macros to access it. Macros will check for
|
|
+ * attempts to overflow an acx_ptr with value > 0xffffffff.
|
|
+ *
|
|
+ * Attempts to use acx_ptr without macros result in compile-time errors */
|
|
+
|
|
+typedef struct {
|
|
+ u32 v;
|
|
+} ACX_PACKED acx_ptr;
|
|
+
|
|
+#if ACX_DEBUG
|
|
+#define CHECK32(n) BUG_ON(sizeof(n)>4 && (long)(n)>0xffffff00)
|
|
+#else
|
|
+#define CHECK32(n) ((void)0)
|
|
+#endif
|
|
+
|
|
+/* acx_ptr <-> integer conversion */
|
|
+#define cpu2acx(n) ({ CHECK32(n); ((acx_ptr){ .v = cpu_to_le32(n) }); })
|
|
+#define acx2cpu(a) (le32_to_cpu(a.v))
|
|
+
|
|
+/* acx_ptr <-> pointer conversion */
|
|
+#define ptr2acx(p) ({ CHECK32(p); ((acx_ptr){ .v = cpu_to_le32((u32)(long)(p)) }); })
|
|
+#define acx2ptr(a) ((void*)le32_to_cpu(a.v))
|
|
+
|
|
+/* Values for rate field (acx100 only) */
|
|
+#define RATE100_1 10
|
|
+#define RATE100_2 20
|
|
+#define RATE100_5 55
|
|
+#define RATE100_11 110
|
|
+#define RATE100_22 220
|
|
+/* This bit denotes use of PBCC:
|
|
+** (PBCC encoding is usable with 11 and 22 Mbps speeds only) */
|
|
+#define RATE100_PBCC511 0x80
|
|
+
|
|
+/* Bit values for rate111 field */
|
|
+#define RATE111_1 0x0001 /* DBPSK */
|
|
+#define RATE111_2 0x0002 /* DQPSK */
|
|
+#define RATE111_5 0x0004 /* CCK or PBCC */
|
|
+#define RATE111_6 0x0008 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_9 0x0010 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_11 0x0020 /* CCK or PBCC */
|
|
+#define RATE111_12 0x0040 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_18 0x0080 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_22 0x0100 /* PBCC */
|
|
+#define RATE111_24 0x0200 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_36 0x0400 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_48 0x0800 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_54 0x1000 /* CCK-OFDM or OFDM */
|
|
+#define RATE111_RESERVED 0x2000
|
|
+#define RATE111_PBCC511 0x4000 /* PBCC mod at 5.5 or 11Mbit (else CCK) */
|
|
+#define RATE111_SHORTPRE 0x8000 /* short preamble */
|
|
+/* Special 'try everything' value */
|
|
+#define RATE111_ALL 0x1fff
|
|
+/* These bits denote acx100 compatible settings */
|
|
+#define RATE111_ACX100_COMPAT 0x0127
|
|
+/* These bits denote 802.11b compatible settings */
|
|
+#define RATE111_80211B_COMPAT 0x0027
|
|
+
|
|
+/* Descriptor Ctl field bits
|
|
+ * init value is 0x8e, "idle" value is 0x82 (in idle tx descs)
|
|
+ */
|
|
+#define DESC_CTL_SHORT_PREAMBLE 0x01 /* preamble type: 0 = long; 1 = short */
|
|
+#define DESC_CTL_FIRSTFRAG 0x02 /* this is the 1st frag of the frame */
|
|
+#define DESC_CTL_AUTODMA 0x04
|
|
+#define DESC_CTL_RECLAIM 0x08 /* ready to reuse */
|
|
+#define DESC_CTL_HOSTDONE 0x20 /* host has finished processing */
|
|
+#define DESC_CTL_ACXDONE 0x40 /* acx has finished processing */
|
|
+/* host owns the desc [has to be released last, AFTER modifying all other desc fields!] */
|
|
+#define DESC_CTL_HOSTOWN 0x80
|
|
+#define DESC_CTL_ACXDONE_HOSTOWN (DESC_CTL_ACXDONE | DESC_CTL_HOSTOWN)
|
|
+
|
|
+/* Descriptor Status field
|
|
+ */
|
|
+#define DESC_STATUS_FULL (1 << 31)
|
|
+
|
|
+/* NB: some bits may be interesting for Monitor mode tx (aka Raw tx): */
|
|
+#define DESC_CTL2_SEQ 0x01 /* don't increase sequence field */
|
|
+#define DESC_CTL2_FCS 0x02 /* don't add the FCS */
|
|
+#define DESC_CTL2_MORE_FRAG 0x04
|
|
+#define DESC_CTL2_RETRY 0x08 /* don't increase retry field */
|
|
+#define DESC_CTL2_POWER 0x10 /* don't increase power mgmt. field */
|
|
+#define DESC_CTL2_RTS 0x20 /* do RTS/CTS magic before sending */
|
|
+#define DESC_CTL2_WEP 0x40 /* encrypt this frame */
|
|
+#define DESC_CTL2_DUR 0x80 /* don't increase duration field */
|
|
+
|
|
+/***********************************************************************
|
|
+** PCI structures
|
|
+*/
|
|
+/* IRQ Constants
|
|
+** (outside of "#ifdef PCI" because USB (mis)uses HOST_INT_SCAN_COMPLETE) */
|
|
+#define HOST_INT_RX_DATA 0x0001
|
|
+#define HOST_INT_TX_COMPLETE 0x0002
|
|
+#define HOST_INT_TX_XFER 0x0004
|
|
+#define HOST_INT_RX_COMPLETE 0x0008
|
|
+#define HOST_INT_DTIM 0x0010
|
|
+#define HOST_INT_BEACON 0x0020
|
|
+#define HOST_INT_TIMER 0x0040
|
|
+#define HOST_INT_KEY_NOT_FOUND 0x0080
|
|
+#define HOST_INT_IV_ICV_FAILURE 0x0100
|
|
+#define HOST_INT_CMD_COMPLETE 0x0200
|
|
+#define HOST_INT_INFO 0x0400
|
|
+#define HOST_INT_OVERFLOW 0x0800
|
|
+#define HOST_INT_PROCESS_ERROR 0x1000
|
|
+#define HOST_INT_SCAN_COMPLETE 0x2000
|
|
+#define HOST_INT_FCS_THRESHOLD 0x4000
|
|
+#define HOST_INT_UNKNOWN 0x8000
|
|
+
|
|
+/* Outside of "#ifdef PCI" because USB needs to know sizeof()
|
|
+** of txdesc and rxdesc: */
|
|
+struct txdesc {
|
|
+ acx_ptr pNextDesc; /* pointer to next txdesc */
|
|
+ acx_ptr HostMemPtr; /* 0x04 */
|
|
+ acx_ptr AcxMemPtr; /* 0x08 */
|
|
+ u32 tx_time; /* 0x0c */
|
|
+ u16 total_length; /* 0x10 */
|
|
+ u16 Reserved; /* 0x12 */
|
|
+
|
|
+/* The following 16 bytes do not change when acx100 owns the descriptor */
|
|
+/* BUG: fw clears last byte of this area which is supposedly reserved
|
|
+** for driver use. amd64 blew up. We dare not use it now */
|
|
+ u32 dummy[4];
|
|
+
|
|
+ u8 Ctl_8; /* 0x24, 8bit value */
|
|
+ u8 Ctl2_8; /* 0x25, 8bit value */
|
|
+ u8 error; /* 0x26 */
|
|
+ u8 ack_failures; /* 0x27 */
|
|
+
|
|
+ union {
|
|
+ /*
|
|
+ * Packing doesn't work correctly on ARM unless unions are on
|
|
+ * 4 byte boundaries.
|
|
+ */
|
|
+ struct {
|
|
+ u8 rts_failures; /* 0x28 */
|
|
+ u8 rts_ok; /* 0x29 */
|
|
+ u16 d1;
|
|
+ } ACX_PACKED rts;
|
|
+ struct {
|
|
+ u16 d1;
|
|
+ u8 rate; /* 0x2a */
|
|
+ u8 queue_ctrl; /* 0x2b */
|
|
+ } ACX_PACKED r1;
|
|
+ struct {
|
|
+ u16 d1;
|
|
+ u16 rate111; /* 0x2a */
|
|
+ } ACX_PACKED r2;
|
|
+ } ACX_PACKED u;
|
|
+ u32 queue_info; /* 0x2c (acx100, reserved on acx111) */
|
|
+} ACX_PACKED; /* size : 48 = 0x30 */
|
|
+/* NB: acx111 txdesc structure is 4 byte larger */
|
|
+/* All these 4 extra bytes are reserved. tx alloc code takes them into account */
|
|
+
|
|
+struct rxdesc {
|
|
+ acx_ptr pNextDesc; /* 0x00 */
|
|
+ acx_ptr HostMemPtr; /* 0x04 */
|
|
+ acx_ptr ACXMemPtr; /* 0x08 */
|
|
+ u32 rx_time; /* 0x0c */
|
|
+ u16 total_length; /* 0x10 */
|
|
+ u16 WEP_length; /* 0x12 */
|
|
+ u32 WEP_ofs; /* 0x14 */
|
|
+
|
|
+/* the following 16 bytes do not change when acx100 owns the descriptor */
|
|
+ u8 driverWorkspace[16]; /* 0x18 */
|
|
+
|
|
+ u8 Ctl_8;
|
|
+ u8 rate;
|
|
+ u8 error;
|
|
+ u8 SNR; /* Signal-to-Noise Ratio */
|
|
+ u8 RxLevel;
|
|
+ u8 queue_ctrl;
|
|
+ u16 unknown;
|
|
+ u32 unknown2;
|
|
+} ACX_PACKED; /* size 52 = 0x34 */
|
|
+
|
|
+#if defined(ACX_PCI) || defined(ACX_MEM)
|
|
+
|
|
+/* Register I/O offsets */
|
|
+#define ACX100_EEPROM_ID_OFFSET 0x380
|
|
+
|
|
+/* please add further ACX hardware register definitions only when
|
|
+ it turns out you need them in the driver, and please try to use
|
|
+ firmware functionality instead, since using direct I/O access instead
|
|
+ of letting the firmware do it might confuse the firmware's state
|
|
+ machine */
|
|
+
|
|
+/* ***** ABSOLUTELY ALWAYS KEEP OFFSETS IN SYNC WITH THE INITIALIZATION
|
|
+** OF THE I/O ARRAYS!!!! (grep for '^IO_ACX') ***** */
|
|
+enum {
|
|
+ IO_ACX_SOFT_RESET = 0,
|
|
+
|
|
+ IO_ACX_SLV_MEM_ADDR,
|
|
+ IO_ACX_SLV_MEM_DATA,
|
|
+ IO_ACX_SLV_MEM_CTL,
|
|
+ IO_ACX_SLV_END_CTL,
|
|
+
|
|
+ IO_ACX_FEMR, /* Function Event Mask */
|
|
+
|
|
+ IO_ACX_INT_TRIG,
|
|
+ IO_ACX_IRQ_MASK,
|
|
+ IO_ACX_IRQ_STATUS_NON_DES,
|
|
+ IO_ACX_IRQ_STATUS_CLEAR, /* CLEAR = clear on read */
|
|
+ IO_ACX_IRQ_ACK,
|
|
+ IO_ACX_HINT_TRIG,
|
|
+
|
|
+ IO_ACX_ENABLE,
|
|
+
|
|
+ IO_ACX_EEPROM_CTL,
|
|
+ IO_ACX_EEPROM_ADDR,
|
|
+ IO_ACX_EEPROM_DATA,
|
|
+ IO_ACX_EEPROM_CFG,
|
|
+
|
|
+ IO_ACX_PHY_ADDR,
|
|
+ IO_ACX_PHY_DATA,
|
|
+ IO_ACX_PHY_CTL,
|
|
+
|
|
+ IO_ACX_GPIO_OE,
|
|
+
|
|
+ IO_ACX_GPIO_OUT,
|
|
+
|
|
+ IO_ACX_CMD_MAILBOX_OFFS,
|
|
+ IO_ACX_INFO_MAILBOX_OFFS,
|
|
+ IO_ACX_EEPROM_INFORMATION,
|
|
+
|
|
+ IO_ACX_EE_START,
|
|
+ IO_ACX_SOR_CFG,
|
|
+ IO_ACX_ECPU_CTRL
|
|
+};
|
|
+/* ***** ABSOLUTELY ALWAYS KEEP OFFSETS IN SYNC WITH THE INITIALIZATION
|
|
+** OF THE I/O ARRAYS!!!! (grep for '^IO_ACX') ***** */
|
|
+
|
|
+/* Values for IO_ACX_INT_TRIG register: */
|
|
+/* inform hw that rxdesc in queue needs processing */
|
|
+#define INT_TRIG_RXPRC 0x08
|
|
+/* inform hw that txdesc in queue needs processing */
|
|
+#define INT_TRIG_TXPRC 0x04
|
|
+/* ack that we received info from info mailbox */
|
|
+#define INT_TRIG_INFOACK 0x02
|
|
+/* inform hw that we have filled command mailbox */
|
|
+#define INT_TRIG_CMD 0x01
|
|
+
|
|
+struct txhostdesc {
|
|
+ acx_ptr data_phy; /* 0x00 [u8 *] */
|
|
+ u16 data_offset; /* 0x04 */
|
|
+ u16 reserved; /* 0x06 */
|
|
+ u16 Ctl_16; /* 16bit value, endianness!! */
|
|
+ u16 length; /* 0x0a */
|
|
+ acx_ptr desc_phy_next; /* 0x0c [txhostdesc *] */
|
|
+ acx_ptr pNext; /* 0x10 [txhostdesc *] */
|
|
+ u32 Status; /* 0x14, unused on Tx */
|
|
+/* From here on you can use this area as you want (variable length, too!) */
|
|
+ u8 *data;
|
|
+} ACX_PACKED;
|
|
+
|
|
+struct rxhostdesc {
|
|
+ acx_ptr data_phy; /* 0x00 [rxbuffer_t *] */
|
|
+ u16 data_offset; /* 0x04 */
|
|
+ u16 reserved; /* 0x06 */
|
|
+ u16 Ctl_16; /* 0x08; 16bit value, endianness!! */
|
|
+ u16 length; /* 0x0a */
|
|
+ acx_ptr desc_phy_next; /* 0x0c [rxhostdesc_t *] */
|
|
+ acx_ptr pNext; /* 0x10 [rxhostdesc_t *] */
|
|
+ u32 Status; /* 0x14 */
|
|
+/* From here on you can use this area as you want (variable length, too!) */
|
|
+ rxbuffer_t *data;
|
|
+} ACX_PACKED;
|
|
+
|
|
+#endif /* ACX_PCI */
|
|
+
|
|
+/***********************************************************************
|
|
+** USB structures and constants
|
|
+*/
|
|
+#ifdef ACX_USB
|
|
+
|
|
+/* Used for usb_txbuffer.desc field */
|
|
+#define USB_TXBUF_TXDESC 0xA
|
|
+/* Size of header (everything up to data[]) */
|
|
+#define USB_TXBUF_HDRSIZE 14
|
|
+typedef struct usb_txbuffer {
|
|
+ u16 desc;
|
|
+ u16 mpdu_len;
|
|
+ u8 queue_index;
|
|
+ u8 rate;
|
|
+ u32 hostdata;
|
|
+ u8 ctrl1;
|
|
+ u8 ctrl2;
|
|
+ u16 data_len;
|
|
+ /* wlan packet content is placed here: */
|
|
+ u8 data[WLAN_A4FR_MAXLEN_WEP_FCS];
|
|
+} ACX_PACKED usb_txbuffer_t;
|
|
+
|
|
+/* USB returns either rx packets (see rxbuffer) or
|
|
+** these "tx status" structs: */
|
|
+typedef struct usb_txstatus {
|
|
+ u16 mac_cnt_rcvd; /* only 12 bits are len! (0xfff) */
|
|
+ u8 queue_index;
|
|
+ u8 mac_status; /* seen 0x20 on tx failure */
|
|
+ u32 hostdata;
|
|
+ u8 rate;
|
|
+ u8 ack_failures;
|
|
+ u8 rts_failures;
|
|
+ u8 rts_ok;
|
|
+} ACX_PACKED usb_txstatus_t;
|
|
+
|
|
+typedef struct usb_tx {
|
|
+ unsigned busy:1;
|
|
+ struct urb *urb;
|
|
+ acx_device_t *adev;
|
|
+ /* actual USB bulk output data block is here: */
|
|
+ usb_txbuffer_t bulkout;
|
|
+} usb_tx_t;
|
|
+
|
|
+struct usb_rx_plain {
|
|
+ unsigned busy:1;
|
|
+ struct urb *urb;
|
|
+ acx_device_t *adev;
|
|
+ rxbuffer_t bulkin;
|
|
+};
|
|
+
|
|
+typedef struct usb_rx {
|
|
+ unsigned busy:1;
|
|
+ struct urb *urb;
|
|
+ acx_device_t *adev;
|
|
+ rxbuffer_t bulkin;
|
|
+ /* Make entire structure 4k. Report if it breaks something. */
|
|
+ u8 padding[4*1024 - sizeof(struct usb_rx_plain)];
|
|
+} usb_rx_t;
|
|
+#endif /* ACX_USB */
|
|
+
|
|
+
|
|
+/* Config Option structs */
|
|
+
|
|
+typedef struct co_antennas {
|
|
+ u8 type;
|
|
+ u8 len;
|
|
+ u8 list[2];
|
|
+} ACX_PACKED co_antennas_t;
|
|
+
|
|
+typedef struct co_powerlevels {
|
|
+ u8 type;
|
|
+ u8 len;
|
|
+ u16 list[8];
|
|
+} ACX_PACKED co_powerlevels_t;
|
|
+
|
|
+typedef struct co_datarates {
|
|
+ u8 type;
|
|
+ u8 len;
|
|
+ u8 list[8];
|
|
+} ACX_PACKED co_datarates_t;
|
|
+
|
|
+typedef struct co_domains {
|
|
+ u8 type;
|
|
+ u8 len;
|
|
+ u8 list[6];
|
|
+} ACX_PACKED co_domains_t;
|
|
+
|
|
+typedef struct co_product_id {
|
|
+ u8 type;
|
|
+ u8 len;
|
|
+ u8 list[128];
|
|
+} ACX_PACKED co_product_id_t;
|
|
+
|
|
+typedef struct co_manuf_id {
|
|
+ u8 type;
|
|
+ u8 len;
|
|
+ u8 list[128];
|
|
+} ACX_PACKED co_manuf_t;
|
|
+
|
|
+typedef struct co_fixed {
|
|
+ char NVSv[8];
|
|
+/* u16 NVS_vendor_offs; ACX111-only */
|
|
+/* u16 unknown; ACX111-only */
|
|
+ u8 MAC[6]; /* ACX100-only */
|
|
+ u16 probe_delay; /* ACX100-only */
|
|
+ u32 eof_memory;
|
|
+ u8 dot11CCAModes;
|
|
+ u8 dot11Diversity;
|
|
+ u8 dot11ShortPreambleOption;
|
|
+ u8 dot11PBCCOption;
|
|
+ u8 dot11ChannelAgility;
|
|
+ u8 dot11PhyType; /* FIXME: does 802.11 call it "dot11PHYType"? */
|
|
+ u8 dot11TempType;
|
|
+ u8 table_count;
|
|
+} ACX_PACKED co_fixed_t;
|
|
+
|
|
+typedef struct acx111_ie_configoption {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+/* Do not access below members directly, they are in fact variable length */
|
|
+ co_fixed_t fixed;
|
|
+ co_antennas_t antennas;
|
|
+ co_powerlevels_t power_levels;
|
|
+ co_datarates_t data_rates;
|
|
+ co_domains_t domains;
|
|
+ co_product_id_t product_id;
|
|
+ co_manuf_t manufacturer;
|
|
+ u8 _padding[4];
|
|
+} ACX_PACKED acx111_ie_configoption_t;
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Main acx per-device data structure
|
|
+*/
|
|
+#define ACX_STATE_FW_LOADED 0x01
|
|
+#define ACX_STATE_IFACE_UP 0x02
|
|
+
|
|
+/* MAC mode (BSS type) defines
|
|
+ * Note that they shouldn't be redefined, since they are also used
|
|
+ * during communication with firmware */
|
|
+#define ACX_MODE_0_ADHOC 0
|
|
+#define ACX_MODE_1_UNUSED 1
|
|
+#define ACX_MODE_2_STA 2
|
|
+#define ACX_MODE_3_AP 3
|
|
+/* These are our own inventions. Sending these to firmware
|
|
+** makes it stop emitting beacons, which is exactly what we want
|
|
+** for these modes */
|
|
+#define ACX_MODE_MONITOR 0xfe
|
|
+#define ACX_MODE_OFF 0xff
|
|
+/* 'Submode': identifies exact status of ADHOC/STA host */
|
|
+#define ACX_STATUS_0_STOPPED 0
|
|
+#define ACX_STATUS_1_SCANNING 1
|
|
+#define ACX_STATUS_2_WAIT_AUTH 2
|
|
+#define ACX_STATUS_3_AUTHENTICATED 3
|
|
+#define ACX_STATUS_4_ASSOCIATED 4
|
|
+
|
|
+/* FIXME: this should be named something like struct acx_priv (typedef'd to
|
|
+ * acx_priv_t) */
|
|
+
|
|
+/* non-firmware struct, no packing necessary */
|
|
+struct acx_device {
|
|
+ /* most frequent accesses first (dereferencing and cache line!) */
|
|
+
|
|
+ /*** Locking ***/
|
|
+ /* FIXME: try to convert semaphore to more efficient mutex according
|
|
+ to Ingo Molnar's docs (but not before driver is in mainline or
|
|
+ pre-mutex Linux 2.6.10 is very outdated). */
|
|
+ struct semaphore sem;
|
|
+ spinlock_t lock;
|
|
+#if defined(PARANOID_LOCKING) /* Lock debugging */
|
|
+ const char *last_sem;
|
|
+ const char *last_lock;
|
|
+ unsigned long sem_time;
|
|
+ unsigned long lock_time;
|
|
+#endif
|
|
+#ifdef ACX_MEM
|
|
+ spinlock_t txbuf_lock;
|
|
+#endif
|
|
+
|
|
+ /*** Linux network device ***/
|
|
+ struct net_device *ndev; /* pointer to linux netdevice */
|
|
+
|
|
+ /*** Device statistics ***/
|
|
+ struct net_device_stats stats; /* net device statistics */
|
|
+#ifdef WIRELESS_EXT
|
|
+ struct iw_statistics wstats; /* wireless statistics */
|
|
+#endif
|
|
+ /*** Power managment ***/
|
|
+ struct pm_dev *pm; /* PM crap */
|
|
+
|
|
+ /*** Management timer ***/
|
|
+ struct timer_list mgmt_timer;
|
|
+
|
|
+ /*** Hardware identification ***/
|
|
+ const char *chip_name;
|
|
+ u8 dev_type;
|
|
+ u8 chip_type;
|
|
+ u8 form_factor;
|
|
+ u8 radio_type;
|
|
+ u8 eeprom_version;
|
|
+
|
|
+ /*** Config retrieved from EEPROM ***/
|
|
+ char cfgopt_NVSv[8];
|
|
+ u16 cfgopt_NVS_vendor_offs;
|
|
+ u8 cfgopt_MAC[6];
|
|
+ u16 cfgopt_probe_delay;
|
|
+ u32 cfgopt_eof_memory;
|
|
+ u8 cfgopt_dot11CCAModes;
|
|
+ u8 cfgopt_dot11Diversity;
|
|
+ u8 cfgopt_dot11ShortPreambleOption;
|
|
+ u8 cfgopt_dot11PBCCOption;
|
|
+ u8 cfgopt_dot11ChannelAgility;
|
|
+ u8 cfgopt_dot11PhyType;
|
|
+ u8 cfgopt_dot11TempType;
|
|
+ co_antennas_t cfgopt_antennas;
|
|
+ co_powerlevels_t cfgopt_power_levels;
|
|
+ co_datarates_t cfgopt_data_rates;
|
|
+ co_domains_t cfgopt_domains;
|
|
+ co_product_id_t cfgopt_product_id;
|
|
+ co_manuf_t cfgopt_manufacturer;
|
|
+
|
|
+ /*** Firmware identification ***/
|
|
+ char firmware_version[FW_ID_SIZE+1];
|
|
+ u32 firmware_numver;
|
|
+ u32 firmware_id;
|
|
+ const u16 *ie_len;
|
|
+ const u16 *ie_len_dot11;
|
|
+
|
|
+ /*** Device state ***/
|
|
+ u16 dev_state_mask;
|
|
+ u8 led_power; /* power LED status */
|
|
+ u32 get_mask; /* mask of settings to fetch from the card */
|
|
+ u32 set_mask; /* mask of settings to write to the card */
|
|
+
|
|
+ /* Barely used in USB case */
|
|
+ u16 irq_status;
|
|
+
|
|
+ u8 after_interrupt_jobs; /* mini job list for doing actions after an interrupt occurred */
|
|
+ WORK_STRUCT after_interrupt_task; /* our task for after interrupt actions */
|
|
+
|
|
+ /*** scanning ***/
|
|
+ u16 scan_count; /* number of times to do channel scan */
|
|
+ u8 scan_mode; /* 0 == active, 1 == passive, 2 == background */
|
|
+ u8 scan_rate;
|
|
+ u16 scan_duration;
|
|
+ u16 scan_probe_delay;
|
|
+#if WIRELESS_EXT > 15
|
|
+ struct iw_spy_data spy_data; /* FIXME: needs to be implemented! */
|
|
+#endif
|
|
+
|
|
+ /*** Wireless network settings ***/
|
|
+ /* copy of the device address (ifconfig hw ether) that we actually use
|
|
+ ** for 802.11; copied over from the network device's MAC address
|
|
+ ** (ifconfig) when it makes sense only */
|
|
+ u8 dev_addr[MAX_ADDR_LEN];
|
|
+ u8 bssid[ETH_ALEN]; /* the BSSID after having joined */
|
|
+ u8 ap[ETH_ALEN]; /* The AP we want, FF:FF:FF:FF:FF:FF is any */
|
|
+ u16 aid; /* The Association ID sent from the AP / last used AID if we're an AP */
|
|
+ u16 mode; /* mode from iwconfig */
|
|
+ int monitor_type; /* ARPHRD_IEEE80211 or ARPHRD_IEEE80211_PRISM */
|
|
+ u16 status; /* 802.11 association status */
|
|
+ u8 essid_active; /* specific ESSID active, or select any? */
|
|
+ u8 essid_len; /* to avoid dozens of strlen() */
|
|
+ /* INCLUDES \0 termination for easy printf - but many places
|
|
+ ** simply want the string data memcpy'd plus a length indicator!
|
|
+ ** Keep that in mind... */
|
|
+ char essid[IW_ESSID_MAX_SIZE+1];
|
|
+ /* essid we are going to use for association, in case of "essid 'any'"
|
|
+ ** and in case of hidden ESSID (use configured ESSID then) */
|
|
+ char essid_for_assoc[IW_ESSID_MAX_SIZE+1];
|
|
+ char nick[IW_ESSID_MAX_SIZE+1]; /* see essid! */
|
|
+ u8 channel;
|
|
+ u8 reg_dom_id; /* reg domain setting */
|
|
+ u16 reg_dom_chanmask;
|
|
+ u16 auth_or_assoc_retries;
|
|
+ u16 scan_retries;
|
|
+ unsigned long scan_start; /* YES, jiffies is defined as "unsigned long" */
|
|
+
|
|
+ /* stations known to us (if we're an ap) */
|
|
+ client_t sta_list[32]; /* tab is larger than list, so that */
|
|
+ client_t *sta_hash_tab[64]; /* hash collisions are not likely */
|
|
+ client_t *ap_client; /* this one is our AP (STA mode only) */
|
|
+
|
|
+ int dup_count;
|
|
+ int nondup_count;
|
|
+ unsigned long dup_msg_expiry;
|
|
+ u16 last_seq_ctrl; /* duplicate packet detection */
|
|
+
|
|
+ /* 802.11 power save mode */
|
|
+ u8 ps_wakeup_cfg;
|
|
+ u8 ps_listen_interval;
|
|
+ u8 ps_options;
|
|
+ u8 ps_hangover_period;
|
|
+ u32 ps_enhanced_transition_time;
|
|
+ u32 ps_beacon_rx_time;
|
|
+
|
|
+ /*** PHY settings ***/
|
|
+ u8 fallback_threshold;
|
|
+ u8 stepup_threshold;
|
|
+ u16 rate_basic;
|
|
+ u16 rate_oper;
|
|
+ u16 rate_bcast;
|
|
+ u16 rate_bcast100;
|
|
+ u8 rate_auto; /* false if "iwconfig rate N" (WITHOUT 'auto'!) */
|
|
+ u8 preamble_mode; /* 0 == Long Preamble, 1 == Short, 2 == Auto */
|
|
+ u8 preamble_cur;
|
|
+
|
|
+ u8 tx_disabled;
|
|
+ u8 tx_level_dbm;
|
|
+ /* u8 tx_level_val; */
|
|
+ /* u8 tx_level_auto; whether to do automatic power adjustment */
|
|
+
|
|
+ unsigned long recalib_time_last_success;
|
|
+ unsigned long recalib_time_last_attempt;
|
|
+ int recalib_failure_count;
|
|
+ int recalib_msg_ratelimit;
|
|
+ int retry_errors_msg_ratelimit;
|
|
+
|
|
+ unsigned long brange_time_last_state_change; /* time the power LED was last changed */
|
|
+ u8 brange_last_state; /* last state of the LED */
|
|
+ u8 brange_max_quality; /* maximum quality that equates to full speed */
|
|
+
|
|
+ u8 sensitivity;
|
|
+ u8 antenna; /* antenna settings */
|
|
+ u8 ed_threshold; /* energy detect threshold */
|
|
+ u8 cca; /* clear channel assessment */
|
|
+
|
|
+ u16 rts_threshold;
|
|
+ u16 frag_threshold;
|
|
+ u32 short_retry;
|
|
+ u32 long_retry;
|
|
+ u16 msdu_lifetime;
|
|
+ u16 listen_interval; /* given in units of beacon interval */
|
|
+ u32 beacon_interval;
|
|
+
|
|
+ u16 capabilities;
|
|
+ u8 rate_supported_len;
|
|
+ u8 rate_supported[13];
|
|
+
|
|
+ /*** Encryption settings (WEP) ***/
|
|
+ u32 auth_alg; /* used in transmit_authen1 */
|
|
+ u8 wep_enabled;
|
|
+ u8 wep_restricted;
|
|
+ u8 wep_current_index;
|
|
+ wep_key_t wep_keys[DOT11_MAX_DEFAULT_WEP_KEYS]; /* the default WEP keys */
|
|
+ key_struct_t wep_key_struct[10];
|
|
+
|
|
+ /*** Unknown ***/
|
|
+ u8 dtim_interval;
|
|
+
|
|
+#ifdef ACX_MEM
|
|
+ u32 acx_txbuf_start;
|
|
+ int acx_txbuf_numblocks;
|
|
+ u32 acx_txbuf_free; /* addr of head of free list */
|
|
+ int acx_txbuf_blocks_free; /* how many are still open */
|
|
+ queueindicator_t *acx_queue_indicator;
|
|
+#endif
|
|
+
|
|
+ /*** Card Rx/Tx management ***/
|
|
+ u16 rx_config_1;
|
|
+ u16 rx_config_2;
|
|
+ u16 memblocksize;
|
|
+ unsigned int tx_free;
|
|
+ unsigned int tx_head; /* keep as close as possible to Tx stuff below (cache line) */
|
|
+ u16 phy_header_len;
|
|
+
|
|
+/*************************************************************************
|
|
+ *** PCI/USB/... must be last or else hw agnostic code breaks horribly ***
|
|
+ *************************************************************************/
|
|
+
|
|
+ /* hack to let common code compile. FIXME */
|
|
+ dma_addr_t rxhostdesc_startphy;
|
|
+
|
|
+ /*** PCI stuff ***/
|
|
+#if defined(ACX_PCI) || defined(ACX_MEM)
|
|
+ /* pointers to tx buffers, tx host descriptors (in host memory)
|
|
+ ** and tx descs in device memory */
|
|
+ unsigned int tx_tail;
|
|
+ u8 *txbuf_start;
|
|
+ txhostdesc_t *txhostdesc_start;
|
|
+ txdesc_t *txdesc_start; /* points to PCI-mapped memory */
|
|
+ dma_addr_t txbuf_startphy;
|
|
+ dma_addr_t txhostdesc_startphy;
|
|
+ /* sizes of above host memory areas */
|
|
+ unsigned int txbuf_area_size;
|
|
+ unsigned int txhostdesc_area_size;
|
|
+
|
|
+ unsigned int txdesc_size; /* size of txdesc; ACX111 = ACX100 + 4 */
|
|
+ client_t *txc[TX_CNT];
|
|
+ u16 txr[TX_CNT];
|
|
+
|
|
+ /* same for rx */
|
|
+ unsigned int rx_tail;
|
|
+ rxbuffer_t *rxbuf_start;
|
|
+ rxhostdesc_t *rxhostdesc_start;
|
|
+ rxdesc_t *rxdesc_start;
|
|
+ /* physical addresses of above host memory areas */
|
|
+ dma_addr_t rxbuf_startphy;
|
|
+ /* dma_addr_t rxhostdesc_startphy; */
|
|
+ unsigned int rxbuf_area_size;
|
|
+ unsigned int rxhostdesc_area_size;
|
|
+
|
|
+ u8 need_radio_fw;
|
|
+ u8 irqs_active; /* whether irq sending is activated */
|
|
+
|
|
+ const u16 *io; /* points to ACX100 or ACX111 PCI I/O register address set */
|
|
+
|
|
+#ifdef ACX_PCI
|
|
+ struct pci_dev *pdev;
|
|
+#endif
|
|
+#ifdef ACX_MEM
|
|
+ struct device *dev;
|
|
+#endif
|
|
+
|
|
+#ifdef ACX_PCI
|
|
+ unsigned long membase;
|
|
+#endif
|
|
+#ifdef ACX_MEM
|
|
+ volatile u32 *membase;
|
|
+#endif
|
|
+ unsigned long membase2;
|
|
+#ifdef ACX_PCI
|
|
+ void __iomem *iobase;
|
|
+#endif
|
|
+#ifdef ACX_MEM
|
|
+ volatile u32 *iobase;
|
|
+#endif
|
|
+ void __iomem *iobase2;
|
|
+ /* command interface */
|
|
+ u8 __iomem *cmd_area;
|
|
+ u8 __iomem *info_area;
|
|
+
|
|
+ u16 irq_mask; /* interrupt types to mask out (not wanted) with many IRQs activated */
|
|
+ u16 irq_mask_off; /* interrupt types to mask out (not wanted) with IRQs off */
|
|
+ unsigned int irq_loops_this_jiffy;
|
|
+ unsigned long irq_last_jiffies;
|
|
+#endif
|
|
+
|
|
+ /*** USB stuff ***/
|
|
+#ifdef ACX_USB
|
|
+ struct usb_device *usbdev;
|
|
+
|
|
+ rxbuffer_t rxtruncbuf;
|
|
+
|
|
+ usb_tx_t *usb_tx;
|
|
+ usb_rx_t *usb_rx;
|
|
+
|
|
+ int bulkinep; /* bulk-in endpoint */
|
|
+ int bulkoutep; /* bulk-out endpoint */
|
|
+ int rxtruncsize;
|
|
+#endif
|
|
+
|
|
+};
|
|
+
|
|
+static inline acx_device_t*
|
|
+ndev2adev(struct net_device *ndev)
|
|
+{
|
|
+ return netdev_priv(ndev);
|
|
+}
|
|
+
|
|
+
|
|
+/* For use with ACX1xx_IE_RXCONFIG */
|
|
+/* bit description
|
|
+ * 13 include additional header (length etc.) *required*
|
|
+ * struct is defined in 'struct rxbuffer'
|
|
+ * is this bit acx100 only? does acx111 always put the header,
|
|
+ * and bit setting is irrelevant? --vda
|
|
+ * 10 receive frames only with SSID used in last join cmd
|
|
+ * 9 discard broadcast
|
|
+ * 8 receive packets for multicast address 1
|
|
+ * 7 receive packets for multicast address 0
|
|
+ * 6 discard all multicast packets
|
|
+ * 5 discard frames from foreign BSSID
|
|
+ * 4 discard frames with foreign destination MAC address
|
|
+ * 3 promiscuous mode (receive ALL frames, disable filter)
|
|
+ * 2 include FCS
|
|
+ * 1 include phy header
|
|
+ * 0 ???
|
|
+ */
|
|
+#define RX_CFG1_INCLUDE_RXBUF_HDR 0x2000 /* ACX100 only */
|
|
+#define RX_CFG1_FILTER_SSID 0x0400
|
|
+#define RX_CFG1_FILTER_BCAST 0x0200
|
|
+#define RX_CFG1_RCV_MC_ADDR1 0x0100
|
|
+#define RX_CFG1_RCV_MC_ADDR0 0x0080
|
|
+#define RX_CFG1_FILTER_ALL_MULTI 0x0040
|
|
+#define RX_CFG1_FILTER_BSSID 0x0020
|
|
+#define RX_CFG1_FILTER_MAC 0x0010
|
|
+#define RX_CFG1_RCV_PROMISCUOUS 0x0008
|
|
+#define RX_CFG1_INCLUDE_FCS 0x0004
|
|
+#define RX_CFG1_INCLUDE_PHY_HDR (WANT_PHY_HDR ? 0x0002 : 0)
|
|
+/* bit description
|
|
+ * 11 receive association requests etc.
|
|
+ * 10 receive authentication frames
|
|
+ * 9 receive beacon frames
|
|
+ * 8 receive contention free packets
|
|
+ * 7 receive control frames
|
|
+ * 6 receive data frames
|
|
+ * 5 receive broken frames
|
|
+ * 4 receive management frames
|
|
+ * 3 receive probe requests
|
|
+ * 2 receive probe responses
|
|
+ * 1 receive RTS/CTS/ACK frames
|
|
+ * 0 receive other
|
|
+ */
|
|
+#define RX_CFG2_RCV_ASSOC_REQ 0x0800
|
|
+#define RX_CFG2_RCV_AUTH_FRAMES 0x0400
|
|
+#define RX_CFG2_RCV_BEACON_FRAMES 0x0200
|
|
+#define RX_CFG2_RCV_CONTENTION_FREE 0x0100
|
|
+#define RX_CFG2_RCV_CTRL_FRAMES 0x0080
|
|
+#define RX_CFG2_RCV_DATA_FRAMES 0x0040
|
|
+#define RX_CFG2_RCV_BROKEN_FRAMES 0x0020
|
|
+#define RX_CFG2_RCV_MGMT_FRAMES 0x0010
|
|
+#define RX_CFG2_RCV_PROBE_REQ 0x0008
|
|
+#define RX_CFG2_RCV_PROBE_RESP 0x0004
|
|
+#define RX_CFG2_RCV_ACK_FRAMES 0x0002
|
|
+#define RX_CFG2_RCV_OTHER 0x0001
|
|
+
|
|
+/* For use with ACX1xx_IE_FEATURE_CONFIG */
|
|
+#define FEATURE1_80MHZ_CLOCK 0x00000040L
|
|
+#define FEATURE1_4X 0x00000020L
|
|
+#define FEATURE1_LOW_RX 0x00000008L
|
|
+#define FEATURE1_EXTRA_LOW_RX 0x00000001L
|
|
+
|
|
+#define FEATURE2_SNIFFER 0x00000080L
|
|
+#define FEATURE2_NO_TXCRYPT 0x00000001L
|
|
+
|
|
+/*-- get and set mask values --*/
|
|
+#define GETSET_LED_POWER 0x00000001L
|
|
+#define GETSET_STATION_ID 0x00000002L
|
|
+#define SET_TEMPLATES 0x00000004L
|
|
+#define SET_STA_LIST 0x00000008L
|
|
+#define GETSET_TX 0x00000010L
|
|
+#define GETSET_RX 0x00000020L
|
|
+#define SET_RXCONFIG 0x00000040L
|
|
+#define GETSET_ANTENNA 0x00000080L
|
|
+#define GETSET_SENSITIVITY 0x00000100L
|
|
+#define GETSET_TXPOWER 0x00000200L
|
|
+#define GETSET_ED_THRESH 0x00000400L
|
|
+#define GETSET_CCA 0x00000800L
|
|
+#define GETSET_POWER_80211 0x00001000L
|
|
+#define GETSET_RETRY 0x00002000L
|
|
+#define GETSET_REG_DOMAIN 0x00004000L
|
|
+#define GETSET_CHANNEL 0x00008000L
|
|
+/* Used when ESSID changes etc and we need to scan for AP anew */
|
|
+#define GETSET_RESCAN 0x00010000L
|
|
+#define GETSET_MODE 0x00020000L
|
|
+#define GETSET_WEP 0x00040000L
|
|
+#define SET_WEP_OPTIONS 0x00080000L
|
|
+#define SET_MSDU_LIFETIME 0x00100000L
|
|
+#define SET_RATE_FALLBACK 0x00200000L
|
|
+
|
|
+/* keep in sync with the above */
|
|
+#define GETSET_ALL (0 \
|
|
+/* GETSET_LED_POWER */ | 0x00000001L \
|
|
+/* GETSET_STATION_ID */ | 0x00000002L \
|
|
+/* SET_TEMPLATES */ | 0x00000004L \
|
|
+/* SET_STA_LIST */ | 0x00000008L \
|
|
+/* GETSET_TX */ | 0x00000010L \
|
|
+/* GETSET_RX */ | 0x00000020L \
|
|
+/* SET_RXCONFIG */ | 0x00000040L \
|
|
+/* GETSET_ANTENNA */ | 0x00000080L \
|
|
+/* GETSET_SENSITIVITY */| 0x00000100L \
|
|
+/* GETSET_TXPOWER */ | 0x00000200L \
|
|
+/* GETSET_ED_THRESH */ | 0x00000400L \
|
|
+/* GETSET_CCA */ | 0x00000800L \
|
|
+/* GETSET_POWER_80211 */| 0x00001000L \
|
|
+/* GETSET_RETRY */ | 0x00002000L \
|
|
+/* GETSET_REG_DOMAIN */ | 0x00004000L \
|
|
+/* GETSET_CHANNEL */ | 0x00008000L \
|
|
+/* GETSET_RESCAN */ | 0x00010000L \
|
|
+/* GETSET_MODE */ | 0x00020000L \
|
|
+/* GETSET_WEP */ | 0x00040000L \
|
|
+/* SET_WEP_OPTIONS */ | 0x00080000L \
|
|
+/* SET_MSDU_LIFETIME */ | 0x00100000L \
|
|
+/* SET_RATE_FALLBACK */ | 0x00200000L \
|
|
+ )
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Firmware loading
|
|
+*/
|
|
+#include <linux/firmware.h> /* request_firmware() */
|
|
+#include <linux/pci.h> /* struct pci_device */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+typedef struct acx100_ie_memblocksize {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u16 size;
|
|
+} ACX_PACKED acx100_ie_memblocksize_t;
|
|
+
|
|
+typedef struct acx100_ie_queueconfig {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u32 AreaSize;
|
|
+ u32 RxQueueStart;
|
|
+ u8 QueueOptions;
|
|
+ u8 NumTxQueues;
|
|
+ u8 NumRxDesc; /* for USB only */
|
|
+ u8 pad1;
|
|
+ u32 QueueEnd;
|
|
+ u32 HostQueueEnd; /* QueueEnd2 */
|
|
+ u32 TxQueueStart;
|
|
+ u8 TxQueuePri;
|
|
+ u8 NumTxDesc;
|
|
+ u16 pad2;
|
|
+} ACX_PACKED acx100_ie_queueconfig_t;
|
|
+
|
|
+typedef struct acx111_ie_queueconfig {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u32 tx_memory_block_address;
|
|
+ u32 rx_memory_block_address;
|
|
+ u32 rx1_queue_address;
|
|
+ u32 reserved1;
|
|
+ u32 tx1_queue_address;
|
|
+ u8 tx1_attributes;
|
|
+ u16 reserved2;
|
|
+ u8 reserved3;
|
|
+} ACX_PACKED acx111_ie_queueconfig_t;
|
|
+
|
|
+typedef struct acx100_ie_memconfigoption {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u32 DMA_config;
|
|
+ acx_ptr pRxHostDesc;
|
|
+ u32 rx_mem;
|
|
+ u32 tx_mem;
|
|
+ u16 RxBlockNum;
|
|
+ u16 TxBlockNum;
|
|
+} ACX_PACKED acx100_ie_memconfigoption_t;
|
|
+
|
|
+typedef struct acx111_ie_memoryconfig {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u16 no_of_stations;
|
|
+ u16 memory_block_size;
|
|
+ u8 tx_rx_memory_block_allocation;
|
|
+ u8 count_rx_queues;
|
|
+ u8 count_tx_queues;
|
|
+ u8 options;
|
|
+ u8 fragmentation;
|
|
+ u16 reserved1;
|
|
+ u8 reserved2;
|
|
+
|
|
+ /* start of rx1 block */
|
|
+ u8 rx_queue1_count_descs;
|
|
+ u8 rx_queue1_reserved1;
|
|
+ u8 rx_queue1_type; /* must be set to 7 */
|
|
+ u8 rx_queue1_prio; /* must be set to 0 */
|
|
+ acx_ptr rx_queue1_host_rx_start;
|
|
+ /* end of rx1 block */
|
|
+
|
|
+ /* start of tx1 block */
|
|
+ u8 tx_queue1_count_descs;
|
|
+ u8 tx_queue1_reserved1;
|
|
+ u8 tx_queue1_reserved2;
|
|
+ u8 tx_queue1_attributes;
|
|
+ /* end of tx1 block */
|
|
+} ACX_PACKED acx111_ie_memoryconfig_t;
|
|
+
|
|
+typedef struct acx_ie_memmap {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u32 CodeStart;
|
|
+ u32 CodeEnd;
|
|
+ u32 WEPCacheStart;
|
|
+ u32 WEPCacheEnd;
|
|
+ u32 PacketTemplateStart;
|
|
+ u32 PacketTemplateEnd;
|
|
+ u32 QueueStart;
|
|
+ u32 QueueEnd;
|
|
+ u32 PoolStart;
|
|
+ u32 PoolEnd;
|
|
+} ACX_PACKED acx_ie_memmap_t;
|
|
+
|
|
+typedef struct acx111_ie_feature_config {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u32 feature_options;
|
|
+ u32 data_flow_options;
|
|
+} ACX_PACKED acx111_ie_feature_config_t;
|
|
+
|
|
+typedef struct acx111_ie_tx_level {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u8 level;
|
|
+} ACX_PACKED acx111_ie_tx_level_t;
|
|
+
|
|
+#define PS_CFG_ENABLE 0x80
|
|
+#define PS_CFG_PENDING 0x40 /* status flag when entering PS */
|
|
+#define PS_CFG_WAKEUP_MODE_MASK 0x07
|
|
+#define PS_CFG_WAKEUP_BY_HOST 0x03
|
|
+#define PS_CFG_WAKEUP_EACH_ITVL 0x02
|
|
+#define PS_CFG_WAKEUP_ON_DTIM 0x01
|
|
+#define PS_CFG_WAKEUP_ALL_BEAC 0x00
|
|
+
|
|
+/* Enhanced PS mode: sleep until Rx Beacon w/ the STA's AID bit set
|
|
+** in the TIM; newer firmwares only(?) */
|
|
+#define PS_OPT_ENA_ENHANCED_PS 0x04
|
|
+#define PS_OPT_TX_PSPOLL 0x02 /* send PSPoll frame to fetch waiting frames from AP (on frame with matching AID) */
|
|
+#define PS_OPT_STILL_RCV_BCASTS 0x01
|
|
+
|
|
+typedef struct acx100_ie_powersave {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u8 wakeup_cfg;
|
|
+ u8 listen_interval; /* for EACH_ITVL: wake up every "beacon units" interval */
|
|
+ u8 options;
|
|
+ u8 hangover_period; /* remaining wake time after Tx MPDU w/ PS bit, in values of 1/1024 seconds */
|
|
+ u16 enhanced_ps_transition_time; /* rem. wake time for Enh. PS */
|
|
+} ACX_PACKED acx100_ie_powersave_t;
|
|
+
|
|
+typedef struct acx111_ie_powersave {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u8 wakeup_cfg;
|
|
+ u8 listen_interval; /* for EACH_ITVL: wake up every "beacon units" interval */
|
|
+ u8 options;
|
|
+ u8 hangover_period; /* remaining wake time after Tx MPDU w/ PS bit, in values of 1/1024 seconds */
|
|
+ u32 beacon_rx_time;
|
|
+ u32 enhanced_ps_transition_time; /* rem. wake time for Enh. PS */
|
|
+} ACX_PACKED acx111_ie_powersave_t;
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Commands and template structures
|
|
+*/
|
|
+
|
|
+/*
|
|
+** SCAN command structure
|
|
+**
|
|
+** even though acx100 scan rates match RATE100 constants,
|
|
+** acx111 ones do not match! Therefore we do not use RATE100 #defines */
|
|
+#define ACX_SCAN_RATE_1 10
|
|
+#define ACX_SCAN_RATE_2 20
|
|
+#define ACX_SCAN_RATE_5 55
|
|
+#define ACX_SCAN_RATE_11 110
|
|
+#define ACX_SCAN_RATE_22 220
|
|
+#define ACX_SCAN_RATE_PBCC 0x80 /* OR with this if needed */
|
|
+#define ACX_SCAN_OPT_ACTIVE 0x00 /* a bit mask */
|
|
+#define ACX_SCAN_OPT_PASSIVE 0x01
|
|
+/* Background scan: we go into Power Save mode (by transmitting
|
|
+** NULL data frame to AP with the power mgmt bit set), do the scan,
|
|
+** and then exit Power Save mode. A plus is that AP buffers frames
|
|
+** for us while we do background scan. Thus we avoid frame losses.
|
|
+** Background scan can be active or passive, just like normal one */
|
|
+#define ACX_SCAN_OPT_BACKGROUND 0x02
|
|
+typedef struct acx100_scan {
|
|
+ u16 count; /* number of scans to do, 0xffff == continuous */
|
|
+ u16 start_chan;
|
|
+ u16 flags; /* channel list mask; 0x8000 == all channels? */
|
|
+ u8 max_rate; /* max. probe rate */
|
|
+ u8 options; /* bit mask, see defines above */
|
|
+ u16 chan_duration;
|
|
+ u16 max_probe_delay;
|
|
+} ACX_PACKED acx100_scan_t; /* length 0xc */
|
|
+
|
|
+#define ACX111_SCAN_RATE_6 0x0B
|
|
+#define ACX111_SCAN_RATE_9 0x0F
|
|
+#define ACX111_SCAN_RATE_12 0x0A
|
|
+#define ACX111_SCAN_RATE_18 0x0E
|
|
+#define ACX111_SCAN_RATE_24 0x09
|
|
+#define ACX111_SCAN_RATE_36 0x0D
|
|
+#define ACX111_SCAN_RATE_48 0x08
|
|
+#define ACX111_SCAN_RATE_54 0x0C
|
|
+#define ACX111_SCAN_OPT_5GHZ 0x04 /* else 2.4GHZ */
|
|
+#define ACX111_SCAN_MOD_SHORTPRE 0x01 /* you can combine SHORTPRE and PBCC */
|
|
+#define ACX111_SCAN_MOD_PBCC 0x80
|
|
+#define ACX111_SCAN_MOD_OFDM 0x40
|
|
+typedef struct acx111_scan {
|
|
+ u16 count; /* number of scans to do */
|
|
+ u8 channel_list_select; /* 0: scan all channels, 1: from chan_list only */
|
|
+ u16 reserved1;
|
|
+ u8 reserved2;
|
|
+ u8 rate; /* rate for probe requests (if active scan) */
|
|
+ u8 options; /* bit mask, see defines above */
|
|
+ u16 chan_duration; /* min time to wait for reply on one channel (in TU) */
|
|
+ /* (active scan only) (802.11 section 11.1.3.2.2) */
|
|
+ u16 max_probe_delay; /* max time to wait for reply on one channel (active scan) */
|
|
+ /* time to listen on a channel (passive scan) */
|
|
+ u8 modulation;
|
|
+ u8 channel_list[26]; /* bits 7:0 first byte: channels 8:1 */
|
|
+ /* bits 7:0 second byte: channels 16:9 */
|
|
+ /* 26 bytes is enough to cover 802.11a */
|
|
+} ACX_PACKED acx111_scan_t;
|
|
+
|
|
+
|
|
+/*
|
|
+** Radio calibration command structure
|
|
+*/
|
|
+typedef struct acx111_cmd_radiocalib {
|
|
+/* 0x80000000 == automatic calibration by firmware, according to interval;
|
|
+ * bits 0..3: select calibration methods to go through:
|
|
+ * calib based on DC, AfeDC, Tx mismatch, Tx equilization */
|
|
+ u32 methods;
|
|
+ u32 interval;
|
|
+} ACX_PACKED acx111_cmd_radiocalib_t;
|
|
+
|
|
+
|
|
+/*
|
|
+** Packet template structures
|
|
+**
|
|
+** Packet templates store contents of Beacon, Probe response, Probe request,
|
|
+** Null data frame, and TIM data frame. Firmware automatically transmits
|
|
+** contents of template at appropriate time:
|
|
+** - Beacon: when configured as AP or Ad-hoc
|
|
+** - Probe response: when configured as AP or Ad-hoc, whenever
|
|
+** a Probe request frame is received
|
|
+** - Probe request: when host issues SCAN command (active)
|
|
+** - Null data frame: when entering 802.11 power save mode
|
|
+** - TIM data: at the end of Beacon frames (if no TIM template
|
|
+** is configured, then transmits default TIM)
|
|
+** NB:
|
|
+** - size field must be set to size of actual template
|
|
+** (NOT sizeof(struct) - templates are variable in length),
|
|
+** size field is not itself counted.
|
|
+** - members flagged with an asterisk must be initialized with host,
|
|
+** rest must be zero filled.
|
|
+** - variable length fields shown only in comments */
|
|
+typedef struct acx_template_tim {
|
|
+ u16 size;
|
|
+ u8 tim_eid; /* 00 1 TIM IE ID * */
|
|
+ u8 len; /* 01 1 Length * */
|
|
+ u8 dtim_cnt; /* 02 1 DTIM Count */
|
|
+ u8 dtim_period; /* 03 1 DTIM Period */
|
|
+ u8 bitmap_ctrl; /* 04 1 Bitmap Control * (except bit0) */
|
|
+ /* 05 n Partial Virtual Bitmap * */
|
|
+ u8 variable[0x100 - 1-1-1-1-1];
|
|
+} ACX_PACKED acx_template_tim_t;
|
|
+
|
|
+typedef struct acx_template_probereq {
|
|
+ u16 size;
|
|
+ u16 fc; /* 00 2 fc * */
|
|
+ u16 dur; /* 02 2 Duration */
|
|
+ u8 da[6]; /* 04 6 Destination Address * */
|
|
+ u8 sa[6]; /* 0A 6 Source Address * */
|
|
+ u8 bssid[6]; /* 10 6 BSSID * */
|
|
+ u16 seq; /* 16 2 Sequence Control */
|
|
+ /* 18 n SSID * */
|
|
+ /* nn n Supported Rates * */
|
|
+ u8 variable[0x44 - 2-2-6-6-6-2];
|
|
+} ACX_PACKED acx_template_probereq_t;
|
|
+
|
|
+typedef struct acx_template_proberesp {
|
|
+ u16 size;
|
|
+ u16 fc; /* 00 2 fc * (bits [15:12] and [10:8] per 802.11 section 7.1.3.1) */
|
|
+ u16 dur; /* 02 2 Duration */
|
|
+ u8 da[6]; /* 04 6 Destination Address */
|
|
+ u8 sa[6]; /* 0A 6 Source Address */
|
|
+ u8 bssid[6]; /* 10 6 BSSID */
|
|
+ u16 seq; /* 16 2 Sequence Control */
|
|
+ u8 timestamp[8];/* 18 8 Timestamp */
|
|
+ u16 beacon_interval; /* 20 2 Beacon Interval * */
|
|
+ u16 cap; /* 22 2 Capability Information * */
|
|
+ /* 24 n SSID * */
|
|
+ /* nn n Supported Rates * */
|
|
+ /* nn 1 DS Parameter Set * */
|
|
+ u8 variable[0x54 - 2-2-6-6-6-2-8-2-2];
|
|
+} ACX_PACKED acx_template_proberesp_t;
|
|
+#define acx_template_beacon_t acx_template_proberesp_t
|
|
+#define acx_template_beacon acx_template_proberesp
|
|
+
|
|
+typedef struct acx_template_nullframe {
|
|
+ u16 size;
|
|
+ struct wlan_hdr_a3 hdr;
|
|
+} ACX_PACKED acx_template_nullframe_t;
|
|
+
|
|
+
|
|
+/*
|
|
+** JOIN command structure
|
|
+**
|
|
+** as opposed to acx100, acx111 dtim interval is AFTER rates_basic111.
|
|
+** NOTE: took me about an hour to get !@#$%^& packing right --> struct packing is eeeeevil... */
|
|
+typedef struct acx_joinbss {
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u16 beacon_interval;
|
|
+ union {
|
|
+ struct {
|
|
+ u8 dtim_interval;
|
|
+ u8 rates_basic;
|
|
+ u8 rates_supported;
|
|
+ /*
|
|
+ * ARM compiler doesn't pack correctly unless unions
|
|
+ * inside structures are multiples of 4 bytes. Ugh.
|
|
+ */
|
|
+ u8 genfrm_txrate; /* generated frame (bcn, proberesp, RTS, PSpoll) tx rate */
|
|
+ } ACX_PACKED acx100;
|
|
+ struct {
|
|
+ u16 rates_basic;
|
|
+ u8 dtim_interval;
|
|
+ u8 genfrm_txrate; /* generated frame (bcn, proberesp, RTS, PSpoll) tx rate */
|
|
+ } ACX_PACKED acx111;
|
|
+ /*
|
|
+ * ARM compiler doesn't pack correctly unles unions are aligned on
|
|
+ * 4 byte boundaries and are multiples of 4 bytes.
|
|
+ */
|
|
+ struct {
|
|
+ u8 d1;
|
|
+ u8 d2;
|
|
+ u8 d3;
|
|
+ u8 genfrm_txrate;
|
|
+ } ACX_PACKED txrate;
|
|
+ } ACX_PACKED u;
|
|
+ u8 genfrm_mod_pre; /* generated frame modulation/preamble:
|
|
+ ** bit7: PBCC, bit6: OFDM (else CCK/DQPSK/DBPSK)
|
|
+ ** bit5: short pre */
|
|
+ u8 macmode; /* BSS Type, must be one of ACX_MODE_xxx */
|
|
+ u8 channel;
|
|
+ u8 essid_len;
|
|
+ char essid[IW_ESSID_MAX_SIZE];
|
|
+} ACX_PACKED acx_joinbss_t;
|
|
+
|
|
+#define JOINBSS_RATES_1 0x01
|
|
+#define JOINBSS_RATES_2 0x02
|
|
+#define JOINBSS_RATES_5 0x04
|
|
+#define JOINBSS_RATES_11 0x08
|
|
+#define JOINBSS_RATES_22 0x10
|
|
+
|
|
+/* Looks like missing bits are used to indicate 11g rates!
|
|
+** (it follows from the fact that constants below match 1:1 to RATE111_nn)
|
|
+** This was actually seen! Look at that Assoc Request sent by acx111,
|
|
+** it _does_ contain 11g rates in basic set:
|
|
+01:30:20.070772 Beacon (xxx) [1.0* 2.0* 5.5* 11.0* 6.0* 9.0* 12.0* 18.0* 24.0* 36.0* 48.0* 54.0* Mbit] ESS CH: 1
|
|
+01:30:20.074425 Authentication (Open System)-1: Succesful
|
|
+01:30:20.076539 Authentication (Open System)-2:
|
|
+01:30:20.076620 Acknowledgment
|
|
+01:30:20.088546 Assoc Request (xxx) [1.0* 2.0* 5.5* 6.0* 9.0* 11.0* 12.0* 18.0* 24.0* 36.0* 48.0* 54.0* Mbit]
|
|
+01:30:20.122413 Assoc Response AID(1) :: Succesful
|
|
+01:30:20.122679 Acknowledgment
|
|
+01:30:20.173204 Beacon (xxx) [1.0* 2.0* 5.5* 11.0* 6.0* 9.0* 12.0* 18.0* 24.0* 36.0* 48.0* 54.0* Mbit] ESS CH: 1
|
|
+*/
|
|
+#define JOINBSS_RATES_BASIC111_1 0x0001
|
|
+#define JOINBSS_RATES_BASIC111_2 0x0002
|
|
+#define JOINBSS_RATES_BASIC111_5 0x0004
|
|
+#define JOINBSS_RATES_BASIC111_11 0x0020
|
|
+#define JOINBSS_RATES_BASIC111_22 0x0100
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+typedef struct mem_read_write {
|
|
+ u16 addr;
|
|
+ u16 type; /* 0x0 int. RAM / 0xffff MAC reg. / 0x81 PHY RAM / 0x82 PHY reg.; or maybe it's actually 0x30 for MAC? Better verify it by writing and reading back and checking whether the value holds! */
|
|
+ u32 len;
|
|
+ u32 data;
|
|
+} ACX_PACKED mem_read_write_t;
|
|
+
|
|
+typedef struct firmware_image {
|
|
+ u32 chksum;
|
|
+ u32 size;
|
|
+ u8 data[1]; /* the byte array of the actual firmware... */
|
|
+} ACX_PACKED firmware_image_t;
|
|
+
|
|
+typedef struct acx_cmd_radioinit {
|
|
+ u32 offset;
|
|
+ u32 len;
|
|
+} ACX_PACKED acx_cmd_radioinit_t;
|
|
+
|
|
+typedef struct acx100_ie_wep_options {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u16 NumKeys; /* max # of keys */
|
|
+ u8 WEPOption; /* 0 == decrypt default key only, 1 == override decrypt */
|
|
+ u8 Pad; /* used only for acx111 */
|
|
+} ACX_PACKED acx100_ie_wep_options_t;
|
|
+
|
|
+typedef struct ie_dot11WEPDefaultKey {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u8 action;
|
|
+ u8 keySize;
|
|
+ u8 defaultKeyNum;
|
|
+ u8 key[29]; /* check this! was Key[19] */
|
|
+} ACX_PACKED ie_dot11WEPDefaultKey_t;
|
|
+
|
|
+typedef struct acx111WEPDefaultKey {
|
|
+ u8 MacAddr[ETH_ALEN];
|
|
+ u16 action; /* NOTE: this is a u16, NOT a u8!! */
|
|
+ u16 reserved;
|
|
+ u8 keySize;
|
|
+ u8 type;
|
|
+ u8 index;
|
|
+ u8 defaultKeyNum;
|
|
+ u8 counter[6];
|
|
+ u8 key[32]; /* up to 32 bytes (for TKIP!) */
|
|
+} ACX_PACKED acx111WEPDefaultKey_t;
|
|
+
|
|
+typedef struct ie_dot11WEPDefaultKeyID {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u8 KeyID;
|
|
+} ACX_PACKED ie_dot11WEPDefaultKeyID_t;
|
|
+
|
|
+typedef struct acx100_cmd_wep_mgmt {
|
|
+ u8 MacAddr[ETH_ALEN];
|
|
+ u16 Action;
|
|
+ u16 KeySize;
|
|
+ u8 Key[29]; /* 29*8 == 232bits == WEP256 */
|
|
+} ACX_PACKED acx100_cmd_wep_mgmt_t;
|
|
+
|
|
+typedef struct acx_ie_generic {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ union {
|
|
+ /* Association ID IE: just a 16bit value: */
|
|
+ u16 aid;
|
|
+ /* generic member for quick implementation of commands */
|
|
+ u8 bytes[32];
|
|
+ } ACX_PACKED m;
|
|
+} ACX_PACKED acx_ie_generic_t;
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#define CHECK_SIZEOF(type,size) { \
|
|
+ extern void BUG_bad_size_for_##type(void); \
|
|
+ if (sizeof(type)!=(size)) BUG_bad_size_for_##type(); \
|
|
+}
|
|
+
|
|
+static inline void
|
|
+acx_struct_size_check(void)
|
|
+{
|
|
+ CHECK_SIZEOF(txdesc_t, 0x30);
|
|
+ CHECK_SIZEOF(acx100_ie_memconfigoption_t, 24);
|
|
+ CHECK_SIZEOF(acx100_ie_queueconfig_t, 0x20);
|
|
+ CHECK_SIZEOF(acx_joinbss_t, 0x30);
|
|
+ /* IEs need 4 bytes for (type,len) tuple */
|
|
+ CHECK_SIZEOF(acx111_ie_configoption_t, ACX111_IE_CONFIG_OPTIONS_LEN + 4);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Global data
|
|
+*/
|
|
+extern const u8 acx_bitpos2ratebyte[];
|
|
+extern const u8 acx_bitpos2rate100[];
|
|
+
|
|
+extern const u8 acx_reg_domain_ids[];
|
|
+extern const char * const acx_reg_domain_strings[];
|
|
+enum {
|
|
+ acx_reg_domain_ids_len = 8
|
|
+};
|
|
+
|
|
+extern const struct iw_handler_def acx_ioctl_handler_def;
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/common.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/common.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,7388 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+#include <linux/module.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/proc_fs.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <net/iw_handler.h>
|
|
+
|
|
+#include "acx_hw.h"
|
|
+#include "acx.h"
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static client_t *acx_l_sta_list_alloc(acx_device_t *adev);
|
|
+static client_t *acx_l_sta_list_get_from_hash(acx_device_t *adev, const u8 *address);
|
|
+
|
|
+static int acx_l_process_data_frame_master(acx_device_t *adev, rxbuffer_t *rxbuf);
|
|
+static int acx_l_process_data_frame_client(acx_device_t *adev, rxbuffer_t *rxbuf);
|
|
+/* static int acx_l_process_NULL_frame(acx_device_t *adev, rxbuffer_t *rxbuf, int vala); */
|
|
+static int acx_l_process_mgmt_frame(acx_device_t *adev, rxbuffer_t *rxbuf);
|
|
+static void acx_l_process_disassoc_from_sta(acx_device_t *adev, const wlan_fr_disassoc_t *req);
|
|
+static void acx_l_process_disassoc_from_ap(acx_device_t *adev, const wlan_fr_disassoc_t *req);
|
|
+static void acx_l_process_deauth_from_sta(acx_device_t *adev, const wlan_fr_deauthen_t *req);
|
|
+static void acx_l_process_deauth_from_ap(acx_device_t *adev, const wlan_fr_deauthen_t *req);
|
|
+static int acx_l_process_probe_response(acx_device_t *adev, wlan_fr_proberesp_t *req, const rxbuffer_t *rxbuf);
|
|
+static int acx_l_process_assocresp(acx_device_t *adev, const wlan_fr_assocresp_t *req);
|
|
+static int acx_l_process_reassocresp(acx_device_t *adev, const wlan_fr_reassocresp_t *req);
|
|
+static int acx_l_process_authen(acx_device_t *adev, const wlan_fr_authen_t *req);
|
|
+static int acx_l_transmit_assocresp(acx_device_t *adev, const wlan_fr_assocreq_t *req);
|
|
+static int acx_l_transmit_reassocresp(acx_device_t *adev, const wlan_fr_reassocreq_t *req);
|
|
+static int acx_l_transmit_deauthen(acx_device_t *adev, const u8 *addr, u16 reason);
|
|
+static int acx_l_transmit_authen1(acx_device_t *adev);
|
|
+static int acx_l_transmit_authen2(acx_device_t *adev, const wlan_fr_authen_t *req, client_t *clt);
|
|
+static int acx_l_transmit_authen3(acx_device_t *adev, const wlan_fr_authen_t *req);
|
|
+static int acx_l_transmit_authen4(acx_device_t *adev, const wlan_fr_authen_t *req);
|
|
+static int acx_l_transmit_assoc_req(acx_device_t *adev);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#if ACX_DEBUG
|
|
+unsigned int acx_debug /* will add __read_mostly later */ = ACX_DEFAULT_MSG;
|
|
+/* parameter is 'debug', corresponding var is acx_debug */
|
|
+module_param_named(debug, acx_debug, uint, 0);
|
|
+MODULE_PARM_DESC(debug, "Debug level mask (see L_xxx constants)");
|
|
+#endif
|
|
+
|
|
+#ifdef MODULE_LICENSE
|
|
+MODULE_LICENSE("Dual MPL/GPL");
|
|
+#endif
|
|
+/* USB had this: MODULE_AUTHOR("Martin Wawro <martin.wawro AT uni-dortmund.de>"); */
|
|
+MODULE_AUTHOR("ACX100 Open Source Driver development team");
|
|
+MODULE_DESCRIPTION("Driver for TI ACX1xx based wireless cards (CardBus/PCI/USB)");
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+/* Probably a number of acx's intermediate buffers for USB transfers,
|
|
+** not to be confused with number of descriptors in tx/rx rings
|
|
+** (which are not directly accessible to host in USB devices) */
|
|
+#define USB_RX_CNT 10
|
|
+#define USB_TX_CNT 10
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+
|
|
+/* minutes to wait until next radio recalibration: */
|
|
+#define RECALIB_PAUSE 5
|
|
+
|
|
+/* Please keep acx_reg_domain_ids_len in sync... */
|
|
+const u8 acx_reg_domain_ids[acx_reg_domain_ids_len] =
|
|
+ { 0x10, 0x20, 0x30, 0x31, 0x32, 0x40, 0x41, 0x51 };
|
|
+static const u16 reg_domain_channel_masks[acx_reg_domain_ids_len] =
|
|
+#ifdef ACX_ALLOW_ALLCHANNELS
|
|
+ { 0x3fff, 0x07ff, 0x1fff, 0x0600, 0x1e00, 0x2000, 0x3fff, 0x01fc };
|
|
+#else
|
|
+ { 0x07ff, 0x07ff, 0x1fff, 0x0600, 0x1e00, 0x2000, 0x3fff, 0x01fc };
|
|
+#endif
|
|
+const char * const
|
|
+acx_reg_domain_strings[] = {
|
|
+ /* 0 */ " 1-11 FCC (USA)",
|
|
+ /* 1 */ " 1-11 DOC/IC (Canada)",
|
|
+/* BTW: WLAN use in ETSI is regulated by ETSI standard EN 300 328-2 V1.1.2 */
|
|
+ /* 2 */ " 1-13 ETSI (Europe)",
|
|
+ /* 3 */ "10-11 Spain",
|
|
+ /* 4 */ "10-13 France",
|
|
+ /* 5 */ " 14 MKK (Japan)",
|
|
+ /* 6 */ " 1-14 MKK1",
|
|
+ /* 7 */ " 3-9 Israel (not all firmware versions)",
|
|
+ NULL /* needs to remain as last entry */
|
|
+};
|
|
+
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Debugging support
|
|
+*/
|
|
+#ifdef PARANOID_LOCKING
|
|
+static unsigned max_lock_time;
|
|
+static unsigned max_sem_time;
|
|
+
|
|
+void
|
|
+acx_lock_unhold() { max_lock_time = 0; }
|
|
+void
|
|
+acx_sem_unhold() { max_sem_time = 0; }
|
|
+
|
|
+static inline const char*
|
|
+sanitize_str(const char *s)
|
|
+{
|
|
+ const char* t = strrchr(s, '/');
|
|
+ if (t) return t + 1;
|
|
+ return s;
|
|
+}
|
|
+
|
|
+void
|
|
+acx_lock_debug(acx_device_t *adev, const char* where)
|
|
+{
|
|
+ unsigned int count = 100*1000*1000;
|
|
+ where = sanitize_str(where);
|
|
+ while (--count) {
|
|
+ if (!spin_is_locked(&adev->lock)) break;
|
|
+ cpu_relax();
|
|
+ }
|
|
+ if (!count) {
|
|
+ printk(KERN_EMERG "LOCKUP: already taken at %s!\n", adev->last_lock);
|
|
+ BUG();
|
|
+ }
|
|
+ adev->last_lock = where;
|
|
+ rdtscl(adev->lock_time);
|
|
+}
|
|
+void
|
|
+acx_unlock_debug(acx_device_t *adev, const char* where)
|
|
+{
|
|
+#ifdef SMP
|
|
+ if (!spin_is_locked(&adev->lock)) {
|
|
+ where = sanitize_str(where);
|
|
+ printk(KERN_EMERG "STRAY UNLOCK at %s!\n", where);
|
|
+ BUG();
|
|
+ }
|
|
+#endif
|
|
+ if (acx_debug & L_LOCK) {
|
|
+ unsigned long diff;
|
|
+ rdtscl(diff);
|
|
+ diff -= adev->lock_time;
|
|
+ if (diff > max_lock_time) {
|
|
+ where = sanitize_str(where);
|
|
+ printk("max lock hold time %ld CPU ticks from %s "
|
|
+ "to %s\n", diff, adev->last_lock, where);
|
|
+ max_lock_time = diff;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+void
|
|
+acx_down_debug(acx_device_t *adev, const char* where)
|
|
+{
|
|
+ int sem_count;
|
|
+ unsigned long timeout = jiffies + 5*HZ;
|
|
+
|
|
+ where = sanitize_str(where);
|
|
+
|
|
+ for (;;) {
|
|
+ sem_count = atomic_read(&adev->sem.count);
|
|
+ if (sem_count) break;
|
|
+ if (time_after(jiffies, timeout))
|
|
+ break;
|
|
+ msleep(5);
|
|
+ }
|
|
+ if (!sem_count) {
|
|
+ printk(KERN_EMERG "D STATE at %s! last sem at %s\n",
|
|
+ where, adev->last_sem);
|
|
+ dump_stack();
|
|
+ }
|
|
+ adev->last_sem = where;
|
|
+ adev->sem_time = jiffies;
|
|
+ down(&adev->sem);
|
|
+ if (acx_debug & L_LOCK) {
|
|
+ printk("%s: sem_down %d -> %d\n",
|
|
+ where, sem_count, atomic_read(&adev->sem.count));
|
|
+ }
|
|
+}
|
|
+void
|
|
+acx_up_debug(acx_device_t *adev, const char* where)
|
|
+{
|
|
+ int sem_count = atomic_read(&adev->sem.count);
|
|
+ if (sem_count) {
|
|
+ where = sanitize_str(where);
|
|
+ printk(KERN_EMERG "STRAY UP at %s! sem.count=%d\n", where, sem_count);
|
|
+ dump_stack();
|
|
+ }
|
|
+ if (acx_debug & L_LOCK) {
|
|
+ unsigned long diff = jiffies - adev->sem_time;
|
|
+ if (diff > max_sem_time) {
|
|
+ where = sanitize_str(where);
|
|
+ printk("max sem hold time %ld jiffies from %s "
|
|
+ "to %s\n", diff, adev->last_sem, where);
|
|
+ max_sem_time = diff;
|
|
+ }
|
|
+ }
|
|
+ up(&adev->sem);
|
|
+ if (acx_debug & L_LOCK) {
|
|
+ where = sanitize_str(where);
|
|
+ printk("%s: sem_up %d -> %d\n",
|
|
+ where, sem_count, atomic_read(&adev->sem.count));
|
|
+ }
|
|
+}
|
|
+#endif /* PARANOID_LOCKING */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#if ACX_DEBUG > 1
|
|
+
|
|
+static int acx_debug_func_indent;
|
|
+#define DEBUG_TSC 0
|
|
+#define FUNC_INDENT_INCREMENT 2
|
|
+
|
|
+#if DEBUG_TSC
|
|
+#define TIMESTAMP(d) unsigned long d; rdtscl(d)
|
|
+#else
|
|
+#define TIMESTAMP(d) unsigned long d = jiffies
|
|
+#endif
|
|
+
|
|
+static const char
|
|
+spaces[] = " " " "; /* Nx10 spaces */
|
|
+
|
|
+void
|
|
+log_fn_enter(const char *funcname)
|
|
+{
|
|
+ int indent;
|
|
+ TIMESTAMP(d);
|
|
+
|
|
+ indent = acx_debug_func_indent;
|
|
+ if (indent >= sizeof(spaces))
|
|
+ indent = sizeof(spaces)-1;
|
|
+
|
|
+ printk("%08ld %s==> %s\n",
|
|
+ d % 100000000,
|
|
+ spaces + (sizeof(spaces)-1) - indent,
|
|
+ funcname
|
|
+ );
|
|
+
|
|
+ acx_debug_func_indent += FUNC_INDENT_INCREMENT;
|
|
+}
|
|
+void
|
|
+log_fn_exit(const char *funcname)
|
|
+{
|
|
+ int indent;
|
|
+ TIMESTAMP(d);
|
|
+
|
|
+ acx_debug_func_indent -= FUNC_INDENT_INCREMENT;
|
|
+
|
|
+ indent = acx_debug_func_indent;
|
|
+ if (indent >= sizeof(spaces))
|
|
+ indent = sizeof(spaces)-1;
|
|
+
|
|
+ printk("%08ld %s<== %s\n",
|
|
+ d % 100000000,
|
|
+ spaces + (sizeof(spaces)-1) - indent,
|
|
+ funcname
|
|
+ );
|
|
+}
|
|
+void
|
|
+log_fn_exit_v(const char *funcname, int v)
|
|
+{
|
|
+ int indent;
|
|
+ TIMESTAMP(d);
|
|
+
|
|
+ acx_debug_func_indent -= FUNC_INDENT_INCREMENT;
|
|
+
|
|
+ indent = acx_debug_func_indent;
|
|
+ if (indent >= sizeof(spaces))
|
|
+ indent = sizeof(spaces)-1;
|
|
+
|
|
+ printk("%08ld %s<== %s: %08X\n",
|
|
+ d % 100000000,
|
|
+ spaces + (sizeof(spaces)-1) - indent,
|
|
+ funcname,
|
|
+ v
|
|
+ );
|
|
+}
|
|
+#endif /* ACX_DEBUG > 1 */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Basically a msleep with logging
|
|
+*/
|
|
+void
|
|
+acx_s_msleep(int ms)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ msleep(ms);
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Not inlined: it's larger than it seems
|
|
+*/
|
|
+void
|
|
+acx_print_mac(const char *head, const u8 *mac, const char *tail)
|
|
+{
|
|
+ printk("%s"MACSTR"%s", head, MAC(mac), tail);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_get_status_name
|
|
+*/
|
|
+static const char*
|
|
+acx_get_status_name(u16 status)
|
|
+{
|
|
+ static const char * const str[] = {
|
|
+ "STOPPED", "SCANNING", "WAIT_AUTH",
|
|
+ "AUTHENTICATED", "ASSOCIATED", "INVALID??"
|
|
+ };
|
|
+ if (status > VEC_SIZE(str)-1)
|
|
+ status = VEC_SIZE(str)-1;
|
|
+
|
|
+ return str[status];
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_get_packet_type_string
|
|
+*/
|
|
+#if ACX_DEBUG
|
|
+const char*
|
|
+acx_get_packet_type_string(u16 fc)
|
|
+{
|
|
+ static const char * const mgmt_arr[] = {
|
|
+ "MGMT/AssocReq", "MGMT/AssocResp", "MGMT/ReassocReq",
|
|
+ "MGMT/ReassocResp", "MGMT/ProbeReq", "MGMT/ProbeResp",
|
|
+ "MGMT/UNKNOWN", "MGMT/UNKNOWN", "MGMT/Beacon", "MGMT/ATIM",
|
|
+ "MGMT/Disassoc", "MGMT/Authen", "MGMT/Deauthen"
|
|
+ };
|
|
+ static const char * const ctl_arr[] = {
|
|
+ "CTL/PSPoll", "CTL/RTS", "CTL/CTS", "CTL/Ack", "CTL/CFEnd",
|
|
+ "CTL/CFEndCFAck"
|
|
+ };
|
|
+ static const char * const data_arr[] = {
|
|
+ "DATA/DataOnly", "DATA/Data CFAck", "DATA/Data CFPoll",
|
|
+ "DATA/Data CFAck/CFPoll", "DATA/Null", "DATA/CFAck",
|
|
+ "DATA/CFPoll", "DATA/CFAck/CFPoll"
|
|
+ };
|
|
+ const char *str;
|
|
+ u8 fstype = (WF_FC_FSTYPE & fc) >> 4;
|
|
+ u8 ctl;
|
|
+
|
|
+ switch (WF_FC_FTYPE & fc) {
|
|
+ case WF_FTYPE_MGMT:
|
|
+ if (fstype < VEC_SIZE(mgmt_arr))
|
|
+ str = mgmt_arr[fstype];
|
|
+ else
|
|
+ str = "MGMT/UNKNOWN";
|
|
+ break;
|
|
+ case WF_FTYPE_CTL:
|
|
+ ctl = fstype - 0x0a;
|
|
+ if (ctl < VEC_SIZE(ctl_arr))
|
|
+ str = ctl_arr[ctl];
|
|
+ else
|
|
+ str = "CTL/UNKNOWN";
|
|
+ break;
|
|
+ case WF_FTYPE_DATA:
|
|
+ if (fstype < VEC_SIZE(data_arr))
|
|
+ str = data_arr[fstype];
|
|
+ else
|
|
+ str = "DATA/UNKNOWN";
|
|
+ break;
|
|
+ default:
|
|
+ str = "UNKNOWN";
|
|
+ break;
|
|
+ }
|
|
+ return str;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_wlan_reason_str
|
|
+*/
|
|
+static inline const char*
|
|
+acx_wlan_reason_str(u16 reason)
|
|
+{
|
|
+ static const char* const reason_str[] = {
|
|
+ /* 0 */ "?",
|
|
+ /* 1 */ "unspecified",
|
|
+ /* 2 */ "prev auth is not valid",
|
|
+ /* 3 */ "leaving BBS",
|
|
+ /* 4 */ "due to inactivity",
|
|
+ /* 5 */ "AP is busy",
|
|
+ /* 6 */ "got class 2 frame from non-auth'ed STA",
|
|
+ /* 7 */ "got class 3 frame from non-assoc'ed STA",
|
|
+ /* 8 */ "STA has left BSS",
|
|
+ /* 9 */ "assoc without auth is not allowed",
|
|
+ /* 10 */ "bad power setting (802.11h)",
|
|
+ /* 11 */ "bad channel (802.11i)",
|
|
+ /* 12 */ "?",
|
|
+ /* 13 */ "invalid IE",
|
|
+ /* 14 */ "MIC failure",
|
|
+ /* 15 */ "four-way handshake timeout",
|
|
+ /* 16 */ "group key handshake timeout",
|
|
+ /* 17 */ "IE is different",
|
|
+ /* 18 */ "invalid group cipher",
|
|
+ /* 19 */ "invalid pairwise cipher",
|
|
+ /* 20 */ "invalid AKMP",
|
|
+ /* 21 */ "unsupported RSN version",
|
|
+ /* 22 */ "invalid RSN IE cap",
|
|
+ /* 23 */ "802.1x failed",
|
|
+ /* 24 */ "cipher suite rejected"
|
|
+ };
|
|
+ return reason < VEC_SIZE(reason_str) ? reason_str[reason] : "?";
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_cmd_status_str
|
|
+*/
|
|
+const char*
|
|
+acx_cmd_status_str(unsigned int state)
|
|
+{
|
|
+ static const char * const cmd_error_strings[] = {
|
|
+ "Idle",
|
|
+ "Success",
|
|
+ "Unknown Command",
|
|
+ "Invalid Information Element",
|
|
+ "Channel rejected",
|
|
+ "Channel invalid in current regulatory domain",
|
|
+ "MAC invalid",
|
|
+ "Command rejected (read-only information element)",
|
|
+ "Command rejected",
|
|
+ "Already asleep",
|
|
+ "TX in progress",
|
|
+ "Already awake",
|
|
+ "Write only",
|
|
+ "RX in progress",
|
|
+ "Invalid parameter",
|
|
+ "Scan in progress",
|
|
+ "Failed"
|
|
+ };
|
|
+ return state < VEC_SIZE(cmd_error_strings) ?
|
|
+ cmd_error_strings[state] : "?";
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** get_status_string
|
|
+*/
|
|
+static inline const char*
|
|
+get_status_string(unsigned int status)
|
|
+{
|
|
+ /* A bit shortened, but hopefully still understandable */
|
|
+ static const char * const status_str[] = {
|
|
+ /* 0 */ "Successful",
|
|
+ /* 1 */ "Unspecified failure",
|
|
+ /* 2 */ "reserved",
|
|
+ /* 3 */ "reserved",
|
|
+ /* 4 */ "reserved",
|
|
+ /* 5 */ "reserved",
|
|
+ /* 6 */ "reserved",
|
|
+ /* 7 */ "reserved",
|
|
+ /* 8 */ "reserved",
|
|
+ /* 9 */ "reserved",
|
|
+ /*10 */ "Cannot support all requested capabilities in Capability Information field",
|
|
+ /*11 */ "Reassoc denied (reason outside of 802.11b scope)",
|
|
+ /*12 */ "Assoc denied (reason outside of 802.11b scope) -- maybe MAC filtering by peer?",
|
|
+ /*13 */ "Responding station doesnt support specified auth algorithm -- maybe WEP auth Open vs. Restricted?",
|
|
+ /*14 */ "Auth rejected: wrong transaction sequence number",
|
|
+ /*15 */ "Auth rejected: challenge failure",
|
|
+ /*16 */ "Auth rejected: timeout for next frame in sequence",
|
|
+ /*17 */ "Assoc denied: too many STAs on this AP",
|
|
+ /*18 */ "Assoc denied: requesting STA doesnt support all data rates in basic set",
|
|
+ /*19 */ "Assoc denied: requesting STA doesnt support Short Preamble",
|
|
+ /*20 */ "Assoc denied: requesting STA doesnt support PBCC Modulation",
|
|
+ /*21 */ "Assoc denied: requesting STA doesnt support Channel Agility"
|
|
+ /*22 */ "reserved",
|
|
+ /*23 */ "reserved",
|
|
+ /*24 */ "reserved",
|
|
+ /*25 */ "Assoc denied: requesting STA doesnt support Short Slot Time",
|
|
+ /*26 */ "Assoc denied: requesting STA doesnt support DSSS-OFDM"
|
|
+ };
|
|
+
|
|
+ return status_str[status < VEC_SIZE(status_str) ? status : 2];
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void
|
|
+acx_log_bad_eid(wlan_hdr_t* hdr, int len, wlan_ie_t* ie_ptr)
|
|
+{
|
|
+ if (acx_debug & L_ASSOC) {
|
|
+ int offset = (u8*)ie_ptr - (u8*)hdr;
|
|
+ printk("acx: unknown EID %d in mgmt frame at offset %d. IE: ",
|
|
+ ie_ptr->eid, offset);
|
|
+ /* IE len can be bogus, IE can extend past packet end. Oh well... */
|
|
+ acx_dump_bytes(ie_ptr, ie_ptr->len + 2);
|
|
+ if (acx_debug & L_DATA) {
|
|
+ printk("frame (%s): ",
|
|
+ acx_get_packet_type_string(le16_to_cpu(hdr->fc)));
|
|
+ acx_dump_bytes(hdr, len);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#if ACX_DEBUG
|
|
+void
|
|
+acx_dump_bytes(const void *data, int num)
|
|
+{
|
|
+ const u8* ptr = (const u8*)data;
|
|
+
|
|
+ if (num <= 0) {
|
|
+ printk("\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ while (num >= 16) {
|
|
+ printk( "%02X %02X %02X %02X %02X %02X %02X %02X "
|
|
+ "%02X %02X %02X %02X %02X %02X %02X %02X\n",
|
|
+ ptr[0], ptr[1], ptr[2], ptr[3],
|
|
+ ptr[4], ptr[5], ptr[6], ptr[7],
|
|
+ ptr[8], ptr[9], ptr[10], ptr[11],
|
|
+ ptr[12], ptr[13], ptr[14], ptr[15]);
|
|
+ num -= 16;
|
|
+ ptr += 16;
|
|
+ }
|
|
+ if (num > 0) {
|
|
+ while (--num > 0)
|
|
+ printk("%02X ", *ptr++);
|
|
+ printk("%02X\n", *ptr);
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_get_firmware_version
|
|
+*/
|
|
+void
|
|
+acx_s_get_firmware_version(acx_device_t *adev)
|
|
+{
|
|
+ fw_ver_t fw;
|
|
+ u8 hexarr[4] = { 0, 0, 0, 0 };
|
|
+ int hexidx = 0, val = 0;
|
|
+ const char *num;
|
|
+ char c;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ memset(fw.fw_id, 'E', FW_ID_SIZE);
|
|
+ acx_s_interrogate(adev, &fw, ACX1xx_IE_FWREV);
|
|
+ memcpy(adev->firmware_version, fw.fw_id, FW_ID_SIZE);
|
|
+ adev->firmware_version[FW_ID_SIZE] = '\0';
|
|
+
|
|
+ log(L_DEBUG, "fw_ver: fw_id='%s' hw_id=%08X\n",
|
|
+ adev->firmware_version, fw.hw_id);
|
|
+
|
|
+ if (strncmp(fw.fw_id, "Rev ", 4) != 0) {
|
|
+ printk("acx: strange firmware version string "
|
|
+ "'%s', please report\n", adev->firmware_version);
|
|
+ adev->firmware_numver = 0x01090407; /* assume 1.9.4.7 */
|
|
+ } else {
|
|
+ num = &fw.fw_id[4];
|
|
+ while (1) {
|
|
+ c = *num++;
|
|
+ if ((c == '.') || (c == '\0')) {
|
|
+ hexarr[hexidx++] = val;
|
|
+ if ((hexidx > 3) || (c == '\0')) /* end? */
|
|
+ break;
|
|
+ val = 0;
|
|
+ continue;
|
|
+ }
|
|
+ if ((c >= '0') && (c <= '9'))
|
|
+ c -= '0';
|
|
+ else
|
|
+ c = c - 'a' + (char)10;
|
|
+ val = val*16 + c;
|
|
+ }
|
|
+
|
|
+ adev->firmware_numver = (u32)(
|
|
+ (hexarr[0] << 24) | (hexarr[1] << 16)
|
|
+ | (hexarr[2] << 8) | hexarr[3]);
|
|
+ log(L_DEBUG, "firmware_numver 0x%08X\n", adev->firmware_numver);
|
|
+ }
|
|
+ if (IS_ACX111(adev)) {
|
|
+ if (adev->firmware_numver == 0x00010011) {
|
|
+ /* This one does not survive floodpinging */
|
|
+ printk("acx: firmware '%s' is known to be buggy, "
|
|
+ "please upgrade\n", adev->firmware_version);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ adev->firmware_id = le32_to_cpu(fw.hw_id);
|
|
+
|
|
+ /* we're able to find out more detailed chip names now */
|
|
+ switch (adev->firmware_id & 0xffff0000) {
|
|
+ case 0x01010000:
|
|
+ case 0x01020000:
|
|
+ adev->chip_name = "TNETW1100A";
|
|
+ break;
|
|
+ case 0x01030000:
|
|
+ adev->chip_name = "TNETW1100B";
|
|
+ break;
|
|
+ case 0x03000000:
|
|
+ case 0x03010000:
|
|
+ adev->chip_name = "TNETW1130";
|
|
+ break;
|
|
+ case 0x04030000: /* 0x04030101 is TNETW1450 */
|
|
+ adev->chip_name = "TNETW1450";
|
|
+ break;
|
|
+ default:
|
|
+ printk("acx: unknown chip ID 0x%08X, "
|
|
+ "please report\n", adev->firmware_id);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_display_hardware_details
|
|
+**
|
|
+** Displays hw/fw version, radio type etc...
|
|
+*/
|
|
+void
|
|
+acx_display_hardware_details(acx_device_t *adev)
|
|
+{
|
|
+ const char *radio_str, *form_str;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ switch (adev->radio_type) {
|
|
+ case RADIO_MAXIM_0D:
|
|
+ radio_str = "Maxim";
|
|
+ break;
|
|
+ case RADIO_RFMD_11:
|
|
+ radio_str = "RFMD";
|
|
+ break;
|
|
+ case RADIO_RALINK_15:
|
|
+ radio_str = "Ralink";
|
|
+ break;
|
|
+ case RADIO_RADIA_16:
|
|
+ radio_str = "Radia";
|
|
+ break;
|
|
+ case RADIO_UNKNOWN_17:
|
|
+ /* TI seems to have a radio which is
|
|
+ * additionally 802.11a capable, too */
|
|
+ radio_str = "802.11a/b/g radio?! Please report";
|
|
+ break;
|
|
+ case RADIO_UNKNOWN_19:
|
|
+ radio_str = "A radio used by Safecom cards?! Please report";
|
|
+ break;
|
|
+ case RADIO_UNKNOWN_1B:
|
|
+ radio_str = "An unknown radio used by TNETW1450 USB adapters";
|
|
+ break;
|
|
+ default:
|
|
+ radio_str = "UNKNOWN, please report radio type name!";
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ switch (adev->form_factor) {
|
|
+ case 0x00:
|
|
+ form_str = "unspecified";
|
|
+ break;
|
|
+ case 0x01:
|
|
+ form_str = "(mini-)PCI / CardBus";
|
|
+ break;
|
|
+ case 0x02:
|
|
+ form_str = "USB";
|
|
+ break;
|
|
+ case 0x03:
|
|
+ form_str = "Compact Flash";
|
|
+ break;
|
|
+ default:
|
|
+ form_str = "UNKNOWN, please report";
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ printk("acx: === chipset %s, radio type 0x%02X (%s), "
|
|
+ "form factor 0x%02X (%s), EEPROM version 0x%02X: "
|
|
+ "uploaded firmware '%s' ===\n",
|
|
+ adev->chip_name, adev->radio_type, radio_str,
|
|
+ adev->form_factor, form_str, adev->eeprom_version,
|
|
+ adev->firmware_version);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx_e_change_mtu(struct net_device *ndev, int mtu)
|
|
+{
|
|
+ enum {
|
|
+ MIN_MTU = 256,
|
|
+ MAX_MTU = WLAN_DATA_MAXLEN - (ETH_HLEN)
|
|
+ };
|
|
+
|
|
+ if (mtu < MIN_MTU || mtu > MAX_MTU)
|
|
+ return -EINVAL;
|
|
+
|
|
+ ndev->mtu = mtu;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_e_get_stats, acx_e_get_wireless_stats
|
|
+*/
|
|
+struct net_device_stats*
|
|
+acx_e_get_stats(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ return &adev->stats;
|
|
+}
|
|
+
|
|
+struct iw_statistics*
|
|
+acx_e_get_wireless_stats(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ return &adev->wstats;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** maps acx111 tx descr rate field to acx100 one
|
|
+*/
|
|
+const u8
|
|
+acx_bitpos2rate100[] = {
|
|
+ RATE100_1 ,/* 0 */
|
|
+ RATE100_2 ,/* 1 */
|
|
+ RATE100_5 ,/* 2 */
|
|
+ RATE100_2 ,/* 3, should not happen */
|
|
+ RATE100_2 ,/* 4, should not happen */
|
|
+ RATE100_11 ,/* 5 */
|
|
+ RATE100_2 ,/* 6, should not happen */
|
|
+ RATE100_2 ,/* 7, should not happen */
|
|
+ RATE100_22 ,/* 8 */
|
|
+ RATE100_2 ,/* 9, should not happen */
|
|
+ RATE100_2 ,/* 10, should not happen */
|
|
+ RATE100_2 ,/* 11, should not happen */
|
|
+ RATE100_2 ,/* 12, should not happen */
|
|
+ RATE100_2 ,/* 13, should not happen */
|
|
+ RATE100_2 ,/* 14, should not happen */
|
|
+ RATE100_2 ,/* 15, should not happen */
|
|
+};
|
|
+
|
|
+u8
|
|
+acx_rate111to100(u16 r) {
|
|
+ return acx_bitpos2rate100[highest_bit(r)];
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Calculate level like the feb 2003 windows driver seems to do
|
|
+*/
|
|
+static u8
|
|
+acx_signal_to_winlevel(u8 rawlevel)
|
|
+{
|
|
+ /* u8 winlevel = (u8) (0.5 + 0.625 * rawlevel); */
|
|
+ u8 winlevel = ((4 + (rawlevel * 5)) / 8);
|
|
+
|
|
+ if (winlevel > 100)
|
|
+ winlevel = 100;
|
|
+ return winlevel;
|
|
+}
|
|
+
|
|
+u8
|
|
+acx_signal_determine_quality(u8 signal, u8 noise)
|
|
+{
|
|
+ int qual;
|
|
+
|
|
+ qual = (((signal - 30) * 100 / 70) + (100 - noise * 4)) / 2;
|
|
+
|
|
+ if (qual > 100)
|
|
+ return 100;
|
|
+ if (qual < 0)
|
|
+ return 0;
|
|
+ return qual;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Interrogate/configure commands
|
|
+*/
|
|
+
|
|
+/* FIXME: the lengths given here probably aren't always correct.
|
|
+ * They should be gradually replaced by proper "sizeof(acx1XX_ie_XXXX)-4",
|
|
+ * unless the firmware actually expects a different length than the struct length */
|
|
+static const u16
|
|
+acx100_ie_len[] = {
|
|
+ 0,
|
|
+ ACX100_IE_ACX_TIMER_LEN,
|
|
+ sizeof(acx100_ie_powersave_t)-4, /* is that 6 or 8??? */
|
|
+ ACX1xx_IE_QUEUE_CONFIG_LEN,
|
|
+ ACX100_IE_BLOCK_SIZE_LEN,
|
|
+ ACX1xx_IE_MEMORY_CONFIG_OPTIONS_LEN,
|
|
+ ACX1xx_IE_RATE_FALLBACK_LEN,
|
|
+ ACX100_IE_WEP_OPTIONS_LEN,
|
|
+ ACX1xx_IE_MEMORY_MAP_LEN, /* ACX1xx_IE_SSID_LEN, */
|
|
+ 0,
|
|
+ ACX1xx_IE_ASSOC_ID_LEN,
|
|
+ 0,
|
|
+ ACX111_IE_CONFIG_OPTIONS_LEN,
|
|
+ ACX1xx_IE_FWREV_LEN,
|
|
+ ACX1xx_IE_FCS_ERROR_COUNT_LEN,
|
|
+ ACX1xx_IE_MEDIUM_USAGE_LEN,
|
|
+ ACX1xx_IE_RXCONFIG_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ sizeof(fw_stats_t)-4,
|
|
+ 0,
|
|
+ ACX1xx_IE_FEATURE_CONFIG_LEN,
|
|
+ ACX111_IE_KEY_CHOOSE_LEN,
|
|
+ ACX1FF_IE_MISC_CONFIG_TABLE_LEN,
|
|
+ ACX1FF_IE_WONE_CONFIG_LEN,
|
|
+ 0,
|
|
+ ACX1FF_IE_TID_CONFIG_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ ACX1FF_IE_CALIB_ASSESSMENT_LEN,
|
|
+ ACX1FF_IE_BEACON_FILTER_OPTIONS_LEN,
|
|
+ ACX1FF_IE_LOW_RSSI_THRESH_OPT_LEN,
|
|
+ ACX1FF_IE_NOISE_HISTOGRAM_RESULTS_LEN,
|
|
+ 0,
|
|
+ ACX1FF_IE_PACKET_DETECT_THRESH_LEN,
|
|
+ ACX1FF_IE_TX_CONFIG_OPTIONS_LEN,
|
|
+ ACX1FF_IE_CCA_THRESHOLD_LEN,
|
|
+ ACX1FF_IE_EVENT_MASK_LEN,
|
|
+ ACX1FF_IE_DTIM_PERIOD_LEN,
|
|
+ 0,
|
|
+ ACX1FF_IE_ACI_CONFIG_SET_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ ACX1FF_IE_EEPROM_VER_LEN,
|
|
+};
|
|
+
|
|
+static const u16
|
|
+acx100_ie_len_dot11[] = {
|
|
+ 0,
|
|
+ ACX1xx_IE_DOT11_STATION_ID_LEN,
|
|
+ 0,
|
|
+ ACX100_IE_DOT11_BEACON_PERIOD_LEN,
|
|
+ ACX1xx_IE_DOT11_DTIM_PERIOD_LEN,
|
|
+ ACX1xx_IE_DOT11_SHORT_RETRY_LIMIT_LEN,
|
|
+ ACX1xx_IE_DOT11_LONG_RETRY_LIMIT_LEN,
|
|
+ ACX100_IE_DOT11_WEP_DEFAULT_KEY_WRITE_LEN,
|
|
+ ACX1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME_LEN,
|
|
+ 0,
|
|
+ ACX1xx_IE_DOT11_CURRENT_REG_DOMAIN_LEN,
|
|
+ ACX1xx_IE_DOT11_CURRENT_ANTENNA_LEN,
|
|
+ 0,
|
|
+ ACX1xx_IE_DOT11_TX_POWER_LEVEL_LEN,
|
|
+ ACX1xx_IE_DOT11_CURRENT_CCA_MODE_LEN,
|
|
+ ACX100_IE_DOT11_ED_THRESHOLD_LEN,
|
|
+ ACX1xx_IE_DOT11_WEP_DEFAULT_KEY_SET_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+};
|
|
+
|
|
+static const u16
|
|
+acx111_ie_len[] = {
|
|
+ 0,
|
|
+ ACX100_IE_ACX_TIMER_LEN,
|
|
+ sizeof(acx111_ie_powersave_t)-4,
|
|
+ ACX1xx_IE_QUEUE_CONFIG_LEN,
|
|
+ ACX100_IE_BLOCK_SIZE_LEN,
|
|
+ ACX1xx_IE_MEMORY_CONFIG_OPTIONS_LEN,
|
|
+ ACX1xx_IE_RATE_FALLBACK_LEN,
|
|
+ ACX100_IE_WEP_OPTIONS_LEN,
|
|
+ ACX1xx_IE_MEMORY_MAP_LEN, /* ACX1xx_IE_SSID_LEN, */
|
|
+ 0,
|
|
+ ACX1xx_IE_ASSOC_ID_LEN,
|
|
+ 0,
|
|
+ ACX111_IE_CONFIG_OPTIONS_LEN,
|
|
+ ACX1xx_IE_FWREV_LEN,
|
|
+ ACX1xx_IE_FCS_ERROR_COUNT_LEN,
|
|
+ ACX1xx_IE_MEDIUM_USAGE_LEN,
|
|
+ ACX1xx_IE_RXCONFIG_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ sizeof(fw_stats_t)-4,
|
|
+ 0,
|
|
+ ACX1xx_IE_FEATURE_CONFIG_LEN,
|
|
+ ACX111_IE_KEY_CHOOSE_LEN,
|
|
+ ACX1FF_IE_MISC_CONFIG_TABLE_LEN,
|
|
+ ACX1FF_IE_WONE_CONFIG_LEN,
|
|
+ 0,
|
|
+ ACX1FF_IE_TID_CONFIG_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ ACX1FF_IE_CALIB_ASSESSMENT_LEN,
|
|
+ ACX1FF_IE_BEACON_FILTER_OPTIONS_LEN,
|
|
+ ACX1FF_IE_LOW_RSSI_THRESH_OPT_LEN,
|
|
+ ACX1FF_IE_NOISE_HISTOGRAM_RESULTS_LEN,
|
|
+ 0,
|
|
+ ACX1FF_IE_PACKET_DETECT_THRESH_LEN,
|
|
+ ACX1FF_IE_TX_CONFIG_OPTIONS_LEN,
|
|
+ ACX1FF_IE_CCA_THRESHOLD_LEN,
|
|
+ ACX1FF_IE_EVENT_MASK_LEN,
|
|
+ ACX1FF_IE_DTIM_PERIOD_LEN,
|
|
+ 0,
|
|
+ ACX1FF_IE_ACI_CONFIG_SET_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+ ACX1FF_IE_EEPROM_VER_LEN,
|
|
+};
|
|
+
|
|
+static const u16
|
|
+acx111_ie_len_dot11[] = {
|
|
+ 0,
|
|
+ ACX1xx_IE_DOT11_STATION_ID_LEN,
|
|
+ 0,
|
|
+ ACX100_IE_DOT11_BEACON_PERIOD_LEN,
|
|
+ ACX1xx_IE_DOT11_DTIM_PERIOD_LEN,
|
|
+ ACX1xx_IE_DOT11_SHORT_RETRY_LIMIT_LEN,
|
|
+ ACX1xx_IE_DOT11_LONG_RETRY_LIMIT_LEN,
|
|
+ ACX100_IE_DOT11_WEP_DEFAULT_KEY_WRITE_LEN,
|
|
+ ACX1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME_LEN,
|
|
+ 0,
|
|
+ ACX1xx_IE_DOT11_CURRENT_REG_DOMAIN_LEN,
|
|
+ ACX1xx_IE_DOT11_CURRENT_ANTENNA_LEN,
|
|
+ 0,
|
|
+ ACX1xx_IE_DOT11_TX_POWER_LEVEL_LEN,
|
|
+ ACX1xx_IE_DOT11_CURRENT_CCA_MODE_LEN,
|
|
+ ACX100_IE_DOT11_ED_THRESHOLD_LEN,
|
|
+ ACX1xx_IE_DOT11_WEP_DEFAULT_KEY_SET_LEN,
|
|
+ 0,
|
|
+ 0,
|
|
+ 0,
|
|
+};
|
|
+
|
|
+
|
|
+#undef FUNC
|
|
+#define FUNC "configure"
|
|
+#if !ACX_DEBUG
|
|
+int
|
|
+acx_s_configure(acx_device_t *adev, void *pdr, int type)
|
|
+{
|
|
+#else
|
|
+int
|
|
+acx_s_configure_debug(acx_device_t *adev, void *pdr, int type, const char* typestr)
|
|
+{
|
|
+#endif
|
|
+ u16 len;
|
|
+ int res;
|
|
+
|
|
+ if (type < 0x1000)
|
|
+ len = adev->ie_len[type];
|
|
+ else
|
|
+ len = adev->ie_len_dot11[type - 0x1000];
|
|
+
|
|
+ log(L_CTL, FUNC"(type:%s,len:%u)\n", typestr, len);
|
|
+ if (unlikely(!len)) {
|
|
+ log(L_DEBUG, "zero-length type %s?!\n", typestr);
|
|
+ }
|
|
+
|
|
+ ((acx_ie_generic_t *)pdr)->type = cpu_to_le16(type);
|
|
+ ((acx_ie_generic_t *)pdr)->len = cpu_to_le16(len);
|
|
+ res = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIGURE, pdr, len + 4);
|
|
+ if (unlikely(OK != res)) {
|
|
+#if ACX_DEBUG
|
|
+ printk("%s: "FUNC"(type:%s) FAILED\n", adev->ndev->name, typestr);
|
|
+#else
|
|
+ printk("%s: "FUNC"(type:0x%X) FAILED\n", adev->ndev->name, type);
|
|
+#endif
|
|
+ /* dump_stack() is already done in issue_cmd() */
|
|
+ }
|
|
+ return res;
|
|
+}
|
|
+
|
|
+#undef FUNC
|
|
+#define FUNC "interrogate"
|
|
+#if !ACX_DEBUG
|
|
+int
|
|
+acx_s_interrogate(acx_device_t *adev, void *pdr, int type)
|
|
+{
|
|
+#else
|
|
+int
|
|
+acx_s_interrogate_debug(acx_device_t *adev, void *pdr, int type,
|
|
+ const char* typestr)
|
|
+{
|
|
+#endif
|
|
+ u16 len;
|
|
+ int res;
|
|
+
|
|
+ /* FIXME: no check whether this exceeds the array yet.
|
|
+ * We should probably remember the number of entries... */
|
|
+ if (type < 0x1000)
|
|
+ len = adev->ie_len[type];
|
|
+ else
|
|
+ len = adev->ie_len_dot11[type-0x1000];
|
|
+
|
|
+ log(L_CTL, FUNC"(type:%s,len:%u)\n", typestr, len);
|
|
+
|
|
+ ((acx_ie_generic_t *)pdr)->type = cpu_to_le16(type);
|
|
+ ((acx_ie_generic_t *)pdr)->len = cpu_to_le16(len);
|
|
+ res = acx_s_issue_cmd(adev, ACX1xx_CMD_INTERROGATE, pdr, len + 4);
|
|
+ if (unlikely(OK != res)) {
|
|
+#if ACX_DEBUG
|
|
+ printk("%s: "FUNC"(type:%s) FAILED\n", adev->ndev->name, typestr);
|
|
+#else
|
|
+ printk("%s: "FUNC"(type:0x%X) FAILED\n", adev->ndev->name, type);
|
|
+#endif
|
|
+ /* dump_stack() is already done in issue_cmd() */
|
|
+ }
|
|
+ return res;
|
|
+}
|
|
+
|
|
+#if CMD_DISCOVERY
|
|
+void
|
|
+great_inquisitor(acx_device_t *adev)
|
|
+{
|
|
+ static struct {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ /* 0x200 was too large here: */
|
|
+ u8 data[0x100 - 4];
|
|
+ } ACX_PACKED ie;
|
|
+ u16 type;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* 0..0x20, 0x1000..0x1020 */
|
|
+ for (type = 0; type <= 0x1020; type++) {
|
|
+ if (type == 0x21)
|
|
+ type = 0x1000;
|
|
+ ie.type = cpu_to_le16(type);
|
|
+ ie.len = cpu_to_le16(sizeof(ie) - 4);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_INTERROGATE, &ie, sizeof(ie));
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+#ifdef CONFIG_PROC_FS
|
|
+/***********************************************************************
|
|
+** /proc files
|
|
+*/
|
|
+/***********************************************************************
|
|
+** acx_l_proc_output
|
|
+** Generate content for our /proc entry
|
|
+**
|
|
+** Arguments:
|
|
+** buf is a pointer to write output to
|
|
+** adev is the usual pointer to our private struct acx_device
|
|
+** Returns:
|
|
+** number of bytes actually written to buf
|
|
+** Side effects:
|
|
+** none
|
|
+*/
|
|
+static int
|
|
+acx_l_proc_output(char *buf, acx_device_t *adev)
|
|
+{
|
|
+ char *p = buf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "acx driver version:\t\t" ACX_RELEASE "\n"
|
|
+ "Wireless extension version:\t" STRING(WIRELESS_EXT) "\n"
|
|
+ "chip name:\t\t\t%s (0x%08X)\n"
|
|
+ "radio type:\t\t\t0x%02X\n"
|
|
+ "form factor:\t\t\t0x%02X\n"
|
|
+ "EEPROM version:\t\t\t0x%02X\n"
|
|
+ "firmware version:\t\t%s (0x%08X)\n",
|
|
+ adev->chip_name, adev->firmware_id,
|
|
+ adev->radio_type,
|
|
+ adev->form_factor,
|
|
+ adev->eeprom_version,
|
|
+ adev->firmware_version, adev->firmware_numver);
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ struct client *bss = &adev->sta_list[i];
|
|
+ if (!bss->used) continue;
|
|
+ p += sprintf(p, "BSS %u BSSID "MACSTR" ESSID %s channel %u "
|
|
+ "Cap 0x%X SIR %u SNR %u\n",
|
|
+ i, MAC(bss->bssid), (char*)bss->essid, bss->channel,
|
|
+ bss->cap_info, bss->sir, bss->snr);
|
|
+ }
|
|
+ p += sprintf(p, "status:\t\t\t%u (%s)\n",
|
|
+ adev->status, acx_get_status_name(adev->status));
|
|
+
|
|
+ FN_EXIT1(p - buf);
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_s_proc_diag_output(char *buf, acx_device_t *adev)
|
|
+{
|
|
+ char *p = buf;
|
|
+ unsigned long flags;
|
|
+ unsigned int len = 0, partlen;
|
|
+ u32 temp1, temp2;
|
|
+ u8 *st, *st_end;
|
|
+#ifdef __BIG_ENDIAN
|
|
+ u8 *st2;
|
|
+#endif
|
|
+ fw_stats_t *fw_stats;
|
|
+ char *part_str = NULL;
|
|
+ fw_stats_tx_t *tx = NULL;
|
|
+ fw_stats_rx_t *rx = NULL;
|
|
+ fw_stats_dma_t *dma = NULL;
|
|
+ fw_stats_irq_t *irq = NULL;
|
|
+ fw_stats_wep_t *wep = NULL;
|
|
+ fw_stats_pwr_t *pwr = NULL;
|
|
+ fw_stats_mic_t *mic = NULL;
|
|
+ fw_stats_aes_t *aes = NULL;
|
|
+ fw_stats_event_t *evt = NULL;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+#if defined (ACX_MEM)
|
|
+ p = acxmem_s_proc_diag_output(p, adev);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ p = acxpci_s_proc_diag_output(p, adev);
|
|
+#endif
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "\n"
|
|
+ "** network status **\n"
|
|
+ "dev_state_mask 0x%04X\n"
|
|
+ "status %u (%s), "
|
|
+ "mode %u, channel %u, "
|
|
+ "reg_dom_id 0x%02X, reg_dom_chanmask 0x%04X, ",
|
|
+ adev->dev_state_mask,
|
|
+ adev->status, acx_get_status_name(adev->status),
|
|
+ adev->mode, adev->channel,
|
|
+ adev->reg_dom_id, adev->reg_dom_chanmask
|
|
+ );
|
|
+ p += sprintf(p,
|
|
+ "ESSID \"%s\", essid_active %d, essid_len %d, "
|
|
+ "essid_for_assoc \"%s\", nick \"%s\"\n"
|
|
+ "WEP ena %d, restricted %d, idx %d\n",
|
|
+ adev->essid, adev->essid_active, (int)adev->essid_len,
|
|
+ adev->essid_for_assoc, adev->nick,
|
|
+ adev->wep_enabled, adev->wep_restricted,
|
|
+ adev->wep_current_index);
|
|
+ p += sprintf(p, "dev_addr "MACSTR"\n", MAC(adev->dev_addr));
|
|
+ p += sprintf(p, "bssid "MACSTR"\n", MAC(adev->bssid));
|
|
+ p += sprintf(p, "ap_filter "MACSTR"\n", MAC(adev->ap));
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "\n"
|
|
+ "** PHY status **\n"
|
|
+ "tx_disabled %d, tx_level_dbm %d\n" /* "tx_level_val %d, tx_level_auto %d\n" */
|
|
+ "sensitivity %d, antenna 0x%02X, ed_threshold %d, cca %d, preamble_mode %d\n"
|
|
+ "rate_basic 0x%04X, rate_oper 0x%04X\n"
|
|
+ "rts_threshold %d, frag_threshold %d, short_retry %d, long_retry %d\n"
|
|
+ "msdu_lifetime %d, listen_interval %d, beacon_interval %d\n",
|
|
+ adev->tx_disabled, adev->tx_level_dbm, /* adev->tx_level_val, adev->tx_level_auto, */
|
|
+ adev->sensitivity, adev->antenna, adev->ed_threshold, adev->cca, adev->preamble_mode,
|
|
+ adev->rate_basic, adev->rate_oper,
|
|
+ adev->rts_threshold, adev->frag_threshold, adev->short_retry, adev->long_retry,
|
|
+ adev->msdu_lifetime, adev->listen_interval, adev->beacon_interval);
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "\n"
|
|
+ "** Firmware **\n"
|
|
+ "NOTE: version dependent statistics layout, "
|
|
+ "please report if you suspect wrong parsing!\n"
|
|
+ "\n"
|
|
+ "version \"%s\"\n", adev->firmware_version);
|
|
+
|
|
+ /* TODO: may replace kmalloc/memset with kzalloc once
|
|
+ * Linux 2.6.14 is widespread */
|
|
+ fw_stats = kmalloc(sizeof(*fw_stats), GFP_KERNEL);
|
|
+ if (!fw_stats) {
|
|
+ FN_EXIT1(0);
|
|
+ return 0;
|
|
+ }
|
|
+ memset(fw_stats, 0, sizeof(*fw_stats));
|
|
+
|
|
+ st = (u8 *)fw_stats;
|
|
+
|
|
+ part_str = "statistics query command";
|
|
+
|
|
+ if (OK != acx_s_interrogate(adev, st, ACX1xx_IE_FIRMWARE_STATISTICS))
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ st += sizeof(u16);
|
|
+ len = *(u16 *)st;
|
|
+
|
|
+ if (len > sizeof(*fw_stats)) {
|
|
+ p += sprintf(p,
|
|
+ "firmware version with bigger fw_stats struct detected\n"
|
|
+ "(%u vs. %u), please report\n", len, sizeof(fw_stats_t));
|
|
+ if (len > sizeof(*fw_stats)) {
|
|
+ p += sprintf(p, "struct size exceeded allocation!\n");
|
|
+ len = sizeof(*fw_stats);
|
|
+ }
|
|
+ }
|
|
+ st += sizeof(u16);
|
|
+ st_end = st - 2*sizeof(u16) + len;
|
|
+
|
|
+#ifdef __BIG_ENDIAN
|
|
+ /* let's make one bold assumption here:
|
|
+ * (hopefully!) *all* statistics fields are u32 only,
|
|
+ * thus if we need to make endianness corrections
|
|
+ * we can simply do them in one go, in advance */
|
|
+ st2 = (u8 *)fw_stats;
|
|
+ for (temp1 = 0; temp1 < len; temp1 += 4, st2 += 4)
|
|
+ *(u32 *)st2 = le32_to_cpu(*(u32 *)st2);
|
|
+#endif
|
|
+
|
|
+ part_str = "Rx/Tx";
|
|
+
|
|
+ /* directly at end of a struct part? --> no error! */
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ tx = (fw_stats_tx_t *)st;
|
|
+ st += sizeof(fw_stats_tx_t);
|
|
+ rx = (fw_stats_rx_t *)st;
|
|
+ st += sizeof(fw_stats_rx_t);
|
|
+ partlen = sizeof(fw_stats_tx_t) + sizeof(fw_stats_rx_t);
|
|
+
|
|
+ if (IS_ACX100(adev)) {
|
|
+ /* at least ACX100 PCI F/W 1.9.8.b
|
|
+ * and ACX100 USB F/W 1.0.7-USB
|
|
+ * don't have those two fields... */
|
|
+ st -= 2*sizeof(u32);
|
|
+
|
|
+ /* our parsing doesn't quite match this firmware yet,
|
|
+ * log failure */
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+ temp1 = temp2 = 999999999;
|
|
+ } else {
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+ temp1 = rx->rx_aci_events;
|
|
+ temp2 = rx->rx_aci_resets;
|
|
+ }
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " tx_desc_overfl %u\n"
|
|
+ " rx_OutOfMem %u, rx_hdr_overfl %u, rx_hw_stuck %u\n"
|
|
+ " rx_dropped_frame %u, rx_frame_ptr_err %u, rx_xfr_hint_trig %u\n"
|
|
+ " rx_aci_events %u, rx_aci_resets %u\n",
|
|
+ part_str,
|
|
+ tx->tx_desc_of,
|
|
+ rx->rx_oom,
|
|
+ rx->rx_hdr_of,
|
|
+ rx->rx_hw_stuck,
|
|
+ rx->rx_dropped_frame,
|
|
+ rx->rx_frame_ptr_err,
|
|
+ rx->rx_xfr_hint_trig,
|
|
+ temp1,
|
|
+ temp2);
|
|
+
|
|
+ part_str = "DMA";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ dma = (fw_stats_dma_t *)st;
|
|
+ partlen = sizeof(fw_stats_dma_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " rx_dma_req %u, rx_dma_err %u, tx_dma_req %u, tx_dma_err %u\n",
|
|
+ part_str,
|
|
+ dma->rx_dma_req,
|
|
+ dma->rx_dma_err,
|
|
+ dma->tx_dma_req,
|
|
+ dma->tx_dma_err);
|
|
+
|
|
+ part_str = "IRQ";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ irq = (fw_stats_irq_t *)st;
|
|
+ partlen = sizeof(fw_stats_irq_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " cmd_cplt %u, fiq %u\n"
|
|
+ " rx_hdrs %u, rx_cmplt %u, rx_mem_overfl %u, rx_rdys %u\n"
|
|
+ " irqs %u, tx_procs %u, decrypt_done %u\n"
|
|
+ " dma_0_done %u, dma_1_done %u, tx_exch_complet %u\n"
|
|
+ " commands %u, rx_procs %u, hw_pm_mode_changes %u\n"
|
|
+ " host_acks %u, pci_pm %u, acm_wakeups %u\n",
|
|
+ part_str,
|
|
+ irq->cmd_cplt,
|
|
+ irq->fiq,
|
|
+ irq->rx_hdrs,
|
|
+ irq->rx_cmplt,
|
|
+ irq->rx_mem_of,
|
|
+ irq->rx_rdys,
|
|
+ irq->irqs,
|
|
+ irq->tx_procs,
|
|
+ irq->decrypt_done,
|
|
+ irq->dma_0_done,
|
|
+ irq->dma_1_done,
|
|
+ irq->tx_exch_complet,
|
|
+ irq->commands,
|
|
+ irq->rx_procs,
|
|
+ irq->hw_pm_mode_changes,
|
|
+ irq->host_acks,
|
|
+ irq->pci_pm,
|
|
+ irq->acm_wakeups);
|
|
+
|
|
+ part_str = "WEP";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ wep = (fw_stats_wep_t *)st;
|
|
+ partlen = sizeof(fw_stats_wep_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (
|
|
+ (IS_PCI(adev) && IS_ACX100(adev))
|
|
+ || (IS_USB(adev) && IS_ACX100(adev))
|
|
+ || (IS_MEM(adev) && IS_ACX100(adev))
|
|
+ ) {
|
|
+ /* at least ACX100 PCI F/W 1.9.8.b,
|
|
+ * ACX100 USB F/W 1.0.7-USB
|
|
+ * and ACX100 Generic Slave F/W 1.10.7.K
|
|
+ * don't have those two fields...
|
|
+ */
|
|
+ st -= 2*sizeof(u32);
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+ temp1 = temp2 = 999999999;
|
|
+ } else {
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+ temp1 = wep->wep_pkt_decrypt;
|
|
+ temp2 = wep->wep_decrypt_irqs;
|
|
+ }
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " wep_key_count %u, wep_default_key_count %u, dot11_def_key_mib %u\n"
|
|
+ " wep_key_not_found %u, wep_decrypt_fail %u\n"
|
|
+ " wep_pkt_decrypt %u, wep_decrypt_irqs %u\n",
|
|
+ part_str,
|
|
+ wep->wep_key_count,
|
|
+ wep->wep_default_key_count,
|
|
+ wep->dot11_def_key_mib,
|
|
+ wep->wep_key_not_found,
|
|
+ wep->wep_decrypt_fail,
|
|
+ temp1,
|
|
+ temp2);
|
|
+
|
|
+ part_str = "power";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ pwr = (fw_stats_pwr_t *)st;
|
|
+ partlen = sizeof(fw_stats_pwr_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " tx_start_ctr %u, no_ps_tx_too_short %u\n"
|
|
+ " rx_start_ctr %u, no_ps_rx_too_short %u\n"
|
|
+ " lppd_started %u\n"
|
|
+ " no_lppd_too_noisy %u, no_lppd_too_short %u, no_lppd_matching_frame %u\n",
|
|
+ part_str,
|
|
+ pwr->tx_start_ctr,
|
|
+ pwr->no_ps_tx_too_short,
|
|
+ pwr->rx_start_ctr,
|
|
+ pwr->no_ps_rx_too_short,
|
|
+ pwr->lppd_started,
|
|
+ pwr->no_lppd_too_noisy,
|
|
+ pwr->no_lppd_too_short,
|
|
+ pwr->no_lppd_matching_frame);
|
|
+
|
|
+ part_str = "MIC";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ mic = (fw_stats_mic_t *)st;
|
|
+ partlen = sizeof(fw_stats_mic_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " mic_rx_pkts %u, mic_calc_fail %u\n",
|
|
+ part_str,
|
|
+ mic->mic_rx_pkts,
|
|
+ mic->mic_calc_fail);
|
|
+
|
|
+ part_str = "AES";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ aes = (fw_stats_aes_t *)st;
|
|
+ partlen = sizeof(fw_stats_aes_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " aes_enc_fail %u, aes_dec_fail %u\n"
|
|
+ " aes_enc_pkts %u, aes_dec_pkts %u\n"
|
|
+ " aes_enc_irq %u, aes_dec_irq %u\n",
|
|
+ part_str,
|
|
+ aes->aes_enc_fail,
|
|
+ aes->aes_dec_fail,
|
|
+ aes->aes_enc_pkts,
|
|
+ aes->aes_dec_pkts,
|
|
+ aes->aes_enc_irq,
|
|
+ aes->aes_dec_irq);
|
|
+
|
|
+ part_str = "event";
|
|
+
|
|
+ if (st == st_end)
|
|
+ goto fw_stats_end;
|
|
+
|
|
+ evt = (fw_stats_event_t *)st;
|
|
+ partlen = sizeof(fw_stats_event_t);
|
|
+ st += partlen;
|
|
+
|
|
+ if (st > st_end)
|
|
+ goto fw_stats_fail;
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "%s:\n"
|
|
+ " heartbeat %u, calibration %u\n"
|
|
+ " rx_mismatch %u, rx_mem_empty %u, rx_pool %u\n"
|
|
+ " oom_late %u\n"
|
|
+ " phy_tx_err %u, tx_stuck %u\n",
|
|
+ part_str,
|
|
+ evt->heartbeat,
|
|
+ evt->calibration,
|
|
+ evt->rx_mismatch,
|
|
+ evt->rx_mem_empty,
|
|
+ evt->rx_pool,
|
|
+ evt->oom_late,
|
|
+ evt->phy_tx_err,
|
|
+ evt->tx_stuck);
|
|
+
|
|
+ if (st < st_end)
|
|
+ goto fw_stats_bigger;
|
|
+
|
|
+ goto fw_stats_end;
|
|
+
|
|
+fw_stats_fail:
|
|
+ st -= partlen;
|
|
+ p += sprintf(p,
|
|
+ "failed at %s part (size %u), offset %u (struct size %u), "
|
|
+ "please report\n", part_str, partlen,
|
|
+ (int)st - (int)fw_stats, len);
|
|
+
|
|
+fw_stats_bigger:
|
|
+ for (; st < st_end; st += 4)
|
|
+ p += sprintf(p,
|
|
+ "UNKN%3d: %u\n", (int)st - (int)fw_stats, *(u32 *)st);
|
|
+
|
|
+fw_stats_end:
|
|
+ kfree(fw_stats);
|
|
+
|
|
+ FN_EXIT1(p - buf);
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_s_proc_phy_output(char *buf, acx_device_t *adev)
|
|
+{
|
|
+ char *p = buf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /*
|
|
+ if (RADIO_RFMD_11 != adev->radio_type) {
|
|
+ printk("sorry, not yet adapted for radio types "
|
|
+ "other than RFMD, please verify "
|
|
+ "PHY size etc. first!\n");
|
|
+ goto end;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ /* The PHY area is only 0x80 bytes long; further pages after that
|
|
+ * only have some page number registers with altered value,
|
|
+ * all other registers remain the same. */
|
|
+ for (i = 0; i < 0x80; i++) {
|
|
+ acx_s_read_phy_reg(adev, i, p++);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(p - buf);
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_e_read_proc_XXXX
|
|
+** Handle our /proc entry
|
|
+**
|
|
+** Arguments:
|
|
+** standard kernel read_proc interface
|
|
+** Returns:
|
|
+** number of bytes written to buf
|
|
+** Side effects:
|
|
+** none
|
|
+*/
|
|
+static int
|
|
+acx_e_read_proc(char *buf, char **start, off_t offset, int count,
|
|
+ int *eof, void *data)
|
|
+{
|
|
+ acx_device_t *adev = (acx_device_t*)data;
|
|
+ unsigned long flags;
|
|
+ int length;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ acx_lock(adev, flags);
|
|
+ /* fill buf */
|
|
+ length = acx_l_proc_output(buf, adev);
|
|
+ acx_unlock(adev, flags);
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ /* housekeeping */
|
|
+ if (length <= offset + count)
|
|
+ *eof = 1;
|
|
+ *start = buf + offset;
|
|
+ length -= offset;
|
|
+ if (length > count)
|
|
+ length = count;
|
|
+ if (length < 0)
|
|
+ length = 0;
|
|
+ FN_EXIT1(length);
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static char _buf[32768];
|
|
+static int
|
|
+acx_e_read_proc_diag(char *buf, char **start, off_t offset, int count,
|
|
+ int *eof, void *data)
|
|
+{
|
|
+ acx_device_t *adev = (acx_device_t*)data;
|
|
+ int length;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ /* fill buf */
|
|
+ length = acx_s_proc_diag_output(_buf, adev);
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ memcpy(buf, _buf + offset, count);
|
|
+
|
|
+ /* housekeeping */
|
|
+ if (length <= offset + count)
|
|
+ *eof = 1;
|
|
+ *start = count;
|
|
+ length -= offset;
|
|
+ if (length > count)
|
|
+ length = count;
|
|
+ if (length < 0)
|
|
+ length = 0;
|
|
+ FN_EXIT1(length);
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_e_read_proc_eeprom(char *buf, char **start, off_t offset, int count,
|
|
+ int *eof, void *data)
|
|
+{
|
|
+ acx_device_t *adev = (acx_device_t*)data;
|
|
+ int length;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* fill buf */
|
|
+ length = 0;
|
|
+#if defined (ACX_MEM)
|
|
+ acx_sem_lock(adev);
|
|
+ length = acxmem_proc_eeprom_output(buf, adev);
|
|
+ acx_sem_unlock(adev);
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ acx_sem_lock(adev);
|
|
+ length = acxpci_proc_eeprom_output(buf, adev);
|
|
+ acx_sem_unlock(adev);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* housekeeping */
|
|
+ if (length <= offset + count)
|
|
+ *eof = 1;
|
|
+ *start = buf + offset;
|
|
+ length -= offset;
|
|
+ if (length > count)
|
|
+ length = count;
|
|
+ if (length < 0)
|
|
+ length = 0;
|
|
+ FN_EXIT1(length);
|
|
+ return length;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_e_read_proc_phy(char *buf, char **start, off_t offset, int count,
|
|
+ int *eof, void *data)
|
|
+{
|
|
+ acx_device_t *adev = (acx_device_t*)data;
|
|
+ int length;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ /* fill buf */
|
|
+ length = acx_s_proc_phy_output(buf, adev);
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ /* housekeeping */
|
|
+ if (length <= offset + count)
|
|
+ *eof = 1;
|
|
+ *start = buf + offset;
|
|
+ length -= offset;
|
|
+ if (length > count)
|
|
+ length = count;
|
|
+ if (length < 0)
|
|
+ length = 0;
|
|
+ FN_EXIT1(length);
|
|
+ return length;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** /proc files registration
|
|
+*/
|
|
+static const char * const
|
|
+proc_files[] = { "", "_diag", "_eeprom", "_phy" };
|
|
+
|
|
+static read_proc_t * const
|
|
+proc_funcs[] = {
|
|
+ acx_e_read_proc,
|
|
+ acx_e_read_proc_diag,
|
|
+ acx_e_read_proc_eeprom,
|
|
+ acx_e_read_proc_phy
|
|
+};
|
|
+
|
|
+static int
|
|
+manage_proc_entries(const struct net_device *ndev, int remove)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev((struct net_device *)ndev);
|
|
+ char procbuf[80];
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(proc_files); i++) {
|
|
+ snprintf(procbuf, sizeof(procbuf),
|
|
+ "driver/acx_%s%s", ndev->name, proc_files[i]);
|
|
+ log(L_INIT, "%sing /proc entry %s\n",
|
|
+ remove ? "remov" : "creat", procbuf);
|
|
+ if (!remove) {
|
|
+ if (!create_proc_read_entry(procbuf, 0, 0, proc_funcs[i], adev)) {
|
|
+ printk("acx: cannot register /proc entry %s\n", procbuf);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+ } else {
|
|
+ remove_proc_entry(procbuf, NULL);
|
|
+ }
|
|
+ }
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+int
|
|
+acx_proc_register_entries(const struct net_device *ndev)
|
|
+{
|
|
+ return manage_proc_entries(ndev, 0);
|
|
+}
|
|
+
|
|
+int
|
|
+acx_proc_unregister_entries(const struct net_device *ndev)
|
|
+{
|
|
+ return manage_proc_entries(ndev, 1);
|
|
+}
|
|
+#endif /* CONFIG_PROC_FS */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_cmd_join_bssid
|
|
+**
|
|
+** Common code for both acx100 and acx111.
|
|
+*/
|
|
+/* NB: does NOT match RATE100_nn but matches ACX[111]_SCAN_RATE_n */
|
|
+static const u8
|
|
+bitpos2genframe_txrate[] = {
|
|
+ 10, /* 0. 1 Mbit/s */
|
|
+ 20, /* 1. 2 Mbit/s */
|
|
+ 55, /* 2. 5.5 Mbit/s */
|
|
+ 0x0B, /* 3. 6 Mbit/s */
|
|
+ 0x0F, /* 4. 9 Mbit/s */
|
|
+ 110, /* 5. 11 Mbit/s */
|
|
+ 0x0A, /* 6. 12 Mbit/s */
|
|
+ 0x0E, /* 7. 18 Mbit/s */
|
|
+ 220, /* 8. 22 Mbit/s */
|
|
+ 0x09, /* 9. 24 Mbit/s */
|
|
+ 0x0D, /* 10. 36 Mbit/s */
|
|
+ 0x08, /* 11. 48 Mbit/s */
|
|
+ 0x0C, /* 12. 54 Mbit/s */
|
|
+ 10, /* 13. 1 Mbit/s, should never happen */
|
|
+ 10, /* 14. 1 Mbit/s, should never happen */
|
|
+ 10, /* 15. 1 Mbit/s, should never happen */
|
|
+};
|
|
+
|
|
+/* Looks scary, eh?
|
|
+** Actually, each one compiled into one AND and one SHIFT,
|
|
+** 31 bytes in x86 asm (more if uints are replaced by u16/u8) */
|
|
+static inline unsigned int
|
|
+rate111to5bits(unsigned int rate)
|
|
+{
|
|
+ return (rate & 0x7)
|
|
+ | ( (rate & RATE111_11) / (RATE111_11/JOINBSS_RATES_11) )
|
|
+ | ( (rate & RATE111_22) / (RATE111_22/JOINBSS_RATES_22) )
|
|
+ ;
|
|
+}
|
|
+
|
|
+static void
|
|
+acx_s_cmd_join_bssid(acx_device_t *adev, const u8 *bssid)
|
|
+{
|
|
+ acx_joinbss_t tmp;
|
|
+ int dtim_interval;
|
|
+ int i;
|
|
+
|
|
+ if (mac_is_zero(bssid))
|
|
+ return;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ dtim_interval = (ACX_MODE_0_ADHOC == adev->mode) ?
|
|
+ 1 : adev->dtim_interval;
|
|
+
|
|
+ memset(&tmp, 0, sizeof(tmp));
|
|
+
|
|
+ for (i = 0; i < ETH_ALEN; i++) {
|
|
+ tmp.bssid[i] = bssid[ETH_ALEN-1 - i];
|
|
+ }
|
|
+
|
|
+ tmp.beacon_interval = cpu_to_le16(adev->beacon_interval);
|
|
+
|
|
+ /* Basic rate set. Control frame responses (such as ACK or CTS frames)
|
|
+ ** are sent with one of these rates */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* It was experimentally determined that rates_basic
|
|
+ ** can take 11g rates as well, not only rates
|
|
+ ** defined with JOINBSS_RATES_BASIC111_nnn.
|
|
+ ** Just use RATE111_nnn constants... */
|
|
+ tmp.u.acx111.dtim_interval = dtim_interval;
|
|
+ tmp.u.acx111.rates_basic = cpu_to_le16(adev->rate_basic);
|
|
+ log(L_ASSOC, "rates_basic:%04X, rates_supported:%04X\n",
|
|
+ adev->rate_basic, adev->rate_oper);
|
|
+ } else {
|
|
+ tmp.u.acx100.dtim_interval = dtim_interval;
|
|
+ tmp.u.acx100.rates_basic = rate111to5bits(adev->rate_basic);
|
|
+ tmp.u.acx100.rates_supported = rate111to5bits(adev->rate_oper);
|
|
+ log(L_ASSOC, "rates_basic:%04X->%02X, "
|
|
+ "rates_supported:%04X->%02X\n",
|
|
+ adev->rate_basic, tmp.u.acx100.rates_basic,
|
|
+ adev->rate_oper, tmp.u.acx100.rates_supported);
|
|
+ }
|
|
+
|
|
+ /* Setting up how Beacon, Probe Response, RTS, and PS-Poll frames
|
|
+ ** will be sent (rate/modulation/preamble) */
|
|
+ tmp.u.txrate.genfrm_txrate = bitpos2genframe_txrate[lowest_bit(adev->rate_basic)];
|
|
+ tmp.genfrm_mod_pre = 0; /* FIXME: was = adev->capab_short (which was always 0); */
|
|
+ /* we can use short pre *if* all peers can understand it */
|
|
+ /* FIXME #2: we need to correctly set PBCC/OFDM bits here too */
|
|
+
|
|
+ /* we switch fw to STA mode in MONITOR mode, it seems to be
|
|
+ ** the only mode where fw does not emit beacons by itself
|
|
+ ** but allows us to send anything (we really want to retain
|
|
+ ** ability to tx arbitrary frames in MONITOR mode)
|
|
+ */
|
|
+ tmp.macmode = (adev->mode != ACX_MODE_MONITOR ? adev->mode : ACX_MODE_2_STA);
|
|
+ tmp.channel = adev->channel;
|
|
+ tmp.essid_len = adev->essid_len;
|
|
+ /* NOTE: the code memcpy'd essid_len + 1 before, which is WRONG! */
|
|
+ memcpy(tmp.essid, adev->essid, tmp.essid_len);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_JOIN, &tmp, tmp.essid_len + 0x11);
|
|
+
|
|
+ log(L_ASSOC|L_DEBUG, "BSS_Type = %u\n", tmp.macmode);
|
|
+ acxlog_mac(L_ASSOC|L_DEBUG, "JoinBSSID MAC:", adev->bssid, "\n");
|
|
+
|
|
+ acx_update_capabilities(adev);
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_cmd_start_scan
|
|
+**
|
|
+** Issue scan command to the hardware
|
|
+**
|
|
+** unified function for both ACX111 and ACX100
|
|
+*/
|
|
+static void
|
|
+acx_s_scan_chan(acx_device_t *adev)
|
|
+{
|
|
+ union {
|
|
+ acx111_scan_t acx111;
|
|
+ acx100_scan_t acx100;
|
|
+ } s;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ memset(&s, 0, sizeof(s));
|
|
+
|
|
+ /* first common positions... */
|
|
+
|
|
+ s.acx111.count = cpu_to_le16(adev->scan_count);
|
|
+ s.acx111.rate = adev->scan_rate;
|
|
+ s.acx111.options = adev->scan_mode;
|
|
+ s.acx111.chan_duration = cpu_to_le16(adev->scan_duration);
|
|
+ s.acx111.max_probe_delay = cpu_to_le16(adev->scan_probe_delay);
|
|
+
|
|
+ /* ...then differences */
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ s.acx111.channel_list_select = 0; /* scan every allowed channel */
|
|
+ /*s.acx111.channel_list_select = 1;*/ /* scan given channels */
|
|
+ /*s.acx111.modulation = 0x40;*/ /* long preamble? OFDM? -> only for active scan */
|
|
+ s.acx111.modulation = 0;
|
|
+ /*s.acx111.channel_list[0] = 6;
|
|
+ s.acx111.channel_list[1] = 4;*/
|
|
+ } else {
|
|
+ s.acx100.start_chan = cpu_to_le16(1);
|
|
+ s.acx100.flags = cpu_to_le16(0x8000);
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_SCAN, &s, sizeof(s));
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+acx_s_cmd_start_scan(acx_device_t *adev)
|
|
+{
|
|
+ /* time_before check is 'just in case' thing */
|
|
+ if (!(adev->irq_status & HOST_INT_SCAN_COMPLETE)
|
|
+ && time_before(jiffies, adev->scan_start + 10*HZ)
|
|
+ ) {
|
|
+ log(L_INIT, "start_scan: seems like previous scan "
|
|
+ "is still running. Not starting anew. Please report\n");
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ log(L_INIT, "starting radio scan\n");
|
|
+ /* remember that fw is commanded to do scan */
|
|
+ adev->scan_start = jiffies;
|
|
+ CLEAR_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE);
|
|
+ /* issue it */
|
|
+ acx_s_scan_chan(adev);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx111 feature config
|
|
+*/
|
|
+static int
|
|
+acx111_s_get_feature_config(acx_device_t *adev,
|
|
+ u32 *feature_options, u32 *data_flow_options)
|
|
+{
|
|
+ struct acx111_ie_feature_config feat;
|
|
+
|
|
+ if (!IS_ACX111(adev)) {
|
|
+ return NOT_OK;
|
|
+ }
|
|
+
|
|
+ memset(&feat, 0, sizeof(feat));
|
|
+
|
|
+ if (OK != acx_s_interrogate(adev, &feat, ACX1xx_IE_FEATURE_CONFIG)) {
|
|
+ return NOT_OK;
|
|
+ }
|
|
+ log(L_DEBUG,
|
|
+ "got Feature option:0x%X, DataFlow option: 0x%X\n",
|
|
+ feat.feature_options,
|
|
+ feat.data_flow_options);
|
|
+
|
|
+ if (feature_options)
|
|
+ *feature_options = le32_to_cpu(feat.feature_options);
|
|
+ if (data_flow_options)
|
|
+ *data_flow_options = le32_to_cpu(feat.data_flow_options);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx111_s_set_feature_config(acx_device_t *adev,
|
|
+ u32 feature_options, u32 data_flow_options,
|
|
+ unsigned int mode /* 0 == remove, 1 == add, 2 == set */)
|
|
+{
|
|
+ struct acx111_ie_feature_config feat;
|
|
+
|
|
+ if (!IS_ACX111(adev)) {
|
|
+ return NOT_OK;
|
|
+ }
|
|
+
|
|
+ if ((mode < 0) || (mode > 2))
|
|
+ return NOT_OK;
|
|
+
|
|
+ if (mode != 2)
|
|
+ /* need to modify old data */
|
|
+ acx111_s_get_feature_config(adev, &feat.feature_options, &feat.data_flow_options);
|
|
+ else {
|
|
+ /* need to set a completely new value */
|
|
+ feat.feature_options = 0;
|
|
+ feat.data_flow_options = 0;
|
|
+ }
|
|
+
|
|
+ if (mode == 0) { /* remove */
|
|
+ CLEAR_BIT(feat.feature_options, cpu_to_le32(feature_options));
|
|
+ CLEAR_BIT(feat.data_flow_options, cpu_to_le32(data_flow_options));
|
|
+ } else { /* add or set */
|
|
+ SET_BIT(feat.feature_options, cpu_to_le32(feature_options));
|
|
+ SET_BIT(feat.data_flow_options, cpu_to_le32(data_flow_options));
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG,
|
|
+ "old: feature 0x%08X dataflow 0x%08X. mode: %u\n"
|
|
+ "new: feature 0x%08X dataflow 0x%08X\n",
|
|
+ feature_options, data_flow_options, mode,
|
|
+ le32_to_cpu(feat.feature_options),
|
|
+ le32_to_cpu(feat.data_flow_options));
|
|
+
|
|
+ if (OK != acx_s_configure(adev, &feat, ACX1xx_IE_FEATURE_CONFIG)) {
|
|
+ return NOT_OK;
|
|
+ }
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx111_s_feature_off(acx_device_t *adev, u32 f, u32 d)
|
|
+{
|
|
+ return acx111_s_set_feature_config(adev, f, d, 0);
|
|
+}
|
|
+static inline int
|
|
+acx111_s_feature_on(acx_device_t *adev, u32 f, u32 d)
|
|
+{
|
|
+ return acx111_s_set_feature_config(adev, f, d, 1);
|
|
+}
|
|
+static inline int
|
|
+acx111_s_feature_set(acx_device_t *adev, u32 f, u32 d)
|
|
+{
|
|
+ return acx111_s_set_feature_config(adev, f, d, 2);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx100_s_init_memory_pools
|
|
+*/
|
|
+static int
|
|
+acx100_s_init_memory_pools(acx_device_t *adev, const acx_ie_memmap_t *mmt)
|
|
+{
|
|
+ acx100_ie_memblocksize_t MemoryBlockSize;
|
|
+ acx100_ie_memconfigoption_t MemoryConfigOption;
|
|
+ int TotalMemoryBlocks;
|
|
+ int RxBlockNum;
|
|
+ int TotalRxBlockSize;
|
|
+ int TxBlockNum;
|
|
+ int TotalTxBlockSize;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* Let's see if we can follow this:
|
|
+ first we select our memory block size (which I think is
|
|
+ completely arbitrary) */
|
|
+ MemoryBlockSize.size = cpu_to_le16(adev->memblocksize);
|
|
+
|
|
+ /* Then we alert the card to our decision of block size */
|
|
+ if (OK != acx_s_configure(adev, &MemoryBlockSize, ACX100_IE_BLOCK_SIZE)) {
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* We figure out how many total blocks we can create, using
|
|
+ the block size we chose, and the beginning and ending
|
|
+ memory pointers, i.e.: end-start/size */
|
|
+ TotalMemoryBlocks = (le32_to_cpu(mmt->PoolEnd) - le32_to_cpu(mmt->PoolStart)) / adev->memblocksize;
|
|
+
|
|
+ log(L_DEBUG, "TotalMemoryBlocks=%u (%u bytes)\n",
|
|
+ TotalMemoryBlocks, TotalMemoryBlocks*adev->memblocksize);
|
|
+
|
|
+ /* MemoryConfigOption.DMA_config bitmask:
|
|
+ access to ACX memory is to be done:
|
|
+ 0x00080000 using PCI conf space?!
|
|
+ 0x00040000 using IO instructions?
|
|
+ 0x00000000 using memory access instructions
|
|
+ 0x00020000 using local memory block linked list (else what?)
|
|
+ 0x00010000 using host indirect descriptors (else host must access ACX memory?)
|
|
+ */
|
|
+#if defined (ACX_MEM)
|
|
+ /*
|
|
+ * ACX ignores DMA_config for generic slave mode.
|
|
+ */
|
|
+ MemoryConfigOption.DMA_config = 0;
|
|
+ /* Declare start of the Rx host pool */
|
|
+ MemoryConfigOption.pRxHostDesc = cpu2acx(0);
|
|
+ log(L_DEBUG, "pRxHostDesc 0x%08X, rxhostdesc_startphy 0x%lX\n",
|
|
+ acx2cpu(MemoryConfigOption.pRxHostDesc),
|
|
+ (long)adev->rxhostdesc_startphy);
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ MemoryConfigOption.DMA_config = cpu_to_le32(0x30000);
|
|
+ /* Declare start of the Rx host pool */
|
|
+ MemoryConfigOption.pRxHostDesc = cpu2acx(adev->rxhostdesc_startphy);
|
|
+ log(L_DEBUG, "pRxHostDesc 0x%08X, rxhostdesc_startphy 0x%lX\n",
|
|
+ acx2cpu(MemoryConfigOption.pRxHostDesc),
|
|
+ (long)adev->rxhostdesc_startphy);
|
|
+ } else {
|
|
+ MemoryConfigOption.DMA_config = cpu_to_le32(0x20000);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* 50% of the allotment of memory blocks go to tx descriptors */
|
|
+ TxBlockNum = TotalMemoryBlocks / 2;
|
|
+ MemoryConfigOption.TxBlockNum = cpu_to_le16(TxBlockNum);
|
|
+
|
|
+ /* and 50% go to the rx descriptors */
|
|
+ RxBlockNum = TotalMemoryBlocks - TxBlockNum;
|
|
+ MemoryConfigOption.RxBlockNum = cpu_to_le16(RxBlockNum);
|
|
+
|
|
+ /* size of the tx and rx descriptor queues */
|
|
+ TotalTxBlockSize = TxBlockNum * adev->memblocksize;
|
|
+ TotalRxBlockSize = RxBlockNum * adev->memblocksize;
|
|
+ log(L_DEBUG, "TxBlockNum %u RxBlockNum %u TotalTxBlockSize %u "
|
|
+ "TotalTxBlockSize %u\n", TxBlockNum, RxBlockNum,
|
|
+ TotalTxBlockSize, TotalRxBlockSize);
|
|
+
|
|
+
|
|
+ /* align the tx descriptor queue to an alignment of 0x20 (32 bytes) */
|
|
+ MemoryConfigOption.rx_mem =
|
|
+ cpu_to_le32((le32_to_cpu(mmt->PoolStart) + 0x1f) & ~0x1f);
|
|
+
|
|
+ /* align the rx descriptor queue to units of 0x20
|
|
+ * and offset it by the tx descriptor queue */
|
|
+ MemoryConfigOption.tx_mem =
|
|
+ cpu_to_le32((le32_to_cpu(mmt->PoolStart) + TotalRxBlockSize + 0x1f) & ~0x1f);
|
|
+ log(L_DEBUG, "rx_mem %08X rx_mem %08X\n",
|
|
+ MemoryConfigOption.tx_mem, MemoryConfigOption.rx_mem);
|
|
+
|
|
+ /* alert the device to our decision */
|
|
+ if (OK != acx_s_configure(adev, &MemoryConfigOption, ACX1xx_IE_MEMORY_CONFIG_OPTIONS)) {
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* and tell the device to kick it into gear */
|
|
+ if (OK != acx_s_issue_cmd(adev, ACX100_CMD_INIT_MEMORY, NULL, 0)) {
|
|
+ goto bad;
|
|
+ }
|
|
+#ifdef ACX_MEM
|
|
+ /*
|
|
+ * slave memory interface has to manage the transmit pools for the ACX,
|
|
+ * so it needs to know what we chose here.
|
|
+ */
|
|
+ adev->acx_txbuf_start = MemoryConfigOption.tx_mem;
|
|
+ adev->acx_txbuf_numblocks = MemoryConfigOption.TxBlockNum;
|
|
+#endif
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx100_s_create_dma_regions
|
|
+**
|
|
+** Note that this fn messes up heavily with hardware, but we cannot
|
|
+** lock it (we need to sleep). Not a problem since IRQs can't happen
|
|
+*/
|
|
+static int
|
|
+acx100_s_create_dma_regions(acx_device_t *adev)
|
|
+{
|
|
+ acx100_ie_queueconfig_t queueconf;
|
|
+ acx_ie_memmap_t memmap;
|
|
+ int res = NOT_OK;
|
|
+ u32 tx_queue_start, rx_queue_start;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* read out the acx100 physical start address for the queues */
|
|
+ if (OK != acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ tx_queue_start = le32_to_cpu(memmap.QueueStart);
|
|
+ rx_queue_start = tx_queue_start + TX_CNT * sizeof(txdesc_t);
|
|
+
|
|
+ log(L_DEBUG, "initializing Queue Indicator\n");
|
|
+
|
|
+ memset(&queueconf, 0, sizeof(queueconf));
|
|
+
|
|
+ /* Not needed for PCI or slave memory, so we can avoid setting them altogether */
|
|
+ if (IS_USB(adev)) {
|
|
+ queueconf.NumTxDesc = USB_TX_CNT;
|
|
+ queueconf.NumRxDesc = USB_RX_CNT;
|
|
+ }
|
|
+
|
|
+ /* calculate size of queues */
|
|
+ queueconf.AreaSize = cpu_to_le32(
|
|
+ TX_CNT * sizeof(txdesc_t) +
|
|
+ RX_CNT * sizeof(rxdesc_t) + 8
|
|
+ );
|
|
+ queueconf.NumTxQueues = 1; /* number of tx queues */
|
|
+ /* sets the beginning of the tx descriptor queue */
|
|
+ queueconf.TxQueueStart = memmap.QueueStart;
|
|
+ /* done by memset: queueconf.TxQueuePri = 0; */
|
|
+ queueconf.RxQueueStart = cpu_to_le32(rx_queue_start);
|
|
+ queueconf.QueueOptions = 1; /* auto reset descriptor */
|
|
+ /* sets the end of the rx descriptor queue */
|
|
+ queueconf.QueueEnd = cpu_to_le32(
|
|
+ rx_queue_start + RX_CNT * sizeof(rxdesc_t)
|
|
+ );
|
|
+ /* sets the beginning of the next queue */
|
|
+ queueconf.HostQueueEnd = cpu_to_le32(le32_to_cpu(queueconf.QueueEnd) + 8);
|
|
+ if (OK != acx_s_configure(adev, &queueconf, ACX1xx_IE_QUEUE_CONFIG)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+#if defined (ACX_MEM)
|
|
+ /* sets the beginning of the rx descriptor queue, after the tx descrs */
|
|
+ adev->acx_queue_indicator =
|
|
+ (queueindicator_t *) le32_to_cpu (queueconf.QueueEnd);
|
|
+ if (OK != acxmem_s_create_hostdesc_queues(adev))
|
|
+ goto fail;
|
|
+
|
|
+ acxmem_create_desc_queues(adev, tx_queue_start, rx_queue_start);
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ /* sets the beginning of the rx descriptor queue, after the tx descrs */
|
|
+ if (OK != acxpci_s_create_hostdesc_queues(adev))
|
|
+ goto fail;
|
|
+ acxpci_create_desc_queues(adev, tx_queue_start, rx_queue_start);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (OK != acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Have to make sure we skip past the Queue Indicator (QueueEnd) and Host Queue Indicator
|
|
+ * maps, each of which are 8 bytes and follow immediately after the transmit and
|
|
+ * receive queues.
|
|
+ */
|
|
+ memmap.PoolStart = cpu_to_le32(
|
|
+ (le32_to_cpu(memmap.QueueEnd) + 4 + 0x1f) & ~0x1f
|
|
+ );
|
|
+
|
|
+ if (OK != acx_s_configure(adev, &memmap, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ if (OK != acx100_s_init_memory_pools(adev, &memmap)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ res = OK;
|
|
+ goto end;
|
|
+
|
|
+fail:
|
|
+ acx_s_msleep(1000); /* ? */
|
|
+#if defined (ACX_MEM)
|
|
+ acxmem_free_desc_queues(adev);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ acxpci_free_desc_queues(adev);
|
|
+#endif
|
|
+end:
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx111_s_create_dma_regions
|
|
+**
|
|
+** Note that this fn messes heavily with hardware, but we cannot
|
|
+** lock it (we need to sleep). Not a problem since IRQs can't happen
|
|
+*/
|
|
+#define ACX111_PERCENT(percent) ((percent)/5)
|
|
+
|
|
+static int
|
|
+acx111_s_create_dma_regions(acx_device_t *adev)
|
|
+{
|
|
+ struct acx111_ie_memoryconfig memconf;
|
|
+ struct acx111_ie_queueconfig queueconf;
|
|
+ u32 tx_queue_start, rx_queue_start;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* Calculate memory positions and queue sizes */
|
|
+
|
|
+ /* Set up our host descriptor pool + data pool */
|
|
+#if defined (ACX_MEM)
|
|
+ if (OK != acxmem_s_create_hostdesc_queues(adev))
|
|
+ goto fail;
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ if (OK != acxpci_s_create_hostdesc_queues(adev))
|
|
+ goto fail;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ memset(&memconf, 0, sizeof(memconf));
|
|
+ /* the number of STAs (STA contexts) to support
|
|
+ ** NB: was set to 1 and everything seemed to work nevertheless... */
|
|
+ memconf.no_of_stations = cpu_to_le16(VEC_SIZE(adev->sta_list));
|
|
+ /* specify the memory block size. Default is 256 */
|
|
+ memconf.memory_block_size = cpu_to_le16(adev->memblocksize);
|
|
+ /* let's use 50%/50% for tx/rx (specify percentage, units of 5%) */
|
|
+ memconf.tx_rx_memory_block_allocation = ACX111_PERCENT(50);
|
|
+ /* set the count of our queues
|
|
+ ** NB: struct acx111_ie_memoryconfig shall be modified
|
|
+ ** if we ever will switch to more than one rx and/or tx queue */
|
|
+ memconf.count_rx_queues = 1;
|
|
+ memconf.count_tx_queues = 1;
|
|
+ /* 0 == Busmaster Indirect Memory Organization, which is what we want
|
|
+ * (using linked host descs with their allocated mem).
|
|
+ * 2 == Generic Bus Slave */
|
|
+ /* done by memset: memconf.options = 0; */
|
|
+ /* let's use 25% for fragmentations and 75% for frame transfers
|
|
+ * (specified in units of 5%) */
|
|
+ memconf.fragmentation = ACX111_PERCENT(75);
|
|
+ /* Rx descriptor queue config */
|
|
+ memconf.rx_queue1_count_descs = RX_CNT;
|
|
+ memconf.rx_queue1_type = 7; /* must be set to 7 */
|
|
+ /* done by memset: memconf.rx_queue1_prio = 0; low prio */
|
|
+#if defined (ACX_MEM)
|
|
+ memconf.rx_queue1_host_rx_start = cpu2acx(adev->rxhostdesc_startphy);
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ memconf.rx_queue1_host_rx_start = cpu2acx(adev->rxhostdesc_startphy);
|
|
+ }
|
|
+#endif
|
|
+ /* Tx descriptor queue config */
|
|
+ memconf.tx_queue1_count_descs = TX_CNT;
|
|
+ /* done by memset: memconf.tx_queue1_attributes = 0; lowest priority */
|
|
+
|
|
+ /* NB1: this looks wrong: (memconf,ACX1xx_IE_QUEUE_CONFIG),
|
|
+ ** (queueconf,ACX1xx_IE_MEMORY_CONFIG_OPTIONS) look swapped, eh?
|
|
+ ** But it is actually correct wrt IE numbers.
|
|
+ ** NB2: sizeof(memconf) == 28 == 0x1c but configure(ACX1xx_IE_QUEUE_CONFIG)
|
|
+ ** writes 0x20 bytes (because same IE for acx100 uses struct acx100_ie_queueconfig
|
|
+ ** which is 4 bytes larger. what a mess. TODO: clean it up) */
|
|
+ if (OK != acx_s_configure(adev, &memconf, ACX1xx_IE_QUEUE_CONFIG)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ acx_s_interrogate(adev, &queueconf, ACX1xx_IE_MEMORY_CONFIG_OPTIONS);
|
|
+
|
|
+ tx_queue_start = le32_to_cpu(queueconf.tx1_queue_address);
|
|
+ rx_queue_start = le32_to_cpu(queueconf.rx1_queue_address);
|
|
+
|
|
+ log(L_INIT, "dump queue head (from card):\n"
|
|
+ "len: %u\n"
|
|
+ "tx_memory_block_address: %X\n"
|
|
+ "rx_memory_block_address: %X\n"
|
|
+ "tx1_queue address: %X\n"
|
|
+ "rx1_queue address: %X\n",
|
|
+ le16_to_cpu(queueconf.len),
|
|
+ le32_to_cpu(queueconf.tx_memory_block_address),
|
|
+ le32_to_cpu(queueconf.rx_memory_block_address),
|
|
+ tx_queue_start,
|
|
+ rx_queue_start);
|
|
+
|
|
+#if defined (ACX_MEM)
|
|
+ acxmem_create_desc_queues(adev, tx_queue_start, rx_queue_start);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ acxpci_create_desc_queues(adev, tx_queue_start, rx_queue_start);
|
|
+#endif
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+#if defined (ACX_MEM)
|
|
+ acxmem_free_desc_queues(adev);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ acxpci_free_desc_queues(adev);
|
|
+#endif
|
|
+
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static void
|
|
+acx_s_initialize_rx_config(acx_device_t *adev)
|
|
+{
|
|
+ struct {
|
|
+ u16 id;
|
|
+ u16 len;
|
|
+ u16 rx_cfg1;
|
|
+ u16 rx_cfg2;
|
|
+ } ACX_PACKED cfg;
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_OFF:
|
|
+ adev->rx_config_1 = (u16) (0
|
|
+ /* | RX_CFG1_INCLUDE_RXBUF_HDR */
|
|
+ /* | RX_CFG1_FILTER_SSID */
|
|
+ /* | RX_CFG1_FILTER_BCAST */
|
|
+ /* | RX_CFG1_RCV_MC_ADDR1 */
|
|
+ /* | RX_CFG1_RCV_MC_ADDR0 */
|
|
+ /* | RX_CFG1_FILTER_ALL_MULTI */
|
|
+ /* | RX_CFG1_FILTER_BSSID */
|
|
+ /* | RX_CFG1_FILTER_MAC */
|
|
+ /* | RX_CFG1_RCV_PROMISCUOUS */
|
|
+ /* | RX_CFG1_INCLUDE_FCS */
|
|
+ /* | RX_CFG1_INCLUDE_PHY_HDR */
|
|
+ );
|
|
+ adev->rx_config_2 = (u16) (0
|
|
+ /*| RX_CFG2_RCV_ASSOC_REQ */
|
|
+ /*| RX_CFG2_RCV_AUTH_FRAMES */
|
|
+ /*| RX_CFG2_RCV_BEACON_FRAMES */
|
|
+ /*| RX_CFG2_RCV_CONTENTION_FREE */
|
|
+ /*| RX_CFG2_RCV_CTRL_FRAMES */
|
|
+ /*| RX_CFG2_RCV_DATA_FRAMES */
|
|
+ /*| RX_CFG2_RCV_BROKEN_FRAMES */
|
|
+ /*| RX_CFG2_RCV_MGMT_FRAMES */
|
|
+ /*| RX_CFG2_RCV_PROBE_REQ */
|
|
+ /*| RX_CFG2_RCV_PROBE_RESP */
|
|
+ /*| RX_CFG2_RCV_ACK_FRAMES */
|
|
+ /*| RX_CFG2_RCV_OTHER */
|
|
+ );
|
|
+ break;
|
|
+ case ACX_MODE_MONITOR:
|
|
+ adev->rx_config_1 = (u16) (0
|
|
+ /* | RX_CFG1_INCLUDE_RXBUF_HDR */
|
|
+ /* | RX_CFG1_FILTER_SSID */
|
|
+ /* | RX_CFG1_FILTER_BCAST */
|
|
+ /* | RX_CFG1_RCV_MC_ADDR1 */
|
|
+ /* | RX_CFG1_RCV_MC_ADDR0 */
|
|
+ /* | RX_CFG1_FILTER_ALL_MULTI */
|
|
+ /* | RX_CFG1_FILTER_BSSID */
|
|
+ /* | RX_CFG1_FILTER_MAC */
|
|
+ | RX_CFG1_RCV_PROMISCUOUS
|
|
+ /* | RX_CFG1_INCLUDE_FCS */
|
|
+ /* | RX_CFG1_INCLUDE_PHY_HDR */
|
|
+ );
|
|
+ adev->rx_config_2 = (u16) (0
|
|
+ | RX_CFG2_RCV_ASSOC_REQ
|
|
+ | RX_CFG2_RCV_AUTH_FRAMES
|
|
+ | RX_CFG2_RCV_BEACON_FRAMES
|
|
+ | RX_CFG2_RCV_CONTENTION_FREE
|
|
+ | RX_CFG2_RCV_CTRL_FRAMES
|
|
+ | RX_CFG2_RCV_DATA_FRAMES
|
|
+ | RX_CFG2_RCV_BROKEN_FRAMES
|
|
+ | RX_CFG2_RCV_MGMT_FRAMES
|
|
+ | RX_CFG2_RCV_PROBE_REQ
|
|
+ | RX_CFG2_RCV_PROBE_RESP
|
|
+ | RX_CFG2_RCV_ACK_FRAMES
|
|
+ | RX_CFG2_RCV_OTHER
|
|
+ );
|
|
+ break;
|
|
+ default:
|
|
+ adev->rx_config_1 = (u16) (0
|
|
+ /* | RX_CFG1_INCLUDE_RXBUF_HDR */
|
|
+ /* | RX_CFG1_FILTER_SSID */
|
|
+ /* | RX_CFG1_FILTER_BCAST */
|
|
+ /* | RX_CFG1_RCV_MC_ADDR1 */
|
|
+ /* | RX_CFG1_RCV_MC_ADDR0 */
|
|
+ /* | RX_CFG1_FILTER_ALL_MULTI */
|
|
+ /* | RX_CFG1_FILTER_BSSID */
|
|
+ | RX_CFG1_FILTER_MAC
|
|
+ /* | RX_CFG1_RCV_PROMISCUOUS */
|
|
+ /* | RX_CFG1_INCLUDE_FCS */
|
|
+ /* | RX_CFG1_INCLUDE_PHY_HDR */
|
|
+ );
|
|
+ adev->rx_config_2 = (u16) (0
|
|
+ | RX_CFG2_RCV_ASSOC_REQ
|
|
+ | RX_CFG2_RCV_AUTH_FRAMES
|
|
+ | RX_CFG2_RCV_BEACON_FRAMES
|
|
+ | RX_CFG2_RCV_CONTENTION_FREE
|
|
+ | RX_CFG2_RCV_CTRL_FRAMES
|
|
+ | RX_CFG2_RCV_DATA_FRAMES
|
|
+ /*| RX_CFG2_RCV_BROKEN_FRAMES */
|
|
+ | RX_CFG2_RCV_MGMT_FRAMES
|
|
+ | RX_CFG2_RCV_PROBE_REQ
|
|
+ | RX_CFG2_RCV_PROBE_RESP
|
|
+ /*| RX_CFG2_RCV_ACK_FRAMES */
|
|
+ | RX_CFG2_RCV_OTHER
|
|
+ );
|
|
+ break;
|
|
+ }
|
|
+ adev->rx_config_1 |= RX_CFG1_INCLUDE_RXBUF_HDR;
|
|
+
|
|
+ if ((adev->rx_config_1 & RX_CFG1_INCLUDE_PHY_HDR)
|
|
+ || (adev->firmware_numver >= 0x02000000))
|
|
+ adev->phy_header_len = IS_ACX111(adev) ? 8 : 4;
|
|
+ else
|
|
+ adev->phy_header_len = 0;
|
|
+
|
|
+ log(L_INIT, "setting RXconfig to %04X:%04X\n",
|
|
+ adev->rx_config_1, adev->rx_config_2);
|
|
+ cfg.rx_cfg1 = cpu_to_le16(adev->rx_config_1);
|
|
+ cfg.rx_cfg2 = cpu_to_le16(adev->rx_config_2);
|
|
+ acx_s_configure(adev, &cfg, ACX1xx_IE_RXCONFIG);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_set_defaults
|
|
+*/
|
|
+void
|
|
+acx_s_set_defaults(acx_device_t *adev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* do it before getting settings, prevent bogus channel 0 warning */
|
|
+ adev->channel = 1;
|
|
+
|
|
+ /* query some settings from the card.
|
|
+ * NOTE: for some settings, e.g. CCA and ED (ACX100!), an initial
|
|
+ * query is REQUIRED, otherwise the card won't work correctly! */
|
|
+ adev->get_mask = GETSET_ANTENNA|GETSET_SENSITIVITY|GETSET_STATION_ID|GETSET_REG_DOMAIN;
|
|
+ /* Only ACX100 supports ED and CCA */
|
|
+ if (IS_ACX100(adev))
|
|
+ adev->get_mask |= GETSET_CCA|GETSET_ED_THRESH;
|
|
+
|
|
+ acx_s_update_card_settings(adev);
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* set our global interrupt mask */
|
|
+#if defined (ACX_MEM)
|
|
+ acxmem_set_interrupt_mask(adev);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ acxpci_set_interrupt_mask(adev);
|
|
+#endif
|
|
+
|
|
+ adev->led_power = 1; /* LED is active on startup */
|
|
+ adev->brange_max_quality = 60; /* LED blink max quality is 60 */
|
|
+ adev->brange_time_last_state_change = jiffies;
|
|
+
|
|
+ /* copy the MAC address we just got from the card
|
|
+ * into our MAC address used during current 802.11 session */
|
|
+ MAC_COPY(adev->dev_addr, adev->ndev->dev_addr);
|
|
+ MAC_BCAST(adev->ap);
|
|
+
|
|
+ adev->essid_len =
|
|
+ snprintf(adev->essid, sizeof(adev->essid), "STA%02X%02X%02X",
|
|
+ adev->dev_addr[3], adev->dev_addr[4], adev->dev_addr[5]);
|
|
+ adev->essid_active = 1;
|
|
+
|
|
+ /* we have a nick field to waste, so why not abuse it
|
|
+ * to announce the driver version? ;-) */
|
|
+ strncpy(adev->nick, "acx " ACX_RELEASE, IW_ESSID_MAX_SIZE);
|
|
+
|
|
+#if defined (ACX_MEM)
|
|
+ adev->reg_dom_id = adev->cfgopt_domains.list[0];
|
|
+#else
|
|
+ if (IS_PCI(adev)) { /* FIXME: this should be made to apply to USB, too! */
|
|
+ /* first regulatory domain entry in EEPROM == default reg. domain */
|
|
+ adev->reg_dom_id = adev->cfgopt_domains.list[0];
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ /* 0xffff would be better, but then we won't get a "scan complete"
|
|
+ * interrupt, so our current infrastructure will fail: */
|
|
+ adev->scan_count = 1;
|
|
+ adev->scan_mode = ACX_SCAN_OPT_ACTIVE;
|
|
+ adev->scan_duration = 100;
|
|
+ adev->scan_probe_delay = 200;
|
|
+ /* reported to break scanning: adev->scan_probe_delay = adev->cfgopt_probe_delay; */
|
|
+ adev->scan_rate = ACX_SCAN_RATE_1;
|
|
+
|
|
+ adev->mode = ACX_MODE_2_STA;
|
|
+ adev->auth_alg = WLAN_AUTH_ALG_OPENSYSTEM;
|
|
+ adev->listen_interval = 100;
|
|
+ adev->beacon_interval = DEFAULT_BEACON_INTERVAL;
|
|
+ adev->dtim_interval = DEFAULT_DTIM_INTERVAL;
|
|
+
|
|
+ adev->msdu_lifetime = DEFAULT_MSDU_LIFETIME;
|
|
+
|
|
+ adev->rts_threshold = DEFAULT_RTS_THRESHOLD;
|
|
+ adev->frag_threshold = 2346;
|
|
+
|
|
+ /* use standard default values for retry limits */
|
|
+ adev->short_retry = 7; /* max. retries for (short) non-RTS packets */
|
|
+ adev->long_retry = 4; /* max. retries for long (RTS) packets */
|
|
+
|
|
+ adev->preamble_mode = 2; /* auto */
|
|
+ adev->fallback_threshold = 3;
|
|
+ adev->stepup_threshold = 10;
|
|
+ adev->rate_bcast = RATE111_1;
|
|
+ adev->rate_bcast100 = RATE100_1;
|
|
+ adev->rate_basic = RATE111_1 | RATE111_2;
|
|
+ adev->rate_auto = 1;
|
|
+ if (IS_ACX111(adev)) {
|
|
+ adev->rate_oper = RATE111_ALL;
|
|
+ } else {
|
|
+ adev->rate_oper = RATE111_ACX100_COMPAT;
|
|
+ }
|
|
+
|
|
+ /* Supported Rates element - the rates here are given in units of
|
|
+ * 500 kbit/s, plus 0x80 added. See 802.11-1999.pdf item 7.3.2.2 */
|
|
+ acx_l_update_ratevector(adev);
|
|
+
|
|
+ /* set some more defaults */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* 30mW (15dBm) is default, at least in my acx111 card: */
|
|
+ adev->tx_level_dbm = 15;
|
|
+ } else {
|
|
+ /* don't use max. level, since it might be dangerous
|
|
+ * (e.g. WRT54G people experience
|
|
+ * excessive Tx power damage!) */
|
|
+ adev->tx_level_dbm = 18;
|
|
+ /*
|
|
+ * Lower power for the iPaq hx4700
|
|
+ */
|
|
+ if (IS_MEM(adev)) {
|
|
+ adev->tx_level_dbm = 14;
|
|
+ }
|
|
+ }
|
|
+ /* adev->tx_level_auto = 1; */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* start with sensitivity level 1 out of 3: */
|
|
+ adev->sensitivity = 1;
|
|
+ }
|
|
+
|
|
+/* #define ENABLE_POWER_SAVE */
|
|
+#ifdef ENABLE_POWER_SAVE
|
|
+ adev->ps_wakeup_cfg = PS_CFG_ENABLE | PS_CFG_WAKEUP_ALL_BEAC;
|
|
+ adev->ps_listen_interval = 1;
|
|
+ adev->ps_options = PS_OPT_ENA_ENHANCED_PS | PS_OPT_TX_PSPOLL | PS_OPT_STILL_RCV_BCASTS;
|
|
+ adev->ps_hangover_period = 30;
|
|
+ adev->ps_enhanced_transition_time = 0;
|
|
+#else
|
|
+ adev->ps_wakeup_cfg = 0;
|
|
+ adev->ps_listen_interval = 0;
|
|
+ adev->ps_options = 0;
|
|
+ adev->ps_hangover_period = 0;
|
|
+ adev->ps_enhanced_transition_time = 0;
|
|
+#endif
|
|
+
|
|
+ /* These settings will be set in fw on ifup */
|
|
+ adev->set_mask = 0
|
|
+ | GETSET_RETRY
|
|
+ | SET_MSDU_LIFETIME
|
|
+ /* configure card to do rate fallback when in auto rate mode */
|
|
+ | SET_RATE_FALLBACK
|
|
+ | SET_RXCONFIG
|
|
+ | GETSET_TXPOWER
|
|
+ /* better re-init the antenna value we got above */
|
|
+ | GETSET_ANTENNA
|
|
+#if POWER_SAVE_80211
|
|
+ | GETSET_POWER_80211
|
|
+#endif
|
|
+ ;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+ acx_lock_unhold(); /* hold time 844814 CPU ticks @2GHz */
|
|
+
|
|
+ acx_s_initialize_rx_config(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** FIXME: this should be solved in a general way for all radio types
|
|
+** by decoding the radio firmware module,
|
|
+** since it probably has some standard structure describing how to
|
|
+** set the power level of the radio module which it controls.
|
|
+** Or maybe not, since the radio module probably has a function interface
|
|
+** instead which then manages Tx level programming :-\
|
|
+*/
|
|
+static int
|
|
+acx111_s_set_tx_level(acx_device_t *adev, u8 level_dbm)
|
|
+{
|
|
+ struct acx111_ie_tx_level tx_level;
|
|
+
|
|
+ /* my acx111 card has two power levels in its configoptions (== EEPROM):
|
|
+ * 1 (30mW) [15dBm]
|
|
+ * 2 (10mW) [10dBm]
|
|
+ * For now, just assume all other acx111 cards have the same.
|
|
+ * FIXME: Ideally we would query it here, but we first need a
|
|
+ * standard way to query individual configoptions easily.
|
|
+ * Well, now we have proper cfgopt txpower variables, but this still
|
|
+ * hasn't been done yet, since it also requires dBm <-> mW conversion here... */
|
|
+ if (level_dbm <= 12) {
|
|
+ tx_level.level = 2; /* 10 dBm */
|
|
+ adev->tx_level_dbm = 10;
|
|
+ } else {
|
|
+ tx_level.level = 1; /* 15 dBm */
|
|
+ adev->tx_level_dbm = 15;
|
|
+ }
|
|
+ if (level_dbm != adev->tx_level_dbm)
|
|
+ log(L_INIT, "acx111 firmware has specific "
|
|
+ "power levels only: adjusted %d dBm to %d dBm!\n",
|
|
+ level_dbm, adev->tx_level_dbm);
|
|
+
|
|
+ return acx_s_configure(adev, &tx_level, ACX1xx_IE_DOT11_TX_POWER_LEVEL);
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_s_set_tx_level(acx_device_t *adev, u8 level_dbm)
|
|
+{
|
|
+ if (IS_ACX111(adev)) {
|
|
+ return acx111_s_set_tx_level(adev, level_dbm);
|
|
+ }
|
|
+#if defined (ACX_MEM)
|
|
+ return acx100mem_s_set_tx_level(adev, level_dbm);
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ return acx100pci_s_set_tx_level(adev, level_dbm);
|
|
+ }
|
|
+#endif
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#ifdef UNUSED
|
|
+/* Returns the current tx level (ACX111) */
|
|
+static u8
|
|
+acx111_s_get_tx_level(acx_device_t *adev)
|
|
+{
|
|
+ struct acx111_ie_tx_level tx_level;
|
|
+
|
|
+ tx_level.level = 0;
|
|
+ acx_s_interrogate(adev, &tx_level, ACX1xx_IE_DOT11_TX_POWER_LEVEL);
|
|
+ return tx_level.level;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_rxmonitor
|
|
+** Called from IRQ context only
|
|
+*/
|
|
+static void
|
|
+acx_l_rxmonitor(acx_device_t *adev, const rxbuffer_t *rxbuf)
|
|
+{
|
|
+ wlansniffrm_t *msg;
|
|
+ struct sk_buff *skb;
|
|
+ void *datap;
|
|
+ unsigned int skb_len;
|
|
+ int payload_offset;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* we are in big luck: the acx100 doesn't modify any of the fields */
|
|
+ /* in the 802.11 frame. just pass this packet into the PF_PACKET */
|
|
+ /* subsystem. yeah. */
|
|
+ payload_offset = ((u8*)acx_get_wlan_hdr(adev, rxbuf) - (u8*)rxbuf);
|
|
+ skb_len = RXBUF_BYTES_USED(rxbuf) - payload_offset;
|
|
+
|
|
+ /* sanity check */
|
|
+ if (unlikely(skb_len > WLAN_A4FR_MAXLEN_WEP)) {
|
|
+ printk("%s: monitor mode panic: oversized frame!\n",
|
|
+ adev->ndev->name);
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ if (adev->ndev->type == ARPHRD_IEEE80211_PRISM)
|
|
+ skb_len += sizeof(*msg);
|
|
+
|
|
+ /* allocate skb */
|
|
+ skb = dev_alloc_skb(skb_len);
|
|
+ if (unlikely(!skb)) {
|
|
+ printk("%s: no memory for skb (%u bytes)\n",
|
|
+ adev->ndev->name, skb_len);
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ skb_put(skb, skb_len);
|
|
+
|
|
+ if (adev->ndev->type == ARPHRD_IEEE80211) {
|
|
+ /* when in raw 802.11 mode, just copy frame as-is */
|
|
+ datap = skb->data;
|
|
+ } else if (adev->ndev->type == ARPHRD_IEEE80211_PRISM) {
|
|
+ /* emulate prism header */
|
|
+ msg = (wlansniffrm_t*)skb->data;
|
|
+ datap = msg + 1;
|
|
+
|
|
+ msg->msgcode = WLANSNIFFFRM;
|
|
+ msg->msglen = sizeof(*msg);
|
|
+ strncpy(msg->devname, adev->ndev->name, sizeof(msg->devname)-1);
|
|
+ msg->devname[sizeof(msg->devname)-1] = '\0';
|
|
+
|
|
+ msg->hosttime.did = WLANSNIFFFRM_hosttime;
|
|
+ msg->hosttime.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->hosttime.len = 4;
|
|
+ msg->hosttime.data = jiffies;
|
|
+
|
|
+ msg->mactime.did = WLANSNIFFFRM_mactime;
|
|
+ msg->mactime.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->mactime.len = 4;
|
|
+ msg->mactime.data = rxbuf->time;
|
|
+
|
|
+ msg->channel.did = WLANSNIFFFRM_channel;
|
|
+ msg->channel.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->channel.len = 4;
|
|
+ msg->channel.data = adev->channel;
|
|
+
|
|
+ msg->rssi.did = WLANSNIFFFRM_rssi;
|
|
+ msg->rssi.status = WLANITEM_STATUS_no_value;
|
|
+ msg->rssi.len = 4;
|
|
+ msg->rssi.data = 0;
|
|
+
|
|
+ msg->sq.did = WLANSNIFFFRM_sq;
|
|
+ msg->sq.status = WLANITEM_STATUS_no_value;
|
|
+ msg->sq.len = 4;
|
|
+ msg->sq.data = 0;
|
|
+
|
|
+ msg->signal.did = WLANSNIFFFRM_signal;
|
|
+ msg->signal.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->signal.len = 4;
|
|
+ msg->signal.data = rxbuf->phy_snr;
|
|
+
|
|
+ msg->noise.did = WLANSNIFFFRM_noise;
|
|
+ msg->noise.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->noise.len = 4;
|
|
+ msg->noise.data = rxbuf->phy_level;
|
|
+
|
|
+ msg->rate.did = WLANSNIFFFRM_rate;
|
|
+ msg->rate.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->rate.len = 4;
|
|
+ msg->rate.data = rxbuf->phy_plcp_signal / 5;
|
|
+
|
|
+ msg->istx.did = WLANSNIFFFRM_istx;
|
|
+ msg->istx.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->istx.len = 4;
|
|
+ msg->istx.data = 0; /* tx=0: it's not a tx packet */
|
|
+
|
|
+ skb_len -= sizeof(*msg);
|
|
+
|
|
+ msg->frmlen.did = WLANSNIFFFRM_signal;
|
|
+ msg->frmlen.status = WLANITEM_STATUS_data_ok;
|
|
+ msg->frmlen.len = 4;
|
|
+ msg->frmlen.data = skb_len;
|
|
+ } else {
|
|
+ printk("acx: unsupported netdev type %d!\n", adev->ndev->type);
|
|
+ dev_kfree_skb(skb);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* sanity check (keep it here) */
|
|
+ if (unlikely((int)skb_len < 0)) {
|
|
+ printk("acx: skb_len=%d. Driver bug, please report\n", (int)skb_len);
|
|
+ dev_kfree_skb(skb);
|
|
+ return;
|
|
+ }
|
|
+ memcpy(datap, ((unsigned char*)rxbuf)+payload_offset, skb_len);
|
|
+
|
|
+ skb->dev = adev->ndev;
|
|
+ skb->dev->last_rx = jiffies;
|
|
+
|
|
+ skb_reset_mac_header(skb);
|
|
+ skb->ip_summed = CHECKSUM_NONE;
|
|
+ skb->pkt_type = PACKET_OTHERHOST;
|
|
+ skb->protocol = htons(ETH_P_80211_RAW);
|
|
+ netif_rx(skb);
|
|
+
|
|
+ adev->stats.rx_packets++;
|
|
+ adev->stats.rx_bytes += skb->len;
|
|
+
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_rx_ieee802_11_frame
|
|
+**
|
|
+** Called from IRQ context only
|
|
+*/
|
|
+
|
|
+/* All these contortions are for saner dup logging
|
|
+**
|
|
+** We want: (a) to know about excessive dups
|
|
+** (b) to not spam kernel log about occasional dups
|
|
+**
|
|
+** 1/64 threshold was chosen by running "ping -A"
|
|
+** It gave "rx: 59 DUPs in 2878 packets" only with 4 parallel
|
|
+** "ping -A" streams running. */
|
|
+/* 2005-10-11: bumped up to 1/8
|
|
+** subtract a $smallint from dup_count in order to
|
|
+** avoid "2 DUPs in 19 packets" messages */
|
|
+static inline int
|
|
+acx_l_handle_dup(acx_device_t *adev, u16 seq)
|
|
+{
|
|
+ if (adev->dup_count) {
|
|
+ adev->nondup_count++;
|
|
+ if (time_after(jiffies, adev->dup_msg_expiry)) {
|
|
+ /* Log only if more than 1 dup in 64 packets */
|
|
+ if (adev->nondup_count/8 < adev->dup_count-5) {
|
|
+ printk(KERN_INFO "%s: rx: %d DUPs in "
|
|
+ "%d packets received in 10 secs\n",
|
|
+ adev->ndev->name,
|
|
+ adev->dup_count,
|
|
+ adev->nondup_count);
|
|
+ }
|
|
+ adev->dup_count = 0;
|
|
+ adev->nondup_count = 0;
|
|
+ }
|
|
+ }
|
|
+ if (unlikely(seq == adev->last_seq_ctrl)) {
|
|
+ if (!adev->dup_count++)
|
|
+ adev->dup_msg_expiry = jiffies + 10*HZ;
|
|
+ adev->stats.rx_errors++;
|
|
+ return 1; /* a dup */
|
|
+ }
|
|
+ adev->last_seq_ctrl = seq;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_l_rx_ieee802_11_frame(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ unsigned int ftype, fstype;
|
|
+ const wlan_hdr_t *hdr;
|
|
+ int result = NOT_OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+
|
|
+ /* see IEEE 802.11-1999.pdf chapter 7 "MAC frame formats" */
|
|
+ if (unlikely((hdr->fc & WF_FC_PVERi) != 0)) {
|
|
+ printk_ratelimited(KERN_INFO "rx: unsupported 802.11 protocol\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ ftype = hdr->fc & WF_FC_FTYPEi;
|
|
+ fstype = hdr->fc & WF_FC_FSTYPEi;
|
|
+
|
|
+ switch (ftype) {
|
|
+ /* check data frames first, for speed */
|
|
+ case WF_FTYPE_DATAi:
|
|
+ switch (fstype) {
|
|
+ case WF_FSTYPE_DATAONLYi:
|
|
+ if (acx_l_handle_dup(adev, hdr->seq))
|
|
+ break; /* a dup, simply discard it */
|
|
+
|
|
+ /* TODO:
|
|
+ if (WF_FC_FROMTODSi == (hdr->fc & WF_FC_FROMTODSi)) {
|
|
+ result = acx_l_process_data_frame_wds(adev, rxbuf);
|
|
+ break;
|
|
+ }
|
|
+ */
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_3_AP:
|
|
+ result = acx_l_process_data_frame_master(adev, rxbuf);
|
|
+ break;
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ result = acx_l_process_data_frame_client(adev, rxbuf);
|
|
+ break;
|
|
+ }
|
|
+ case WF_FSTYPE_DATA_CFACKi:
|
|
+ case WF_FSTYPE_DATA_CFPOLLi:
|
|
+ case WF_FSTYPE_DATA_CFACK_CFPOLLi:
|
|
+ case WF_FSTYPE_CFPOLLi:
|
|
+ case WF_FSTYPE_CFACK_CFPOLLi:
|
|
+ /* see above.
|
|
+ acx_process_class_frame(adev, rxbuf, 3); */
|
|
+ break;
|
|
+ case WF_FSTYPE_NULLi:
|
|
+ /* acx_l_process_NULL_frame(adev, rxbuf, 3); */
|
|
+ break;
|
|
+ /* FIXME: same here, see above */
|
|
+ case WF_FSTYPE_CFACKi:
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+ break;
|
|
+ case WF_FTYPE_MGMTi:
|
|
+ result = acx_l_process_mgmt_frame(adev, rxbuf);
|
|
+ break;
|
|
+ case WF_FTYPE_CTLi:
|
|
+ if (fstype == WF_FSTYPE_PSPOLLi)
|
|
+ result = OK;
|
|
+ /* this call is irrelevant, since
|
|
+ * acx_process_class_frame is a stub, so return
|
|
+ * immediately instead.
|
|
+ * return acx_process_class_frame(adev, rxbuf, 3); */
|
|
+ break;
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_rxbuf
|
|
+**
|
|
+** NB: used by USB code also
|
|
+*/
|
|
+void
|
|
+acx_l_process_rxbuf(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ struct wlan_hdr *hdr;
|
|
+ unsigned int qual;
|
|
+ int buf_len;
|
|
+ u16 fc;
|
|
+
|
|
+ hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+ fc = le16_to_cpu(hdr->fc);
|
|
+ /* length of frame from control field to first byte of FCS */
|
|
+ buf_len = RXBUF_BYTES_RCVD(adev, rxbuf);
|
|
+
|
|
+ if ( ((WF_FC_FSTYPE & fc) != WF_FSTYPE_BEACON)
|
|
+ || (acx_debug & L_XFER_BEACON)
|
|
+ ) {
|
|
+ log(L_XFER|L_DATA, "rx: %s "
|
|
+ "time:%u len:%u signal:%u SNR:%u macstat:%02X "
|
|
+ "phystat:%02X phyrate:%u status:%u\n",
|
|
+ acx_get_packet_type_string(fc),
|
|
+ le32_to_cpu(rxbuf->time),
|
|
+ buf_len,
|
|
+ acx_signal_to_winlevel(rxbuf->phy_level),
|
|
+ acx_signal_to_winlevel(rxbuf->phy_snr),
|
|
+ rxbuf->mac_status,
|
|
+ rxbuf->phy_stat_baseband,
|
|
+ rxbuf->phy_plcp_signal,
|
|
+ adev->status);
|
|
+ }
|
|
+
|
|
+ if (unlikely(acx_debug & L_DATA)) {
|
|
+ printk("rx: 802.11 buf[%u]: ", buf_len);
|
|
+ acx_dump_bytes(hdr, buf_len);
|
|
+ }
|
|
+
|
|
+ /* FIXME: should check for Rx errors (rxbuf->mac_status?
|
|
+ * discard broken packets - but NOT for monitor!)
|
|
+ * and update Rx packet statistics here */
|
|
+
|
|
+ if (unlikely(adev->mode == ACX_MODE_MONITOR)) {
|
|
+ acx_l_rxmonitor(adev, rxbuf);
|
|
+ } else if (likely(buf_len >= WLAN_HDR_A3_LEN)) {
|
|
+ acx_l_rx_ieee802_11_frame(adev, rxbuf);
|
|
+ } else {
|
|
+ log(L_DEBUG|L_XFER|L_DATA,
|
|
+ "rx: NOT receiving packet (%s): "
|
|
+ "size too small (%u)\n",
|
|
+ acx_get_packet_type_string(fc),
|
|
+ buf_len);
|
|
+ }
|
|
+
|
|
+ /* Now check Rx quality level, AFTER processing packet.
|
|
+ * I tried to figure out how to map these levels to dBm
|
|
+ * values, but for the life of me I really didn't
|
|
+ * manage to get it. Either these values are not meant to
|
|
+ * be expressed in dBm, or it's some pretty complicated
|
|
+ * calculation. */
|
|
+
|
|
+#ifdef FROM_SCAN_SOURCE_ONLY
|
|
+ /* only consider packets originating from the MAC
|
|
+ * address of the device that's managing our BSSID.
|
|
+ * Disable it for now, since it removes information (levels
|
|
+ * from different peers) and slows the Rx path. */
|
|
+ if (adev->ap_client
|
|
+ && mac_is_equal(hdr->a2, adev->ap_client->address)) {
|
|
+#endif
|
|
+ adev->wstats.qual.level = acx_signal_to_winlevel(rxbuf->phy_level);
|
|
+ adev->wstats.qual.noise = acx_signal_to_winlevel(rxbuf->phy_snr);
|
|
+#ifndef OLD_QUALITY
|
|
+ qual = acx_signal_determine_quality(adev->wstats.qual.level,
|
|
+ adev->wstats.qual.noise);
|
|
+#else
|
|
+ qual = (adev->wstats.qual.noise <= 100) ?
|
|
+ 100 - adev->wstats.qual.noise : 0;
|
|
+#endif
|
|
+ adev->wstats.qual.qual = qual;
|
|
+ adev->wstats.qual.updated = 7; /* all 3 indicators updated */
|
|
+#ifdef FROM_SCAN_SOURCE_ONLY
|
|
+ }
|
|
+#endif
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_handle_txrate_auto
|
|
+**
|
|
+** Theory of operation:
|
|
+** client->rate_cap is a bitmask of rates client is capable of.
|
|
+** client->rate_cfg is a bitmask of allowed (configured) rates.
|
|
+** It is set as a result of iwconfig rate N [auto]
|
|
+** or iwpriv set_rates "N,N,N N,N,N" commands.
|
|
+** It can be fixed (e.g. 0x0080 == 18Mbit only),
|
|
+** auto (0x00ff == 18Mbit or any lower value),
|
|
+** and code handles any bitmask (0x1081 == try 54Mbit,18Mbit,1Mbit _only_).
|
|
+**
|
|
+** client->rate_cur is a value for rate111 field in tx descriptor.
|
|
+** It is always set to txrate_cfg sans zero or more most significant
|
|
+** bits. This routine handles selection of new rate_cur value depending on
|
|
+** outcome of last tx event.
|
|
+**
|
|
+** client->rate_100 is a precalculated rate value for acx100
|
|
+** (we can do without it, but will need to calculate it on each tx).
|
|
+**
|
|
+** You cannot configure mixed usage of 5.5 and/or 11Mbit rate
|
|
+** with PBCC and CCK modulation. Either both at CCK or both at PBCC.
|
|
+** In theory you can implement it, but so far it is considered not worth doing.
|
|
+**
|
|
+** 22Mbit, of course, is PBCC always. */
|
|
+
|
|
+/* maps acx100 tx descr rate field to acx111 one */
|
|
+static u16
|
|
+rate100to111(u8 r)
|
|
+{
|
|
+ switch (r) {
|
|
+ case RATE100_1: return RATE111_1;
|
|
+ case RATE100_2: return RATE111_2;
|
|
+ case RATE100_5:
|
|
+ case (RATE100_5 | RATE100_PBCC511): return RATE111_5;
|
|
+ case RATE100_11:
|
|
+ case (RATE100_11 | RATE100_PBCC511): return RATE111_11;
|
|
+ case RATE100_22: return RATE111_22;
|
|
+ default:
|
|
+ printk("acx: unexpected acx100 txrate: %u! "
|
|
+ "Please report\n", r);
|
|
+ return RATE111_1;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+acx_l_handle_txrate_auto(acx_device_t *adev, struct client *txc,
|
|
+ u16 cur, u8 rate100, u16 rate111,
|
|
+ u8 error, int pkts_to_ignore)
|
|
+{
|
|
+ u16 sent_rate;
|
|
+ int slower_rate_was_used;
|
|
+
|
|
+ /* vda: hmm. current code will do this:
|
|
+ ** 1. send packets at 11 Mbit, stepup++
|
|
+ ** 2. will try to send at 22Mbit. hardware will see no ACK,
|
|
+ ** retries at 11Mbit, success. code notes that used rate
|
|
+ ** is lower. stepup = 0, fallback++
|
|
+ ** 3. repeat step 2 fallback_count times. Fall back to
|
|
+ ** 11Mbit. go to step 1.
|
|
+ ** If stepup_count is large (say, 16) and fallback_count
|
|
+ ** is small (3), this wouldn't be too bad wrt throughput */
|
|
+
|
|
+ if (unlikely(!cur)) {
|
|
+ printk("acx: BUG! ratemask is empty\n");
|
|
+ return; /* or else we may lock up the box */
|
|
+ }
|
|
+
|
|
+ /* do some preparations, i.e. calculate the one rate that was
|
|
+ * used to send this packet */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ sent_rate = 1 << highest_bit(rate111 & RATE111_ALL);
|
|
+ } else {
|
|
+ sent_rate = rate100to111(rate100);
|
|
+ }
|
|
+ /* sent_rate has only one bit set now, corresponding to tx rate
|
|
+ * which was used by hardware to tx this particular packet */
|
|
+
|
|
+ /* now do the actual auto rate management */
|
|
+ log(L_XFER, "tx: %sclient=%p/"MACSTR" used=%04X cur=%04X cfg=%04X "
|
|
+ "__=%u/%u ^^=%u/%u\n",
|
|
+ (txc->ignore_count > 0) ? "[IGN] " : "",
|
|
+ txc, MAC(txc->address), sent_rate, cur, txc->rate_cfg,
|
|
+ txc->fallback_count, adev->fallback_threshold,
|
|
+ txc->stepup_count, adev->stepup_threshold
|
|
+ );
|
|
+
|
|
+ /* we need to ignore old packets already in the tx queue since
|
|
+ * they use older rate bytes configured before our last rate change,
|
|
+ * otherwise our mechanism will get confused by interpreting old data.
|
|
+ * Do it after logging above */
|
|
+ if (txc->ignore_count) {
|
|
+ txc->ignore_count--;
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* true only if the only nonzero bit in sent_rate is
|
|
+ ** less significant than highest nonzero bit in cur */
|
|
+ slower_rate_was_used = ( cur > ((sent_rate<<1)-1) );
|
|
+
|
|
+ if (slower_rate_was_used || error) {
|
|
+ txc->stepup_count = 0;
|
|
+ if (++txc->fallback_count <= adev->fallback_threshold)
|
|
+ return;
|
|
+ txc->fallback_count = 0;
|
|
+
|
|
+ /* clear highest 1 bit in cur */
|
|
+ sent_rate = RATE111_54;
|
|
+ while (!(cur & sent_rate)) sent_rate >>= 1;
|
|
+ CLEAR_BIT(cur, sent_rate);
|
|
+ if (!cur) /* we can't disable all rates! */
|
|
+ cur = sent_rate;
|
|
+ log(L_XFER, "tx: falling back to ratemask %04X\n", cur);
|
|
+
|
|
+ } else { /* there was neither lower rate nor error */
|
|
+ txc->fallback_count = 0;
|
|
+ if (++txc->stepup_count <= adev->stepup_threshold)
|
|
+ return;
|
|
+ txc->stepup_count = 0;
|
|
+
|
|
+ /* Sanitize. Sort of not needed, but I dont trust hw that much...
|
|
+ ** what if it can report bogus tx rates sometimes? */
|
|
+ while (!(cur & sent_rate)) sent_rate >>= 1;
|
|
+
|
|
+ /* try to find a higher sent_rate that isn't yet in our
|
|
+ * current set, but is an allowed cfg */
|
|
+ while (1) {
|
|
+ sent_rate <<= 1;
|
|
+ if (sent_rate > txc->rate_cfg)
|
|
+ /* no higher rates allowed by config */
|
|
+ return;
|
|
+ if (!(cur & sent_rate) && (txc->rate_cfg & sent_rate))
|
|
+ /* found */
|
|
+ break;
|
|
+ /* not found, try higher one */
|
|
+ }
|
|
+ SET_BIT(cur, sent_rate);
|
|
+ log(L_XFER, "tx: stepping up to ratemask %04X\n", cur);
|
|
+ }
|
|
+
|
|
+ txc->rate_cur = cur;
|
|
+ txc->ignore_count = pkts_to_ignore;
|
|
+ /* calculate acx100 style rate byte if needed */
|
|
+ if (IS_ACX100(adev)) {
|
|
+ txc->rate_100 = acx_bitpos2rate100[highest_bit(cur)];
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_i_start_xmit
|
|
+**
|
|
+** Called by network core. Can be called outside of process context.
|
|
+*/
|
|
+int
|
|
+acx_i_start_xmit(struct sk_buff *skb, struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ tx_t *tx;
|
|
+ void *txbuf;
|
|
+ unsigned long flags;
|
|
+ int txresult = NOT_OK;
|
|
+ int len;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(!skb)) {
|
|
+ /* indicate success */
|
|
+ txresult = OK;
|
|
+ goto end_no_unlock;
|
|
+ }
|
|
+ if (unlikely(!adev)) {
|
|
+ goto end_no_unlock;
|
|
+ }
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ if (unlikely(!(adev->dev_state_mask & ACX_STATE_IFACE_UP))) {
|
|
+ goto end;
|
|
+ }
|
|
+ if (unlikely(adev->mode == ACX_MODE_OFF)) {
|
|
+ goto end;
|
|
+ }
|
|
+ if (unlikely(acx_queue_stopped(ndev))) {
|
|
+ log(L_DEBUG, "%s: called when queue stopped\n", __func__);
|
|
+ goto end;
|
|
+ }
|
|
+ if (unlikely(ACX_STATUS_4_ASSOCIATED != adev->status)) {
|
|
+ log(L_XFER, "trying to xmit, but not associated yet: "
|
|
+ "aborting...\n");
|
|
+ /* silently drop the packet, since we're not connected yet */
|
|
+ txresult = OK;
|
|
+ /* ...but indicate an error nevertheless */
|
|
+ adev->stats.tx_errors++;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (unlikely(!tx)) {
|
|
+#ifndef ACX_MEM
|
|
+ /*
|
|
+ * generic slave interface has to make do with the tiny amount, around
|
|
+ * 7k, of transmit buffer space on the ACX itself. It is likely this will
|
|
+ * frequently be full.
|
|
+ */
|
|
+ printk_ratelimited("%s: start_xmit: txdesc ring is full, "
|
|
+ "dropping tx\n", ndev->name);
|
|
+#endif
|
|
+ txresult = NOT_OK;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ txbuf = acx_l_get_txbuf(adev, tx);
|
|
+ if (unlikely(!txbuf)) {
|
|
+ /* Card was removed */
|
|
+ txresult = NOT_OK;
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto end;
|
|
+ }
|
|
+ len = acx_ether_to_txbuf(adev, txbuf, skb);
|
|
+ if (unlikely(len < 0)) {
|
|
+ /* Error in packet conversion */
|
|
+ txresult = NOT_OK;
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto end;
|
|
+ }
|
|
+ acx_l_tx_data(adev, tx, len);
|
|
+ ndev->trans_start = jiffies;
|
|
+
|
|
+ txresult = OK;
|
|
+ adev->stats.tx_packets++;
|
|
+ adev->stats.tx_bytes += skb->len;
|
|
+
|
|
+end:
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+end_no_unlock:
|
|
+ if ((txresult == OK) && skb)
|
|
+ dev_kfree_skb_any(skb);
|
|
+
|
|
+ FN_EXIT1(txresult);
|
|
+ return txresult;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_update_ratevector
|
|
+**
|
|
+** Updates adev->rate_supported[_len] according to rate_{basic,oper}
|
|
+*/
|
|
+const u8
|
|
+acx_bitpos2ratebyte[] = {
|
|
+ DOT11RATEBYTE_1,
|
|
+ DOT11RATEBYTE_2,
|
|
+ DOT11RATEBYTE_5_5,
|
|
+ DOT11RATEBYTE_6_G,
|
|
+ DOT11RATEBYTE_9_G,
|
|
+ DOT11RATEBYTE_11,
|
|
+ DOT11RATEBYTE_12_G,
|
|
+ DOT11RATEBYTE_18_G,
|
|
+ DOT11RATEBYTE_22,
|
|
+ DOT11RATEBYTE_24_G,
|
|
+ DOT11RATEBYTE_36_G,
|
|
+ DOT11RATEBYTE_48_G,
|
|
+ DOT11RATEBYTE_54_G,
|
|
+};
|
|
+
|
|
+void
|
|
+acx_l_update_ratevector(acx_device_t *adev)
|
|
+{
|
|
+ u16 bcfg = adev->rate_basic;
|
|
+ u16 ocfg = adev->rate_oper;
|
|
+ u8 *supp = adev->rate_supported;
|
|
+ const u8 *dot11 = acx_bitpos2ratebyte;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ while (ocfg) {
|
|
+ if (ocfg & 1) {
|
|
+ *supp = *dot11;
|
|
+ if (bcfg & 1) {
|
|
+ *supp |= 0x80;
|
|
+ }
|
|
+ supp++;
|
|
+ }
|
|
+ dot11++;
|
|
+ ocfg >>= 1;
|
|
+ bcfg >>= 1;
|
|
+ }
|
|
+ adev->rate_supported_len = supp - adev->rate_supported;
|
|
+ if (acx_debug & L_ASSOC) {
|
|
+ printk("new ratevector: ");
|
|
+ acx_dump_bytes(adev->rate_supported, adev->rate_supported_len);
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_init
|
|
+*/
|
|
+static void
|
|
+acx_l_sta_list_init(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ memset(adev->sta_hash_tab, 0, sizeof(adev->sta_hash_tab));
|
|
+ memset(adev->sta_list, 0, sizeof(adev->sta_list));
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_get_from_hash
|
|
+*/
|
|
+static inline client_t*
|
|
+acx_l_sta_list_get_from_hash(acx_device_t *adev, const u8 *address)
|
|
+{
|
|
+ return adev->sta_hash_tab[address[5] % VEC_SIZE(adev->sta_hash_tab)];
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_get
|
|
+*/
|
|
+client_t*
|
|
+acx_l_sta_list_get(acx_device_t *adev, const u8 *address)
|
|
+{
|
|
+ client_t *client;
|
|
+ FN_ENTER;
|
|
+ client = acx_l_sta_list_get_from_hash(adev, address);
|
|
+ while (client) {
|
|
+ if (mac_is_equal(address, client->address)) {
|
|
+ client->mtime = jiffies;
|
|
+ break;
|
|
+ }
|
|
+ client = client->next;
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+ return client;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_del
|
|
+*/
|
|
+void
|
|
+acx_l_sta_list_del(acx_device_t *adev, client_t *victim)
|
|
+{
|
|
+ client_t *client, *next;
|
|
+
|
|
+ client = acx_l_sta_list_get_from_hash(adev, victim->address);
|
|
+ next = client;
|
|
+ /* tricky. next = client on first iteration only,
|
|
+ ** on all other iters next = client->next */
|
|
+ while (next) {
|
|
+ if (next == victim) {
|
|
+ client->next = victim->next;
|
|
+ /* Overkill */
|
|
+ memset(victim, 0, sizeof(*victim));
|
|
+ break;
|
|
+ }
|
|
+ client = next;
|
|
+ next = client->next;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_alloc
|
|
+**
|
|
+** Never fails - will evict oldest client if needed
|
|
+*/
|
|
+static client_t*
|
|
+acx_l_sta_list_alloc(acx_device_t *adev)
|
|
+{
|
|
+ int i;
|
|
+ unsigned long age, oldest_age;
|
|
+ client_t *client, *oldest;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ oldest = &adev->sta_list[0];
|
|
+ oldest_age = 0;
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ client = &adev->sta_list[i];
|
|
+
|
|
+ if (!client->used) {
|
|
+ goto found;
|
|
+ } else {
|
|
+ age = jiffies - client->mtime;
|
|
+ if (oldest_age < age) {
|
|
+ oldest_age = age;
|
|
+ oldest = client;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ acx_l_sta_list_del(adev, oldest);
|
|
+ client = oldest;
|
|
+found:
|
|
+ memset(client, 0, sizeof(*client));
|
|
+ FN_EXIT0;
|
|
+ return client;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_add
|
|
+**
|
|
+** Never fails - will evict oldest client if needed
|
|
+*/
|
|
+/* In case we will reimplement it differently... */
|
|
+#define STA_LIST_ADD_CAN_FAIL 0
|
|
+
|
|
+static client_t*
|
|
+acx_l_sta_list_add(acx_device_t *adev, const u8 *address)
|
|
+{
|
|
+ client_t *client;
|
|
+ int index;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ client = acx_l_sta_list_alloc(adev);
|
|
+
|
|
+ client->mtime = jiffies;
|
|
+ MAC_COPY(client->address, address);
|
|
+ client->used = CLIENT_EXIST_1;
|
|
+ client->auth_alg = WLAN_AUTH_ALG_SHAREDKEY;
|
|
+ client->auth_step = 1;
|
|
+ /* give some tentative peer rate values
|
|
+ ** (needed because peer may do auth without probing us first,
|
|
+ ** thus we'll have no idea of peer's ratevector yet).
|
|
+ ** Will be overwritten by scanning or assoc code */
|
|
+ client->rate_cap = adev->rate_basic;
|
|
+ client->rate_cfg = adev->rate_basic;
|
|
+ client->rate_cur = 1 << lowest_bit(adev->rate_basic);
|
|
+
|
|
+ index = address[5] % VEC_SIZE(adev->sta_hash_tab);
|
|
+ client->next = adev->sta_hash_tab[index];
|
|
+ adev->sta_hash_tab[index] = client;
|
|
+
|
|
+ acxlog_mac(L_ASSOC, "sta_list_add: sta=", address, "\n");
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return client;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_sta_list_get_or_add
|
|
+**
|
|
+** Never fails - will evict oldest client if needed
|
|
+*/
|
|
+static client_t*
|
|
+acx_l_sta_list_get_or_add(acx_device_t *adev, const u8 *address)
|
|
+{
|
|
+ client_t *client = acx_l_sta_list_get(adev, address);
|
|
+ if (!client)
|
|
+ client = acx_l_sta_list_add(adev, address);
|
|
+ return client;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_set_status
|
|
+**
|
|
+** This function is called in many atomic regions, must not sleep
|
|
+**
|
|
+** This function does not need locking UNLESS you call it
|
|
+** as acx_set_status(ACX_STATUS_4_ASSOCIATED), bacause this can
|
|
+** wake queue. This can race with stop_queue elsewhere.
|
|
+** See acx_stop_queue comment. */
|
|
+void
|
|
+acx_set_status(acx_device_t *adev, u16 new_status)
|
|
+{
|
|
+#define QUEUE_OPEN_AFTER_ASSOC 1 /* this really seems to be needed now */
|
|
+ u16 old_status = adev->status;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_ASSOC, "%s(%d):%s\n",
|
|
+ __func__, new_status, acx_get_status_name(new_status));
|
|
+
|
|
+ /* wireless_send_event never sleeps */
|
|
+ if (ACX_STATUS_4_ASSOCIATED == new_status) {
|
|
+ union iwreq_data wrqu;
|
|
+
|
|
+ wrqu.data.length = 0;
|
|
+ wrqu.data.flags = 0;
|
|
+ wireless_send_event(adev->ndev, SIOCGIWSCAN, &wrqu, NULL);
|
|
+
|
|
+ wrqu.data.length = 0;
|
|
+ wrqu.data.flags = 0;
|
|
+ MAC_COPY(wrqu.ap_addr.sa_data, adev->bssid);
|
|
+ wrqu.ap_addr.sa_family = ARPHRD_ETHER;
|
|
+ wireless_send_event(adev->ndev, SIOCGIWAP, &wrqu, NULL);
|
|
+ } else {
|
|
+ union iwreq_data wrqu;
|
|
+
|
|
+ /* send event with empty BSSID to indicate we're not associated */
|
|
+ MAC_ZERO(wrqu.ap_addr.sa_data);
|
|
+ wrqu.ap_addr.sa_family = ARPHRD_ETHER;
|
|
+ wireless_send_event(adev->ndev, SIOCGIWAP, &wrqu, NULL);
|
|
+ }
|
|
+
|
|
+ adev->status = new_status;
|
|
+
|
|
+ switch (new_status) {
|
|
+ case ACX_STATUS_1_SCANNING:
|
|
+ adev->scan_retries = 0;
|
|
+ /* 1.0 s initial scan time */
|
|
+ acx_set_timer(adev, 1000000);
|
|
+ break;
|
|
+ case ACX_STATUS_2_WAIT_AUTH:
|
|
+ case ACX_STATUS_3_AUTHENTICATED:
|
|
+ adev->auth_or_assoc_retries = 0;
|
|
+ acx_set_timer(adev, 1500000); /* 1.5 s */
|
|
+ break;
|
|
+ }
|
|
+
|
|
+#if QUEUE_OPEN_AFTER_ASSOC
|
|
+ if (new_status == ACX_STATUS_4_ASSOCIATED) {
|
|
+ if (old_status < ACX_STATUS_4_ASSOCIATED) {
|
|
+ /* ah, we're newly associated now,
|
|
+ * so let's indicate carrier */
|
|
+ acx_carrier_on(adev->ndev, "after association");
|
|
+ acx_wake_queue(adev->ndev, "after association");
|
|
+ }
|
|
+ } else {
|
|
+ /* not associated any more, so let's kill carrier */
|
|
+ if (old_status >= ACX_STATUS_4_ASSOCIATED) {
|
|
+ acx_carrier_off(adev->ndev, "after losing association");
|
|
+ acx_stop_queue(adev->ndev, "after losing association");
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_i_timer
|
|
+**
|
|
+** Fires up periodically. Used to kick scan/auth/assoc if something goes wrong
|
|
+*/
|
|
+void
|
|
+acx_i_timer(unsigned long address)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ acx_device_t *adev = (acx_device_t*)address;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ log(L_DEBUG|L_ASSOC, "%s: adev->status=%d (%s)\n",
|
|
+ __func__, adev->status, acx_get_status_name(adev->status));
|
|
+
|
|
+ switch (adev->status) {
|
|
+ case ACX_STATUS_1_SCANNING:
|
|
+ /* was set to 0 by set_status() */
|
|
+ if (++adev->scan_retries < 7) {
|
|
+ acx_set_timer(adev, 1000000);
|
|
+ /* used to interrogate for scan status.
|
|
+ ** We rely on SCAN_COMPLETE IRQ instead */
|
|
+ log(L_ASSOC, "continuing scan (%d sec)\n",
|
|
+ adev->scan_retries);
|
|
+ } else {
|
|
+ log(L_ASSOC, "stopping scan\n");
|
|
+ /* send stop_scan cmd when we leave the interrupt context,
|
|
+ * and make a decision what to do next (COMPLETE_SCAN) */
|
|
+ acx_schedule_task(adev,
|
|
+ ACX_AFTER_IRQ_CMD_STOP_SCAN + ACX_AFTER_IRQ_COMPLETE_SCAN);
|
|
+ }
|
|
+ break;
|
|
+ case ACX_STATUS_2_WAIT_AUTH:
|
|
+ /* was set to 0 by set_status() */
|
|
+ if (++adev->auth_or_assoc_retries < 10) {
|
|
+ log(L_ASSOC, "resend authen1 request (attempt %d)\n",
|
|
+ adev->auth_or_assoc_retries + 1);
|
|
+ acx_l_transmit_authen1(adev);
|
|
+ } else {
|
|
+ /* time exceeded: fall back to scanning mode */
|
|
+ log(L_ASSOC,
|
|
+ "authen1 request reply timeout, giving up\n");
|
|
+ /* we are a STA, need to find AP anyhow */
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING);
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_RESTART_SCAN);
|
|
+ }
|
|
+ /* used to be 1500000, but some other driver uses 2.5s */
|
|
+ acx_set_timer(adev, 2500000);
|
|
+ break;
|
|
+ case ACX_STATUS_3_AUTHENTICATED:
|
|
+ /* was set to 0 by set_status() */
|
|
+ if (++adev->auth_or_assoc_retries < 10) {
|
|
+ log(L_ASSOC, "resend assoc request (attempt %d)\n",
|
|
+ adev->auth_or_assoc_retries + 1);
|
|
+ acx_l_transmit_assoc_req(adev);
|
|
+ } else {
|
|
+ /* time exceeded: give up */
|
|
+ log(L_ASSOC,
|
|
+ "association request reply timeout, giving up\n");
|
|
+ /* we are a STA, need to find AP anyhow */
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING);
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_RESTART_SCAN);
|
|
+ }
|
|
+ acx_set_timer(adev, 2500000); /* see above */
|
|
+ break;
|
|
+ case ACX_STATUS_4_ASSOCIATED:
|
|
+ default:
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_set_timer
|
|
+**
|
|
+** Sets the 802.11 state management timer's timeout.
|
|
+*/
|
|
+void
|
|
+acx_set_timer(acx_device_t *adev, int timeout_us)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_DEBUG|L_IRQ, "%s(%u ms)\n", __func__, timeout_us/1000);
|
|
+ if (!(adev->dev_state_mask & ACX_STATE_IFACE_UP)) {
|
|
+ printk("attempt to set the timer "
|
|
+ "when the card interface is not up!\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* first check if the timer was already initialized, THEN modify it */
|
|
+ if (adev->mgmt_timer.function) {
|
|
+ mod_timer(&adev->mgmt_timer,
|
|
+ jiffies + (timeout_us * HZ / 1000000));
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_assocresp
|
|
+**
|
|
+** We are an AP here
|
|
+*/
|
|
+static const u8
|
|
+dot11ratebyte[] = {
|
|
+ DOT11RATEBYTE_1,
|
|
+ DOT11RATEBYTE_2,
|
|
+ DOT11RATEBYTE_5_5,
|
|
+ DOT11RATEBYTE_6_G,
|
|
+ DOT11RATEBYTE_9_G,
|
|
+ DOT11RATEBYTE_11,
|
|
+ DOT11RATEBYTE_12_G,
|
|
+ DOT11RATEBYTE_18_G,
|
|
+ DOT11RATEBYTE_22,
|
|
+ DOT11RATEBYTE_24_G,
|
|
+ DOT11RATEBYTE_36_G,
|
|
+ DOT11RATEBYTE_48_G,
|
|
+ DOT11RATEBYTE_54_G,
|
|
+};
|
|
+
|
|
+static inline int
|
|
+find_pos(const u8 *p, int size, u8 v)
|
|
+{
|
|
+ int i;
|
|
+ for (i = 0; i < size; i++)
|
|
+ if (p[i] == v)
|
|
+ return i;
|
|
+ /* printk a message about strange byte? */
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static void
|
|
+add_bits_to_ratemasks(u8* ratevec, int len, u16* brate, u16* orate)
|
|
+{
|
|
+ while (len--) {
|
|
+ int n = 1 << find_pos(dot11ratebyte,
|
|
+ sizeof(dot11ratebyte), *ratevec & 0x7f);
|
|
+ if (*ratevec & 0x80)
|
|
+ *brate |= n;
|
|
+ *orate |= n;
|
|
+ ratevec++;
|
|
+ }
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_l_transmit_assocresp(acx_device_t *adev, const wlan_fr_assocreq_t *req)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct assocresp_frame_body *body;
|
|
+ u8 *p;
|
|
+ const u8 *da;
|
|
+ /* const u8 *sa; */
|
|
+ const u8 *bssid;
|
|
+ client_t *clt;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* sa = req->hdr->a1; */
|
|
+ da = req->hdr->a2;
|
|
+ bssid = req->hdr->a3;
|
|
+
|
|
+ clt = acx_l_sta_list_get(adev, da);
|
|
+ if (!clt)
|
|
+ goto ok;
|
|
+
|
|
+ /* Assoc without auth is a big no-no */
|
|
+ /* Let's be liberal: if already assoc'ed STA sends assoc req again,
|
|
+ ** we won't be rude */
|
|
+ if (clt->used != CLIENT_AUTHENTICATED_2
|
|
+ && clt->used != CLIENT_ASSOCIATED_3) {
|
|
+ acx_l_transmit_deauthen(adev, da, WLAN_MGMT_REASON_CLASS2_NONAUTH);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ clt->used = CLIENT_ASSOCIATED_3;
|
|
+
|
|
+ if (clt->aid == 0)
|
|
+ clt->aid = ++adev->aid;
|
|
+ clt->cap_info = ieee2host16(*(req->cap_info));
|
|
+
|
|
+ /* We cheat here a bit. We don't really care which rates are flagged
|
|
+ ** as basic by the client, so we stuff them in single ratemask */
|
|
+ clt->rate_cap = 0;
|
|
+ if (req->supp_rates)
|
|
+ add_bits_to_ratemasks(req->supp_rates->rates,
|
|
+ req->supp_rates->len, &clt->rate_cap, &clt->rate_cap);
|
|
+ if (req->ext_rates)
|
|
+ add_bits_to_ratemasks(req->ext_rates->rates,
|
|
+ req->ext_rates->len, &clt->rate_cap, &clt->rate_cap);
|
|
+ /* We can check that client supports all basic rates,
|
|
+ ** and deny assoc if not. But let's be liberal, right? ;) */
|
|
+ clt->rate_cfg = clt->rate_cap & adev->rate_oper;
|
|
+ if (!clt->rate_cfg) clt->rate_cfg = 1 << lowest_bit(adev->rate_oper);
|
|
+ clt->rate_cur = 1 << lowest_bit(clt->rate_cfg);
|
|
+ if (IS_ACX100(adev))
|
|
+ clt->rate_100 = acx_bitpos2rate100[lowest_bit(clt->rate_cfg)];
|
|
+ clt->fallback_count = clt->stepup_count = 0;
|
|
+ clt->ignore_count = 16;
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto bad;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto bad;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = WF_FSTYPE_ASSOCRESPi;
|
|
+ head->dur = req->hdr->dur;
|
|
+ MAC_COPY(head->da, da);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, bssid);
|
|
+ head->seq = req->hdr->seq;
|
|
+
|
|
+ body->cap_info = host2ieee16(adev->capabilities);
|
|
+ body->status = host2ieee16(0);
|
|
+ body->aid = host2ieee16(clt->aid);
|
|
+ p = wlan_fill_ie_rates((u8*)&body->rates, adev->rate_supported_len,
|
|
+ adev->rate_supported);
|
|
+ p = wlan_fill_ie_rates_ext(p, adev->rate_supported_len,
|
|
+ adev->rate_supported);
|
|
+
|
|
+ acx_l_tx_data(adev, tx, p - (u8*)head);
|
|
+ok:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+* acx_l_transmit_reassocresp
|
|
+
|
|
+You may be wondering, just like me, what the hell ReAuth is.
|
|
+In practice it was seen sent by STA when STA feels like losing connection.
|
|
+
|
|
+[802.11]
|
|
+
|
|
+5.4.2.3 Reassociation
|
|
+
|
|
+Association is sufficient for no-transition message delivery between
|
|
+IEEE 802.11 stations. Additional functionality is needed to support
|
|
+BSS-transition mobility. The additional required functionality
|
|
+is provided by the reassociation service. Reassociation is a DSS.
|
|
+The reassociation service is invoked to 'move' a current association
|
|
+from one AP to another. This keeps the DS informed of the current
|
|
+mapping between AP and STA as the station moves from BSS to BSS within
|
|
+an ESS. Reassociation also enables changing association attributes
|
|
+of an established association while the STA remains associated with
|
|
+the same AP. Reassociation is always initiated by the mobile STA.
|
|
+
|
|
+5.4.3.1 Authentication
|
|
+...
|
|
+A STA may be authenticated with many other STAs at any given instant.
|
|
+
|
|
+5.4.3.1.1 Preauthentication
|
|
+
|
|
+Because the authentication process could be time-consuming (depending
|
|
+on the authentication protocol in use), the authentication service can
|
|
+be invoked independently of the association service. Preauthentication
|
|
+is typically done by a STA while it is already associated with an AP
|
|
+(with which it previously authenticated). IEEE 802.11 does not require
|
|
+that STAs preauthenticate with APs. However, authentication is required
|
|
+before an association can be established. If the authentication is left
|
|
+until reassociation time, this may impact the speed with which a STA can
|
|
+reassociate between APs, limiting BSS-transition mobility performance.
|
|
+The use of preauthentication takes the authentication service overhead
|
|
+out of the time-critical reassociation process.
|
|
+
|
|
+5.7.3 Reassociation
|
|
+
|
|
+For a STA to reassociate, the reassociation service causes the following
|
|
+message to occur:
|
|
+
|
|
+ Reassociation request
|
|
+
|
|
+* Message type: Management
|
|
+* Message subtype: Reassociation request
|
|
+* Information items:
|
|
+ - IEEE address of the STA
|
|
+ - IEEE address of the AP with which the STA will reassociate
|
|
+ - IEEE address of the AP with which the STA is currently associated
|
|
+ - ESSID
|
|
+* Direction of message: From STA to 'new' AP
|
|
+
|
|
+The address of the current AP is included for efficiency. The inclusion
|
|
+of the current AP address facilitates MAC reassociation to be independent
|
|
+of the DS implementation.
|
|
+
|
|
+ Reassociation response
|
|
+* Message type: Management
|
|
+* Message subtype: Reassociation response
|
|
+* Information items:
|
|
+ - Result of the requested reassociation. (success/failure)
|
|
+ - If the reassociation is successful, the response shall include the AID.
|
|
+* Direction of message: From AP to STA
|
|
+
|
|
+7.2.3.6 Reassociation Request frame format
|
|
+
|
|
+The frame body of a management frame of subtype Reassociation Request
|
|
+contains the information shown in Table 9.
|
|
+
|
|
+Table 9 Reassociation Request frame body
|
|
+Order Information
|
|
+1 Capability information
|
|
+2 Listen interval
|
|
+3 Current AP address
|
|
+4 SSID
|
|
+5 Supported rates
|
|
+
|
|
+7.2.3.7 Reassociation Response frame format
|
|
+
|
|
+The frame body of a management frame of subtype Reassociation Response
|
|
+contains the information shown in Table 10.
|
|
+
|
|
+Table 10 Reassociation Response frame body
|
|
+Order Information
|
|
+1 Capability information
|
|
+2 Status code
|
|
+3 Association ID (AID)
|
|
+4 Supported rates
|
|
+
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_reassocresp(acx_device_t *adev, const wlan_fr_reassocreq_t *req)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct reassocresp_frame_body *body;
|
|
+ u8 *p;
|
|
+ const u8 *da;
|
|
+ /* const u8 *sa; */
|
|
+ const u8 *bssid;
|
|
+ client_t *clt;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* sa = req->hdr->a1; */
|
|
+ da = req->hdr->a2;
|
|
+ bssid = req->hdr->a3;
|
|
+
|
|
+ /* Must be already authenticated, so it must be in the list */
|
|
+ clt = acx_l_sta_list_get(adev, da);
|
|
+ if (!clt)
|
|
+ goto ok;
|
|
+
|
|
+ /* Assoc without auth is a big no-no */
|
|
+ /* Already assoc'ed STAs sending ReAssoc req are ok per 802.11 */
|
|
+ if (clt->used != CLIENT_AUTHENTICATED_2
|
|
+ && clt->used != CLIENT_ASSOCIATED_3) {
|
|
+ acx_l_transmit_deauthen(adev, da, WLAN_MGMT_REASON_CLASS2_NONAUTH);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ clt->used = CLIENT_ASSOCIATED_3;
|
|
+ if (clt->aid == 0) {
|
|
+ clt->aid = ++adev->aid;
|
|
+ }
|
|
+ if (req->cap_info)
|
|
+ clt->cap_info = ieee2host16(*(req->cap_info));
|
|
+
|
|
+ /* We cheat here a bit. We don't really care which rates are flagged
|
|
+ ** as basic by the client, so we stuff them in single ratemask */
|
|
+ clt->rate_cap = 0;
|
|
+ if (req->supp_rates)
|
|
+ add_bits_to_ratemasks(req->supp_rates->rates,
|
|
+ req->supp_rates->len, &clt->rate_cap, &clt->rate_cap);
|
|
+ if (req->ext_rates)
|
|
+ add_bits_to_ratemasks(req->ext_rates->rates,
|
|
+ req->ext_rates->len, &clt->rate_cap, &clt->rate_cap);
|
|
+ /* We can check that client supports all basic rates,
|
|
+ ** and deny assoc if not. But let's be liberal, right? ;) */
|
|
+ clt->rate_cfg = clt->rate_cap & adev->rate_oper;
|
|
+ if (!clt->rate_cfg) clt->rate_cfg = 1 << lowest_bit(adev->rate_oper);
|
|
+ clt->rate_cur = 1 << lowest_bit(clt->rate_cfg);
|
|
+ if (IS_ACX100(adev))
|
|
+ clt->rate_100 = acx_bitpos2rate100[lowest_bit(clt->rate_cfg)];
|
|
+
|
|
+ clt->fallback_count = clt->stepup_count = 0;
|
|
+ clt->ignore_count = 16;
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto ok;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto ok;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = WF_FSTYPE_REASSOCRESPi;
|
|
+ head->dur = req->hdr->dur;
|
|
+ MAC_COPY(head->da, da);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, bssid);
|
|
+ head->seq = req->hdr->seq;
|
|
+
|
|
+ /* IEs: 1. caps */
|
|
+ body->cap_info = host2ieee16(adev->capabilities);
|
|
+ /* 2. status code */
|
|
+ body->status = host2ieee16(0);
|
|
+ /* 3. AID */
|
|
+ body->aid = host2ieee16(clt->aid);
|
|
+ /* 4. supp rates */
|
|
+ p = wlan_fill_ie_rates((u8*)&body->rates, adev->rate_supported_len,
|
|
+ adev->rate_supported);
|
|
+ /* 5. ext supp rates */
|
|
+ p = wlan_fill_ie_rates_ext(p, adev->rate_supported_len,
|
|
+ adev->rate_supported);
|
|
+
|
|
+ acx_l_tx_data(adev, tx, p - (u8*)head);
|
|
+ok:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_disassoc_from_sta
|
|
+*/
|
|
+static void
|
|
+acx_l_process_disassoc_from_sta(acx_device_t *adev, const wlan_fr_disassoc_t *req)
|
|
+{
|
|
+ const u8 *ta;
|
|
+ client_t *clt;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ta = req->hdr->a2;
|
|
+ clt = acx_l_sta_list_get(adev, ta);
|
|
+ if (!clt)
|
|
+ goto end;
|
|
+
|
|
+ if (clt->used != CLIENT_ASSOCIATED_3
|
|
+ && clt->used != CLIENT_AUTHENTICATED_2) {
|
|
+ /* it's disassociating, but it's
|
|
+ ** not even authenticated! Let it know that */
|
|
+ acxlog_mac(L_ASSOC|L_XFER, "peer ", ta, "has sent disassoc "
|
|
+ "req but it is not even auth'ed! sending deauth\n");
|
|
+ acx_l_transmit_deauthen(adev, ta,
|
|
+ WLAN_MGMT_REASON_CLASS2_NONAUTH);
|
|
+ clt->used = CLIENT_EXIST_1;
|
|
+ } else {
|
|
+ /* mark it as auth'ed only */
|
|
+ clt->used = CLIENT_AUTHENTICATED_2;
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_deauthen_from_sta
|
|
+*/
|
|
+static void
|
|
+acx_l_process_deauth_from_sta(acx_device_t *adev, const wlan_fr_deauthen_t *req)
|
|
+{
|
|
+ const wlan_hdr_t *hdr;
|
|
+ client_t *client;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = req->hdr;
|
|
+
|
|
+ if (acx_debug & L_ASSOC) {
|
|
+ acx_print_mac("got deauth from sta:", hdr->a2, " ");
|
|
+ acx_print_mac("a1:", hdr->a1, " ");
|
|
+ acx_print_mac("a3:", hdr->a3, " ");
|
|
+ acx_print_mac("adev->addr:", adev->dev_addr, " ");
|
|
+ acx_print_mac("adev->bssid:", adev->bssid, "\n");
|
|
+ }
|
|
+
|
|
+ if (!mac_is_equal(adev->dev_addr, hdr->a1)) {
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ client = acx_l_sta_list_get(adev, hdr->a2);
|
|
+ if (!client) {
|
|
+ goto end;
|
|
+ }
|
|
+ client->used = CLIENT_EXIST_1;
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_disassoc_from_ap
|
|
+*/
|
|
+static void
|
|
+acx_l_process_disassoc_from_ap(acx_device_t *adev, const wlan_fr_disassoc_t *req)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (!adev->ap_client) {
|
|
+ /* Hrm, we aren't assoc'ed yet anyhow... */
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ printk("%s: got disassoc frame with reason %d (%s)\n",
|
|
+ adev->ndev->name, *req->reason,
|
|
+ acx_wlan_reason_str(*req->reason));
|
|
+
|
|
+ if (mac_is_equal(adev->dev_addr, req->hdr->a1)) {
|
|
+ acx_l_transmit_deauthen(adev, adev->bssid,
|
|
+ WLAN_MGMT_REASON_DEAUTH_LEAVING);
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_UPDATE_CARD_CFG);
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_deauth_from_ap
|
|
+*/
|
|
+static void
|
|
+acx_l_process_deauth_from_ap(acx_device_t *adev, const wlan_fr_deauthen_t *req)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (!adev->ap_client) {
|
|
+ /* Hrm, we aren't assoc'ed yet anyhow... */
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ printk("%s: got deauth frame with reason %d (%s)\n",
|
|
+ adev->ndev->name, *req->reason,
|
|
+ acx_wlan_reason_str(*req->reason));
|
|
+
|
|
+ /* Chk: is ta verified to be from our AP? */
|
|
+ if (mac_is_equal(adev->dev_addr, req->hdr->a1)) {
|
|
+ log(L_DEBUG, "AP sent us deauth packet\n");
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_UPDATE_CARD_CFG);
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_rx
|
|
+**
|
|
+** The end of the Rx path. Pulls data from a rxhostdesc into a socket
|
|
+** buffer and feeds it to the network stack via netif_rx().
|
|
+*/
|
|
+static void
|
|
+acx_l_rx(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ if (likely(adev->dev_state_mask & ACX_STATE_IFACE_UP)) {
|
|
+ struct sk_buff *skb;
|
|
+ skb = acx_rxbuf_to_ether(adev, rxbuf);
|
|
+ if (likely(skb)) {
|
|
+ netif_rx(skb);
|
|
+ adev->ndev->last_rx = jiffies;
|
|
+ adev->stats.rx_packets++;
|
|
+ adev->stats.rx_bytes += skb->len;
|
|
+ }
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_data_frame_master
|
|
+*/
|
|
+static int
|
|
+acx_l_process_data_frame_master(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ struct wlan_hdr *hdr;
|
|
+ struct tx *tx;
|
|
+ void *txbuf;
|
|
+ int len;
|
|
+ int result = NOT_OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+
|
|
+ switch (WF_FC_FROMTODSi & hdr->fc) {
|
|
+ case 0:
|
|
+ case WF_FC_FROMDSi:
|
|
+ log(L_DEBUG, "ap->sta or adhoc->adhoc data frame ignored\n");
|
|
+ goto done;
|
|
+ case WF_FC_TODSi:
|
|
+ break;
|
|
+ default: /* WF_FC_FROMTODSi */
|
|
+ log(L_DEBUG, "wds data frame ignored (TODO)\n");
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ /* check if it is our BSSID, if not, leave */
|
|
+ if (!mac_is_equal(adev->bssid, hdr->a1)) {
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ if (mac_is_equal(adev->dev_addr, hdr->a3)) {
|
|
+ /* this one is for us */
|
|
+ acx_l_rx(adev, rxbuf);
|
|
+ } else {
|
|
+ if (mac_is_bcast(hdr->a3)) {
|
|
+ /* this one is bcast, rx it too */
|
|
+ acx_l_rx(adev, rxbuf);
|
|
+ }
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx) {
|
|
+ goto fail;
|
|
+ }
|
|
+ /* repackage, tx, and hope it someday reaches its destination */
|
|
+ /* order is important, we do it in-place */
|
|
+ MAC_COPY(hdr->a1, hdr->a3);
|
|
+ MAC_COPY(hdr->a3, hdr->a2);
|
|
+ MAC_COPY(hdr->a2, adev->bssid);
|
|
+ /* To_DS = 0, From_DS = 1 */
|
|
+ hdr->fc = WF_FC_FROMDSi + WF_FTYPE_DATAi;
|
|
+
|
|
+ txbuf = acx_l_get_txbuf(adev, tx);
|
|
+ if (txbuf) {
|
|
+ len = RXBUF_BYTES_RCVD(adev, rxbuf);
|
|
+ memcpy(txbuf, hdr, len);
|
|
+ acx_l_tx_data(adev, tx, len);
|
|
+ } else {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ }
|
|
+ }
|
|
+done:
|
|
+ result = OK;
|
|
+fail:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_data_frame_client
|
|
+*/
|
|
+static int
|
|
+acx_l_process_data_frame_client(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ const u8 *da, *bssid;
|
|
+ const wlan_hdr_t *hdr;
|
|
+ struct net_device *ndev = adev->ndev;
|
|
+ int result = NOT_OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (ACX_STATUS_4_ASSOCIATED != adev->status)
|
|
+ goto drop;
|
|
+
|
|
+ hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+
|
|
+ switch (WF_FC_FROMTODSi & hdr->fc) {
|
|
+ case 0:
|
|
+ if (adev->mode != ACX_MODE_0_ADHOC) {
|
|
+ log(L_DEBUG, "adhoc->adhoc data frame ignored\n");
|
|
+ goto drop;
|
|
+ }
|
|
+ bssid = hdr->a3;
|
|
+ break;
|
|
+ case WF_FC_FROMDSi:
|
|
+ if (adev->mode != ACX_MODE_2_STA) {
|
|
+ log(L_DEBUG, "ap->sta data frame ignored\n");
|
|
+ goto drop;
|
|
+ }
|
|
+ bssid = hdr->a2;
|
|
+ break;
|
|
+ case WF_FC_TODSi:
|
|
+ log(L_DEBUG, "sta->ap data frame ignored\n");
|
|
+ goto drop;
|
|
+ default: /* WF_FC_FROMTODSi: wds->wds */
|
|
+ log(L_DEBUG, "wds data frame ignored (todo)\n");
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ da = hdr->a1;
|
|
+
|
|
+ if (unlikely(acx_debug & L_DEBUG)) {
|
|
+ acx_print_mac("rx: da=", da, "");
|
|
+ acx_print_mac(" bssid=", bssid, "");
|
|
+ acx_print_mac(" adev->bssid=", adev->bssid, "");
|
|
+ acx_print_mac(" adev->addr=", adev->dev_addr, "\n");
|
|
+ }
|
|
+
|
|
+ /* promiscuous mode --> receive all packets */
|
|
+ if (unlikely(ndev->flags & IFF_PROMISC))
|
|
+ goto process;
|
|
+
|
|
+ /* FIRST, check if it is our BSSID */
|
|
+ if (!mac_is_equal(adev->bssid, bssid)) {
|
|
+ /* is not our BSSID, so bail out */
|
|
+ goto drop;
|
|
+ }
|
|
+
|
|
+ /* then, check if it is our address */
|
|
+ if (mac_is_equal(adev->dev_addr, da)) {
|
|
+ goto process;
|
|
+ }
|
|
+
|
|
+ /* then, check if it is broadcast */
|
|
+ if (mac_is_bcast(da)) {
|
|
+ goto process;
|
|
+ }
|
|
+
|
|
+ if (mac_is_mcast(da)) {
|
|
+ /* unconditionally receive all multicasts */
|
|
+ if (ndev->flags & IFF_ALLMULTI)
|
|
+ goto process;
|
|
+
|
|
+ /* FIXME: need to check against the list of
|
|
+ * multicast addresses that are configured
|
|
+ * for the interface (ifconfig) */
|
|
+ log(L_XFER, "FIXME: multicast packet, need to check "
|
|
+ "against a list of multicast addresses "
|
|
+ "(to be created!); accepting packet for now\n");
|
|
+ /* for now, just accept it here */
|
|
+ goto process;
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "rx: foreign packet, dropping\n");
|
|
+ goto drop;
|
|
+process:
|
|
+ /* receive packet */
|
|
+ acx_l_rx(adev, rxbuf);
|
|
+
|
|
+ result = OK;
|
|
+drop:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_mgmt_frame
|
|
+**
|
|
+** Theory of operation: mgmt packet gets parsed (to make it easy
|
|
+** to access variable-sized IEs), results stored in 'parsed'.
|
|
+** Then we react to the packet.
|
|
+*/
|
|
+typedef union parsed_mgmt_req {
|
|
+ wlan_fr_mgmt_t mgmt;
|
|
+ wlan_fr_assocreq_t assocreq;
|
|
+ wlan_fr_reassocreq_t reassocreq;
|
|
+ wlan_fr_assocresp_t assocresp;
|
|
+ wlan_fr_reassocresp_t reassocresp;
|
|
+ wlan_fr_beacon_t beacon;
|
|
+ wlan_fr_disassoc_t disassoc;
|
|
+ wlan_fr_authen_t authen;
|
|
+ wlan_fr_deauthen_t deauthen;
|
|
+ wlan_fr_proberesp_t proberesp;
|
|
+} parsed_mgmt_req_t;
|
|
+
|
|
+void BUG_excessive_stack_usage(void);
|
|
+
|
|
+static int
|
|
+acx_l_process_mgmt_frame(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ parsed_mgmt_req_t parsed; /* takes ~100 bytes of stack */
|
|
+ wlan_hdr_t *hdr;
|
|
+ int adhoc, sta_scan, sta, ap;
|
|
+ int len;
|
|
+
|
|
+ if (sizeof(parsed) > 256)
|
|
+ BUG_excessive_stack_usage();
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+
|
|
+ /* Management frames never have these set */
|
|
+ if (WF_FC_FROMTODSi & hdr->fc) {
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+
|
|
+ len = RXBUF_BYTES_RCVD(adev, rxbuf);
|
|
+ if (WF_FC_ISWEPi & hdr->fc)
|
|
+ len -= 0x10;
|
|
+
|
|
+ adhoc = (adev->mode == ACX_MODE_0_ADHOC);
|
|
+ sta_scan = ((adev->mode == ACX_MODE_2_STA)
|
|
+ && (adev->status != ACX_STATUS_4_ASSOCIATED));
|
|
+ sta = ((adev->mode == ACX_MODE_2_STA)
|
|
+ && (adev->status == ACX_STATUS_4_ASSOCIATED));
|
|
+ ap = (adev->mode == ACX_MODE_3_AP);
|
|
+
|
|
+ switch (WF_FC_FSTYPEi & hdr->fc) {
|
|
+ /* beacons first, for speed */
|
|
+ case WF_FSTYPE_BEACONi:
|
|
+ memset(&parsed.beacon, 0, sizeof(parsed.beacon));
|
|
+ parsed.beacon.hdr = hdr;
|
|
+ parsed.beacon.len = len;
|
|
+ if (acx_debug & L_DATA) {
|
|
+ printk("beacon len:%d fc:%04X dur:%04X seq:%04X",
|
|
+ len, hdr->fc, hdr->dur, hdr->seq);
|
|
+ acx_print_mac(" a1:", hdr->a1, "");
|
|
+ acx_print_mac(" a2:", hdr->a2, "");
|
|
+ acx_print_mac(" a3:", hdr->a3, "\n");
|
|
+ }
|
|
+ wlan_mgmt_decode_beacon(&parsed.beacon);
|
|
+ /* beacon and probe response are very similar, so... */
|
|
+ acx_l_process_probe_response(adev, &parsed.beacon, rxbuf);
|
|
+ break;
|
|
+ case WF_FSTYPE_ASSOCREQi:
|
|
+ if (!ap)
|
|
+ break;
|
|
+ memset(&parsed.assocreq, 0, sizeof(parsed.assocreq));
|
|
+ parsed.assocreq.hdr = hdr;
|
|
+ parsed.assocreq.len = len;
|
|
+ wlan_mgmt_decode_assocreq(&parsed.assocreq);
|
|
+ if (mac_is_equal(hdr->a1, adev->bssid)
|
|
+ && mac_is_equal(hdr->a3, adev->bssid)) {
|
|
+ acx_l_transmit_assocresp(adev, &parsed.assocreq);
|
|
+ }
|
|
+ break;
|
|
+ case WF_FSTYPE_REASSOCREQi:
|
|
+ if (!ap)
|
|
+ break;
|
|
+ memset(&parsed.assocreq, 0, sizeof(parsed.assocreq));
|
|
+ parsed.assocreq.hdr = hdr;
|
|
+ parsed.assocreq.len = len;
|
|
+ wlan_mgmt_decode_assocreq(&parsed.assocreq);
|
|
+ /* reassocreq and assocreq are equivalent */
|
|
+ acx_l_transmit_reassocresp(adev, &parsed.reassocreq);
|
|
+ break;
|
|
+ case WF_FSTYPE_ASSOCRESPi:
|
|
+ if (!sta_scan)
|
|
+ break;
|
|
+ memset(&parsed.assocresp, 0, sizeof(parsed.assocresp));
|
|
+ parsed.assocresp.hdr = hdr;
|
|
+ parsed.assocresp.len = len;
|
|
+ wlan_mgmt_decode_assocresp(&parsed.assocresp);
|
|
+ acx_l_process_assocresp(adev, &parsed.assocresp);
|
|
+ break;
|
|
+ case WF_FSTYPE_REASSOCRESPi:
|
|
+ if (!sta_scan)
|
|
+ break;
|
|
+ memset(&parsed.assocresp, 0, sizeof(parsed.assocresp));
|
|
+ parsed.assocresp.hdr = hdr;
|
|
+ parsed.assocresp.len = len;
|
|
+ wlan_mgmt_decode_assocresp(&parsed.assocresp);
|
|
+ acx_l_process_reassocresp(adev, &parsed.reassocresp);
|
|
+ break;
|
|
+ case WF_FSTYPE_PROBEREQi:
|
|
+ if (ap || adhoc) {
|
|
+ /* FIXME: since we're supposed to be an AP,
|
|
+ ** we need to return a Probe Response packet.
|
|
+ ** Currently firmware is doing it for us,
|
|
+ ** but firmware is buggy! See comment elsewhere --vda */
|
|
+ }
|
|
+ break;
|
|
+ case WF_FSTYPE_PROBERESPi:
|
|
+ memset(&parsed.proberesp, 0, sizeof(parsed.proberesp));
|
|
+ parsed.proberesp.hdr = hdr;
|
|
+ parsed.proberesp.len = len;
|
|
+ wlan_mgmt_decode_proberesp(&parsed.proberesp);
|
|
+ acx_l_process_probe_response(adev, &parsed.proberesp, rxbuf);
|
|
+ break;
|
|
+ case 6:
|
|
+ case 7:
|
|
+ /* exit */
|
|
+ break;
|
|
+ case WF_FSTYPE_ATIMi:
|
|
+ /* exit */
|
|
+ break;
|
|
+ case WF_FSTYPE_DISASSOCi:
|
|
+ if (!sta && !ap)
|
|
+ break;
|
|
+ memset(&parsed.disassoc, 0, sizeof(parsed.disassoc));
|
|
+ parsed.disassoc.hdr = hdr;
|
|
+ parsed.disassoc.len = len;
|
|
+ wlan_mgmt_decode_disassoc(&parsed.disassoc);
|
|
+ if (sta)
|
|
+ acx_l_process_disassoc_from_ap(adev, &parsed.disassoc);
|
|
+ else
|
|
+ acx_l_process_disassoc_from_sta(adev, &parsed.disassoc);
|
|
+ break;
|
|
+ case WF_FSTYPE_AUTHENi:
|
|
+ if (!sta_scan && !ap)
|
|
+ break;
|
|
+ memset(&parsed.authen, 0, sizeof(parsed.authen));
|
|
+ parsed.authen.hdr = hdr;
|
|
+ parsed.authen.len = len;
|
|
+ wlan_mgmt_decode_authen(&parsed.authen);
|
|
+ acx_l_process_authen(adev, &parsed.authen);
|
|
+ break;
|
|
+ case WF_FSTYPE_DEAUTHENi:
|
|
+ if (!sta && !ap)
|
|
+ break;
|
|
+ memset(&parsed.deauthen, 0, sizeof(parsed.deauthen));
|
|
+ parsed.deauthen.hdr = hdr;
|
|
+ parsed.deauthen.len = len;
|
|
+ wlan_mgmt_decode_deauthen(&parsed.deauthen);
|
|
+ if (sta)
|
|
+ acx_l_process_deauth_from_ap(adev, &parsed.deauthen);
|
|
+ else
|
|
+ acx_l_process_deauth_from_sta(adev, &parsed.deauthen);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+#ifdef UNUSED
|
|
+/***********************************************************************
|
|
+** acx_process_class_frame
|
|
+**
|
|
+** Called from IRQ context only
|
|
+*/
|
|
+static int
|
|
+acx_process_class_frame(acx_device_t *adev, rxbuffer_t *rxbuf, int vala)
|
|
+{
|
|
+ return OK;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_NULL_frame
|
|
+*/
|
|
+#ifdef BOGUS_ITS_NOT_A_NULL_FRAME_HANDLER_AT_ALL
|
|
+static int
|
|
+acx_l_process_NULL_frame(acx_device_t *adev, rxbuffer_t *rxbuf, int vala)
|
|
+{
|
|
+ const signed char *esi;
|
|
+ const u8 *ebx;
|
|
+ const wlan_hdr_t *hdr;
|
|
+ const client_t *client;
|
|
+ int result = NOT_OK;
|
|
+
|
|
+ hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+
|
|
+ switch (WF_FC_FROMTODSi & hdr->fc) {
|
|
+ case 0:
|
|
+ esi = hdr->a1;
|
|
+ ebx = hdr->a2;
|
|
+ break;
|
|
+ case WF_FC_FROMDSi:
|
|
+ esi = hdr->a1;
|
|
+ ebx = hdr->a3;
|
|
+ break;
|
|
+ case WF_FC_TODSi:
|
|
+ esi = hdr->a1;
|
|
+ ebx = hdr->a2;
|
|
+ break;
|
|
+ default: /* WF_FC_FROMTODSi */
|
|
+ esi = hdr->a1; /* added by me! --vda */
|
|
+ ebx = hdr->a2;
|
|
+ }
|
|
+
|
|
+ if (esi[0x0] < 0) {
|
|
+ result = OK;
|
|
+ goto done;
|
|
+ }
|
|
+
|
|
+ client = acx_l_sta_list_get(adev, ebx);
|
|
+ if (client)
|
|
+ result = NOT_OK;
|
|
+ else {
|
|
+#ifdef IS_IT_BROKEN
|
|
+ log(L_DEBUG|L_XFER, "<transmit_deauth 7>\n");
|
|
+ acx_l_transmit_deauthen(adev, ebx,
|
|
+ WLAN_MGMT_REASON_CLASS2_NONAUTH);
|
|
+#else
|
|
+ log(L_DEBUG, "received NULL frame from unknown client! "
|
|
+ "We really shouldn't send deauthen here, right?\n");
|
|
+#endif
|
|
+ result = OK;
|
|
+ }
|
|
+done:
|
|
+ return result;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_probe_response
|
|
+*/
|
|
+static int
|
|
+acx_l_process_probe_response(acx_device_t *adev, wlan_fr_proberesp_t *req,
|
|
+ const rxbuffer_t *rxbuf)
|
|
+{
|
|
+ struct client *bss;
|
|
+ wlan_hdr_t *hdr;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = req->hdr;
|
|
+
|
|
+ if (mac_is_equal(hdr->a3, adev->dev_addr)) {
|
|
+ log(L_ASSOC, "huh, scan found our own MAC!?\n");
|
|
+ goto ok; /* just skip this one silently */
|
|
+ }
|
|
+
|
|
+ bss = acx_l_sta_list_get_or_add(adev, hdr->a2);
|
|
+
|
|
+ /* NB: be careful modifying bss data! It may be one
|
|
+ ** of the already known clients (like our AP if we are a STA)
|
|
+ ** Thus do not blindly modify e.g. current ratemask! */
|
|
+
|
|
+ if (STA_LIST_ADD_CAN_FAIL && !bss) {
|
|
+ /* uh oh, we found more sites/stations than we can handle with
|
|
+ * our current setup: pull the emergency brake and stop scanning! */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_STOP_SCAN);
|
|
+ /* TODO: a nice comment what below call achieves --vda */
|
|
+ acx_set_status(adev, ACX_STATUS_2_WAIT_AUTH);
|
|
+ goto ok;
|
|
+ }
|
|
+ /* NB: get_or_add already filled bss->address = hdr->a2 */
|
|
+ MAC_COPY(bss->bssid, hdr->a3);
|
|
+
|
|
+ /* copy the ESSID element */
|
|
+ if (req->ssid && req->ssid->len <= IW_ESSID_MAX_SIZE) {
|
|
+ bss->essid_len = req->ssid->len;
|
|
+ memcpy(bss->essid, req->ssid->ssid, req->ssid->len);
|
|
+ bss->essid[req->ssid->len] = '\0';
|
|
+ } else {
|
|
+ /* Either no ESSID IE or oversized one */
|
|
+ printk("%s: received packet has bogus ESSID\n",
|
|
+ adev->ndev->name);
|
|
+ }
|
|
+
|
|
+ if (req->ds_parms)
|
|
+ bss->channel = req->ds_parms->curr_ch;
|
|
+ if (req->cap_info)
|
|
+ bss->cap_info = ieee2host16(*req->cap_info);
|
|
+
|
|
+ bss->sir = acx_signal_to_winlevel(rxbuf->phy_level);
|
|
+ bss->snr = acx_signal_to_winlevel(rxbuf->phy_snr);
|
|
+
|
|
+ bss->rate_cap = 0; /* operational mask */
|
|
+ bss->rate_bas = 0; /* basic mask */
|
|
+ if (req->supp_rates)
|
|
+ add_bits_to_ratemasks(req->supp_rates->rates,
|
|
+ req->supp_rates->len, &bss->rate_bas, &bss->rate_cap);
|
|
+ if (req->ext_rates)
|
|
+ add_bits_to_ratemasks(req->ext_rates->rates,
|
|
+ req->ext_rates->len, &bss->rate_bas, &bss->rate_cap);
|
|
+ /* Fix up any possible bogosity - code elsewhere
|
|
+ * is not expecting empty masks */
|
|
+ if (!bss->rate_cap)
|
|
+ bss->rate_cap = adev->rate_basic;
|
|
+ if (!bss->rate_bas)
|
|
+ bss->rate_bas = 1 << lowest_bit(bss->rate_cap);
|
|
+ if (!bss->rate_cur)
|
|
+ bss->rate_cur = 1 << lowest_bit(bss->rate_bas);
|
|
+
|
|
+ /* People moan about this being too noisy at L_ASSOC */
|
|
+ log(L_DEBUG,
|
|
+ "found %s: ESSID=\"%s\" ch=%d "
|
|
+ "BSSID="MACSTR" caps=0x%04X SIR=%d SNR=%d\n",
|
|
+ (bss->cap_info & WF_MGMT_CAP_IBSS) ? "Ad-Hoc peer" : "AP",
|
|
+ bss->essid, bss->channel, MAC(bss->bssid), bss->cap_info,
|
|
+ bss->sir, bss->snr);
|
|
+ok:
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_assocresp
|
|
+*/
|
|
+static int
|
|
+acx_l_process_assocresp(acx_device_t *adev, const wlan_fr_assocresp_t *req)
|
|
+{
|
|
+ const wlan_hdr_t *hdr;
|
|
+ int res = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = req->hdr;
|
|
+
|
|
+ if ((ACX_MODE_2_STA == adev->mode)
|
|
+ && mac_is_equal(adev->dev_addr, hdr->a1)) {
|
|
+ u16 st = ieee2host16(*(req->status));
|
|
+ if (WLAN_MGMT_STATUS_SUCCESS == st) {
|
|
+ adev->aid = ieee2host16(*(req->aid));
|
|
+ /* tell the card we are associated when
|
|
+ ** we are out of interrupt context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_ASSOCIATE);
|
|
+ } else {
|
|
+
|
|
+ /* TODO: we shall delete peer from sta_list, and try
|
|
+ ** other candidates... */
|
|
+
|
|
+ printk("%s: association FAILED: peer sent "
|
|
+ "Status Code %d (%s)\n",
|
|
+ adev->ndev->name, st, get_status_string(st));
|
|
+ res = NOT_OK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_reassocresp
|
|
+*/
|
|
+static int
|
|
+acx_l_process_reassocresp(acx_device_t *adev, const wlan_fr_reassocresp_t *req)
|
|
+{
|
|
+ const wlan_hdr_t *hdr;
|
|
+ int result = NOT_OK;
|
|
+ u16 st;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = req->hdr;
|
|
+
|
|
+ if (!mac_is_equal(adev->dev_addr, hdr->a1)) {
|
|
+ goto end;
|
|
+ }
|
|
+ st = ieee2host16(*(req->status));
|
|
+ if (st == WLAN_MGMT_STATUS_SUCCESS) {
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED);
|
|
+ result = OK;
|
|
+ } else {
|
|
+ printk("%s: reassociation FAILED: peer sent "
|
|
+ "response code %d (%s)\n",
|
|
+ adev->ndev->name, st, get_status_string(st));
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_process_authen
|
|
+**
|
|
+** Called only in STA_SCAN or AP mode
|
|
+*/
|
|
+static int
|
|
+acx_l_process_authen(acx_device_t *adev, const wlan_fr_authen_t *req)
|
|
+{
|
|
+ const wlan_hdr_t *hdr;
|
|
+ client_t *clt;
|
|
+ wlan_ie_challenge_t *chal;
|
|
+ u16 alg, seq, status;
|
|
+ int ap, result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ hdr = req->hdr;
|
|
+
|
|
+ if (acx_debug & L_ASSOC) {
|
|
+ acx_print_mac("AUTHEN adev->addr=", adev->dev_addr, " ");
|
|
+ acx_print_mac("a1=", hdr->a1, " ");
|
|
+ acx_print_mac("a2=", hdr->a2, " ");
|
|
+ acx_print_mac("a3=", hdr->a3, " ");
|
|
+ acx_print_mac("adev->bssid=", adev->bssid, "\n");
|
|
+ }
|
|
+
|
|
+ if (!mac_is_equal(adev->dev_addr, hdr->a1)
|
|
+ || !mac_is_equal(adev->bssid, hdr->a3)) {
|
|
+ result = OK;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ alg = ieee2host16(*(req->auth_alg));
|
|
+ seq = ieee2host16(*(req->auth_seq));
|
|
+ status = ieee2host16(*(req->status));
|
|
+
|
|
+ log(L_ASSOC, "auth algorithm %d, auth sequence %d, status %d\n", alg, seq, status);
|
|
+
|
|
+ ap = (adev->mode == ACX_MODE_3_AP);
|
|
+
|
|
+ if (adev->auth_alg <= 1) {
|
|
+ if (adev->auth_alg != alg) {
|
|
+ log(L_ASSOC, "auth algorithm mismatch: "
|
|
+ "our:%d peer:%d\n", adev->auth_alg, alg);
|
|
+ result = NOT_OK;
|
|
+ goto end;
|
|
+ }
|
|
+ }
|
|
+ if (ap) {
|
|
+ clt = acx_l_sta_list_get_or_add(adev, hdr->a2);
|
|
+ if (STA_LIST_ADD_CAN_FAIL && !clt) {
|
|
+ log(L_ASSOC, "could not allocate room for client\n");
|
|
+ result = NOT_OK;
|
|
+ goto end;
|
|
+ }
|
|
+ } else {
|
|
+ clt = adev->ap_client;
|
|
+ if (!mac_is_equal(clt->address, hdr->a2)) {
|
|
+ printk("%s: malformed auth frame from AP?!\n",
|
|
+ adev->ndev->name);
|
|
+ result = NOT_OK;
|
|
+ goto end;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* now check which step in the authentication sequence we are
|
|
+ * currently in, and act accordingly */
|
|
+ switch (seq) {
|
|
+ case 1:
|
|
+ if (!ap)
|
|
+ break;
|
|
+ acx_l_transmit_authen2(adev, req, clt);
|
|
+ break;
|
|
+ case 2:
|
|
+ if (ap)
|
|
+ break;
|
|
+ if (status == WLAN_MGMT_STATUS_SUCCESS) {
|
|
+ if (alg == WLAN_AUTH_ALG_OPENSYSTEM) {
|
|
+ acx_set_status(adev, ACX_STATUS_3_AUTHENTICATED);
|
|
+ acx_l_transmit_assoc_req(adev);
|
|
+ } else
|
|
+ if (alg == WLAN_AUTH_ALG_SHAREDKEY) {
|
|
+ acx_l_transmit_authen3(adev, req);
|
|
+ }
|
|
+ } else {
|
|
+ printk("%s: auth FAILED: peer sent "
|
|
+ "response code %d (%s), "
|
|
+ "still waiting for authentication\n",
|
|
+ adev->ndev->name,
|
|
+ status, get_status_string(status));
|
|
+ acx_set_status(adev, ACX_STATUS_2_WAIT_AUTH);
|
|
+ }
|
|
+ break;
|
|
+ case 3:
|
|
+ if (!ap)
|
|
+ break;
|
|
+ if ((clt->auth_alg != WLAN_AUTH_ALG_SHAREDKEY)
|
|
+ || (alg != WLAN_AUTH_ALG_SHAREDKEY)
|
|
+ || (clt->auth_step != 2))
|
|
+ break;
|
|
+ chal = req->challenge;
|
|
+ if (!chal
|
|
+ || memcmp(chal->challenge, clt->challenge_text, WLAN_CHALLENGE_LEN)
|
|
+ || (chal->eid != WLAN_EID_CHALLENGE)
|
|
+ || (chal->len != WLAN_CHALLENGE_LEN)
|
|
+ )
|
|
+ break;
|
|
+ acx_l_transmit_authen4(adev, req);
|
|
+ MAC_COPY(clt->address, hdr->a2);
|
|
+ clt->used = CLIENT_AUTHENTICATED_2;
|
|
+ clt->auth_step = 4;
|
|
+ clt->seq = ieee2host16(hdr->seq);
|
|
+ break;
|
|
+ case 4:
|
|
+ if (ap)
|
|
+ break;
|
|
+ /* ok, we're through: we're authenticated. Woohoo!! */
|
|
+ acx_set_status(adev, ACX_STATUS_3_AUTHENTICATED);
|
|
+ log(L_ASSOC, "Authenticated!\n");
|
|
+ /* now that we're authenticated, request association */
|
|
+ acx_l_transmit_assoc_req(adev);
|
|
+ break;
|
|
+ }
|
|
+ result = OK;
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_gen_challenge
|
|
+*/
|
|
+static inline void
|
|
+acx_gen_challenge(wlan_ie_challenge_t* d)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ d->eid = WLAN_EID_CHALLENGE;
|
|
+ d->len = WLAN_CHALLENGE_LEN;
|
|
+ get_random_bytes(d->challenge, WLAN_CHALLENGE_LEN);
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_deauthen
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_deauthen(acx_device_t *adev, const u8 *addr, u16 reason)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct deauthen_frame_body *body;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto bad;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto bad;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = (WF_FTYPE_MGMTi | WF_FSTYPE_DEAUTHENi);
|
|
+ head->dur = 0;
|
|
+ MAC_COPY(head->da, addr);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, adev->bssid);
|
|
+ head->seq = 0;
|
|
+
|
|
+ log(L_DEBUG|L_ASSOC|L_XFER,
|
|
+ "sending deauthen to "MACSTR" for %d\n",
|
|
+ MAC(addr), reason);
|
|
+
|
|
+ body->reason = host2ieee16(reason);
|
|
+
|
|
+ /* body is fixed size here, but beware of cutting-and-pasting this -
|
|
+ ** do not use sizeof(*body) for variable sized mgmt packets! */
|
|
+ acx_l_tx_data(adev, tx, WLAN_HDR_A3_LEN + sizeof(*body));
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_authen1
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_authen1(acx_device_t *adev)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct auth_frame_body *body;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_ASSOC, "sending authentication1 request (auth algo %d), "
|
|
+ "awaiting response\n", adev->auth_alg);
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto bad;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto bad;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = WF_FSTYPE_AUTHENi;
|
|
+ /* duration should be 0 instead of 0x8000 to have
|
|
+ * the firmware calculate the value, right? */
|
|
+ head->dur = 0;
|
|
+ MAC_COPY(head->da, adev->bssid);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, adev->bssid);
|
|
+ head->seq = 0;
|
|
+
|
|
+ body->auth_alg = host2ieee16(adev->auth_alg);
|
|
+ body->auth_seq = host2ieee16(1);
|
|
+ body->status = host2ieee16(0);
|
|
+
|
|
+ acx_l_tx_data(adev, tx, WLAN_HDR_A3_LEN + 2 + 2 + 2);
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_authen2
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_authen2(acx_device_t *adev, const wlan_fr_authen_t *req,
|
|
+ client_t *clt)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct auth_frame_body *body;
|
|
+ unsigned int packet_len;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (!clt)
|
|
+ goto ok;
|
|
+
|
|
+ MAC_COPY(clt->address, req->hdr->a2);
|
|
+#ifdef UNUSED
|
|
+ clt->ps = ((WF_FC_PWRMGTi & req->hdr->fc) != 0);
|
|
+#endif
|
|
+ clt->auth_alg = ieee2host16(*(req->auth_alg));
|
|
+ clt->auth_step = 2;
|
|
+ clt->seq = ieee2host16(req->hdr->seq);
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto bad;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto bad;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = WF_FSTYPE_AUTHENi;
|
|
+ head->dur = 0 /* req->hdr->dur */;
|
|
+ MAC_COPY(head->da, req->hdr->a2);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, req->hdr->a3);
|
|
+ head->seq = 0 /* req->hdr->seq */;
|
|
+
|
|
+ /* already in IEEE format, no endianness conversion */
|
|
+ body->auth_alg = *(req->auth_alg);
|
|
+ body->auth_seq = host2ieee16(2);
|
|
+ body->status = host2ieee16(0);
|
|
+
|
|
+ packet_len = WLAN_HDR_A3_LEN + 2 + 2 + 2;
|
|
+ if (ieee2host16(*(req->auth_alg)) == WLAN_AUTH_ALG_OPENSYSTEM) {
|
|
+ clt->used = CLIENT_AUTHENTICATED_2;
|
|
+ } else { /* shared key */
|
|
+ acx_gen_challenge(&body->challenge);
|
|
+ memcpy(&clt->challenge_text, body->challenge.challenge, WLAN_CHALLENGE_LEN);
|
|
+ packet_len += 2 + 2 + 2 + 1+1+WLAN_CHALLENGE_LEN;
|
|
+ }
|
|
+
|
|
+ acxlog_mac(L_ASSOC|L_XFER,
|
|
+ "transmit_auth2: BSSID=", head->bssid, "\n");
|
|
+
|
|
+ acx_l_tx_data(adev, tx, packet_len);
|
|
+ok:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_authen3
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_authen3(acx_device_t *adev, const wlan_fr_authen_t *req)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct auth_frame_body *body;
|
|
+ unsigned int packet_len;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto ok;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto ok;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ /* add WF_FC_ISWEPi: auth step 3 needs to be encrypted */
|
|
+ head->fc = WF_FC_ISWEPi + WF_FSTYPE_AUTHENi;
|
|
+ /* FIXME: is this needed?? authen4 does it...
|
|
+ * I think it's even wrong since we shouldn't re-use old
|
|
+ * values but instead let the firmware calculate proper ones
|
|
+ head->dur = req->hdr->dur;
|
|
+ head->seq = req->hdr->seq;
|
|
+ */
|
|
+ MAC_COPY(head->da, adev->bssid);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, adev->bssid);
|
|
+
|
|
+ /* already in IEEE format, no endianness conversion */
|
|
+ body->auth_alg = *(req->auth_alg);
|
|
+ body->auth_seq = host2ieee16(3);
|
|
+ body->status = host2ieee16(0);
|
|
+ memcpy(&body->challenge, req->challenge, req->challenge->len + 2);
|
|
+ packet_len = WLAN_HDR_A3_LEN + 8 + req->challenge->len;
|
|
+
|
|
+ log(L_ASSOC|L_XFER, "transmit_authen3!\n");
|
|
+
|
|
+ acx_l_tx_data(adev, tx, packet_len);
|
|
+ok:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_authen4
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_authen4(acx_device_t *adev, const wlan_fr_authen_t *req)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct auth_frame_body *body;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto ok;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto ok;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = WF_FSTYPE_AUTHENi; /* 0xb0 */
|
|
+ head->dur = 0 /* req->hdr->dur */;
|
|
+ MAC_COPY(head->da, req->hdr->a2);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, req->hdr->a3);
|
|
+ head->seq = 0 /* req->hdr->seq */;
|
|
+
|
|
+ /* already in IEEE format, no endianness conversion */
|
|
+ body->auth_alg = *(req->auth_alg);
|
|
+ body->auth_seq = host2ieee16(4);
|
|
+ body->status = host2ieee16(0);
|
|
+
|
|
+ acx_l_tx_data(adev, tx, WLAN_HDR_A3_LEN + 2 + 2 + 2);
|
|
+ok:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_assoc_req
|
|
+**
|
|
+** adev->ap_client is a current candidate AP here
|
|
+*/
|
|
+static int
|
|
+acx_l_transmit_assoc_req(acx_device_t *adev)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ u8 *body, *p, *prate;
|
|
+ unsigned int packet_len;
|
|
+ u16 cap;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_ASSOC, "sending association request, "
|
|
+ "awaiting response. NOT ASSOCIATED YET\n");
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto bad;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto bad;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+ head->fc = WF_FSTYPE_ASSOCREQi;
|
|
+ head->dur = host2ieee16(0x8000);
|
|
+ MAC_COPY(head->da, adev->bssid);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, adev->bssid);
|
|
+ head->seq = 0;
|
|
+
|
|
+ p = body;
|
|
+ /* now start filling the AssocReq frame body */
|
|
+
|
|
+ /* since this assoc request will most likely only get
|
|
+ * sent in the STA to AP case (and not when Ad-Hoc IBSS),
|
|
+ * the cap combination indicated here will thus be
|
|
+ * WF_MGMT_CAP_ESSi *always* (no IBSS ever)
|
|
+ * The specs are more than non-obvious on all that:
|
|
+ *
|
|
+ * 802.11 7.3.1.4 Capability Information field
|
|
+ ** APs set the ESS subfield to 1 and the IBSS subfield to 0 within
|
|
+ ** Beacon or Probe Response management frames. STAs within an IBSS
|
|
+ ** set the ESS subfield to 0 and the IBSS subfield to 1 in transmitted
|
|
+ ** Beacon or Probe Response management frames
|
|
+ **
|
|
+ ** APs set the Privacy subfield to 1 within transmitted Beacon,
|
|
+ ** Probe Response, Association Response, and Reassociation Response
|
|
+ ** if WEP is required for all data type frames within the BSS.
|
|
+ ** STAs within an IBSS set the Privacy subfield to 1 in Beacon
|
|
+ ** or Probe Response management frames if WEP is required
|
|
+ ** for all data type frames within the IBSS */
|
|
+
|
|
+ /* note that returning 0 will be refused by several APs...
|
|
+ * (so this indicates that you're probably supposed to
|
|
+ * "confirm" the ESS mode) */
|
|
+ cap = WF_MGMT_CAP_ESSi;
|
|
+
|
|
+ /* this one used to be a check on wep_restricted,
|
|
+ * but more likely it's wep_enabled instead */
|
|
+ if (adev->wep_enabled)
|
|
+ SET_BIT(cap, WF_MGMT_CAP_PRIVACYi);
|
|
+
|
|
+ /* Probably we can just set these always, because our hw is
|
|
+ ** capable of shortpre and PBCC --vda */
|
|
+ /* only ask for short preamble if the peer station supports it */
|
|
+ if (adev->ap_client->cap_info & WF_MGMT_CAP_SHORT)
|
|
+ SET_BIT(cap, WF_MGMT_CAP_SHORTi);
|
|
+ /* only ask for PBCC support if the peer station supports it */
|
|
+ if (adev->ap_client->cap_info & WF_MGMT_CAP_PBCC)
|
|
+ SET_BIT(cap, WF_MGMT_CAP_PBCCi);
|
|
+
|
|
+ /* IEs: 1. caps */
|
|
+ *(u16*)p = cap; p += 2;
|
|
+ /* 2. listen interval */
|
|
+ *(u16*)p = host2ieee16(adev->listen_interval); p += 2;
|
|
+ /* 3. ESSID */
|
|
+ p = wlan_fill_ie_ssid(p,
|
|
+ strlen(adev->essid_for_assoc), adev->essid_for_assoc);
|
|
+ /* 4. supp rates */
|
|
+ prate = p;
|
|
+ p = wlan_fill_ie_rates(p,
|
|
+ adev->rate_supported_len, adev->rate_supported);
|
|
+ /* 5. ext supp rates */
|
|
+ p = wlan_fill_ie_rates_ext(p,
|
|
+ adev->rate_supported_len, adev->rate_supported);
|
|
+
|
|
+ if (acx_debug & L_DEBUG) {
|
|
+ printk("association: rates element\n");
|
|
+ acx_dump_bytes(prate, p - prate);
|
|
+ }
|
|
+
|
|
+ /* calculate lengths */
|
|
+ packet_len = WLAN_HDR_A3_LEN + (p - body);
|
|
+
|
|
+ log(L_ASSOC, "association: requesting caps 0x%04X, ESSID \"%s\"\n",
|
|
+ cap, adev->essid_for_assoc);
|
|
+
|
|
+ acx_l_tx_data(adev, tx, packet_len);
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_transmit_disassoc
|
|
+**
|
|
+** FIXME: looks like incomplete implementation of a helper:
|
|
+** acx_l_transmit_disassoc(adev, clt) - kick this client (we're an AP)
|
|
+** acx_l_transmit_disassoc(adev, NULL) - leave BSSID (we're a STA)
|
|
+*/
|
|
+#ifdef BROKEN
|
|
+int
|
|
+acx_l_transmit_disassoc(acx_device_t *adev, client_t *clt)
|
|
+{
|
|
+ struct tx *tx;
|
|
+ struct wlan_hdr_mgmt *head;
|
|
+ struct disassoc_frame_body *body;
|
|
+
|
|
+ FN_ENTER;
|
|
+/* if (clt != NULL) { */
|
|
+ tx = acx_l_alloc_tx(adev);
|
|
+ if (!tx)
|
|
+ goto bad;
|
|
+ head = acx_l_get_txbuf(adev, tx);
|
|
+ if (!head) {
|
|
+ acx_l_dealloc_tx(adev, tx);
|
|
+ goto bad;
|
|
+ }
|
|
+ body = (void*)(head + 1);
|
|
+
|
|
+/* clt->used = CLIENT_AUTHENTICATED_2; - not (yet?) associated */
|
|
+
|
|
+ head->fc = WF_FSTYPE_DISASSOCi;
|
|
+ head->dur = 0;
|
|
+ /* huh? It muchly depends on whether we're STA or AP...
|
|
+ ** sta->ap: da=bssid, sa=own, bssid=bssid
|
|
+ ** ap->sta: da=sta, sa=bssid, bssid=bssid. FIXME! */
|
|
+ MAC_COPY(head->da, adev->bssid);
|
|
+ MAC_COPY(head->sa, adev->dev_addr);
|
|
+ MAC_COPY(head->bssid, adev->dev_addr);
|
|
+ head->seq = 0;
|
|
+
|
|
+ /* "Class 3 frame received from nonassociated station." */
|
|
+ body->reason = host2ieee16(7);
|
|
+
|
|
+ /* fixed size struct, ok to sizeof */
|
|
+ acx_l_tx_data(adev, tx, WLAN_HDR_A3_LEN + sizeof(*body));
|
|
+/* } */
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_complete_scan
|
|
+**
|
|
+** Called either from after_interrupt_task() if:
|
|
+** 1) there was Scan_Complete IRQ, or
|
|
+** 2) scanning expired in timer()
|
|
+** We need to decide which ESS or IBSS to join.
|
|
+** Iterates thru adev->sta_list:
|
|
+** if adev->ap is not bcast, will join only specified
|
|
+** ESS or IBSS with this bssid
|
|
+** checks peers' caps for ESS/IBSS bit
|
|
+** checks peers' SSID, allows exact match or hidden SSID
|
|
+** If station to join is chosen:
|
|
+** points adev->ap_client to the chosen struct client
|
|
+** sets adev->essid_for_assoc for future assoc attempt
|
|
+** Auth/assoc is not yet performed
|
|
+** Returns OK if there is no need to restart scan
|
|
+*/
|
|
+int
|
|
+acx_s_complete_scan(acx_device_t *adev)
|
|
+{
|
|
+ struct client *bss;
|
|
+ unsigned long flags;
|
|
+ u16 needed_cap;
|
|
+ int i;
|
|
+ int idx_found = -1;
|
|
+ int result = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ needed_cap = WF_MGMT_CAP_IBSS; /* 2, we require Ad-Hoc */
|
|
+ break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ needed_cap = WF_MGMT_CAP_ESS; /* 1, we require Managed */
|
|
+ break;
|
|
+ default:
|
|
+ printk("acx: driver bug: mode=%d in complete_scan()\n", adev->mode);
|
|
+ dump_stack();
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* TODO: sta_iterator hiding implementation would be nice here... */
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ bss = &adev->sta_list[i];
|
|
+ if (!bss->used) continue;
|
|
+
|
|
+
|
|
+ log(L_ASSOC, "scan table: SSID=\"%s\" CH=%d SIR=%d SNR=%d\n",
|
|
+ bss->essid, bss->channel, bss->sir, bss->snr);
|
|
+
|
|
+ if (!mac_is_bcast(adev->ap))
|
|
+ if (!mac_is_equal(bss->bssid, adev->ap))
|
|
+ continue; /* keep looking */
|
|
+
|
|
+ /* broken peer with no mode flags set? */
|
|
+ if (unlikely(!(bss->cap_info & (WF_MGMT_CAP_ESS | WF_MGMT_CAP_IBSS)))) {
|
|
+ printk("%s: strange peer "MACSTR" found with "
|
|
+ "neither ESS (AP) nor IBSS (Ad-Hoc) "
|
|
+ "capability - skipped\n",
|
|
+ adev->ndev->name, MAC(bss->address));
|
|
+ continue;
|
|
+ }
|
|
+ log(L_ASSOC, "peer_cap 0x%04X, needed_cap 0x%04X\n",
|
|
+ bss->cap_info, needed_cap);
|
|
+
|
|
+ /* does peer station support what we need? */
|
|
+ if ((bss->cap_info & needed_cap) != needed_cap)
|
|
+ continue; /* keep looking */
|
|
+
|
|
+ /* strange peer with NO basic rates?! */
|
|
+ if (unlikely(!bss->rate_bas)) {
|
|
+ printk("%s: strange peer "MACSTR" with empty rate set "
|
|
+ "- skipped\n",
|
|
+ adev->ndev->name, MAC(bss->address));
|
|
+ continue;
|
|
+ }
|
|
+
|
|
+ /* do we support all basic rates of this peer? */
|
|
+ if ((bss->rate_bas & adev->rate_oper) != bss->rate_bas) {
|
|
+/* we probably need to have all rates as operational rates,
|
|
+ even in case of an 11M-only configuration */
|
|
+#ifdef THIS_IS_TROUBLESOME
|
|
+ printk("%s: peer "MACSTR": incompatible basic rates "
|
|
+ "(AP requests 0x%04X, we have 0x%04X) "
|
|
+ "- skipped\n",
|
|
+ adev->ndev->name, MAC(bss->address),
|
|
+ bss->rate_bas, adev->rate_oper);
|
|
+ continue;
|
|
+#else
|
|
+ printk("%s: peer "MACSTR": incompatible basic rates "
|
|
+ "(AP requests 0x%04X, we have 0x%04X). "
|
|
+ "Considering anyway...\n",
|
|
+ adev->ndev->name, MAC(bss->address),
|
|
+ bss->rate_bas, adev->rate_oper);
|
|
+#endif
|
|
+ }
|
|
+
|
|
+ if ( !(adev->reg_dom_chanmask & (1<<(bss->channel-1))) ) {
|
|
+ printk("%s: warning: peer "MACSTR" is on channel %d "
|
|
+ "outside of channel range of current "
|
|
+ "regulatory domain - couldn't join "
|
|
+ "even if other settings match. "
|
|
+ "You might want to adapt your config\n",
|
|
+ adev->ndev->name, MAC(bss->address),
|
|
+ bss->channel);
|
|
+ continue; /* keep looking */
|
|
+ }
|
|
+
|
|
+ if (!adev->essid_active || !strcmp(bss->essid, adev->essid)) {
|
|
+ log(L_ASSOC,
|
|
+ "found station with matching ESSID! ('%s' "
|
|
+ "station, '%s' config)\n",
|
|
+ bss->essid,
|
|
+ (adev->essid_active) ? adev->essid : "[any]");
|
|
+ /* TODO: continue looking for peer with better SNR */
|
|
+ bss->used = CLIENT_JOIN_CANDIDATE;
|
|
+ idx_found = i;
|
|
+
|
|
+ /* stop searching if this station is
|
|
+ * on the current channel, otherwise
|
|
+ * keep looking for an even better match */
|
|
+ if (bss->channel == adev->channel)
|
|
+ break;
|
|
+ } else
|
|
+ if (is_hidden_essid(bss->essid)) {
|
|
+ /* hmm, station with empty or single-space SSID:
|
|
+ * using hidden SSID broadcast?
|
|
+ */
|
|
+ /* This behaviour is broken: which AP from zillion
|
|
+ ** of APs with hidden SSID you'd try?
|
|
+ ** We should use Probe requests to get Probe responses
|
|
+ ** and check for real SSID (are those never hidden?) */
|
|
+ bss->used = CLIENT_JOIN_CANDIDATE;
|
|
+ if (idx_found == -1)
|
|
+ idx_found = i;
|
|
+ log(L_ASSOC, "found station with empty or "
|
|
+ "single-space (hidden) SSID, considering "
|
|
+ "for assoc attempt\n");
|
|
+ /* ...and keep looking for better matches */
|
|
+ } else {
|
|
+ log(L_ASSOC, "ESSID doesn't match! ('%s' "
|
|
+ "station, '%s' config)\n",
|
|
+ bss->essid,
|
|
+ (adev->essid_active) ? adev->essid : "[any]");
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* TODO: iterate thru join candidates instead */
|
|
+ /* TODO: rescan if not associated within some timeout */
|
|
+ if (idx_found != -1) {
|
|
+ char *essid_src;
|
|
+ size_t essid_len;
|
|
+
|
|
+ bss = &adev->sta_list[idx_found];
|
|
+ adev->ap_client = bss;
|
|
+
|
|
+ if (is_hidden_essid(bss->essid)) {
|
|
+ /* if the ESSID of the station we found is empty
|
|
+ * (no broadcast), then use user-configured ESSID
|
|
+ * instead */
|
|
+ essid_src = adev->essid;
|
|
+ essid_len = adev->essid_len;
|
|
+ } else {
|
|
+ essid_src = bss->essid;
|
|
+ essid_len = strlen(bss->essid);
|
|
+ }
|
|
+
|
|
+ acx_update_capabilities(adev);
|
|
+
|
|
+ memcpy(adev->essid_for_assoc, essid_src, essid_len);
|
|
+ adev->essid_for_assoc[essid_len] = '\0';
|
|
+ adev->channel = bss->channel;
|
|
+ MAC_COPY(adev->bssid, bss->bssid);
|
|
+
|
|
+ bss->rate_cfg = (bss->rate_cap & adev->rate_oper);
|
|
+ bss->rate_cur = 1 << lowest_bit(bss->rate_cfg);
|
|
+ bss->rate_100 = acx_rate111to100(bss->rate_cur);
|
|
+
|
|
+ acxlog_mac(L_ASSOC,
|
|
+ "matching station found: ", adev->bssid, ", joining\n");
|
|
+
|
|
+ /* TODO: do we need to switch to the peer's channel first? */
|
|
+
|
|
+ if (ACX_MODE_0_ADHOC == adev->mode) {
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED);
|
|
+ } else {
|
|
+ acx_l_transmit_authen1(adev);
|
|
+ acx_set_status(adev, ACX_STATUS_2_WAIT_AUTH);
|
|
+ }
|
|
+ } else { /* idx_found == -1 */
|
|
+ /* uh oh, no station found in range */
|
|
+ if (ACX_MODE_0_ADHOC == adev->mode) {
|
|
+ printk("%s: no matching station found in range, "
|
|
+ "generating our own IBSS instead\n",
|
|
+ adev->ndev->name);
|
|
+ /* we do it the HostAP way: */
|
|
+ MAC_COPY(adev->bssid, adev->dev_addr);
|
|
+ adev->bssid[0] |= 0x02; /* 'local assigned addr' bit */
|
|
+ /* add IBSS bit to our caps... */
|
|
+ acx_update_capabilities(adev);
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED);
|
|
+ /* In order to cmd_join be called below */
|
|
+ idx_found = 0;
|
|
+ } else {
|
|
+ /* we shall scan again, AP can be
|
|
+ ** just temporarily powered off */
|
|
+ log(L_ASSOC,
|
|
+ "no matching station found in range yet\n");
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING);
|
|
+ result = NOT_OK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ if (idx_found != -1) {
|
|
+ if (ACX_MODE_0_ADHOC == adev->mode) {
|
|
+ /* need to update channel in beacon template */
|
|
+ SET_BIT(adev->set_mask, SET_TEMPLATES);
|
|
+ if (ACX_STATE_IFACE_UP & adev->dev_state_mask)
|
|
+ acx_s_update_card_settings(adev);
|
|
+ }
|
|
+ /* Inform firmware on our decision to start or join BSS */
|
|
+ acx_s_cmd_join_bssid(adev, adev->bssid);
|
|
+ }
|
|
+
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_read_fw
|
|
+**
|
|
+** Loads a firmware image
|
|
+**
|
|
+** Returns:
|
|
+** 0 unable to load file
|
|
+** pointer to firmware success
|
|
+*/
|
|
+firmware_image_t*
|
|
+acx_s_read_fw(struct device *dev, const char *file, u32 *size)
|
|
+{
|
|
+ firmware_image_t *res;
|
|
+ const struct firmware *fw_entry;
|
|
+
|
|
+ res = NULL;
|
|
+ log(L_INIT, "requesting firmware image '%s'\n", file);
|
|
+ if (!request_firmware(&fw_entry, file, dev)) {
|
|
+ *size = 8;
|
|
+ if (fw_entry->size >= 8)
|
|
+ *size = 8 + le32_to_cpu(*(u32 *)(fw_entry->data + 4));
|
|
+ if (fw_entry->size != *size) {
|
|
+ printk("acx: firmware size does not match "
|
|
+ "firmware header: %d != %d, "
|
|
+ "aborting fw upload\n",
|
|
+ (int) fw_entry->size, (int) *size);
|
|
+ goto release_ret;
|
|
+ }
|
|
+ res = vmalloc(*size);
|
|
+ if (!res) {
|
|
+ printk("acx: no memory for firmware "
|
|
+ "(%u bytes)\n", *size);
|
|
+ goto release_ret;
|
|
+ }
|
|
+ memcpy(res, fw_entry->data, fw_entry->size);
|
|
+release_ret:
|
|
+ release_firmware(fw_entry);
|
|
+ return res;
|
|
+ }
|
|
+ printk("acx: firmware image '%s' was not provided. "
|
|
+ "Check your hotplug scripts\n", file);
|
|
+
|
|
+ /* checksum will be verified in write_fw, so don't bother here */
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_set_wepkey
|
|
+*/
|
|
+static void
|
|
+acx100_s_set_wepkey(acx_device_t *adev)
|
|
+{
|
|
+ ie_dot11WEPDefaultKey_t dk;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < DOT11_MAX_DEFAULT_WEP_KEYS; i++) {
|
|
+ if (adev->wep_keys[i].size != 0) {
|
|
+ log(L_INIT, "setting WEP key: %d with "
|
|
+ "total size: %d\n", i, (int) adev->wep_keys[i].size);
|
|
+ dk.action = 1;
|
|
+ dk.keySize = adev->wep_keys[i].size;
|
|
+ dk.defaultKeyNum = i;
|
|
+ memcpy(dk.key, adev->wep_keys[i].key, dk.keySize);
|
|
+ acx_s_configure(adev, &dk, ACX100_IE_DOT11_WEP_DEFAULT_KEY_WRITE);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+acx111_s_set_wepkey(acx_device_t *adev)
|
|
+{
|
|
+ acx111WEPDefaultKey_t dk;
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < DOT11_MAX_DEFAULT_WEP_KEYS; i++) {
|
|
+ if (adev->wep_keys[i].size != 0) {
|
|
+ log(L_INIT, "setting WEP key: %d with "
|
|
+ "total size: %d\n", i, (int) adev->wep_keys[i].size);
|
|
+ memset(&dk, 0, sizeof(dk));
|
|
+ dk.action = cpu_to_le16(1); /* "add key"; yes, that's a 16bit value */
|
|
+ dk.keySize = adev->wep_keys[i].size;
|
|
+
|
|
+ /* are these two lines necessary? */
|
|
+ dk.type = 0; /* default WEP key */
|
|
+ dk.index = 0; /* ignored when setting default key */
|
|
+
|
|
+ dk.defaultKeyNum = i;
|
|
+ memcpy(dk.key, adev->wep_keys[i].key, dk.keySize);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_WEP_MGMT, &dk, sizeof(dk));
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+acx_s_set_wepkey(acx_device_t *adev)
|
|
+{
|
|
+ if (IS_ACX111(adev))
|
|
+ acx111_s_set_wepkey(adev);
|
|
+ else
|
|
+ acx100_s_set_wepkey(adev);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx100_s_init_wep
|
|
+**
|
|
+** FIXME: this should probably be moved into the new card settings
|
|
+** management, but since we're also modifying the memory map layout here
|
|
+** due to the WEP key space we want, we should take care...
|
|
+*/
|
|
+static int
|
|
+acx100_s_init_wep(acx_device_t *adev)
|
|
+{
|
|
+ acx100_ie_wep_options_t options;
|
|
+ ie_dot11WEPDefaultKeyID_t dk;
|
|
+ acx_ie_memmap_t pt;
|
|
+ int res = NOT_OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (OK != acx_s_interrogate(adev, &pt, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "CodeEnd:%X\n", pt.CodeEnd);
|
|
+
|
|
+ pt.WEPCacheStart = cpu_to_le32(le32_to_cpu(pt.CodeEnd) + 0x4);
|
|
+ pt.WEPCacheEnd = cpu_to_le32(le32_to_cpu(pt.CodeEnd) + 0x4);
|
|
+
|
|
+ if (OK != acx_s_configure(adev, &pt, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* let's choose maximum setting: 4 default keys, plus 10 other keys: */
|
|
+ options.NumKeys = cpu_to_le16(DOT11_MAX_DEFAULT_WEP_KEYS + 10);
|
|
+ options.WEPOption = 0x00;
|
|
+
|
|
+ log(L_ASSOC, "%s: writing WEP options\n", __func__);
|
|
+ acx_s_configure(adev, &options, ACX100_IE_WEP_OPTIONS);
|
|
+
|
|
+ acx100_s_set_wepkey(adev);
|
|
+
|
|
+ if (adev->wep_keys[adev->wep_current_index].size != 0) {
|
|
+ log(L_ASSOC, "setting active default WEP key number: %d\n",
|
|
+ adev->wep_current_index);
|
|
+ dk.KeyID = adev->wep_current_index;
|
|
+ acx_s_configure(adev, &dk, ACX1xx_IE_DOT11_WEP_DEFAULT_KEY_SET); /* 0x1010 */
|
|
+ }
|
|
+ /* FIXME!!! wep_key_struct is filled nowhere! But adev
|
|
+ * is initialized to 0, and we don't REALLY need those keys either */
|
|
+/* for (i = 0; i < 10; i++) {
|
|
+ if (adev->wep_key_struct[i].len != 0) {
|
|
+ MAC_COPY(wep_mgmt.MacAddr, adev->wep_key_struct[i].addr);
|
|
+ wep_mgmt.KeySize = cpu_to_le16(adev->wep_key_struct[i].len);
|
|
+ memcpy(&wep_mgmt.Key, adev->wep_key_struct[i].key, le16_to_cpu(wep_mgmt.KeySize));
|
|
+ wep_mgmt.Action = cpu_to_le16(1);
|
|
+ log(L_ASSOC, "writing WEP key %d (len %d)\n", i, le16_to_cpu(wep_mgmt.KeySize));
|
|
+ if (OK == acx_s_issue_cmd(adev, ACX1xx_CMD_WEP_MGMT, &wep_mgmt, sizeof(wep_mgmt))) {
|
|
+ adev->wep_key_struct[i].index = i;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+*/
|
|
+
|
|
+ /* now retrieve the updated WEPCacheEnd pointer... */
|
|
+ if (OK != acx_s_interrogate(adev, &pt, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ printk("%s: ACX1xx_IE_MEMORY_MAP read #2 FAILED\n",
|
|
+ adev->ndev->name);
|
|
+ goto fail;
|
|
+ }
|
|
+ /* ...and tell it to start allocating templates at that location */
|
|
+ /* (no endianness conversion needed) */
|
|
+ pt.PacketTemplateStart = pt.WEPCacheEnd;
|
|
+
|
|
+ if (OK != acx_s_configure(adev, &pt, ACX1xx_IE_MEMORY_MAP)) {
|
|
+ printk("%s: ACX1xx_IE_MEMORY_MAP write #2 FAILED\n",
|
|
+ adev->ndev->name);
|
|
+ goto fail;
|
|
+ }
|
|
+ res = OK;
|
|
+
|
|
+fail:
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+static int
|
|
+acx_s_init_max_template_generic(acx_device_t *adev, unsigned int len, unsigned int cmd)
|
|
+{
|
|
+ int res;
|
|
+ union {
|
|
+ acx_template_nullframe_t null;
|
|
+ acx_template_beacon_t b;
|
|
+ acx_template_tim_t tim;
|
|
+ acx_template_probereq_t preq;
|
|
+ acx_template_proberesp_t presp;
|
|
+ } templ;
|
|
+
|
|
+ memset(&templ, 0, len);
|
|
+ templ.null.size = cpu_to_le16(len - 2);
|
|
+ res = acx_s_issue_cmd(adev, cmd, &templ, len);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_s_init_max_null_data_template(acx_device_t *adev)
|
|
+{
|
|
+ return acx_s_init_max_template_generic(
|
|
+ adev, sizeof(acx_template_nullframe_t), ACX1xx_CMD_CONFIG_NULL_DATA
|
|
+ );
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_s_init_max_beacon_template(acx_device_t *adev)
|
|
+{
|
|
+ return acx_s_init_max_template_generic(
|
|
+ adev, sizeof(acx_template_beacon_t), ACX1xx_CMD_CONFIG_BEACON
|
|
+ );
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_s_init_max_tim_template(acx_device_t *adev)
|
|
+{
|
|
+ return acx_s_init_max_template_generic(
|
|
+ adev, sizeof(acx_template_tim_t), ACX1xx_CMD_CONFIG_TIM
|
|
+ );
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_s_init_max_probe_response_template(acx_device_t *adev)
|
|
+{
|
|
+ return acx_s_init_max_template_generic(
|
|
+ adev, sizeof(acx_template_proberesp_t), ACX1xx_CMD_CONFIG_PROBE_RESPONSE
|
|
+ );
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_s_init_max_probe_request_template(acx_device_t *adev)
|
|
+{
|
|
+ return acx_s_init_max_template_generic(
|
|
+ adev, sizeof(acx_template_probereq_t), ACX1xx_CMD_CONFIG_PROBE_REQUEST
|
|
+ );
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_set_tim_template
|
|
+**
|
|
+** FIXME: In full blown driver we will regularly update partial virtual bitmap
|
|
+** by calling this function
|
|
+** (it can be done by irq handler on each DTIM irq or by timer...)
|
|
+
|
|
+[802.11 7.3.2.6] TIM information element:
|
|
+- 1 EID
|
|
+- 1 Length
|
|
+1 1 DTIM Count
|
|
+ indicates how many beacons (including this) appear before next DTIM
|
|
+ (0=this one is a DTIM)
|
|
+2 1 DTIM Period
|
|
+ number of beacons between successive DTIMs
|
|
+ (0=reserved, 1=all TIMs are DTIMs, 2=every other, etc)
|
|
+3 1 Bitmap Control
|
|
+ bit0: Traffic Indicator bit associated with Assoc ID 0 (Bcast AID?)
|
|
+ set to 1 in TIM elements with a value of 0 in the DTIM Count field
|
|
+ when one or more broadcast or multicast frames are buffered at the AP.
|
|
+ bit1-7: Bitmap Offset (logically Bitmap_Offset = Bitmap_Control & 0xFE).
|
|
+4 n Partial Virtual Bitmap
|
|
+ Visible part of traffic-indication bitmap.
|
|
+ Full bitmap consists of 2008 bits (251 octets) such that bit number N
|
|
+ (0<=N<=2007) in the bitmap corresponds to bit number (N mod 8)
|
|
+ in octet number N/8 where the low-order bit of each octet is bit0,
|
|
+ and the high order bit is bit7.
|
|
+ Each set bit in virtual bitmap corresponds to traffic buffered by AP
|
|
+ for a specific station (with corresponding AID?).
|
|
+ Partial Virtual Bitmap shows a part of bitmap which has non-zero.
|
|
+ Bitmap Offset is a number of skipped zero octets (see above).
|
|
+ 'Missing' octets at the tail are also assumed to be zero.
|
|
+ Example: Length=6, Bitmap_Offset=2, Partial_Virtual_Bitmap=55 55 55
|
|
+ This means that traffic-indication bitmap is:
|
|
+ 00000000 00000000 01010101 01010101 01010101 00000000 00000000...
|
|
+ (is bit0 in the map is always 0 and real value is in Bitmap Control bit0?)
|
|
+*/
|
|
+static int
|
|
+acx_s_set_tim_template(acx_device_t *adev)
|
|
+{
|
|
+/* For now, configure smallish test bitmap, all zero ("no pending data") */
|
|
+ enum { bitmap_size = 5 };
|
|
+
|
|
+ acx_template_tim_t t;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ memset(&t, 0, sizeof(t));
|
|
+ t.size = 5 + bitmap_size; /* eid+len+count+period+bmap_ctrl + bmap */
|
|
+ t.tim_eid = WLAN_EID_TIM;
|
|
+ t.len = 3 + bitmap_size; /* count+period+bmap_ctrl + bmap */
|
|
+ result = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIG_TIM, &t, sizeof(t));
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_fill_beacon_or_proberesp_template
|
|
+**
|
|
+** For frame format info, please see 802.11-1999.pdf item 7.2.3.9 and below!!
|
|
+**
|
|
+** NB: we use the fact that
|
|
+** struct acx_template_proberesp and struct acx_template_beacon are the same
|
|
+** (well, almost...)
|
|
+**
|
|
+** [802.11] Beacon's body consist of these IEs:
|
|
+** 1 Timestamp
|
|
+** 2 Beacon interval
|
|
+** 3 Capability information
|
|
+** 4 SSID
|
|
+** 5 Supported rates (up to 8 rates)
|
|
+** 6 FH Parameter Set (frequency-hopping PHYs only)
|
|
+** 7 DS Parameter Set (direct sequence PHYs only)
|
|
+** 8 CF Parameter Set (only if PCF is supported)
|
|
+** 9 IBSS Parameter Set (ad-hoc only)
|
|
+**
|
|
+** Beacon only:
|
|
+** 10 TIM (AP only) (see 802.11 7.3.2.6)
|
|
+** 11 Country Information (802.11d)
|
|
+** 12 FH Parameters (802.11d)
|
|
+** 13 FH Pattern Table (802.11d)
|
|
+** ... (?!! did not yet find relevant PDF file... --vda)
|
|
+** 19 ERP Information (extended rate PHYs)
|
|
+** 20 Extended Supported Rates (if more than 8 rates)
|
|
+**
|
|
+** Proberesp only:
|
|
+** 10 Country information (802.11d)
|
|
+** 11 FH Parameters (802.11d)
|
|
+** 12 FH Pattern Table (802.11d)
|
|
+** 13-n Requested information elements (802.11d)
|
|
+** ????
|
|
+** 18 ERP Information (extended rate PHYs)
|
|
+** 19 Extended Supported Rates (if more than 8 rates)
|
|
+*/
|
|
+static int
|
|
+acx_fill_beacon_or_proberesp_template(acx_device_t *adev,
|
|
+ struct acx_template_beacon *templ,
|
|
+ u16 fc /* in host order! */)
|
|
+{
|
|
+ int len;
|
|
+ u8 *p;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ memset(templ, 0, sizeof(*templ));
|
|
+ MAC_BCAST(templ->da);
|
|
+ MAC_COPY(templ->sa, adev->dev_addr);
|
|
+ MAC_COPY(templ->bssid, adev->bssid);
|
|
+
|
|
+ templ->beacon_interval = cpu_to_le16(adev->beacon_interval);
|
|
+ acx_update_capabilities(adev);
|
|
+ templ->cap = cpu_to_le16(adev->capabilities);
|
|
+
|
|
+ p = templ->variable;
|
|
+ p = wlan_fill_ie_ssid(p, adev->essid_len, adev->essid);
|
|
+ p = wlan_fill_ie_rates(p, adev->rate_supported_len, adev->rate_supported);
|
|
+ p = wlan_fill_ie_ds_parms(p, adev->channel);
|
|
+ /* NB: should go AFTER tim, but acx seem to keep tim last always */
|
|
+ p = wlan_fill_ie_rates_ext(p, adev->rate_supported_len, adev->rate_supported);
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ /* ATIM window */
|
|
+ p = wlan_fill_ie_ibss_parms(p, 0); break;
|
|
+ case ACX_MODE_3_AP:
|
|
+ /* TIM IE is set up as separate template */
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ len = p - (u8*)templ;
|
|
+ templ->fc = cpu_to_le16(WF_FTYPE_MGMT | fc);
|
|
+ /* - 2: do not count 'u16 size' field */
|
|
+ templ->size = cpu_to_le16(len - 2);
|
|
+
|
|
+ FN_EXIT1(len);
|
|
+ return len;
|
|
+}
|
|
+
|
|
+
|
|
+#if POWER_SAVE_80211
|
|
+/***********************************************************************
|
|
+** acx_s_set_null_data_template
|
|
+*/
|
|
+static int
|
|
+acx_s_set_null_data_template(acx_device_t *adev)
|
|
+{
|
|
+ struct acx_template_nullframe b;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* memset(&b, 0, sizeof(b)); not needed, setting all members */
|
|
+
|
|
+ b.size = cpu_to_le16(sizeof(b) - 2);
|
|
+ b.hdr.fc = WF_FTYPE_MGMTi | WF_FSTYPE_NULLi;
|
|
+ b.hdr.dur = 0;
|
|
+ MAC_BCAST(b.hdr.a1);
|
|
+ MAC_COPY(b.hdr.a2, adev->dev_addr);
|
|
+ MAC_COPY(b.hdr.a3, adev->bssid);
|
|
+ b.hdr.seq = 0;
|
|
+
|
|
+ result = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIG_NULL_DATA, &b, sizeof(b));
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_set_beacon_template
|
|
+*/
|
|
+static int
|
|
+acx_s_set_beacon_template(acx_device_t *adev)
|
|
+{
|
|
+ struct acx_template_beacon bcn;
|
|
+ int len, result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ len = acx_fill_beacon_or_proberesp_template(adev, &bcn, WF_FSTYPE_BEACON);
|
|
+ result = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIG_BEACON, &bcn, len);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_set_probe_response_template
|
|
+*/
|
|
+static int
|
|
+acx_s_set_probe_response_template(acx_device_t *adev)
|
|
+{
|
|
+ struct acx_template_proberesp pr;
|
|
+ int len, result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ len = acx_fill_beacon_or_proberesp_template(adev, &pr, WF_FSTYPE_PROBERESP);
|
|
+ result = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIG_PROBE_RESPONSE, &pr, len);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_init_packet_templates()
|
|
+**
|
|
+** NOTE: order is very important here, to have a correct memory layout!
|
|
+** init templates: max Probe Request (station mode), max NULL data,
|
|
+** max Beacon, max TIM, max Probe Response.
|
|
+*/
|
|
+static int
|
|
+acx_s_init_packet_templates(acx_device_t *adev)
|
|
+{
|
|
+ acx_ie_memmap_t mm; /* ACX100 only */
|
|
+ int result = NOT_OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_DEBUG|L_INIT, "initializing max packet templates\n");
|
|
+
|
|
+ if (OK != acx_s_init_max_probe_request_template(adev))
|
|
+ goto failed;
|
|
+
|
|
+ if (OK != acx_s_init_max_null_data_template(adev))
|
|
+ goto failed;
|
|
+
|
|
+ if (OK != acx_s_init_max_beacon_template(adev))
|
|
+ goto failed;
|
|
+
|
|
+ if (OK != acx_s_init_max_tim_template(adev))
|
|
+ goto failed;
|
|
+
|
|
+ if (OK != acx_s_init_max_probe_response_template(adev))
|
|
+ goto failed;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111 doesn't need the memory map magic below,
|
|
+ * and the other templates will be set later (acx_start) */
|
|
+ result = OK;
|
|
+ goto success;
|
|
+ }
|
|
+
|
|
+ /* ACX100 will have its TIM template set,
|
|
+ * and we also need to update the memory map */
|
|
+
|
|
+ if (OK != acx_s_set_tim_template(adev))
|
|
+ goto failed_acx100;
|
|
+
|
|
+ log(L_DEBUG, "sizeof(memmap)=%d bytes\n", (int)sizeof(mm));
|
|
+
|
|
+ if (OK != acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP))
|
|
+ goto failed_acx100;
|
|
+
|
|
+ mm.QueueStart = cpu_to_le32(le32_to_cpu(mm.PacketTemplateEnd) + 4);
|
|
+ if (OK != acx_s_configure(adev, &mm, ACX1xx_IE_MEMORY_MAP))
|
|
+ goto failed_acx100;
|
|
+
|
|
+ result = OK;
|
|
+ goto success;
|
|
+
|
|
+failed_acx100:
|
|
+ log(L_DEBUG|L_INIT,
|
|
+ /* "cb=0x%X\n" */
|
|
+ "ACXMemoryMap:\n"
|
|
+ ".CodeStart=0x%X\n"
|
|
+ ".CodeEnd=0x%X\n"
|
|
+ ".WEPCacheStart=0x%X\n"
|
|
+ ".WEPCacheEnd=0x%X\n"
|
|
+ ".PacketTemplateStart=0x%X\n"
|
|
+ ".PacketTemplateEnd=0x%X\n",
|
|
+ /* len, */
|
|
+ le32_to_cpu(mm.CodeStart),
|
|
+ le32_to_cpu(mm.CodeEnd),
|
|
+ le32_to_cpu(mm.WEPCacheStart),
|
|
+ le32_to_cpu(mm.WEPCacheEnd),
|
|
+ le32_to_cpu(mm.PacketTemplateStart),
|
|
+ le32_to_cpu(mm.PacketTemplateEnd));
|
|
+
|
|
+failed:
|
|
+ printk("%s: %s() FAILED\n", adev->ndev->name, __func__);
|
|
+
|
|
+success:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_s_set_probe_request_template(acx_device_t *adev)
|
|
+{
|
|
+ struct acx_template_probereq probereq;
|
|
+ char *p;
|
|
+ int res;
|
|
+ int frame_len;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ memset(&probereq, 0, sizeof(probereq));
|
|
+
|
|
+ probereq.fc = WF_FTYPE_MGMTi | WF_FSTYPE_PROBEREQi;
|
|
+ MAC_BCAST(probereq.da);
|
|
+ MAC_COPY(probereq.sa, adev->dev_addr);
|
|
+ MAC_BCAST(probereq.bssid);
|
|
+
|
|
+ p = probereq.variable;
|
|
+ p = wlan_fill_ie_ssid(p, adev->essid_len, adev->essid);
|
|
+ p = wlan_fill_ie_rates(p, adev->rate_supported_len, adev->rate_supported);
|
|
+ p = wlan_fill_ie_rates_ext(p, adev->rate_supported_len, adev->rate_supported);
|
|
+ frame_len = p - (char*)&probereq;
|
|
+ probereq.size = cpu_to_le16(frame_len - 2);
|
|
+
|
|
+ res = acx_s_issue_cmd(adev, ACX1xx_CMD_CONFIG_PROBE_REQUEST, &probereq, frame_len);
|
|
+ FN_EXIT0;
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_init_mac
|
|
+*/
|
|
+int
|
|
+acx_s_init_mac(acx_device_t *adev)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ adev->ie_len = acx111_ie_len;
|
|
+ adev->ie_len_dot11 = acx111_ie_len_dot11;
|
|
+ } else {
|
|
+ adev->ie_len = acx100_ie_len;
|
|
+ adev->ie_len_dot11 = acx100_ie_len_dot11;
|
|
+ }
|
|
+
|
|
+#if defined (ACX_MEM)
|
|
+ adev->memblocksize = 256; /* 256 is default */
|
|
+ /* try to load radio for both ACX100 and ACX111, since both
|
|
+ * chips have at least some firmware versions making use of an
|
|
+ * external radio module */
|
|
+ acxmem_s_upload_radio(adev);
|
|
+#else
|
|
+ if (IS_PCI(adev)) {
|
|
+ adev->memblocksize = 256; /* 256 is default */
|
|
+ /* try to load radio for both ACX100 and ACX111, since both
|
|
+ * chips have at least some firmware versions making use of an
|
|
+ * external radio module */
|
|
+ acxpci_s_upload_radio(adev);
|
|
+ } else {
|
|
+ adev->memblocksize = 128;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* for ACX111, the order is different from ACX100
|
|
+ 1. init packet templates
|
|
+ 2. create station context and create dma regions
|
|
+ 3. init wep default keys
|
|
+ */
|
|
+ if (OK != acx_s_init_packet_templates(adev))
|
|
+ goto fail;
|
|
+ if (OK != acx111_s_create_dma_regions(adev)) {
|
|
+ printk("%s: acx111_create_dma_regions FAILED\n",
|
|
+ adev->ndev->name);
|
|
+ goto fail;
|
|
+ }
|
|
+ } else {
|
|
+ if (OK != acx100_s_init_wep(adev))
|
|
+ goto fail;
|
|
+ if (OK != acx_s_init_packet_templates(adev))
|
|
+ goto fail;
|
|
+ if (OK != acx100_s_create_dma_regions(adev)) {
|
|
+ printk("%s: acx100_create_dma_regions FAILED\n",
|
|
+ adev->ndev->name);
|
|
+ goto fail;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ MAC_COPY(adev->ndev->dev_addr, adev->dev_addr);
|
|
+ result = OK;
|
|
+
|
|
+fail:
|
|
+ if (result)
|
|
+ printk("acx: init_mac() FAILED\n");
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+acx_s_set_sane_reg_domain(acx_device_t *adev, int do_set)
|
|
+{
|
|
+ unsigned mask;
|
|
+
|
|
+ unsigned int i;
|
|
+
|
|
+ for (i = 0; i < sizeof(acx_reg_domain_ids); i++)
|
|
+ if (acx_reg_domain_ids[i] == adev->reg_dom_id)
|
|
+ break;
|
|
+
|
|
+ if (sizeof(acx_reg_domain_ids) == i) {
|
|
+ log(L_INIT, "Invalid or unsupported regulatory domain"
|
|
+ " 0x%02X specified, falling back to FCC (USA)!"
|
|
+ " Please report if this sounds fishy!\n",
|
|
+ adev->reg_dom_id);
|
|
+ i = 0;
|
|
+ adev->reg_dom_id = acx_reg_domain_ids[i];
|
|
+
|
|
+ /* since there was a mismatch, we need to force updating */
|
|
+ do_set = 1;
|
|
+ }
|
|
+
|
|
+ if (do_set) {
|
|
+ acx_ie_generic_t dom;
|
|
+ dom.m.bytes[0] = adev->reg_dom_id;
|
|
+ acx_s_configure(adev, &dom, ACX1xx_IE_DOT11_CURRENT_REG_DOMAIN);
|
|
+ }
|
|
+
|
|
+ adev->reg_dom_chanmask = reg_domain_channel_masks[i];
|
|
+
|
|
+ mask = (1 << (adev->channel - 1));
|
|
+ if (!(adev->reg_dom_chanmask & mask)) {
|
|
+ /* hmm, need to adjust our channel to reside within domain */
|
|
+ mask = 1;
|
|
+ for (i = 1; i <= 14; i++) {
|
|
+ if (adev->reg_dom_chanmask & mask) {
|
|
+ printk("%s: adjusting selected channel from %d "
|
|
+ "to %d due to new regulatory domain\n",
|
|
+ adev->ndev->name, adev->channel, i);
|
|
+ adev->channel = i;
|
|
+ break;
|
|
+ }
|
|
+ mask <<= 1;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+#if POWER_SAVE_80211
|
|
+static void
|
|
+acx_s_update_80211_powersave_mode(acx_device_t *adev)
|
|
+{
|
|
+ /* merge both structs in a union to be able to have common code */
|
|
+ union {
|
|
+ acx111_ie_powersave_t acx111;
|
|
+ acx100_ie_powersave_t acx100;
|
|
+ } pm;
|
|
+
|
|
+ /* change 802.11 power save mode settings */
|
|
+ log(L_INIT, "updating 802.11 power save mode settings: "
|
|
+ "wakeup_cfg 0x%02X, listen interval %u, "
|
|
+ "options 0x%02X, hangover period %u, "
|
|
+ "enhanced_ps_transition_time %u\n",
|
|
+ adev->ps_wakeup_cfg, adev->ps_listen_interval,
|
|
+ adev->ps_options, adev->ps_hangover_period,
|
|
+ adev->ps_enhanced_transition_time);
|
|
+ acx_s_interrogate(adev, &pm, ACX1xx_IE_POWER_MGMT);
|
|
+ log(L_INIT, "Previous PS mode settings: wakeup_cfg 0x%02X, "
|
|
+ "listen interval %u, options 0x%02X, "
|
|
+ "hangover period %u, "
|
|
+ "enhanced_ps_transition_time %u, beacon_rx_time %u\n",
|
|
+ pm.acx111.wakeup_cfg,
|
|
+ pm.acx111.listen_interval,
|
|
+ pm.acx111.options,
|
|
+ pm.acx111.hangover_period,
|
|
+ IS_ACX111(adev) ?
|
|
+ pm.acx111.enhanced_ps_transition_time
|
|
+ : pm.acx100.enhanced_ps_transition_time,
|
|
+ IS_ACX111(adev) ?
|
|
+ pm.acx111.beacon_rx_time
|
|
+ : (u32)-1
|
|
+ );
|
|
+ pm.acx111.wakeup_cfg = adev->ps_wakeup_cfg;
|
|
+ pm.acx111.listen_interval = adev->ps_listen_interval;
|
|
+ pm.acx111.options = adev->ps_options;
|
|
+ pm.acx111.hangover_period = adev->ps_hangover_period;
|
|
+ if (IS_ACX111(adev)) {
|
|
+ pm.acx111.beacon_rx_time = cpu_to_le32(adev->ps_beacon_rx_time);
|
|
+ pm.acx111.enhanced_ps_transition_time = cpu_to_le32(adev->ps_enhanced_transition_time);
|
|
+ } else {
|
|
+ pm.acx100.enhanced_ps_transition_time = cpu_to_le16(adev->ps_enhanced_transition_time);
|
|
+ }
|
|
+ acx_s_configure(adev, &pm, ACX1xx_IE_POWER_MGMT);
|
|
+ acx_s_interrogate(adev, &pm, ACX1xx_IE_POWER_MGMT);
|
|
+ log(L_INIT, "wakeup_cfg: 0x%02X\n", pm.acx111.wakeup_cfg);
|
|
+ acx_s_msleep(40);
|
|
+ acx_s_interrogate(adev, &pm, ACX1xx_IE_POWER_MGMT);
|
|
+ log(L_INIT, "wakeup_cfg: 0x%02X\n", pm.acx111.wakeup_cfg);
|
|
+ log(L_INIT, "power save mode change %s\n",
|
|
+ (pm.acx111.wakeup_cfg & PS_CFG_PENDING) ? "FAILED" : "was successful");
|
|
+ /* FIXME: maybe verify via PS_CFG_PENDING bit here
|
|
+ * that power save mode change was successful. */
|
|
+ /* FIXME: we shouldn't trigger a scan immediately after
|
|
+ * fiddling with power save mode (since the firmware is sending
|
|
+ * a NULL frame then). */
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_update_card_settings
|
|
+**
|
|
+** Applies accumulated changes in various adev->xxxx members
|
|
+** Called by ioctl commit handler, acx_start, acx_set_defaults,
|
|
+** acx_s_after_interrupt_task (if IRQ_CMD_UPDATE_CARD_CFG),
|
|
+*/
|
|
+static void
|
|
+acx111_s_sens_radio_16_17(acx_device_t *adev)
|
|
+{
|
|
+ u32 feature1, feature2;
|
|
+
|
|
+ if ((adev->sensitivity < 1) || (adev->sensitivity > 3)) {
|
|
+ printk("%s: invalid sensitivity setting (1..3), "
|
|
+ "setting to 1\n", adev->ndev->name);
|
|
+ adev->sensitivity = 1;
|
|
+ }
|
|
+ acx111_s_get_feature_config(adev, &feature1, &feature2);
|
|
+ CLEAR_BIT(feature1, FEATURE1_LOW_RX|FEATURE1_EXTRA_LOW_RX);
|
|
+ if (adev->sensitivity > 1)
|
|
+ SET_BIT(feature1, FEATURE1_LOW_RX);
|
|
+ if (adev->sensitivity > 2)
|
|
+ SET_BIT(feature1, FEATURE1_EXTRA_LOW_RX);
|
|
+ acx111_s_feature_set(adev, feature1, feature2);
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+acx_s_update_card_settings(acx_device_t *adev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+ unsigned int start_scan = 0;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_INIT, "get_mask 0x%08X, set_mask 0x%08X\n",
|
|
+ adev->get_mask, adev->set_mask);
|
|
+
|
|
+ /* Track dependencies betweed various settings */
|
|
+
|
|
+ if (adev->set_mask & (GETSET_MODE|GETSET_RESCAN|GETSET_WEP)) {
|
|
+ log(L_INIT, "important setting has been changed. "
|
|
+ "Need to update packet templates, too\n");
|
|
+ SET_BIT(adev->set_mask, SET_TEMPLATES);
|
|
+ }
|
|
+ if (adev->set_mask & GETSET_CHANNEL) {
|
|
+ /* This will actually tune RX/TX to the channel */
|
|
+ SET_BIT(adev->set_mask, GETSET_RX|GETSET_TX);
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_3_AP:
|
|
+ /* Beacons contain channel# - update them */
|
|
+ SET_BIT(adev->set_mask, SET_TEMPLATES);
|
|
+ }
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ start_scan = 1;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Apply settings */
|
|
+
|
|
+#ifdef WHY_SHOULD_WE_BOTHER /* imagine we were just powered off */
|
|
+ /* send a disassoc request in case it's required */
|
|
+ if (adev->set_mask & (GETSET_MODE|GETSET_RESCAN|GETSET_CHANNEL|GETSET_WEP)) {
|
|
+ if (ACX_MODE_2_STA == adev->mode) {
|
|
+ if (ACX_STATUS_4_ASSOCIATED == adev->status) {
|
|
+ log(L_ASSOC, "we were ASSOCIATED - "
|
|
+ "sending disassoc request\n");
|
|
+ acx_lock(adev, flags);
|
|
+ acx_l_transmit_disassoc(adev, NULL);
|
|
+ /* FIXME: deauth? */
|
|
+ acx_unlock(adev, flags);
|
|
+ }
|
|
+ /* need to reset some other stuff as well */
|
|
+ log(L_DEBUG, "resetting bssid\n");
|
|
+ MAC_ZERO(adev->bssid);
|
|
+ SET_BIT(adev->set_mask, SET_TEMPLATES|SET_STA_LIST);
|
|
+ start_scan = 1;
|
|
+ }
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ if (adev->get_mask & GETSET_STATION_ID) {
|
|
+ u8 stationID[4 + ACX1xx_IE_DOT11_STATION_ID_LEN];
|
|
+ const u8 *paddr;
|
|
+
|
|
+ acx_s_interrogate(adev, &stationID, ACX1xx_IE_DOT11_STATION_ID);
|
|
+ paddr = &stationID[4];
|
|
+ for (i = 0; i < ETH_ALEN; i++) {
|
|
+ /* we copy the MAC address (reversed in
|
|
+ * the card) to the netdevice's MAC
|
|
+ * address, and on ifup it will be
|
|
+ * copied into iwadev->dev_addr */
|
|
+ adev->ndev->dev_addr[ETH_ALEN - 1 - i] = paddr[i];
|
|
+ }
|
|
+ CLEAR_BIT(adev->get_mask, GETSET_STATION_ID);
|
|
+ }
|
|
+
|
|
+ if (adev->get_mask & GETSET_SENSITIVITY) {
|
|
+ if ((RADIO_RFMD_11 == adev->radio_type)
|
|
+ || (RADIO_MAXIM_0D == adev->radio_type)
|
|
+ || (RADIO_RALINK_15 == adev->radio_type)) {
|
|
+ acx_s_read_phy_reg(adev, 0x30, &adev->sensitivity);
|
|
+ } else {
|
|
+ log(L_INIT, "don't know how to get sensitivity "
|
|
+ "for radio type 0x%02X\n", adev->radio_type);
|
|
+ adev->sensitivity = 0;
|
|
+ }
|
|
+ log(L_INIT, "got sensitivity value %u\n", adev->sensitivity);
|
|
+
|
|
+ CLEAR_BIT(adev->get_mask, GETSET_SENSITIVITY);
|
|
+ }
|
|
+
|
|
+ if (adev->get_mask & GETSET_ANTENNA) {
|
|
+ u8 antenna[4 + ACX1xx_IE_DOT11_CURRENT_ANTENNA_LEN];
|
|
+
|
|
+ memset(antenna, 0, sizeof(antenna));
|
|
+ acx_s_interrogate(adev, antenna, ACX1xx_IE_DOT11_CURRENT_ANTENNA);
|
|
+ adev->antenna = antenna[4];
|
|
+ log(L_INIT, "got antenna value 0x%02X\n", adev->antenna);
|
|
+ CLEAR_BIT(adev->get_mask, GETSET_ANTENNA);
|
|
+ }
|
|
+
|
|
+ if (adev->get_mask & GETSET_ED_THRESH) {
|
|
+ if (IS_ACX100(adev)) {
|
|
+ u8 ed_threshold[4 + ACX100_IE_DOT11_ED_THRESHOLD_LEN];
|
|
+
|
|
+ memset(ed_threshold, 0, sizeof(ed_threshold));
|
|
+ acx_s_interrogate(adev, ed_threshold, ACX100_IE_DOT11_ED_THRESHOLD);
|
|
+ adev->ed_threshold = ed_threshold[4];
|
|
+ } else {
|
|
+ log(L_INIT, "acx111 doesn't support ED\n");
|
|
+ adev->ed_threshold = 0;
|
|
+ }
|
|
+ log(L_INIT, "got Energy Detect (ED) threshold %u\n", adev->ed_threshold);
|
|
+ CLEAR_BIT(adev->get_mask, GETSET_ED_THRESH);
|
|
+ }
|
|
+
|
|
+ if (adev->get_mask & GETSET_CCA) {
|
|
+ if (IS_ACX100(adev)) {
|
|
+ u8 cca[4 + ACX1xx_IE_DOT11_CURRENT_CCA_MODE_LEN];
|
|
+
|
|
+ memset(cca, 0, sizeof(adev->cca));
|
|
+ acx_s_interrogate(adev, cca, ACX1xx_IE_DOT11_CURRENT_CCA_MODE);
|
|
+ adev->cca = cca[4];
|
|
+ } else {
|
|
+ log(L_INIT, "acx111 doesn't support CCA\n");
|
|
+ adev->cca = 0;
|
|
+ }
|
|
+ log(L_INIT, "got Channel Clear Assessment (CCA) value %u\n", adev->cca);
|
|
+ CLEAR_BIT(adev->get_mask, GETSET_CCA);
|
|
+ }
|
|
+
|
|
+ if (adev->get_mask & GETSET_REG_DOMAIN) {
|
|
+ acx_ie_generic_t dom;
|
|
+
|
|
+ acx_s_interrogate(adev, &dom, ACX1xx_IE_DOT11_CURRENT_REG_DOMAIN);
|
|
+ adev->reg_dom_id = dom.m.bytes[0];
|
|
+ acx_s_set_sane_reg_domain(adev, 0);
|
|
+ log(L_INIT, "got regulatory domain 0x%02X\n", adev->reg_dom_id);
|
|
+ CLEAR_BIT(adev->get_mask, GETSET_REG_DOMAIN);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_STATION_ID) {
|
|
+ u8 stationID[4 + ACX1xx_IE_DOT11_STATION_ID_LEN];
|
|
+ u8 *paddr;
|
|
+
|
|
+ paddr = &stationID[4];
|
|
+ memcpy(adev->dev_addr, adev->ndev->dev_addr, ETH_ALEN);
|
|
+ for (i = 0; i < ETH_ALEN; i++) {
|
|
+ /* copy the MAC address we obtained when we noticed
|
|
+ * that the ethernet iface's MAC changed
|
|
+ * to the card (reversed in
|
|
+ * the card!) */
|
|
+ paddr[i] = adev->dev_addr[ETH_ALEN - 1 - i];
|
|
+ }
|
|
+ acx_s_configure(adev, &stationID, ACX1xx_IE_DOT11_STATION_ID);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_STATION_ID);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & SET_TEMPLATES) {
|
|
+ log(L_INIT, "updating packet templates\n");
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_2_STA:
|
|
+ acx_s_set_probe_request_template(adev);
|
|
+#if POWER_SAVE_80211
|
|
+ acx_s_set_null_data_template(adev);
|
|
+#endif
|
|
+ break;
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ acx_s_set_probe_request_template(adev);
|
|
+#if POWER_SAVE_80211
|
|
+ /* maybe power save functionality is somehow possible
|
|
+ * for Ad-Hoc mode, too... FIXME: verify it somehow? firmware debug fields? */
|
|
+ acx_s_set_null_data_template(adev);
|
|
+#endif
|
|
+ /* fall through */
|
|
+ case ACX_MODE_3_AP:
|
|
+ acx_s_set_beacon_template(adev);
|
|
+ acx_s_set_tim_template(adev);
|
|
+ /* BTW acx111 firmware would not send probe responses
|
|
+ ** if probe request does not have all basic rates flagged
|
|
+ ** by 0x80! Thus firmware does not conform to 802.11,
|
|
+ ** it should ignore 0x80 bit in ratevector from STA.
|
|
+ ** We can 'fix' it by not using this template and
|
|
+ ** sending probe responses by hand. TODO --vda */
|
|
+ acx_s_set_probe_response_template(adev);
|
|
+ }
|
|
+ /* Needed if generated frames are to be emitted at different tx rate now */
|
|
+ log(L_IRQ, "redoing cmd_join_bssid() after template cfg\n");
|
|
+ acx_s_cmd_join_bssid(adev, adev->bssid);
|
|
+ CLEAR_BIT(adev->set_mask, SET_TEMPLATES);
|
|
+ }
|
|
+ if (adev->set_mask & SET_STA_LIST) {
|
|
+ acx_lock(adev, flags);
|
|
+ acx_l_sta_list_init(adev);
|
|
+ CLEAR_BIT(adev->set_mask, SET_STA_LIST);
|
|
+ acx_unlock(adev, flags);
|
|
+ }
|
|
+ if (adev->set_mask & SET_RATE_FALLBACK) {
|
|
+ u8 rate[4 + ACX1xx_IE_RATE_FALLBACK_LEN];
|
|
+
|
|
+ /* configure to not do fallbacks when not in auto rate mode */
|
|
+ rate[4] = (adev->rate_auto) ? /* adev->txrate_fallback_retries */ 1 : 0;
|
|
+ log(L_INIT, "updating Tx fallback to %u retries\n", rate[4]);
|
|
+ acx_s_configure(adev, &rate, ACX1xx_IE_RATE_FALLBACK);
|
|
+ CLEAR_BIT(adev->set_mask, SET_RATE_FALLBACK);
|
|
+ }
|
|
+ if (adev->set_mask & GETSET_TXPOWER) {
|
|
+ log(L_INIT, "updating transmit power: %u dBm\n",
|
|
+ adev->tx_level_dbm);
|
|
+ acx_s_set_tx_level(adev, adev->tx_level_dbm);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_TXPOWER);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_SENSITIVITY) {
|
|
+ log(L_INIT, "updating sensitivity value: %u\n",
|
|
+ adev->sensitivity);
|
|
+ switch (adev->radio_type) {
|
|
+ case RADIO_RFMD_11:
|
|
+ case RADIO_MAXIM_0D:
|
|
+ case RADIO_RALINK_15:
|
|
+ acx_s_write_phy_reg(adev, 0x30, adev->sensitivity);
|
|
+ break;
|
|
+ case RADIO_RADIA_16:
|
|
+ case RADIO_UNKNOWN_17:
|
|
+ acx111_s_sens_radio_16_17(adev);
|
|
+ break;
|
|
+ default:
|
|
+ log(L_INIT, "don't know how to modify sensitivity "
|
|
+ "for radio type 0x%02X\n", adev->radio_type);
|
|
+ }
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_SENSITIVITY);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_ANTENNA) {
|
|
+ /* antenna */
|
|
+ u8 antenna[4 + ACX1xx_IE_DOT11_CURRENT_ANTENNA_LEN];
|
|
+
|
|
+ memset(antenna, 0, sizeof(antenna));
|
|
+ antenna[4] = adev->antenna;
|
|
+ log(L_INIT, "updating antenna value: 0x%02X\n",
|
|
+ adev->antenna);
|
|
+ acx_s_configure(adev, &antenna, ACX1xx_IE_DOT11_CURRENT_ANTENNA);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_ANTENNA);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_ED_THRESH) {
|
|
+ /* ed_threshold */
|
|
+ log(L_INIT, "updating Energy Detect (ED) threshold: %u\n",
|
|
+ adev->ed_threshold);
|
|
+ if (IS_ACX100(adev)) {
|
|
+ u8 ed_threshold[4 + ACX100_IE_DOT11_ED_THRESHOLD_LEN];
|
|
+
|
|
+ memset(ed_threshold, 0, sizeof(ed_threshold));
|
|
+ ed_threshold[4] = adev->ed_threshold;
|
|
+ acx_s_configure(adev, &ed_threshold, ACX100_IE_DOT11_ED_THRESHOLD);
|
|
+ }
|
|
+ else
|
|
+ log(L_INIT, "acx111 doesn't support ED!\n");
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_ED_THRESH);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_CCA) {
|
|
+ /* CCA value */
|
|
+ log(L_INIT, "updating Channel Clear Assessment "
|
|
+ "(CCA) value: 0x%02X\n", adev->cca);
|
|
+ if (IS_ACX100(adev)) {
|
|
+ u8 cca[4 + ACX1xx_IE_DOT11_CURRENT_CCA_MODE_LEN];
|
|
+
|
|
+ memset(cca, 0, sizeof(cca));
|
|
+ cca[4] = adev->cca;
|
|
+ acx_s_configure(adev, &cca, ACX1xx_IE_DOT11_CURRENT_CCA_MODE);
|
|
+ }
|
|
+ else
|
|
+ log(L_INIT, "acx111 doesn't support CCA!\n");
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_CCA);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_LED_POWER) {
|
|
+ /* Enable Tx */
|
|
+ log(L_INIT, "updating power LED status: %u\n", adev->led_power);
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+#if defined (ACX_MEM)
|
|
+ acxmem_l_power_led(adev, adev->led_power);
|
|
+#else
|
|
+ if (IS_PCI(adev))
|
|
+ acxpci_l_power_led(adev, adev->led_power);
|
|
+#endif
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_LED_POWER);
|
|
+ acx_unlock(adev, flags);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_POWER_80211) {
|
|
+#if POWER_SAVE_80211
|
|
+ acx_s_update_80211_powersave_mode(adev);
|
|
+#endif
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_POWER_80211);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_CHANNEL) {
|
|
+ /* channel */
|
|
+ log(L_INIT, "updating channel to: %u\n", adev->channel);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_CHANNEL);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_TX) {
|
|
+ /* set Tx */
|
|
+ log(L_INIT, "updating: %s Tx\n",
|
|
+ adev->tx_disabled ? "disable" : "enable");
|
|
+ if (adev->tx_disabled)
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0);
|
|
+ else
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_ENABLE_TX, &adev->channel, 1);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_TX);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_RX) {
|
|
+ /* Enable Rx */
|
|
+ log(L_INIT, "updating: enable Rx on channel: %u\n",
|
|
+ adev->channel);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_ENABLE_RX, &adev->channel, 1);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_RX);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_RETRY) {
|
|
+ u8 short_retry[4 + ACX1xx_IE_DOT11_SHORT_RETRY_LIMIT_LEN];
|
|
+ u8 long_retry[4 + ACX1xx_IE_DOT11_LONG_RETRY_LIMIT_LEN];
|
|
+
|
|
+ log(L_INIT, "updating short retry limit: %u, long retry limit: %u\n",
|
|
+ adev->short_retry, adev->long_retry);
|
|
+ short_retry[0x4] = adev->short_retry;
|
|
+ long_retry[0x4] = adev->long_retry;
|
|
+ acx_s_configure(adev, &short_retry, ACX1xx_IE_DOT11_SHORT_RETRY_LIMIT);
|
|
+ acx_s_configure(adev, &long_retry, ACX1xx_IE_DOT11_LONG_RETRY_LIMIT);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_RETRY);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & SET_MSDU_LIFETIME) {
|
|
+ u8 xmt_msdu_lifetime[4 + ACX1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME_LEN];
|
|
+
|
|
+ log(L_INIT, "updating tx MSDU lifetime: %u\n",
|
|
+ adev->msdu_lifetime);
|
|
+ *(u32 *)&xmt_msdu_lifetime[4] = cpu_to_le32((u32)adev->msdu_lifetime);
|
|
+ acx_s_configure(adev, &xmt_msdu_lifetime, ACX1xx_IE_DOT11_MAX_XMIT_MSDU_LIFETIME);
|
|
+ CLEAR_BIT(adev->set_mask, SET_MSDU_LIFETIME);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_REG_DOMAIN) {
|
|
+ log(L_INIT, "updating regulatory domain: 0x%02X\n",
|
|
+ adev->reg_dom_id);
|
|
+ acx_s_set_sane_reg_domain(adev, 1);
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_REG_DOMAIN);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_MODE) {
|
|
+ adev->ndev->type = (adev->mode == ACX_MODE_MONITOR) ?
|
|
+ adev->monitor_type : ARPHRD_ETHER;
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_3_AP:
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ acx_l_sta_list_init(adev);
|
|
+ adev->aid = 0;
|
|
+ adev->ap_client = NULL;
|
|
+ MAC_COPY(adev->bssid, adev->dev_addr);
|
|
+ /* this basically says "we're connected" */
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ acx111_s_feature_off(adev, 0, FEATURE2_NO_TXCRYPT|FEATURE2_SNIFFER);
|
|
+ /* start sending beacons */
|
|
+ acx_s_cmd_join_bssid(adev, adev->bssid);
|
|
+ break;
|
|
+ case ACX_MODE_MONITOR:
|
|
+ acx111_s_feature_on(adev, 0, FEATURE2_NO_TXCRYPT|FEATURE2_SNIFFER);
|
|
+ /* this stops beacons */
|
|
+ acx_s_cmd_join_bssid(adev, adev->bssid);
|
|
+ /* this basically says "we're connected" */
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG|SET_WEP_OPTIONS);
|
|
+ break;
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ acx111_s_feature_off(adev, 0, FEATURE2_NO_TXCRYPT|FEATURE2_SNIFFER);
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ adev->aid = 0;
|
|
+ adev->ap_client = NULL;
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* we want to start looking for peer or AP */
|
|
+ start_scan = 1;
|
|
+ break;
|
|
+ case ACX_MODE_OFF:
|
|
+ /* TODO: disable RX/TX, stop any scanning activity etc: */
|
|
+ /* adev->tx_disabled = 1; */
|
|
+ /* SET_BIT(adev->set_mask, GETSET_RX|GETSET_TX); */
|
|
+
|
|
+ /* This stops beacons (invalid macmode...) */
|
|
+ acx_s_cmd_join_bssid(adev, adev->bssid);
|
|
+ acx_set_status(adev, ACX_STATUS_0_STOPPED);
|
|
+ break;
|
|
+ }
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_MODE);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & SET_RXCONFIG) {
|
|
+ acx_s_initialize_rx_config(adev);
|
|
+ CLEAR_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_RESCAN) {
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ start_scan = 1;
|
|
+ break;
|
|
+ }
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & GETSET_WEP) {
|
|
+ /* encode */
|
|
+
|
|
+ ie_dot11WEPDefaultKeyID_t dkey;
|
|
+#ifdef DEBUG_WEP
|
|
+ struct {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u8 val;
|
|
+ } ACX_PACKED keyindic;
|
|
+#endif
|
|
+ log(L_INIT, "updating WEP key settings\n");
|
|
+
|
|
+ acx_s_set_wepkey(adev);
|
|
+
|
|
+ dkey.KeyID = adev->wep_current_index;
|
|
+ log(L_INIT, "setting WEP key %u as default\n", dkey.KeyID);
|
|
+ acx_s_configure(adev, &dkey, ACX1xx_IE_DOT11_WEP_DEFAULT_KEY_SET);
|
|
+#ifdef DEBUG_WEP
|
|
+ keyindic.val = 3;
|
|
+ acx_s_configure(adev, &keyindic, ACX111_IE_KEY_CHOOSE);
|
|
+#endif
|
|
+ start_scan = 1;
|
|
+ CLEAR_BIT(adev->set_mask, GETSET_WEP);
|
|
+ }
|
|
+
|
|
+ if (adev->set_mask & SET_WEP_OPTIONS) {
|
|
+ acx100_ie_wep_options_t options;
|
|
+ if (IS_ACX111(adev)) {
|
|
+ log(L_DEBUG, "setting WEP Options for acx111 is not supported\n");
|
|
+ } else {
|
|
+ log(L_INIT, "setting WEP Options\n");
|
|
+ acx100_s_init_wep(adev);
|
|
+#if 0
|
|
+ /* let's choose maximum setting: 4 default keys,
|
|
+ * plus 10 other keys: */
|
|
+ options.NumKeys = cpu_to_le16(DOT11_MAX_DEFAULT_WEP_KEYS + 10);
|
|
+ /* don't decrypt default key only,
|
|
+ * don't override decryption: */
|
|
+ options.WEPOption = 0;
|
|
+ if (adev->mode == ACX_MODE_MONITOR) {
|
|
+ /* don't decrypt default key only,
|
|
+ * override decryption mechanism: */
|
|
+ options.WEPOption = 2;
|
|
+ }
|
|
+
|
|
+ acx_s_configure(adev, &options, ACX100_IE_WEP_OPTIONS);
|
|
+#endif
|
|
+ }
|
|
+ CLEAR_BIT(adev->set_mask, SET_WEP_OPTIONS);
|
|
+ }
|
|
+
|
|
+ /* Rescan was requested */
|
|
+ if (start_scan) {
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ /* We can avoid clearing list if join code
|
|
+ ** will be a bit more clever about not picking
|
|
+ ** 'bad' AP over and over again */
|
|
+ acx_lock(adev, flags);
|
|
+ adev->ap_client = NULL;
|
|
+ acx_l_sta_list_init(adev);
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ acx_s_cmd_start_scan(adev);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* debug, rate, and nick don't need any handling */
|
|
+ /* what about sniffing mode?? */
|
|
+
|
|
+ log(L_INIT, "get_mask 0x%08X, set_mask 0x%08X - after update\n",
|
|
+ adev->get_mask, adev->set_mask);
|
|
+
|
|
+/* end: */
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_e_after_interrupt_task
|
|
+*/
|
|
+static int
|
|
+acx_s_recalib_radio(acx_device_t *adev)
|
|
+{
|
|
+ if (IS_ACX111(adev)) {
|
|
+ acx111_cmd_radiocalib_t cal;
|
|
+
|
|
+ printk("%s: recalibrating radio\n", adev->ndev->name);
|
|
+ /* automatic recalibration, choose all methods: */
|
|
+ cal.methods = cpu_to_le32(0x8000000f);
|
|
+ /* automatic recalibration every 60 seconds (value in TUs)
|
|
+ * I wonder what the firmware default here is? */
|
|
+ cal.interval = cpu_to_le32(58594);
|
|
+ return acx_s_issue_cmd_timeo(adev, ACX111_CMD_RADIOCALIB,
|
|
+ &cal, sizeof(cal), CMD_TIMEOUT_MS(100));
|
|
+ } else {
|
|
+ /* On ACX100, we need to recalibrate the radio
|
|
+ * by issuing a GETSET_TX|GETSET_RX */
|
|
+ if (/* (OK == acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0)) &&
|
|
+ (OK == acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0)) && */
|
|
+ (OK == acx_s_issue_cmd(adev, ACX1xx_CMD_ENABLE_TX, &adev->channel, 1)) &&
|
|
+ (OK == acx_s_issue_cmd(adev, ACX1xx_CMD_ENABLE_RX, &adev->channel, 1)) )
|
|
+ return OK;
|
|
+ return NOT_OK;
|
|
+ }
|
|
+}
|
|
+
|
|
+static void
|
|
+acx_s_after_interrupt_recalib(acx_device_t *adev)
|
|
+{
|
|
+ int res;
|
|
+
|
|
+ /* this helps with ACX100 at least;
|
|
+ * hopefully ACX111 also does a
|
|
+ * recalibration here */
|
|
+
|
|
+ /* clear flag beforehand, since we want to make sure
|
|
+ * it's cleared; then only set it again on specific circumstances */
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+
|
|
+ /* better wait a bit between recalibrations to
|
|
+ * prevent overheating due to torturing the card
|
|
+ * into working too long despite high temperature
|
|
+ * (just a safety measure) */
|
|
+ if (adev->recalib_time_last_success
|
|
+ && time_before(jiffies, adev->recalib_time_last_success
|
|
+ + RECALIB_PAUSE * 60 * HZ)) {
|
|
+ if (adev->recalib_msg_ratelimit <= 4) {
|
|
+ printk("%s: less than " STRING(RECALIB_PAUSE)
|
|
+ " minutes since last radio recalibration, "
|
|
+ "not recalibrating (maybe card is too hot?)\n",
|
|
+ adev->ndev->name);
|
|
+ adev->recalib_msg_ratelimit++;
|
|
+ if (adev->recalib_msg_ratelimit == 5)
|
|
+ printk("disabling above message until next recalib\n");
|
|
+ }
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ adev->recalib_msg_ratelimit = 0;
|
|
+
|
|
+ /* note that commands sometimes fail (card busy),
|
|
+ * so only clear flag if we were fully successful */
|
|
+ res = acx_s_recalib_radio(adev);
|
|
+ if (res == OK) {
|
|
+ printk("%s: successfully recalibrated radio\n",
|
|
+ adev->ndev->name);
|
|
+ adev->recalib_time_last_success = jiffies;
|
|
+ adev->recalib_failure_count = 0;
|
|
+ } else {
|
|
+ /* failed: resubmit, but only limited
|
|
+ * amount of times within some time range
|
|
+ * to prevent endless loop */
|
|
+
|
|
+ adev->recalib_time_last_success = 0; /* we failed */
|
|
+
|
|
+ /* if some time passed between last
|
|
+ * attempts, then reset failure retry counter
|
|
+ * to be able to do next recalib attempt */
|
|
+ if (time_after(jiffies, adev->recalib_time_last_attempt + 5*HZ))
|
|
+ adev->recalib_failure_count = 0;
|
|
+
|
|
+ if (adev->recalib_failure_count < 5) {
|
|
+ /* increment inside only, for speedup of outside path */
|
|
+ adev->recalib_failure_count++;
|
|
+ adev->recalib_time_last_attempt = jiffies;
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
|
|
+static void
|
|
+acx_e_after_interrupt_task(struct work_struct *work)
|
|
+{
|
|
+ acx_device_t *adev = container_of(work, acx_device_t, after_interrupt_task);
|
|
+#else
|
|
+ static void
|
|
+ acx_e_after_interrupt_task(void *data)
|
|
+ {
|
|
+ struct net_device *ndev = (struct net_device*)data;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+#endif
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (!adev->after_interrupt_jobs)
|
|
+ goto end; /* no jobs to do */
|
|
+
|
|
+#if TX_CLEANUP_IN_SOFTIRQ
|
|
+ /* can happen only on PCI */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_TX_CLEANUP) {
|
|
+ acx_lock(adev, flags);
|
|
+ acxpci_l_clean_txdesc(adev);
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_TX_CLEANUP);
|
|
+ acx_unlock(adev, flags);
|
|
+ }
|
|
+#endif
|
|
+ /* we see lotsa tx errors */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_CMD_RADIO_RECALIB) {
|
|
+ acx_s_after_interrupt_recalib(adev);
|
|
+ }
|
|
+
|
|
+ /* a poor interrupt code wanted to do update_card_settings() */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_UPDATE_CARD_CFG) {
|
|
+ if (ACX_STATE_IFACE_UP & adev->dev_state_mask)
|
|
+ acx_s_update_card_settings(adev);
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_UPDATE_CARD_CFG);
|
|
+ }
|
|
+
|
|
+ /* 1) we detected that no Scan_Complete IRQ came from fw, or
|
|
+ ** 2) we found too many STAs */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_CMD_STOP_SCAN) {
|
|
+ log(L_IRQ, "sending a stop scan cmd...\n");
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_STOP_SCAN, NULL, 0);
|
|
+ /* HACK: set the IRQ bit, since we won't get a
|
|
+ * scan complete IRQ any more on ACX111 (works on ACX100!),
|
|
+ * since _we_, not a fw, have stopped the scan */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE);
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_CMD_STOP_SCAN);
|
|
+ }
|
|
+
|
|
+ /* either fw sent Scan_Complete or we detected that
|
|
+ ** no Scan_Complete IRQ came from fw. Finish scanning,
|
|
+ ** pick join partner if any */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_COMPLETE_SCAN) {
|
|
+ if (adev->status == ACX_STATUS_1_SCANNING) {
|
|
+ if (OK != acx_s_complete_scan(adev)) {
|
|
+ SET_BIT(adev->after_interrupt_jobs,
|
|
+ ACX_AFTER_IRQ_RESTART_SCAN);
|
|
+ }
|
|
+ } else {
|
|
+ /* + scan kills current join status - restore it
|
|
+ ** (do we need it for STA?) */
|
|
+ /* + does it happen only with active scans?
|
|
+ ** active and passive scans? ALL scans including
|
|
+ ** background one? */
|
|
+ /* + was not verified that everything is restored
|
|
+ ** (but at least we start to emit beacons again) */
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_3_AP:
|
|
+ log(L_IRQ, "redoing cmd_join_bssid() after scan\n");
|
|
+ acx_s_cmd_join_bssid(adev, adev->bssid);
|
|
+ }
|
|
+ }
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_COMPLETE_SCAN);
|
|
+ }
|
|
+
|
|
+ /* STA auth or assoc timed out, start over again */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_RESTART_SCAN) {
|
|
+ log(L_IRQ, "sending a start_scan cmd...\n");
|
|
+ acx_s_cmd_start_scan(adev);
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_RESTART_SCAN);
|
|
+ }
|
|
+
|
|
+ /* whee, we got positive assoc response! 8) */
|
|
+ if (adev->after_interrupt_jobs & ACX_AFTER_IRQ_CMD_ASSOCIATE) {
|
|
+ acx_ie_generic_t pdr;
|
|
+ /* tiny race window exists, checking that we still a STA */
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_2_STA:
|
|
+ pdr.m.aid = cpu_to_le16(adev->aid);
|
|
+ acx_s_configure(adev, &pdr, ACX1xx_IE_ASSOC_ID);
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED);
|
|
+ log(L_ASSOC|L_DEBUG, "ASSOCIATED!\n");
|
|
+ CLEAR_BIT(adev->after_interrupt_jobs, ACX_AFTER_IRQ_CMD_ASSOCIATE);
|
|
+ }
|
|
+ }
|
|
+end:
|
|
+ acx_sem_unlock(adev);
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_schedule_task
|
|
+**
|
|
+** Schedule the call of the after_interrupt method after leaving
|
|
+** the interrupt context.
|
|
+*/
|
|
+void
|
|
+acx_schedule_task(acx_device_t *adev, unsigned int set_flag)
|
|
+{
|
|
+ SET_BIT(adev->after_interrupt_jobs, set_flag);
|
|
+ SCHEDULE_WORK(&adev->after_interrupt_task);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void
|
|
+acx_init_task_scheduler(acx_device_t *adev)
|
|
+{
|
|
+ /* configure task scheduler */
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20)
|
|
+ INIT_WORK(&adev->after_interrupt_task, acx_e_after_interrupt_task);
|
|
+#else
|
|
+ INIT_WORK(&adev->after_interrupt_task, acx_e_after_interrupt_task,
|
|
+ adev->ndev);
|
|
+#endif
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_start
|
|
+*/
|
|
+void
|
|
+acx_s_start(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ /*
|
|
+ * Ok, now we do everything that can possibly be done with ioctl
|
|
+ * calls to make sure that when it was called before the card
|
|
+ * was up we get the changes asked for
|
|
+ */
|
|
+
|
|
+ SET_BIT(adev->set_mask, SET_TEMPLATES|SET_STA_LIST|GETSET_WEP
|
|
+ |GETSET_TXPOWER|GETSET_ANTENNA|GETSET_ED_THRESH|GETSET_CCA
|
|
+ |GETSET_REG_DOMAIN|GETSET_MODE|GETSET_CHANNEL
|
|
+ |GETSET_TX|GETSET_RX|GETSET_STATION_ID);
|
|
+
|
|
+ log(L_INIT, "updating initial settings on iface activation\n");
|
|
+ acx_s_update_card_settings(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_update_capabilities
|
|
+*/
|
|
+void
|
|
+acx_update_capabilities(acx_device_t *adev)
|
|
+{
|
|
+ u16 cap = 0;
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_3_AP:
|
|
+ SET_BIT(cap, WF_MGMT_CAP_ESS); break;
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ SET_BIT(cap, WF_MGMT_CAP_IBSS); break;
|
|
+ /* other types of stations do not emit beacons */
|
|
+ }
|
|
+
|
|
+ if (adev->wep_restricted) {
|
|
+ SET_BIT(cap, WF_MGMT_CAP_PRIVACY);
|
|
+ }
|
|
+ if (adev->cfgopt_dot11ShortPreambleOption) {
|
|
+ SET_BIT(cap, WF_MGMT_CAP_SHORT);
|
|
+ }
|
|
+ if (adev->cfgopt_dot11PBCCOption) {
|
|
+ SET_BIT(cap, WF_MGMT_CAP_PBCC);
|
|
+ }
|
|
+ if (adev->cfgopt_dot11ChannelAgility) {
|
|
+ SET_BIT(cap, WF_MGMT_CAP_AGILITY);
|
|
+ }
|
|
+ log(L_DEBUG, "caps updated from 0x%04X to 0x%04X\n",
|
|
+ adev->capabilities, cap);
|
|
+ adev->capabilities = cap;
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+** Common function to parse ALL configoption struct formats
|
|
+** (ACX100 and ACX111; FIXME: how to make it work with ACX100 USB!?!?).
|
|
+** FIXME: logging should be removed here and added to a /proc file instead
|
|
+*/
|
|
+void
|
|
+acx_s_parse_configoption(acx_device_t *adev, const acx111_ie_configoption_t *pcfg)
|
|
+{
|
|
+ const u8 *pEle;
|
|
+ int i;
|
|
+ int is_acx111 = IS_ACX111(adev);
|
|
+
|
|
+ if (acx_debug & L_DEBUG) {
|
|
+ printk("configoption struct content:\n");
|
|
+ acx_dump_bytes(pcfg, sizeof(*pcfg));
|
|
+ }
|
|
+
|
|
+ if (( is_acx111 && (adev->eeprom_version == 5))
|
|
+ || (!is_acx111 && (adev->eeprom_version == 4))
|
|
+ || (!is_acx111 && (adev->eeprom_version == 5))) {
|
|
+ /* these versions are known to be supported */
|
|
+ } else {
|
|
+ printk("unknown chip and EEPROM version combination (%s, v%d), "
|
|
+ "don't know how to parse config options yet. "
|
|
+ "Please report\n", is_acx111 ? "ACX111" : "ACX100",
|
|
+ adev->eeprom_version);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ /* first custom-parse the first part which has chip-specific layout */
|
|
+
|
|
+ pEle = (const u8 *) pcfg;
|
|
+
|
|
+ pEle += 4; /* skip (type,len) header */
|
|
+
|
|
+ memcpy(adev->cfgopt_NVSv, pEle, sizeof(adev->cfgopt_NVSv));
|
|
+ pEle += sizeof(adev->cfgopt_NVSv);
|
|
+
|
|
+ if (is_acx111) {
|
|
+ adev->cfgopt_NVS_vendor_offs = le16_to_cpu(*(u16 *)pEle);
|
|
+ pEle += sizeof(adev->cfgopt_NVS_vendor_offs);
|
|
+
|
|
+ adev->cfgopt_probe_delay = 200; /* good default value? */
|
|
+ pEle += 2; /* FIXME: unknown, value 0x0001 */
|
|
+ } else {
|
|
+ memcpy(adev->cfgopt_MAC, pEle, sizeof(adev->cfgopt_MAC));
|
|
+ pEle += sizeof(adev->cfgopt_MAC);
|
|
+
|
|
+ adev->cfgopt_probe_delay = le16_to_cpu(*(u16 *)pEle);
|
|
+ pEle += sizeof(adev->cfgopt_probe_delay);
|
|
+ if ((adev->cfgopt_probe_delay < 100) || (adev->cfgopt_probe_delay > 500)) {
|
|
+ printk("strange probe_delay value %d, "
|
|
+ "tweaking to 200\n", adev->cfgopt_probe_delay);
|
|
+ adev->cfgopt_probe_delay = 200;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ adev->cfgopt_eof_memory = le32_to_cpu(*(u32 *)pEle);
|
|
+ pEle += sizeof(adev->cfgopt_eof_memory);
|
|
+
|
|
+ printk("NVS_vendor_offs:%04X probe_delay:%d eof_memory:%d\n",
|
|
+ adev->cfgopt_NVS_vendor_offs,
|
|
+ adev->cfgopt_probe_delay,
|
|
+ adev->cfgopt_eof_memory);
|
|
+
|
|
+ adev->cfgopt_dot11CCAModes = *pEle++;
|
|
+ adev->cfgopt_dot11Diversity = *pEle++;
|
|
+ adev->cfgopt_dot11ShortPreambleOption = *pEle++;
|
|
+ adev->cfgopt_dot11PBCCOption = *pEle++;
|
|
+ adev->cfgopt_dot11ChannelAgility = *pEle++;
|
|
+ adev->cfgopt_dot11PhyType = *pEle++;
|
|
+ adev->cfgopt_dot11TempType = *pEle++;
|
|
+ printk("CCAModes:%02X Diversity:%02X ShortPreOpt:%02X "
|
|
+ "PBCC:%02X ChanAgil:%02X PHY:%02X Temp:%02X\n",
|
|
+ adev->cfgopt_dot11CCAModes,
|
|
+ adev->cfgopt_dot11Diversity,
|
|
+ adev->cfgopt_dot11ShortPreambleOption,
|
|
+ adev->cfgopt_dot11PBCCOption,
|
|
+ adev->cfgopt_dot11ChannelAgility,
|
|
+ adev->cfgopt_dot11PhyType,
|
|
+ adev->cfgopt_dot11TempType);
|
|
+
|
|
+ /* then use common parsing for next part which has common layout */
|
|
+
|
|
+ pEle++; /* skip table_count (6) */
|
|
+
|
|
+ if (IS_MEM(adev) && IS_ACX100(adev))
|
|
+ {
|
|
+ /*
|
|
+ * For iPaq hx4700 Generic Slave F/W 1.10.7.K. I'm not sure if these
|
|
+ * 4 extra bytes are before the dot11 things above or after, so I'm just
|
|
+ * going to guess after. If someone sees these aren't reasonable numbers,
|
|
+ * please fix this.
|
|
+ * The area from which the dot11 values above are read contains:
|
|
+ * 04 01 01 01 00 05 01 06 00 02 01 02
|
|
+ * the 8 dot11 reads above take care of 8 of them, but which 8...
|
|
+ */
|
|
+ pEle += 4;
|
|
+ }
|
|
+
|
|
+ adev->cfgopt_antennas.type = pEle[0];
|
|
+ adev->cfgopt_antennas.len = pEle[1];
|
|
+ printk("AntennaID:%02X Len:%02X Data:",
|
|
+ adev->cfgopt_antennas.type, adev->cfgopt_antennas.len);
|
|
+ for (i = 0; i < pEle[1]; i++) {
|
|
+ adev->cfgopt_antennas.list[i] = pEle[i+2];
|
|
+ printk("%02X ", pEle[i+2]);
|
|
+ }
|
|
+ printk("\n");
|
|
+
|
|
+ pEle += pEle[1] + 2;
|
|
+ adev->cfgopt_power_levels.type = pEle[0];
|
|
+ adev->cfgopt_power_levels.len = pEle[1];
|
|
+ printk("PowerLevelID:%02X Len:%02X Data:",
|
|
+ adev->cfgopt_power_levels.type, adev->cfgopt_power_levels.len);
|
|
+ for (i = 0; i < pEle[1]; i++) {
|
|
+ adev->cfgopt_power_levels.list[i] = le16_to_cpu(*(u16 *)&pEle[i*2+2]);
|
|
+ printk("%04X ", adev->cfgopt_power_levels.list[i]);
|
|
+ }
|
|
+ printk("\n");
|
|
+
|
|
+ pEle += pEle[1]*2 + 2;
|
|
+ adev->cfgopt_data_rates.type = pEle[0];
|
|
+ adev->cfgopt_data_rates.len = pEle[1];
|
|
+ printk("DataRatesID:%02X Len:%02X Data:",
|
|
+ adev->cfgopt_data_rates.type, adev->cfgopt_data_rates.len);
|
|
+ for (i = 0; i < pEle[1]; i++) {
|
|
+ adev->cfgopt_data_rates.list[i] = pEle[i+2];
|
|
+ printk("%02X ", pEle[i+2]);
|
|
+ }
|
|
+ printk("\n");
|
|
+
|
|
+ pEle += pEle[1] + 2;
|
|
+ adev->cfgopt_domains.type = pEle[0];
|
|
+ adev->cfgopt_domains.len = pEle[1];
|
|
+ if (IS_MEM(adev) && IS_ACX100(adev))
|
|
+ {
|
|
+ /*
|
|
+ * For iPaq hx4700 Generic Slave F/W 1.10.7.K.
|
|
+ * There's an extra byte between this structure and the next
|
|
+ * that is not accounted for with this structure's length. It's
|
|
+ * most likely a bug in the firmware, but we can fix it here
|
|
+ * by bumping the length of this field by 1.
|
|
+ */
|
|
+ adev->cfgopt_domains.len++;
|
|
+ }
|
|
+ printk("DomainID:%02X Len:%02X Data:",
|
|
+ adev->cfgopt_domains.type, adev->cfgopt_domains.len);
|
|
+ for (i = 0; i < adev->cfgopt_domains.len; i++) {
|
|
+ adev->cfgopt_domains.list[i] = pEle[i+2];
|
|
+ printk("%02X ", pEle[i+2]);
|
|
+ }
|
|
+ printk("\n");
|
|
+
|
|
+ pEle += adev->cfgopt_domains.len + 2;
|
|
+
|
|
+ adev->cfgopt_product_id.type = pEle[0];
|
|
+ adev->cfgopt_product_id.len = pEle[1];
|
|
+ for (i = 0; i < pEle[1]; i++) {
|
|
+ adev->cfgopt_product_id.list[i] = pEle[i+2];
|
|
+ }
|
|
+ printk("ProductID:%02X Len:%02X Data:%.*s\n",
|
|
+ adev->cfgopt_product_id.type, adev->cfgopt_product_id.len,
|
|
+ adev->cfgopt_product_id.len, (char *)adev->cfgopt_product_id.list);
|
|
+
|
|
+ pEle += pEle[1] + 2;
|
|
+ adev->cfgopt_manufacturer.type = pEle[0];
|
|
+ adev->cfgopt_manufacturer.len = pEle[1];
|
|
+ for (i = 0; i < pEle[1]; i++) {
|
|
+ adev->cfgopt_manufacturer.list[i] = pEle[i+2];
|
|
+ }
|
|
+ printk("ManufacturerID:%02X Len:%02X Data:%.*s\n",
|
|
+ adev->cfgopt_manufacturer.type, adev->cfgopt_manufacturer.len,
|
|
+ adev->cfgopt_manufacturer.len, (char *)adev->cfgopt_manufacturer.list);
|
|
+/*
|
|
+ printk("EEPROM part:\n");
|
|
+ for (i=0; i<58; i++) {
|
|
+ printk("%02X =======> 0x%02X\n",
|
|
+ i, (u8 *)adev->cfgopt_NVSv[i-2]);
|
|
+ }
|
|
+*/
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int __init
|
|
+acx_e_init_module(void)
|
|
+{
|
|
+ int r1,r2,r3,r4;
|
|
+
|
|
+ acx_struct_size_check();
|
|
+
|
|
+ printk("acx: this driver is still EXPERIMENTAL\n"
|
|
+ "acx: reading README file and/or Craig's HOWTO is "
|
|
+ "recommended, visit http://acx100.sf.net in case "
|
|
+ "of further questions/discussion\n");
|
|
+
|
|
+#if defined(CONFIG_ACX_PCI)
|
|
+ r1 = acxpci_e_init_module();
|
|
+#else
|
|
+ r1 = -EINVAL;
|
|
+#endif
|
|
+#if defined(CONFIG_ACX_MEM)
|
|
+ r2 = acxmem_e_init_module();
|
|
+#else
|
|
+ r2 = -EINVAL;
|
|
+#endif
|
|
+#if defined(CONFIG_ACX_USB)
|
|
+ r3 = acxusb_e_init_module();
|
|
+#else
|
|
+ r3 = -EINVAL;
|
|
+#endif
|
|
+#if defined(CONFIG_ACX_CS)
|
|
+ r4 = acx_cs_init();
|
|
+#else
|
|
+ r4 = -EINVAL;
|
|
+#endif
|
|
+ if (r2 && r1 && r3 && r4) { /* all failed! */
|
|
+ if (r3 || r1)
|
|
+ return r3 ? r3 : r1;
|
|
+ else
|
|
+ return r2;
|
|
+ }
|
|
+ /* return success if at least one succeeded */
|
|
+ return 0;
|
|
+
|
|
+}
|
|
+
|
|
+static void __exit
|
|
+acx_e_cleanup_module(void)
|
|
+{
|
|
+#if defined(CONFIG_ACX_PCI)
|
|
+ acxpci_e_cleanup_module();
|
|
+#endif
|
|
+#if defined(CONFIG_ACX_MEM)
|
|
+ acxmem_e_cleanup_module();
|
|
+#endif
|
|
+#if defined(CONFIG_ACX_USB)
|
|
+ acxusb_e_cleanup_module();
|
|
+#endif
|
|
+#if defined(CONFIG_ACX_CS)
|
|
+ acx_cs_cleanup();
|
|
+#endif
|
|
+}
|
|
+
|
|
+module_init(acx_e_init_module)
|
|
+module_exit(acx_e_cleanup_module)
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/conv.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/conv.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,504 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+
|
|
+#include "acx.h"
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** proto_is_stt
|
|
+**
|
|
+** Searches the 802.1h Selective Translation Table for a given
|
|
+** protocol.
|
|
+**
|
|
+** prottype - protocol number (in host order) to search for.
|
|
+**
|
|
+** Returns:
|
|
+** 1 - if the table is empty or a match is found.
|
|
+** 0 - if the table is non-empty and a match is not found.
|
|
+**
|
|
+** Based largely on p80211conv.c of the linux-wlan-ng project
|
|
+*/
|
|
+static inline int
|
|
+proto_is_stt(unsigned int proto)
|
|
+{
|
|
+ /* Always return found for now. This is the behavior used by the */
|
|
+ /* Zoom Win95 driver when 802.1h mode is selected */
|
|
+ /* TODO: If necessary, add an actual search we'll probably
|
|
+ need this to match the CMAC's way of doing things.
|
|
+ Need to do some testing to confirm.
|
|
+ */
|
|
+
|
|
+ if (proto == 0x80f3) /* APPLETALK */
|
|
+ return 1;
|
|
+
|
|
+ return 0;
|
|
+/* return ((prottype == ETH_P_AARP) || (prottype == ETH_P_IPX)); */
|
|
+}
|
|
+
|
|
+/* Helpers */
|
|
+
|
|
+static inline void
|
|
+store_llc_snap(struct wlan_llc *llc)
|
|
+{
|
|
+ llc->dsap = 0xaa; /* SNAP, see IEEE 802 */
|
|
+ llc->ssap = 0xaa;
|
|
+ llc->ctl = 0x03;
|
|
+}
|
|
+static inline int
|
|
+llc_is_snap(const struct wlan_llc *llc)
|
|
+{
|
|
+ return (llc->dsap == 0xaa)
|
|
+ && (llc->ssap == 0xaa)
|
|
+ && (llc->ctl == 0x03);
|
|
+}
|
|
+static inline void
|
|
+store_oui_rfc1042(struct wlan_snap *snap)
|
|
+{
|
|
+ snap->oui[0] = 0;
|
|
+ snap->oui[1] = 0;
|
|
+ snap->oui[2] = 0;
|
|
+}
|
|
+static inline int
|
|
+oui_is_rfc1042(const struct wlan_snap *snap)
|
|
+{
|
|
+ return (snap->oui[0] == 0)
|
|
+ && (snap->oui[1] == 0)
|
|
+ && (snap->oui[2] == 0);
|
|
+}
|
|
+static inline void
|
|
+store_oui_8021h(struct wlan_snap *snap)
|
|
+{
|
|
+ snap->oui[0] = 0;
|
|
+ snap->oui[1] = 0;
|
|
+ snap->oui[2] = 0xf8;
|
|
+}
|
|
+static inline int
|
|
+oui_is_8021h(const struct wlan_snap *snap)
|
|
+{
|
|
+ return (snap->oui[0] == 0)
|
|
+ && (snap->oui[1] == 0)
|
|
+ && (snap->oui[2] == 0xf8);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ether_to_txbuf
|
|
+**
|
|
+** Uses the contents of the ether frame to build the elements of
|
|
+** the 802.11 frame.
|
|
+**
|
|
+** We don't actually set up the frame header here. That's the
|
|
+** MAC's job. We're only handling conversion of DIXII or 802.3+LLC
|
|
+** frames to something that works with 802.11.
|
|
+**
|
|
+** Based largely on p80211conv.c of the linux-wlan-ng project
|
|
+*/
|
|
+int
|
|
+acx_ether_to_txbuf(acx_device_t *adev, void *txbuf, const struct sk_buff *skb)
|
|
+{
|
|
+ struct wlan_hdr_a3 *w_hdr;
|
|
+ struct wlan_ethhdr *e_hdr;
|
|
+ struct wlan_llc *e_llc;
|
|
+ struct wlan_snap *e_snap;
|
|
+ const u8 *a1, *a3;
|
|
+ int header_len, payload_len = -1;
|
|
+ /* protocol type or data length, depending on whether
|
|
+ * DIX or 802.3 ethernet format */
|
|
+ u16 proto;
|
|
+ u16 fc;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(!skb->len)) {
|
|
+ log(L_DEBUG, "zero-length skb!\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ w_hdr = (struct wlan_hdr_a3*)txbuf;
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_MONITOR:
|
|
+ /* NB: one day we might want to play with DESC_CTL2_FCS
|
|
+ ** Will need to stop doing "- WLAN_FCS_LEN" here then */
|
|
+ if (unlikely(skb->len >= WLAN_A4FR_MAXLEN_WEP_FCS - WLAN_FCS_LEN)) {
|
|
+ printk("%s: can't tx oversized frame (%d bytes)\n",
|
|
+ adev->ndev->name, skb->len);
|
|
+ goto end;
|
|
+ }
|
|
+ memcpy(w_hdr, skb->data, skb->len);
|
|
+ payload_len = skb->len;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* step 1: classify ether frame, DIX or 802.3? */
|
|
+ e_hdr = (wlan_ethhdr_t *)skb->data;
|
|
+ proto = ntohs(e_hdr->type);
|
|
+ if (proto <= 1500) {
|
|
+ log(L_DEBUG, "tx: 802.3 len: %d\n", skb->len);
|
|
+ /* codes <= 1500 reserved for 802.3 lengths */
|
|
+ /* it's 802.3, pass ether payload unchanged, */
|
|
+ /* trim off ethernet header and copy payload to txdesc */
|
|
+ header_len = WLAN_HDR_A3_LEN;
|
|
+ } else {
|
|
+ /* it's DIXII, time for some conversion */
|
|
+ /* Create 802.11 packet. Header also contains llc and snap. */
|
|
+
|
|
+ log(L_DEBUG, "tx: DIXII len: %d\n", skb->len);
|
|
+
|
|
+ /* size of header is 802.11 header + llc + snap */
|
|
+ header_len = WLAN_HDR_A3_LEN + sizeof(wlan_llc_t) + sizeof(wlan_snap_t);
|
|
+ /* llc is located behind the 802.11 header */
|
|
+ e_llc = (wlan_llc_t*)(w_hdr + 1);
|
|
+ /* snap is located behind the llc */
|
|
+ e_snap = (wlan_snap_t*)(e_llc + 1);
|
|
+
|
|
+ /* setup the LLC header */
|
|
+ store_llc_snap(e_llc);
|
|
+
|
|
+ /* setup the SNAP header */
|
|
+ e_snap->type = htons(proto);
|
|
+ if (proto_is_stt(proto)) {
|
|
+ store_oui_8021h(e_snap);
|
|
+ } else {
|
|
+ store_oui_rfc1042(e_snap);
|
|
+ }
|
|
+ }
|
|
+ /* trim off ethernet header and copy payload to txbuf */
|
|
+ payload_len = skb->len - sizeof(wlan_ethhdr_t);
|
|
+ /* TODO: can we just let acx DMA payload from skb instead? */
|
|
+ memcpy((u8*)txbuf + header_len, skb->data + sizeof(wlan_ethhdr_t), payload_len);
|
|
+ payload_len += header_len;
|
|
+
|
|
+ /* Set up the 802.11 header */
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ fc = (WF_FTYPE_DATAi | WF_FSTYPE_DATAONLYi);
|
|
+ a1 = e_hdr->daddr;
|
|
+ a3 = adev->bssid;
|
|
+ break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ fc = (WF_FTYPE_DATAi | WF_FSTYPE_DATAONLYi | WF_FC_TODSi);
|
|
+ a1 = adev->bssid;
|
|
+ a3 = e_hdr->daddr;
|
|
+ break;
|
|
+ case ACX_MODE_3_AP:
|
|
+ fc = (WF_FTYPE_DATAi | WF_FSTYPE_DATAONLYi | WF_FC_FROMDSi);
|
|
+ a1 = e_hdr->daddr;
|
|
+ a3 = e_hdr->saddr;
|
|
+ break;
|
|
+ default:
|
|
+ printk("%s: error - converting eth to wlan in unknown mode\n",
|
|
+ adev->ndev->name);
|
|
+ payload_len = -1;
|
|
+ goto end;
|
|
+ }
|
|
+ if (adev->wep_enabled)
|
|
+ SET_BIT(fc, WF_FC_ISWEPi);
|
|
+
|
|
+ w_hdr->fc = fc;
|
|
+ w_hdr->dur = 0;
|
|
+ MAC_COPY(w_hdr->a1, a1);
|
|
+ MAC_COPY(w_hdr->a2, adev->dev_addr);
|
|
+ MAC_COPY(w_hdr->a3, a3);
|
|
+ w_hdr->seq = 0;
|
|
+
|
|
+#ifdef DEBUG_CONVERT
|
|
+ if (acx_debug & L_DATA) {
|
|
+ printk("original eth frame [%d]: ", skb->len);
|
|
+ acx_dump_bytes(skb->data, skb->len);
|
|
+ printk("802.11 frame [%d]: ", payload_len);
|
|
+ acx_dump_bytes(w_hdr, payload_len);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+end:
|
|
+ FN_EXIT1(payload_len);
|
|
+ return payload_len;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_rxbuf_to_ether
|
|
+**
|
|
+** Uses the contents of a received 802.11 frame to build an ether
|
|
+** frame.
|
|
+**
|
|
+** This function extracts the src and dest address from the 802.11
|
|
+** frame to use in the construction of the eth frame.
|
|
+**
|
|
+** Based largely on p80211conv.c of the linux-wlan-ng project
|
|
+*/
|
|
+struct sk_buff*
|
|
+acx_rxbuf_to_ether(acx_device_t *adev, rxbuffer_t *rxbuf)
|
|
+{
|
|
+ struct wlan_hdr *w_hdr;
|
|
+ struct wlan_ethhdr *e_hdr;
|
|
+ struct wlan_llc *e_llc;
|
|
+ struct wlan_snap *e_snap;
|
|
+ struct sk_buff *skb;
|
|
+ const u8 *daddr;
|
|
+ const u8 *saddr;
|
|
+ const u8 *e_payload;
|
|
+ int buflen, payload_length;
|
|
+ unsigned int payload_offset, mtu;
|
|
+ u16 fc;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* This looks complex because it must handle possible
|
|
+ ** phy header in rxbuff */
|
|
+ w_hdr = acx_get_wlan_hdr(adev, rxbuf);
|
|
+ payload_offset = WLAN_HDR_A3_LEN; /* it is relative to w_hdr */
|
|
+ payload_length = RXBUF_BYTES_USED(rxbuf) /* entire rxbuff... */
|
|
+ - ((u8*)w_hdr - (u8*)rxbuf) /* minus space before 802.11 frame */
|
|
+ - WLAN_HDR_A3_LEN; /* minus 802.11 header */
|
|
+
|
|
+ /* setup some vars for convenience */
|
|
+ fc = w_hdr->fc;
|
|
+ switch (WF_FC_FROMTODSi & fc) {
|
|
+ case 0:
|
|
+ daddr = w_hdr->a1;
|
|
+ saddr = w_hdr->a2;
|
|
+ break;
|
|
+ case WF_FC_FROMDSi:
|
|
+ daddr = w_hdr->a1;
|
|
+ saddr = w_hdr->a3;
|
|
+ break;
|
|
+ case WF_FC_TODSi:
|
|
+ daddr = w_hdr->a3;
|
|
+ saddr = w_hdr->a2;
|
|
+ break;
|
|
+ default: /* WF_FC_FROMTODSi */
|
|
+ payload_offset += (WLAN_HDR_A4_LEN - WLAN_HDR_A3_LEN);
|
|
+ payload_length -= (WLAN_HDR_A4_LEN - WLAN_HDR_A3_LEN);
|
|
+ daddr = w_hdr->a3;
|
|
+ saddr = w_hdr->a4;
|
|
+ }
|
|
+
|
|
+ if ((WF_FC_ISWEPi & fc) && IS_ACX100(adev)) {
|
|
+ /* chop off the IV+ICV WEP header and footer */
|
|
+ log(L_DATA|L_DEBUG, "rx: WEP packet, "
|
|
+ "chopping off IV and ICV\n");
|
|
+ payload_offset += WLAN_WEP_IV_LEN;
|
|
+ payload_length -= WLAN_WEP_IV_LEN + WLAN_WEP_ICV_LEN;
|
|
+ }
|
|
+
|
|
+ if (unlikely(payload_length < 0)) {
|
|
+ printk("%s: rx frame too short, ignored\n", adev->ndev->name);
|
|
+ goto ret_null;
|
|
+ }
|
|
+
|
|
+ e_hdr = (wlan_ethhdr_t*) ((u8*) w_hdr + payload_offset);
|
|
+ e_llc = (wlan_llc_t*) e_hdr;
|
|
+ e_snap = (wlan_snap_t*) (e_llc + 1);
|
|
+ mtu = adev->ndev->mtu;
|
|
+ e_payload = (u8*) (e_snap + 1);
|
|
+
|
|
+ log(L_DATA, "rx: payload_offset %d, payload_length %d\n",
|
|
+ payload_offset, payload_length);
|
|
+ log(L_XFER|L_DATA,
|
|
+ "rx: frame info: llc=%02X%02X%02X "
|
|
+ "snap.oui=%02X%02X%02X snap.type=%04X\n",
|
|
+ e_llc->dsap, e_llc->ssap, e_llc->ctl,
|
|
+ e_snap->oui[0], e_snap->oui[1], e_snap->oui[2],
|
|
+ ntohs(e_snap->type));
|
|
+
|
|
+ /* Test for the various encodings */
|
|
+ if ((payload_length >= sizeof(wlan_ethhdr_t))
|
|
+ && ((e_llc->dsap != 0xaa) || (e_llc->ssap != 0xaa))
|
|
+ && ( (mac_is_equal(daddr, e_hdr->daddr))
|
|
+ || (mac_is_equal(saddr, e_hdr->saddr))
|
|
+ )
|
|
+ ) {
|
|
+ /* 802.3 Encapsulated: */
|
|
+ /* wlan frame body contains complete eth frame (header+body) */
|
|
+ log(L_DEBUG|L_DATA, "rx: 802.3 ENCAP len=%d\n", payload_length);
|
|
+
|
|
+ if (unlikely(payload_length > (mtu + ETH_HLEN))) {
|
|
+ printk("%s: rx: ENCAP frame too large (%d > %d)\n",
|
|
+ adev->ndev->name,
|
|
+ payload_length, mtu + ETH_HLEN);
|
|
+ goto ret_null;
|
|
+ }
|
|
+
|
|
+ /* allocate space and setup host buffer */
|
|
+ buflen = payload_length;
|
|
+ /* Attempt to align IP header (14 bytes eth header + 2 = 16) */
|
|
+ skb = dev_alloc_skb(buflen + 2);
|
|
+ if (unlikely(!skb))
|
|
+ goto no_skb;
|
|
+ skb_reserve(skb, 2);
|
|
+ skb_put(skb, buflen); /* make room */
|
|
+
|
|
+ /* now copy the data from the 80211 frame */
|
|
+ memcpy(skb->data, e_hdr, payload_length);
|
|
+
|
|
+ } else if ( (payload_length >= sizeof(wlan_llc_t)+sizeof(wlan_snap_t))
|
|
+ && llc_is_snap(e_llc) ) {
|
|
+ /* wlan frame body contains: AA AA 03 ... (it's a SNAP) */
|
|
+
|
|
+ if ( !oui_is_rfc1042(e_snap)
|
|
+ || (proto_is_stt(ieee2host16(e_snap->type)) /* && (ethconv == WLAN_ETHCONV_8021h) */)) {
|
|
+ log(L_DEBUG|L_DATA, "rx: SNAP+RFC1042 len=%d\n", payload_length);
|
|
+ /* wlan frame body contains: AA AA 03 !(00 00 00) ... -or- */
|
|
+ /* wlan frame body contains: AA AA 03 00 00 00 0x80f3 ... */
|
|
+ /* build eth hdr, type = len, copy AA AA 03... as eth body */
|
|
+ /* it's a SNAP + RFC1042 frame && protocol is in STT */
|
|
+
|
|
+ if (unlikely(payload_length > mtu)) {
|
|
+ printk("%s: rx: SNAP frame too large (%d > %d)\n",
|
|
+ adev->ndev->name,
|
|
+ payload_length, mtu);
|
|
+ goto ret_null;
|
|
+ }
|
|
+
|
|
+ /* allocate space and setup host buffer */
|
|
+ buflen = payload_length + ETH_HLEN;
|
|
+ skb = dev_alloc_skb(buflen + 2);
|
|
+ if (unlikely(!skb))
|
|
+ goto no_skb;
|
|
+ skb_reserve(skb, 2);
|
|
+ skb_put(skb, buflen); /* make room */
|
|
+
|
|
+ /* create 802.3 header */
|
|
+ e_hdr = (wlan_ethhdr_t*) skb->data;
|
|
+ MAC_COPY(e_hdr->daddr, daddr);
|
|
+ MAC_COPY(e_hdr->saddr, saddr);
|
|
+ e_hdr->type = htons(payload_length);
|
|
+
|
|
+ /* Now copy the data from the 80211 frame.
|
|
+ Make room in front for the eth header, and keep the
|
|
+ llc and snap from the 802.11 payload */
|
|
+ memcpy(skb->data + ETH_HLEN,
|
|
+ e_llc, payload_length);
|
|
+
|
|
+ } else {
|
|
+ /* wlan frame body contains: AA AA 03 00 00 00 [type] [tail] */
|
|
+ /* build eth hdr, type=[type], copy [tail] as eth body */
|
|
+ log(L_DEBUG|L_DATA, "rx: 802.1h/RFC1042 len=%d\n",
|
|
+ payload_length);
|
|
+ /* it's an 802.1h frame (an RFC1042 && protocol is not in STT) */
|
|
+ /* build a DIXII + RFC894 */
|
|
+
|
|
+ payload_length -= sizeof(wlan_llc_t) + sizeof(wlan_snap_t);
|
|
+ if (unlikely(payload_length > mtu)) {
|
|
+ printk("%s: rx: DIXII frame too large (%d > %d)\n",
|
|
+ adev->ndev->name,
|
|
+ payload_length, mtu);
|
|
+ goto ret_null;
|
|
+ }
|
|
+
|
|
+ /* allocate space and setup host buffer */
|
|
+ buflen = payload_length + ETH_HLEN;
|
|
+ skb = dev_alloc_skb(buflen + 2);
|
|
+ if (unlikely(!skb))
|
|
+ goto no_skb;
|
|
+ skb_reserve(skb, 2);
|
|
+ skb_put(skb, buflen); /* make room */
|
|
+
|
|
+ /* create 802.3 header */
|
|
+ e_hdr = (wlan_ethhdr_t *) skb->data;
|
|
+ MAC_COPY(e_hdr->daddr, daddr);
|
|
+ MAC_COPY(e_hdr->saddr, saddr);
|
|
+ e_hdr->type = e_snap->type;
|
|
+
|
|
+ /* Now copy the data from the 80211 frame.
|
|
+ Make room in front for the eth header, and cut off the
|
|
+ llc and snap from the 802.11 payload */
|
|
+ memcpy(skb->data + ETH_HLEN,
|
|
+ e_payload, payload_length);
|
|
+ }
|
|
+
|
|
+ } else {
|
|
+ log(L_DEBUG|L_DATA, "rx: NON-ENCAP len=%d\n", payload_length);
|
|
+ /* build eth hdr, type=len, copy wlan body as eth body */
|
|
+ /* any NON-ENCAP */
|
|
+ /* it's a generic 80211+LLC or IPX 'Raw 802.3' */
|
|
+ /* build an 802.3 frame */
|
|
+
|
|
+ if (unlikely(payload_length > mtu)) {
|
|
+ printk("%s: rx: OTHER frame too large (%d > %d)\n",
|
|
+ adev->ndev->name, payload_length, mtu);
|
|
+ goto ret_null;
|
|
+ }
|
|
+
|
|
+ /* allocate space and setup host buffer */
|
|
+ buflen = payload_length + ETH_HLEN;
|
|
+ skb = dev_alloc_skb(buflen + 2);
|
|
+ if (unlikely(!skb))
|
|
+ goto no_skb;
|
|
+ skb_reserve(skb, 2);
|
|
+ skb_put(skb, buflen); /* make room */
|
|
+
|
|
+ /* set up the 802.3 header */
|
|
+ e_hdr = (wlan_ethhdr_t *) skb->data;
|
|
+ MAC_COPY(e_hdr->daddr, daddr);
|
|
+ MAC_COPY(e_hdr->saddr, saddr);
|
|
+ e_hdr->type = htons(payload_length);
|
|
+
|
|
+ /* now copy the data from the 80211 frame */
|
|
+ memcpy(skb->data + ETH_HLEN, e_llc, payload_length);
|
|
+ }
|
|
+
|
|
+ skb->dev = adev->ndev;
|
|
+ skb->protocol = eth_type_trans(skb, adev->ndev);
|
|
+
|
|
+#ifdef DEBUG_CONVERT
|
|
+ if (acx_debug & L_DATA) {
|
|
+ int len = RXBUF_BYTES_RCVD(adev, rxbuf);
|
|
+ printk("p802.11 frame [%d]: ", len);
|
|
+ acx_dump_bytes(w_hdr, len);
|
|
+ printk("eth frame [%d]: ", skb->len);
|
|
+ acx_dump_bytes(skb->data, skb->len);
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return skb;
|
|
+
|
|
+no_skb:
|
|
+ printk("%s: rx: no memory for skb (%d bytes)\n",
|
|
+ adev->ndev->name, buflen + 2);
|
|
+ret_null:
|
|
+ FN_EXIT1((int)NULL);
|
|
+ return NULL;
|
|
+}
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/cs.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/cs.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,5703 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+**
|
|
+** Slave memory interface support:
|
|
+**
|
|
+** Todd Blumer - SDG Systems
|
|
+** Bill Reese - HP
|
|
+** Eric McCorkle - Shadowsun
|
|
+**
|
|
+** CF support, (c) Fabrice Crohas, Paul Sokolovsky
|
|
+*/
|
|
+#define ACX_MEM 1
|
|
+
|
|
+/*
|
|
+ * non-zero makes it dump the ACX memory to the console then
|
|
+ * panic when you cat /proc/driver/acx_wlan0_diag
|
|
+ */
|
|
+#define DUMP_MEM_DEFINED 1
|
|
+
|
|
+#define DUMP_MEM_DURING_DIAG 0
|
|
+#define DUMP_IF_SLOW 0
|
|
+
|
|
+#define PATCH_AROUND_BAD_SPOTS 1
|
|
+#define HX4700_FIRMWARE_CHECKSUM 0x0036862e
|
|
+#define HX4700_ALTERNATE_FIRMWARE_CHECKSUM 0x00368a75
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+
|
|
+/* Linux 2.6.18+ uses <linux/utsrelease.h> */
|
|
+#ifndef UTS_RELEASE
|
|
+#include <linux/utsrelease.h>
|
|
+#endif
|
|
+
|
|
+#include <linux/compiler.h> /* required for Lx 2.6.8 ?? */
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/moduleparam.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/inetdevice.h>
|
|
+
|
|
+#define PCMCIA_DEBUG 1
|
|
+
|
|
+/*
|
|
+ All the PCMCIA modules use PCMCIA_DEBUG to control debugging. If
|
|
+ you do not define PCMCIA_DEBUG at all, all the debug code will be
|
|
+ left out. If you compile with PCMCIA_DEBUG=0, the debug code will
|
|
+ be present but disabled -- but it can then be enabled for specific
|
|
+ modules at load time with a 'pc_debug=#' option to insmod.
|
|
+
|
|
+*/
|
|
+#include <pcmcia/cs_types.h>
|
|
+#include <pcmcia/cs.h>
|
|
+#include <pcmcia/cistpl.h>
|
|
+#include <pcmcia/cisreg.h>
|
|
+#include <pcmcia/ds.h>
|
|
+#include "acx.h"
|
|
+#include "acx_hw.h"
|
|
+
|
|
+#ifdef PCMCIA_DEBUG
|
|
+static int pc_debug = PCMCIA_DEBUG;
|
|
+module_param(pc_debug, int, 0);
|
|
+static char *version = "$Revision: 1.10 $";
|
|
+#define DEBUG(n, args...) if (pc_debug>(n)) printk(KERN_DEBUG args);
|
|
+#else
|
|
+#define DEBUG(n, args...)
|
|
+#endif
|
|
+
|
|
+
|
|
+static win_req_t memwin;
|
|
+
|
|
+typedef struct local_info_t {
|
|
+ dev_node_t node;
|
|
+ struct net_device *ndev;
|
|
+} local_info_t;
|
|
+
|
|
+static struct net_device *resume_ndev;
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+
|
|
+#define CARD_EEPROM_ID_SIZE 6
|
|
+
|
|
+#include <asm/io.h>
|
|
+
|
|
+#define REG_ACX_VENDOR_ID 0x900
|
|
+/*
|
|
+ * This is the vendor id on the HX4700, anyway
|
|
+ */
|
|
+#define ACX_VENDOR_ID 0x8400104c
|
|
+
|
|
+typedef enum {
|
|
+ ACX_SOFT_RESET = 0,
|
|
+
|
|
+ ACX_SLV_REG_ADDR,
|
|
+ ACX_SLV_REG_DATA,
|
|
+ ACX_SLV_REG_ADATA,
|
|
+
|
|
+ ACX_SLV_MEM_CP,
|
|
+ ACX_SLV_MEM_ADDR,
|
|
+ ACX_SLV_MEM_DATA,
|
|
+ ACX_SLV_MEM_CTL,
|
|
+} acxreg_t;
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static void acxmem_i_tx_timeout(struct net_device *ndev);
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+static irqreturn_t acxmem_i_interrupt(int irq, void *dev_id);
|
|
+#else
|
|
+static irqreturn_t acxmem_i_interrupt(int irq, void *dev_id, struct pt_regs *regs);
|
|
+#endif
|
|
+static void acxmem_i_set_multicast_list(struct net_device *ndev);
|
|
+
|
|
+static int acxmem_e_open(struct net_device *ndev);
|
|
+static int acxmem_e_close(struct net_device *ndev);
|
|
+static void acxmem_s_up(struct net_device *ndev);
|
|
+static void acxmem_s_down(struct net_device *ndev);
|
|
+
|
|
+static void dump_acxmem (acx_device_t *adev, u32 start, int length);
|
|
+static int acxmem_complete_hw_reset (acx_device_t *adev);
|
|
+static void acxmem_s_delete_dma_regions(acx_device_t *adev);
|
|
+
|
|
+static int
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+acxmem_e_suspend( struct net_device *ndev, pm_message_t state);
|
|
+#else
|
|
+acxmem_e_suspend( struct net_device *ndev, u32 state);
|
|
+#endif
|
|
+static void
|
|
+fw_resumer(struct work_struct *notused);
|
|
+//fw_resumer( void *data );
|
|
+
|
|
+static int acx_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
|
|
+{
|
|
+ struct net_device *ndev = ptr;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ /*
|
|
+ * Upper level ioctl() handlers send a NETDEV_CHANGEADDR if the MAC address changes.
|
|
+ */
|
|
+
|
|
+ if (NETDEV_CHANGEADDR == event) {
|
|
+ /*
|
|
+ * the upper layers put the new MAC address in ndev->dev_addr; we just copy
|
|
+ * it over and update the ACX with it.
|
|
+ */
|
|
+ MAC_COPY(adev->dev_addr, adev->ndev->dev_addr);
|
|
+ adev->set_mask |= GETSET_STATION_ID;
|
|
+ acx_s_update_card_settings (adev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct notifier_block acx_netdev_notifier = {
|
|
+ .notifier_call = acx_netdev_event,
|
|
+};
|
|
+
|
|
+/***********************************************************************
|
|
+** Register access
|
|
+*/
|
|
+
|
|
+/* Pick one */
|
|
+/* #define INLINE_IO static */
|
|
+#define INLINE_IO static inline
|
|
+
|
|
+INLINE_IO u32
|
|
+read_id_register (acx_device_t *adev)
|
|
+{
|
|
+ writel (0x24, &adev->iobase[ACX_SLV_REG_ADDR]);
|
|
+ return readl (&adev->iobase[ACX_SLV_REG_DATA]);
|
|
+}
|
|
+
|
|
+INLINE_IO u32
|
|
+read_reg32(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ return readl(((u8*)adev->iobase) + addr);
|
|
+ }
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ val = readl( &adev->iobase[ACX_SLV_REG_DATA] );
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+INLINE_IO u16
|
|
+read_reg16(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ u16 lo;
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ return readw(((u8 *) adev->iobase) + addr);
|
|
+ }
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ lo = readw( (u16 *)&adev->iobase[ACX_SLV_REG_DATA] );
|
|
+
|
|
+ return lo;
|
|
+}
|
|
+
|
|
+INLINE_IO u8
|
|
+read_reg8(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ u8 lo;
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20)
|
|
+ return readb(((u8 *)adev->iobase) + addr);
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ lo = readw( (u8 *)&adev->iobase[ACX_SLV_REG_DATA] );
|
|
+
|
|
+ return (u8)lo;
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg32(acx_device_t *adev, unsigned int offset, u32 val)
|
|
+{
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ writel(val, ((u8*)adev->iobase) + addr);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ writel( val, &adev->iobase[ACX_SLV_REG_DATA] );
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg16(acx_device_t *adev, unsigned int offset, u16 val)
|
|
+{
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ writew(val, ((u8 *)adev->iobase) + addr);
|
|
+ return;
|
|
+ }
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ writew( val, (u16 *) &adev->iobase[ACX_SLV_REG_DATA] );
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg8(acx_device_t *adev, unsigned int offset, u8 val)
|
|
+{
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ writeb(val, ((u8 *) adev->iobase) + addr);
|
|
+ return;
|
|
+ }
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ writeb( val, (u8 *)&adev->iobase[ACX_SLV_REG_DATA] );
|
|
+}
|
|
+
|
|
+/* Handle PCI posting properly:
|
|
+ * Make sure that writes reach the adapter in case they require to be executed
|
|
+ * *before* the next write, by reading a random (and safely accessible) register.
|
|
+ * This call has to be made if there is no read following (which would flush the data
|
|
+ * to the adapter), yet the written data has to reach the adapter immediately. */
|
|
+INLINE_IO void
|
|
+write_flush(acx_device_t *adev)
|
|
+{
|
|
+ /* readb(adev->iobase + adev->io[IO_ACX_INFO_MAILBOX_OFFS]); */
|
|
+ /* faster version (accesses the first register, IO_ACX_SOFT_RESET,
|
|
+ * which should also be safe): */
|
|
+ (void) readl(adev->iobase);
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+set_regbits (acx_device_t *adev, unsigned int offset, u32 bits) {
|
|
+ u32 tmp;
|
|
+
|
|
+ tmp = read_reg32 (adev, offset);
|
|
+ tmp = tmp | bits;
|
|
+ write_reg32 (adev, offset, tmp);
|
|
+ write_flush (adev);
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+clear_regbits (acx_device_t *adev, unsigned int offset, u32 bits) {
|
|
+ u32 tmp;
|
|
+
|
|
+ tmp = read_reg32 (adev, offset);
|
|
+ tmp = tmp & ~bits;
|
|
+ write_reg32 (adev, offset, tmp);
|
|
+ write_flush (adev);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Copy from PXA memory to the ACX memory. This assumes both the PXA and ACX
|
|
+ * addresses are 32 bit aligned. Count is in bytes.
|
|
+ */
|
|
+INLINE_IO void
|
|
+write_slavemem32 (acx_device_t *adev, u32 slave_address, u32 val)
|
|
+{
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_CTL, 0x0);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, slave_address);
|
|
+ udelay (10);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_DATA, val);
|
|
+}
|
|
+
|
|
+INLINE_IO u32
|
|
+read_slavemem32 (acx_device_t *adev, u32 slave_address)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_CTL, 0x0);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, slave_address);
|
|
+ udelay (10);
|
|
+ val = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_slavemem8 (acx_device_t *adev, u32 slave_address, u8 val)
|
|
+{
|
|
+ u32 data;
|
|
+ u32 base;
|
|
+ int offset;
|
|
+
|
|
+ /*
|
|
+ * Get the word containing the target address and the byte offset in that word.
|
|
+ */
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+ data &= ~(0xff << offset);
|
|
+ data |= val << offset;
|
|
+ write_slavemem32 (adev, base, data);
|
|
+}
|
|
+
|
|
+INLINE_IO u8
|
|
+read_slavemem8 (acx_device_t *adev, u32 slave_address)
|
|
+{
|
|
+ u8 val;
|
|
+ u32 base;
|
|
+ u32 data;
|
|
+ int offset;
|
|
+
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+
|
|
+ val = (data >> offset) & 0xff;
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * doesn't split across word boundaries
|
|
+ */
|
|
+INLINE_IO void
|
|
+write_slavemem16 (acx_device_t *adev, u32 slave_address, u16 val)
|
|
+{
|
|
+ u32 data;
|
|
+ u32 base;
|
|
+ int offset;
|
|
+
|
|
+ /*
|
|
+ * Get the word containing the target address and the byte offset in that word.
|
|
+ */
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+ data &= ~(0xffff << offset);
|
|
+ data |= val << offset;
|
|
+ write_slavemem32 (adev, base, data);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * doesn't split across word boundaries
|
|
+ */
|
|
+INLINE_IO u16
|
|
+read_slavemem16 (acx_device_t *adev, u32 slave_address)
|
|
+{
|
|
+ u16 val;
|
|
+ u32 base;
|
|
+ u32 data;
|
|
+ int offset;
|
|
+
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+
|
|
+ val = (data >> offset) & 0xffff;
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Copy from slave memory
|
|
+ *
|
|
+ * TODO - rewrite using address autoincrement, handle partial words
|
|
+ */
|
|
+void
|
|
+copy_from_slavemem (acx_device_t *adev, u8 *destination, u32 source, int count) {
|
|
+ u32 tmp = 0;
|
|
+ u8 *ptmp = (u8 *) &tmp;
|
|
+
|
|
+ /*
|
|
+ * Right now I'm making the assumption that the destination is aligned, but
|
|
+ * I'd better check.
|
|
+ */
|
|
+ if ((u32) destination & 3) {
|
|
+ printk ("acx copy_from_slavemem: warning! destination not word-aligned!\n");
|
|
+ }
|
|
+
|
|
+ while (count >= 4) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, source);
|
|
+ udelay (10);
|
|
+ *((u32 *) destination) = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+ count -= 4;
|
|
+ source += 4;
|
|
+ destination += 4;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If the word reads above didn't satisfy the count, read one more word
|
|
+ * and transfer a byte at a time until the request is satisfied.
|
|
+ */
|
|
+ if (count) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, source);
|
|
+ udelay (10);
|
|
+ tmp = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+ while (count--) {
|
|
+ *destination++ = *ptmp++;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Copy to slave memory
|
|
+ *
|
|
+ * TODO - rewrite using autoincrement, handle partial words
|
|
+ */
|
|
+void
|
|
+copy_to_slavemem (acx_device_t *adev, u32 destination, u8 *source, int count)
|
|
+{
|
|
+ u32 tmp = 0;
|
|
+ u8* ptmp = (u8 *) &tmp;
|
|
+ static u8 src[512]; /* make static to avoid huge stack objects */
|
|
+
|
|
+ /*
|
|
+ * For now, make sure the source is word-aligned by copying it to a word-aligned
|
|
+ * buffer. Someday rewrite to avoid the extra copy.
|
|
+ */
|
|
+ if (count > sizeof (src)) {
|
|
+ printk ("acx copy_to_slavemem: Warning! buffer overflow!\n");
|
|
+ count = sizeof (src);
|
|
+ }
|
|
+ memcpy (src, source, count);
|
|
+ source = src;
|
|
+
|
|
+ while (count >= 4) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, destination);
|
|
+ udelay (10);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_DATA, *((u32 *) source));
|
|
+ count -= 4;
|
|
+ source += 4;
|
|
+ destination += 4;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If there are leftovers read the next word from the acx and merge in
|
|
+ * what they want to write.
|
|
+ */
|
|
+ if (count) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, destination);
|
|
+ udelay (10);
|
|
+ tmp = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+ while (count--) {
|
|
+ *ptmp++ = *source++;
|
|
+ }
|
|
+ /*
|
|
+ * reset address in case we're currently in auto-increment mode
|
|
+ */
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, destination);
|
|
+ udelay (10);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_DATA, tmp);
|
|
+ udelay (10);
|
|
+ }
|
|
+
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Block copy to slave buffers using memory block chain mode. Copies to the ACX
|
|
+ * transmit buffer structure with minimal intervention on our part.
|
|
+ * Interrupts should be disabled when calling this.
|
|
+ */
|
|
+void
|
|
+chaincopy_to_slavemem (acx_device_t *adev, u32 destination, u8 *source, int count)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 *data = (u32 *) source;
|
|
+ static u8 aligned_source[WLAN_A4FR_MAXLEN_WEP_FCS];
|
|
+
|
|
+ /*
|
|
+ * Warn if the pointers don't look right. Destination must fit in [23:5] with
|
|
+ * zero elsewhere and source should be 32 bit aligned.
|
|
+ * This should never happen since we're in control of both, but I want to know about
|
|
+ * it if it does.
|
|
+ */
|
|
+ if ((destination & 0x00ffffe0) != destination) {
|
|
+ printk ("acx chaincopy: destination block 0x%04x not aligned!\n", destination);
|
|
+ }
|
|
+ if (count > sizeof aligned_source) {
|
|
+ printk( KERN_ERR "chaincopy_to_slavemem overflow!\n" );
|
|
+ count = sizeof aligned_source;
|
|
+ }
|
|
+ if ((u32) source & 3) {
|
|
+ memcpy (aligned_source, source, count);
|
|
+ data = (u32 *) aligned_source;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CTL[17:16] = memory block chain mode with auto-increment
|
|
+ * SLV_MEM_CTL[5:2] = offset to data portion = 1 word
|
|
+ */
|
|
+ val = 2 << 16 | 1 << 2;
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CTL]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CP[23:5] = start of 1st block
|
|
+ * SLV_MEM_CP[3:2] = offset to memblkptr = 0
|
|
+ */
|
|
+ val = destination & 0x00ffffe0;
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CP]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_ADDR[23:2] = SLV_MEM_CTL[5:2] + SLV_MEM_CP[23:5]
|
|
+ */
|
|
+ val = (destination & 0x00ffffe0) + (1<<2);
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_ADDR]);
|
|
+
|
|
+ /*
|
|
+ * Write the data to the slave data register, rounding up to the end
|
|
+ * of the word containing the last byte (hence the > 0)
|
|
+ */
|
|
+ while (count > 0) {
|
|
+ writel (*data++, &adev->iobase[ACX_SLV_MEM_DATA]);
|
|
+ count -= 4;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * Block copy from slave buffers using memory block chain mode. Copies from the ACX
|
|
+ * receive buffer structures with minimal intervention on our part.
|
|
+ * Interrupts should be disabled when calling this.
|
|
+ */
|
|
+void
|
|
+chaincopy_from_slavemem (acx_device_t *adev, u8 *destination, u32 source, int count)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 *data = (u32 *) destination;
|
|
+ static u8 aligned_destination[WLAN_A4FR_MAXLEN_WEP_FCS];
|
|
+ int saved_count = count;
|
|
+
|
|
+ /*
|
|
+ * Warn if the pointers don't look right. Destination must fit in [23:5] with
|
|
+ * zero elsewhere and source should be 32 bit aligned.
|
|
+ * Turns out the network stack sends unaligned things, so fix them before
|
|
+ * copying to the ACX.
|
|
+ */
|
|
+ if ((source & 0x00ffffe0) != source) {
|
|
+ printk ("acx chaincopy: source block 0x%04x not aligned!\n", source);
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ }
|
|
+ if ((u32) destination & 3) {
|
|
+ //printk ("acx chaincopy: data destination not word aligned!\n");
|
|
+ data = (u32 *) aligned_destination;
|
|
+ if (count > sizeof aligned_destination) {
|
|
+ printk( KERN_ERR "chaincopy_from_slavemem overflow!\n" );
|
|
+ count = sizeof aligned_destination;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CTL[17:16] = memory block chain mode with auto-increment
|
|
+ * SLV_MEM_CTL[5:2] = offset to data portion = 1 word
|
|
+ */
|
|
+ val = (2 << 16) | (1 << 2);
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CTL]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CP[23:5] = start of 1st block
|
|
+ * SLV_MEM_CP[3:2] = offset to memblkptr = 0
|
|
+ */
|
|
+ val = source & 0x00ffffe0;
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CP]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_ADDR[23:2] = SLV_MEM_CTL[5:2] + SLV_MEM_CP[23:5]
|
|
+ */
|
|
+ val = (source & 0x00ffffe0) + (1<<2);
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_ADDR]);
|
|
+
|
|
+ /*
|
|
+ * Read the data from the slave data register, rounding up to the end
|
|
+ * of the word containing the last byte (hence the > 0)
|
|
+ */
|
|
+ while (count > 0) {
|
|
+ *data++ = readl (&adev->iobase[ACX_SLV_MEM_DATA]);
|
|
+ count -= 4;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If the destination wasn't aligned, we would have saved it in
|
|
+ * the aligned buffer, so copy it where it should go.
|
|
+ */
|
|
+ if ((u32) destination & 3) {
|
|
+ memcpy (destination, aligned_destination, saved_count);
|
|
+ }
|
|
+}
|
|
+
|
|
+char
|
|
+printable (char c)
|
|
+{
|
|
+ return ((c >= 20) && (c < 127)) ? c : '.';
|
|
+}
|
|
+
|
|
+#if DUMP_MEM_DEFINED > 0
|
|
+static void
|
|
+dump_acxmem (acx_device_t *adev, u32 start, int length)
|
|
+{
|
|
+ int i;
|
|
+ u8 buf[16];
|
|
+
|
|
+ while (length > 0) {
|
|
+ printk ("%04x ", start);
|
|
+ copy_from_slavemem (adev, buf, start, 16);
|
|
+ for (i = 0; (i < 16) && (i < length); i++) {
|
|
+ printk ("%02x ", buf[i]);
|
|
+ }
|
|
+ for (i = 0; (i < 16) && (i < length); i++) {
|
|
+ printk ("%c", printable (buf[i]));
|
|
+ }
|
|
+ printk ("\n");
|
|
+ start += 16;
|
|
+ length -= 16;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void
|
|
+enable_acx_irq(acx_device_t *adev);
|
|
+static void
|
|
+disable_acx_irq(acx_device_t *adev);
|
|
+
|
|
+/*
|
|
+ * Return an acx pointer to the next transmit data block.
|
|
+ */
|
|
+u32
|
|
+allocate_acx_txbuf_space (acx_device_t *adev, int count) {
|
|
+ u32 block, next, last_block;
|
|
+ int blocks_needed;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&adev->txbuf_lock, flags);
|
|
+ /*
|
|
+ * Take 4 off the memory block size to account for the reserved word at the start of
|
|
+ * the block.
|
|
+ */
|
|
+ blocks_needed = count / (adev->memblocksize - 4);
|
|
+ if (count % (adev->memblocksize - 4))
|
|
+ blocks_needed++;
|
|
+
|
|
+ if (blocks_needed <= adev->acx_txbuf_blocks_free) {
|
|
+ /*
|
|
+ * Take blocks at the head of the free list.
|
|
+ */
|
|
+ last_block = block = adev->acx_txbuf_free;
|
|
+
|
|
+ /*
|
|
+ * Follow block pointers through the requested number of blocks both to
|
|
+ * find the new head of the free list and to set the flags for the blocks
|
|
+ * appropriately.
|
|
+ */
|
|
+ while (blocks_needed--) {
|
|
+ /*
|
|
+ * Keep track of the last block of the allocation
|
|
+ */
|
|
+ last_block = adev->acx_txbuf_free;
|
|
+
|
|
+ /*
|
|
+ * Make sure the end control flag is not set.
|
|
+ */
|
|
+ next = read_slavemem32 (adev, adev->acx_txbuf_free) & 0x7ffff;
|
|
+ write_slavemem32 (adev, adev->acx_txbuf_free, next);
|
|
+
|
|
+ /*
|
|
+ * Update the new head of the free list
|
|
+ */
|
|
+ adev->acx_txbuf_free = next << 5;
|
|
+ adev->acx_txbuf_blocks_free--;
|
|
+
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Flag the last block both by clearing out the next pointer
|
|
+ * and marking the control field.
|
|
+ */
|
|
+ write_slavemem32 (adev, last_block, 0x02000000);
|
|
+
|
|
+ /*
|
|
+ * If we're out of buffers make sure the free list pointer is NULL
|
|
+ */
|
|
+ if (!adev->acx_txbuf_blocks_free) {
|
|
+ adev->acx_txbuf_free = 0;
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ block = 0;
|
|
+ }
|
|
+ spin_unlock_irqrestore (&adev->txbuf_lock, flags);
|
|
+ return block;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Return buffer space back to the pool by following the next pointers until we find
|
|
+ * the block marked as the end. Point the last block to the head of the free list,
|
|
+ * then update the head of the free list to point to the newly freed memory.
|
|
+ * This routine gets called in interrupt context, so it shouldn't block to protect
|
|
+ * the integrity of the linked list. The ISR already holds the lock.
|
|
+ */
|
|
+void
|
|
+reclaim_acx_txbuf_space (acx_device_t *adev, u32 blockptr) {
|
|
+ u32 cur, last, next;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave (&adev->txbuf_lock, flags);
|
|
+ if ((blockptr >= adev->acx_txbuf_start) &&
|
|
+ (blockptr <= adev->acx_txbuf_start +
|
|
+ (adev->acx_txbuf_numblocks - 1) * adev->memblocksize)) {
|
|
+ cur = blockptr;
|
|
+ do {
|
|
+ last = cur;
|
|
+ next = read_slavemem32 (adev, cur);
|
|
+
|
|
+ /*
|
|
+ * Advance to the next block in this allocation
|
|
+ */
|
|
+ cur = (next & 0x7ffff) << 5;
|
|
+
|
|
+ /*
|
|
+ * This block now counts as free.
|
|
+ */
|
|
+ adev->acx_txbuf_blocks_free++;
|
|
+ } while (!(next & 0x02000000));
|
|
+
|
|
+ /*
|
|
+ * last now points to the last block of that allocation. Update the pointer
|
|
+ * in that block to point to the free list and reset the free list to the
|
|
+ * first block of the free call. If there were no free blocks, make sure
|
|
+ * the new end of the list marks itself as truly the end.
|
|
+ */
|
|
+ if (adev->acx_txbuf_free) {
|
|
+ write_slavemem32 (adev, last, adev->acx_txbuf_free >> 5);
|
|
+ }
|
|
+ else {
|
|
+ write_slavemem32 (adev, last, 0x02000000);
|
|
+ }
|
|
+ adev->acx_txbuf_free = blockptr;
|
|
+ }
|
|
+ spin_unlock_irqrestore(&adev->txbuf_lock, flags);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Initialize the pieces managing the transmit buffer pool on the ACX. The transmit
|
|
+ * buffer is a circular queue with one 32 bit word reserved at the beginning of each
|
|
+ * block. The upper 13 bits are a control field, of which only 0x02000000 has any
|
|
+ * meaning. The lower 19 bits are the address of the next block divided by 32.
|
|
+ */
|
|
+void
|
|
+init_acx_txbuf (acx_device_t *adev) {
|
|
+
|
|
+ /*
|
|
+ * acx100_s_init_memory_pools set up txbuf_start and txbuf_numblocks for us.
|
|
+ * All we need to do is reset the rest of the bookeeping.
|
|
+ */
|
|
+
|
|
+ adev->acx_txbuf_free = adev->acx_txbuf_start;
|
|
+ adev->acx_txbuf_blocks_free = adev->acx_txbuf_numblocks;
|
|
+
|
|
+ /*
|
|
+ * Initialization leaves the last transmit pool block without a pointer back to
|
|
+ * the head of the list, but marked as the end of the list. That's how we want
|
|
+ * to see it, too, so leave it alone. This is only ever called after a firmware
|
|
+ * reset, so the ACX memory is in the state we want.
|
|
+ */
|
|
+
|
|
+}
|
|
+
|
|
+INLINE_IO int
|
|
+adev_present(acx_device_t *adev)
|
|
+{
|
|
+ /* fast version (accesses the first register, IO_ACX_SOFT_RESET,
|
|
+ * which should be safe): */
|
|
+ return readl(adev->iobase) != 0xffffffff;
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static inline txdesc_t*
|
|
+get_txdesc(acx_device_t *adev, int index)
|
|
+{
|
|
+ return (txdesc_t*) (((u8*)adev->txdesc_start) + index * adev->txdesc_size);
|
|
+}
|
|
+
|
|
+static inline txdesc_t*
|
|
+advance_txdesc(acx_device_t *adev, txdesc_t* txdesc, int inc)
|
|
+{
|
|
+ return (txdesc_t*) (((u8*)txdesc) + inc * adev->txdesc_size);
|
|
+}
|
|
+
|
|
+static txhostdesc_t*
|
|
+get_txhostdesc(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ return &adev->txhostdesc_start[index*2];
|
|
+}
|
|
+
|
|
+static inline client_t*
|
|
+get_txc(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ return adev->txc[index];
|
|
+}
|
|
+
|
|
+static inline u16
|
|
+get_txr(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ index /= adev->txdesc_size;
|
|
+ return adev->txr[index];
|
|
+}
|
|
+
|
|
+static inline void
|
|
+put_txcr(acx_device_t *adev, txdesc_t* txdesc, client_t* c, u16 r111)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return;
|
|
+ }
|
|
+ adev->txc[index] = c;
|
|
+ adev->txr[index] = r111;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** EEPROM and PHY read/write helpers
|
|
+*/
|
|
+/***********************************************************************
|
|
+** acxmem_read_eeprom_byte
|
|
+**
|
|
+** Function called to read an octet in the EEPROM.
|
|
+**
|
|
+** This function is used by acxmem_e_probe to check if the
|
|
+** connected card is a legal one or not.
|
|
+**
|
|
+** Arguments:
|
|
+** adev ptr to acx_device structure
|
|
+** addr address to read in the EEPROM
|
|
+** charbuf ptr to a char. This is where the read octet
|
|
+** will be stored
|
|
+*/
|
|
+int
|
|
+acxmem_read_eeprom_byte(acx_device_t *adev, u32 addr, u8 *charbuf)
|
|
+{
|
|
+ int result;
|
|
+ int count;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for EEPROM read\n",
|
|
+ adev->ndev->name);
|
|
+ result = NOT_OK;
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ *charbuf = read_reg8(adev, IO_ACX_EEPROM_DATA);
|
|
+ log(L_DEBUG, "EEPROM at 0x%04X = 0x%02X\n", addr, *charbuf);
|
|
+ result = OK;
|
|
+
|
|
+fail:
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** We don't lock hw accesses here since we never r/w eeprom in IRQ
|
|
+** Note: this function sleeps only because of GFP_KERNEL alloc
|
|
+*/
|
|
+#ifdef UNUSED
|
|
+int
|
|
+acxmem_s_write_eeprom(acx_device_t *adev, u32 addr, u32 len, const u8 *charbuf)
|
|
+{
|
|
+ u8 *data_verify = NULL;
|
|
+ unsigned long flags;
|
|
+ int count, i;
|
|
+ int result = NOT_OK;
|
|
+ u16 gpio_orig;
|
|
+
|
|
+ printk("acx: WARNING! I would write to EEPROM now. "
|
|
+ "Since I really DON'T want to unless you know "
|
|
+ "what you're doing (THIS CODE WILL PROBABLY "
|
|
+ "NOT WORK YET!), I will abort that now. And "
|
|
+ "definitely make sure to make a "
|
|
+ "/proc/driver/acx_wlan0_eeprom backup copy first!!! "
|
|
+ "(the EEPROM content includes the PCI config header!! "
|
|
+ "If you kill important stuff, then you WILL "
|
|
+ "get in trouble and people DID get in trouble already)\n");
|
|
+ return OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ data_verify = kmalloc(len, GFP_KERNEL);
|
|
+ if (!data_verify) {
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* first we need to enable the OE (EEPROM Output Enable) GPIO line
|
|
+ * to be able to write to the EEPROM.
|
|
+ * NOTE: an EEPROM writing success has been reported,
|
|
+ * but you probably have to modify GPIO_OUT, too,
|
|
+ * and you probably need to activate a different GPIO
|
|
+ * line instead! */
|
|
+ gpio_orig = read_reg16(adev, IO_ACX_GPIO_OE);
|
|
+ write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig & ~1);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* ok, now start writing the data out */
|
|
+ for (i = 0; i < len; i++) {
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_DATA, *(charbuf + i));
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 1);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("WARNING, DANGER!!! "
|
|
+ "Timeout waiting for EEPROM write\n");
|
|
+ goto end;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* disable EEPROM writing */
|
|
+ write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* now start a verification run */
|
|
+ for (i = 0; i < len; i++) {
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("timeout waiting for EEPROM read\n");
|
|
+ goto end;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ data_verify[i] = read_reg16(adev, IO_ACX_EEPROM_DATA);
|
|
+ }
|
|
+
|
|
+ if (0 == memcmp(charbuf, data_verify, len))
|
|
+ result = OK; /* read data matches, success */
|
|
+
|
|
+end:
|
|
+ kfree(data_verify);
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+#endif /* UNUSED */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_read_phy_reg
|
|
+**
|
|
+** Messing with rx/tx disabling and enabling here
|
|
+** (write_reg32(adev, IO_ACX_ENABLE, 0b000000xx)) kills traffic
|
|
+*/
|
|
+int
|
|
+acxmem_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+ int count;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_PHY_ADDR, reg);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_PHY_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg32(adev, IO_ACX_PHY_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for phy read\n",
|
|
+ adev->ndev->name);
|
|
+ *charbuf = 0;
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "count was %u\n", count);
|
|
+ *charbuf = read_reg8(adev, IO_ACX_PHY_DATA);
|
|
+
|
|
+ log(L_DEBUG, "radio PHY at 0x%04X = 0x%02X\n", *charbuf, reg);
|
|
+ result = OK;
|
|
+ goto fail; /* silence compiler warning */
|
|
+fail:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxmem_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value)
|
|
+{
|
|
+ int count;
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* mprusko said that 32bit accesses result in distorted sensitivity
|
|
+ * on his card. Unconfirmed, looks like it's not true (most likely since we
|
|
+ * now properly flush writes). */
|
|
+ write_reg32(adev, IO_ACX_PHY_DATA, value);
|
|
+ write_reg32(adev, IO_ACX_PHY_ADDR, reg);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_PHY_CTL, 1);
|
|
+ write_flush(adev);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg32(adev, IO_ACX_PHY_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for phy read\n",
|
|
+ adev->ndev->name);
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "radio PHY write 0x%02X at 0x%04X\n", value, reg);
|
|
+ fail:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+#define NO_AUTO_INCREMENT 1
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_write_fw
|
|
+**
|
|
+** Write the firmware image into the card.
|
|
+**
|
|
+** Arguments:
|
|
+** adev wlan device structure
|
|
+** fw_image firmware image.
|
|
+**
|
|
+** Returns:
|
|
+** 1 firmware image corrupted
|
|
+** 0 success
|
|
+*/
|
|
+static int
|
|
+acxmem_s_write_fw(acx_device_t *adev, const firmware_image_t *fw_image, u32 offset)
|
|
+{
|
|
+ int len, size, checkMismatch = -1;
|
|
+ u32 sum, v32, tmp, id;
|
|
+ /* we skip the first four bytes which contain the control sum */
|
|
+ const u8 *p = (u8*)fw_image + 4;
|
|
+
|
|
+ /* start the image checksum by adding the image size value */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+#ifdef NOPE
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */
|
|
+#else
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+#endif
|
|
+ len = 0;
|
|
+ size = le32_to_cpu(fw_image->size) & (~3);
|
|
+
|
|
+ while (likely(len < size)) {
|
|
+ v32 = be32_to_cpu(*(u32*)p);
|
|
+ sum += p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+ len += 4;
|
|
+
|
|
+#ifdef NOPE
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4);
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_DATA, v32);
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+ write_slavemem32 (adev, offset + len - 4, v32);
|
|
+
|
|
+ id = read_id_register (adev);
|
|
+
|
|
+ /*
|
|
+ * check the data written
|
|
+ */
|
|
+ tmp = read_slavemem32 (adev, offset + len - 4);
|
|
+ if (checkMismatch && (tmp != v32)) {
|
|
+ printk ("first data mismatch at 0x%08x good 0x%08x bad 0x%08x id 0x%08x\n",
|
|
+ offset + len - 4, v32, tmp, id);
|
|
+ checkMismatch = 0;
|
|
+ }
|
|
+ }
|
|
+ log(L_DEBUG, "firmware written, size:%d sum1:%x sum2:%x\n",
|
|
+ size, sum, le32_to_cpu(fw_image->chksum));
|
|
+
|
|
+ /* compare our checksum with the stored image checksum */
|
|
+ return (sum != le32_to_cpu(fw_image->chksum));
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_validate_fw
|
|
+**
|
|
+** Compare the firmware image given with
|
|
+** the firmware image written into the card.
|
|
+**
|
|
+** Arguments:
|
|
+** adev wlan device structure
|
|
+** fw_image firmware image.
|
|
+**
|
|
+** Returns:
|
|
+** NOT_OK firmware image corrupted or not correctly written
|
|
+** OK success
|
|
+*/
|
|
+static int
|
|
+acxmem_s_validate_fw(acx_device_t *adev, const firmware_image_t *fw_image,
|
|
+ u32 offset)
|
|
+{
|
|
+ u32 sum, v32, w32;
|
|
+ int len, size;
|
|
+ int result = OK;
|
|
+ /* we skip the first four bytes which contain the control sum */
|
|
+ const u8 *p = (u8*)fw_image + 4;
|
|
+
|
|
+ /* start the image checksum by adding the image size value */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_SLV_END_CTL, 0);
|
|
+
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */
|
|
+#else
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */
|
|
+#endif
|
|
+
|
|
+ len = 0;
|
|
+ size = le32_to_cpu(fw_image->size) & (~3);
|
|
+
|
|
+ while (likely(len < size)) {
|
|
+ v32 = be32_to_cpu(*(u32*)p);
|
|
+ p += 4;
|
|
+ len += 4;
|
|
+
|
|
+#ifdef NOPE
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4);
|
|
+#endif
|
|
+ udelay(10);
|
|
+ w32 = read_reg32(adev, IO_ACX_SLV_MEM_DATA);
|
|
+#endif
|
|
+ w32 = read_slavemem32 (adev, offset + len - 4);
|
|
+
|
|
+ if (unlikely(w32 != v32)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "data parts at offset %d don't match\n(0x%08X vs. 0x%08X)!\n"
|
|
+ "I/O timing issues or defective memory, with DWL-xx0+? "
|
|
+ "ACX_IO_WIDTH=16 may help. Please report\n",
|
|
+ len, v32, w32);
|
|
+ result = NOT_OK;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ sum += (u8)w32 + (u8)(w32>>8) + (u8)(w32>>16) + (u8)(w32>>24);
|
|
+ }
|
|
+
|
|
+ /* sum control verification */
|
|
+ if (result != NOT_OK) {
|
|
+ if (sum != le32_to_cpu(fw_image->chksum)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "checksums don't match!\n");
|
|
+ result = NOT_OK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_upload_fw
|
|
+**
|
|
+** Called from acx_reset_dev
|
|
+*/
|
|
+static int
|
|
+acxmem_s_upload_fw(acx_device_t *adev)
|
|
+{
|
|
+ firmware_image_t *fw_image = NULL;
|
|
+ int res = NOT_OK;
|
|
+ int try;
|
|
+ u32 file_size;
|
|
+ char *filename = "WLANGEN.BIN";
|
|
+#ifdef PATCH_AROUND_BAD_SPOTS
|
|
+ u32 offset;
|
|
+ int i;
|
|
+ /*
|
|
+ * arm-linux-objdump -d patch.bin, or
|
|
+ * od -Ax -t x4 patch.bin after finding the bounds
|
|
+ * of the .text section with arm-linux-objdump -s patch.bin
|
|
+ */
|
|
+ u32 patch[] = {
|
|
+ 0xe584c030, 0xe59fc008,
|
|
+ 0xe92d1000, 0xe59fc004, 0xe8bd8000, 0x0000080c,
|
|
+ 0x0000aa68, 0x605a2200, 0x2c0a689c, 0x2414d80a,
|
|
+ 0x2f00689f, 0x1c27d007, 0x06241e7c, 0x2f000e24,
|
|
+ 0xe000d1f6, 0x602e6018, 0x23036468, 0x480203db,
|
|
+ 0x60ca6003, 0xbdf0750a, 0xffff0808
|
|
+ };
|
|
+#endif
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* No combined image; tell common we need the radio firmware, too */
|
|
+ adev->need_radio_fw = 1;
|
|
+
|
|
+ fw_image = acx_s_read_fw(adev->dev, filename, &file_size);
|
|
+ if (!fw_image) {
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+
|
|
+ for (try = 1; try <= 5; try++) {
|
|
+ res = acxmem_s_write_fw(adev, fw_image, 0);
|
|
+ log(L_DEBUG|L_INIT, "acx_write_fw (main): %d\n", res);
|
|
+ if (OK == res) {
|
|
+ res = acxmem_s_validate_fw(adev, fw_image, 0);
|
|
+ log(L_DEBUG|L_INIT, "acx_validate_fw "
|
|
+ "(main): %d\n", res);
|
|
+ }
|
|
+
|
|
+ if (OK == res) {
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_FW_LOADED);
|
|
+ break;
|
|
+ }
|
|
+ printk("acx: firmware upload attempt #%d FAILED, "
|
|
+ "retrying...\n", try);
|
|
+ acx_s_msleep(1000); /* better wait for a while... */
|
|
+ }
|
|
+
|
|
+#ifdef PATCH_AROUND_BAD_SPOTS
|
|
+ /*
|
|
+ * Only want to do this if the firmware is exactly what we expect for an
|
|
+ * iPaq 4700; otherwise, bad things would ensue.
|
|
+ */
|
|
+ if ((HX4700_FIRMWARE_CHECKSUM == fw_image->chksum) ||
|
|
+ (HX4700_ALTERNATE_FIRMWARE_CHECKSUM == fw_image->chksum)) {
|
|
+ /*
|
|
+ * Put the patch after the main firmware image. 0x950c contains
|
|
+ * the ACX's idea of the end of the firmware. Use that location to
|
|
+ * load ours (which depends on that location being 0xab58) then
|
|
+ * update that location to point to after ours.
|
|
+ */
|
|
+
|
|
+ offset = read_slavemem32 (adev, 0x950c);
|
|
+
|
|
+ log (L_DEBUG, "acx: patching in at 0x%04x\n", offset);
|
|
+
|
|
+ for (i = 0; i < sizeof(patch) / sizeof(patch[0]); i++) {
|
|
+ write_slavemem32 (adev, offset, patch[i]);
|
|
+ offset += sizeof(u32);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Patch the instruction at 0x0804 to branch to our ARM patch at 0xab58
|
|
+ */
|
|
+ write_slavemem32 (adev, 0x0804, 0xea000000 + (0xab58-0x0804-8)/4);
|
|
+
|
|
+ /*
|
|
+ * Patch the instructions at 0x1f40 to branch to our Thumb patch at 0xab74
|
|
+ *
|
|
+ * 4a00 ldr r2, [pc, #0]
|
|
+ * 4710 bx r2
|
|
+ * .data 0xab74+1
|
|
+ */
|
|
+ write_slavemem32 (adev, 0x1f40, 0x47104a00);
|
|
+ write_slavemem32 (adev, 0x1f44, 0x0000ab74+1);
|
|
+
|
|
+ /*
|
|
+ * Bump the end of the firmware up to beyond our patch.
|
|
+ */
|
|
+ write_slavemem32 (adev, 0x950c, offset);
|
|
+
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ vfree(fw_image);
|
|
+
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_upload_radio
|
|
+**
|
|
+** Uploads the appropriate radio module firmware into the card.
|
|
+*/
|
|
+int
|
|
+acxmem_s_upload_radio(acx_device_t *adev)
|
|
+{
|
|
+ acx_ie_memmap_t mm;
|
|
+ firmware_image_t *radio_image;
|
|
+ acx_cmd_radioinit_t radioinit;
|
|
+ int res = NOT_OK;
|
|
+ int try;
|
|
+ u32 offset;
|
|
+ u32 size;
|
|
+ char filename[sizeof("RADIONN.BIN")];
|
|
+
|
|
+ if (!adev->need_radio_fw) return OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP);
|
|
+ offset = le32_to_cpu(mm.CodeEnd);
|
|
+
|
|
+ snprintf(filename, sizeof(filename), "RADIO%02x.BIN",
|
|
+ adev->radio_type);
|
|
+ radio_image = acx_s_read_fw(adev->dev, filename, &size);
|
|
+ if (!radio_image) {
|
|
+ printk("acx: can't load radio module '%s'\n", filename);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_SLEEP, NULL, 0);
|
|
+
|
|
+ for (try = 1; try <= 5; try++) {
|
|
+ res = acxmem_s_write_fw(adev, radio_image, offset);
|
|
+ log(L_DEBUG|L_INIT, "acx_write_fw (radio): %d\n", res);
|
|
+ if (OK == res) {
|
|
+ res = acxmem_s_validate_fw(adev, radio_image, offset);
|
|
+ log(L_DEBUG|L_INIT, "acx_validate_fw (radio): %d\n", res);
|
|
+ }
|
|
+
|
|
+ if (OK == res)
|
|
+ break;
|
|
+ printk("acx: radio firmware upload attempt #%d FAILED, "
|
|
+ "retrying...\n", try);
|
|
+ acx_s_msleep(1000); /* better wait for a while... */
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0);
|
|
+ radioinit.offset = cpu_to_le32(offset);
|
|
+
|
|
+ /* no endian conversion needed, remains in card CPU area: */
|
|
+ radioinit.len = radio_image->size;
|
|
+
|
|
+ vfree(radio_image);
|
|
+
|
|
+ if (OK != res)
|
|
+ goto fail;
|
|
+
|
|
+ /* will take a moment so let's have a big timeout */
|
|
+ acx_s_issue_cmd_timeo(adev, ACX1xx_CMD_RADIOINIT,
|
|
+ &radioinit, sizeof(radioinit), CMD_TIMEOUT_MS(1000));
|
|
+
|
|
+ res = acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP);
|
|
+
|
|
+fail:
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_reset_mac
|
|
+**
|
|
+** MAC will be reset
|
|
+** Call context: reset_dev
|
|
+*/
|
|
+static void
|
|
+acxmem_l_reset_mac(acx_device_t *adev)
|
|
+{
|
|
+ int count;
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* halt eCPU */
|
|
+ set_regbits (adev, IO_ACX_ECPU_CTRL, 0x1);
|
|
+
|
|
+ /* now do soft reset of eCPU, set bit */
|
|
+ set_regbits (adev, IO_ACX_SOFT_RESET, 0x1);
|
|
+ log(L_DEBUG, "%s: enable soft reset...\n", __func__);
|
|
+
|
|
+ /* Windows driver sleeps here for a while with this sequence */
|
|
+ for (count = 0; count < 200; count++) {
|
|
+ udelay (50);
|
|
+ }
|
|
+
|
|
+ /* now clear bit again: deassert eCPU reset */
|
|
+ log(L_DEBUG, "%s: disable soft reset and go to init mode...\n", __func__);
|
|
+ clear_regbits (adev, IO_ACX_SOFT_RESET, 0x1);
|
|
+
|
|
+ /* now start a burst read from initial EEPROM */
|
|
+ set_regbits (adev, IO_ACX_EE_START, 0x1);
|
|
+
|
|
+ /*
|
|
+ * Windows driver sleeps here for a while with this sequence
|
|
+ */
|
|
+ for (count = 0; count < 200; count++) {
|
|
+ udelay (50);
|
|
+ }
|
|
+
|
|
+ /* Windows driver writes 0x10000 to register 0x808 here */
|
|
+
|
|
+ write_reg32 (adev, 0x808, 0x10000);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_verify_init
|
|
+*/
|
|
+static int
|
|
+acxmem_s_verify_init(acx_device_t *adev)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+ unsigned long timeout;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ timeout = jiffies + 2*HZ;
|
|
+ for (;;) {
|
|
+ u32 irqstat = read_reg32(adev, IO_ACX_IRQ_STATUS_NON_DES);
|
|
+ if ((irqstat != 0xFFFFFFFF) && (irqstat & HOST_INT_FCS_THRESHOLD)) {
|
|
+ result = OK;
|
|
+ write_reg32(adev, IO_ACX_IRQ_ACK, HOST_INT_FCS_THRESHOLD);
|
|
+ break;
|
|
+ }
|
|
+ if (time_after(jiffies, timeout))
|
|
+ break;
|
|
+ /* Init may take up to ~0.5 sec total */
|
|
+ acx_s_msleep(50);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** A few low-level helpers
|
|
+**
|
|
+** Note: these functions are not protected by lock
|
|
+** and thus are never allowed to be called from IRQ.
|
|
+** Also they must not race with fw upload which uses same hw regs
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_write_cmd_type_status
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+acxmem_write_cmd_type_status(acx_device_t *adev, u16 type, u16 status)
|
|
+{
|
|
+ write_slavemem32 (adev, (u32) adev->cmd_area, type | (status << 16));
|
|
+ write_flush(adev);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_read_cmd_type_status
|
|
+*/
|
|
+static u32
|
|
+acxmem_read_cmd_type_status(acx_device_t *adev)
|
|
+{
|
|
+ u32 cmd_type, cmd_status;
|
|
+
|
|
+ cmd_type = read_slavemem32 (adev, (u32) adev->cmd_area);
|
|
+
|
|
+ cmd_status = (cmd_type >> 16);
|
|
+ cmd_type = (u16)cmd_type;
|
|
+
|
|
+ log(L_CTL, "cmd_type:%04X cmd_status:%04X [%s]\n",
|
|
+ cmd_type, cmd_status,
|
|
+ acx_cmd_status_str(cmd_status));
|
|
+
|
|
+ return cmd_status;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_reset_dev
|
|
+**
|
|
+** Arguments:
|
|
+** netdevice that contains the adev variable
|
|
+** Returns:
|
|
+** NOT_OK on fail
|
|
+** OK on success
|
|
+** Side effects:
|
|
+** device is hard reset
|
|
+** Call context:
|
|
+** acxmem_e_probe
|
|
+** Comment:
|
|
+** This resets the device using low level hardware calls
|
|
+** as well as uploads and verifies the firmware to the card
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+init_mboxes(acx_device_t *adev)
|
|
+{
|
|
+ u32 cmd_offs, info_offs;
|
|
+
|
|
+ cmd_offs = read_reg32(adev, IO_ACX_CMD_MAILBOX_OFFS);
|
|
+ info_offs = read_reg32(adev, IO_ACX_INFO_MAILBOX_OFFS);
|
|
+ adev->cmd_area = (u8*) cmd_offs;
|
|
+ adev->info_area = (u8*) info_offs;
|
|
+ /*
|
|
+ log(L_DEBUG, "iobase2=%p\n"
|
|
+ */
|
|
+ log( L_DEBUG, "cmd_mbox_offset=%X cmd_area=%p\n"
|
|
+ "info_mbox_offset=%X info_area=%p\n",
|
|
+ cmd_offs, adev->cmd_area,
|
|
+ info_offs, adev->info_area);
|
|
+}
|
|
+
|
|
+
|
|
+static inline void
|
|
+read_eeprom_area(acx_device_t *adev)
|
|
+{
|
|
+#if ACX_DEBUG > 1
|
|
+ int offs;
|
|
+ u8 tmp;
|
|
+
|
|
+ for (offs = 0x8c; offs < 0xb9; offs++)
|
|
+ acxmem_read_eeprom_byte(adev, offs, &tmp);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static int
|
|
+acxmem_s_reset_dev(acx_device_t *adev)
|
|
+{
|
|
+ const char* msg = "";
|
|
+ unsigned long flags;
|
|
+ int result = NOT_OK;
|
|
+ u16 hardware_info;
|
|
+ u16 ecpu_ctrl;
|
|
+ int count;
|
|
+ u32 tmp;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /*
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_CP, 0);
|
|
+ */
|
|
+ /* reset the device to make sure the eCPU is stopped
|
|
+ * to upload the firmware correctly */
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* Windows driver does some funny things here */
|
|
+ /*
|
|
+ * clear bit 0x200 in register 0x2A0
|
|
+ */
|
|
+ clear_regbits (adev, 0x2A0, 0x200);
|
|
+
|
|
+ /*
|
|
+ * Set bit 0x200 in ACX_GPIO_OUT
|
|
+ */
|
|
+ set_regbits (adev, IO_ACX_GPIO_OUT, 0x200);
|
|
+
|
|
+ /*
|
|
+ * read register 0x900 until its value is 0x8400104C, sleeping
|
|
+ * in between reads if it's not immediate
|
|
+ */
|
|
+ tmp = read_reg32 (adev, REG_ACX_VENDOR_ID);
|
|
+ count = 500;
|
|
+ while (count-- && (tmp != ACX_VENDOR_ID)) {
|
|
+ mdelay (10);
|
|
+ tmp = read_reg32 (adev, REG_ACX_VENDOR_ID);
|
|
+ }
|
|
+
|
|
+ /* end what Windows driver does */
|
|
+
|
|
+ acxmem_l_reset_mac(adev);
|
|
+
|
|
+ ecpu_ctrl = read_reg32(adev, IO_ACX_ECPU_CTRL) & 1;
|
|
+ if (!ecpu_ctrl) {
|
|
+ msg = "eCPU is already running. ";
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+#ifdef WE_DONT_NEED_THAT_DO_WE
|
|
+ if (read_reg16(adev, IO_ACX_SOR_CFG) & 2) {
|
|
+ /* eCPU most likely means "embedded CPU" */
|
|
+ msg = "eCPU did not start after boot from flash. ";
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ /* check sense on reset flags */
|
|
+ if (read_reg16(adev, IO_ACX_SOR_CFG) & 0x10) {
|
|
+ printk("%s: eCPU did not start after boot (SOR), "
|
|
+ "is this fatal?\n", adev->ndev->name);
|
|
+ }
|
|
+#endif
|
|
+ /* scan, if any, is stopped now, setting corresponding IRQ bit */
|
|
+ adev->irq_status |= HOST_INT_SCAN_COMPLETE;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* need to know radio type before fw load */
|
|
+ /* Need to wait for arrival of this information in a loop,
|
|
+ * most probably since eCPU runs some init code from EEPROM
|
|
+ * (started burst read in reset_mac()) which also
|
|
+ * sets the radio type ID */
|
|
+
|
|
+ count = 0xffff;
|
|
+ do {
|
|
+ hardware_info = read_reg16(adev, IO_ACX_EEPROM_INFORMATION);
|
|
+ if (!--count) {
|
|
+ msg = "eCPU didn't indicate radio type";
|
|
+ goto end_fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ } while (!(hardware_info & 0xff00)); /* radio type still zero? */
|
|
+ printk("ACX radio type 0x%02x\n", (hardware_info >> 8) & 0xff);
|
|
+ /* printk("DEBUG: count %d\n", count); */
|
|
+ adev->form_factor = hardware_info & 0xff;
|
|
+ adev->radio_type = hardware_info >> 8;
|
|
+
|
|
+ /* load the firmware */
|
|
+ if (OK != acxmem_s_upload_fw(adev))
|
|
+ goto end_fail;
|
|
+
|
|
+ /* acx_s_msleep(10); this one really shouldn't be required */
|
|
+
|
|
+ /* now start eCPU by clearing bit */
|
|
+ clear_regbits (adev, IO_ACX_ECPU_CTRL, 0x1);
|
|
+ log(L_DEBUG, "booted eCPU up and waiting for completion...\n");
|
|
+
|
|
+ /* Windows driver clears bit 0x200 in register 0x2A0 here */
|
|
+ clear_regbits (adev, 0x2A0, 0x200);
|
|
+
|
|
+ /* Windows driver sets bit 0x200 in ACX_GPIO_OUT here */
|
|
+ set_regbits (adev, IO_ACX_GPIO_OUT, 0x200);
|
|
+ /* wait for eCPU bootup */
|
|
+ if (OK != acxmem_s_verify_init(adev)) {
|
|
+ msg = "timeout waiting for eCPU. ";
|
|
+ goto end_fail;
|
|
+ }
|
|
+ log(L_DEBUG, "eCPU has woken up, card is ready to be configured\n");
|
|
+ init_mboxes(adev);
|
|
+ acxmem_write_cmd_type_status(adev, ACX1xx_CMD_RESET, 0);
|
|
+
|
|
+ /* test that EEPROM is readable */
|
|
+ read_eeprom_area(adev);
|
|
+
|
|
+ result = OK;
|
|
+ goto end;
|
|
+
|
|
+/* Finish error message. Indicate which function failed */
|
|
+end_unlock:
|
|
+ acx_unlock(adev, flags);
|
|
+end_fail:
|
|
+ printk("acx: %sreset_dev() FAILED\n", msg);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_issue_cmd_timeo
|
|
+**
|
|
+** Sends command to fw, extract result
|
|
+**
|
|
+** NB: we do _not_ take lock inside, so be sure to not touch anything
|
|
+** which may interfere with IRQ handler operation
|
|
+**
|
|
+** TODO: busy wait is a bit silly, so:
|
|
+** 1) stop doing many iters - go to sleep after first
|
|
+** 2) go to waitqueue based approach: wait, not poll!
|
|
+*/
|
|
+#undef FUNC
|
|
+#define FUNC "issue_cmd"
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+int
|
|
+acxmem_s_issue_cmd_timeo(
|
|
+ acx_device_t *adev,
|
|
+ unsigned int cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned cmd_timeout)
|
|
+{
|
|
+#else
|
|
+int
|
|
+acxmem_s_issue_cmd_timeo_debug(
|
|
+ acx_device_t *adev,
|
|
+ unsigned cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned cmd_timeout,
|
|
+ const char* cmdstr)
|
|
+{
|
|
+ unsigned long start = jiffies;
|
|
+#endif
|
|
+ const char *devname;
|
|
+ unsigned counter;
|
|
+ u16 irqtype;
|
|
+ int i, j;
|
|
+ u8 *p;
|
|
+ u16 cmd_status;
|
|
+ unsigned long timeout;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ devname = adev->ndev->name;
|
|
+ if (!devname || !devname[0] || devname[4]=='%')
|
|
+ devname = "acx";
|
|
+
|
|
+ log(L_CTL, FUNC"(cmd:%s,buflen:%u,timeout:%ums,type:0x%04X)\n",
|
|
+ cmdstr, buflen, cmd_timeout,
|
|
+ buffer ? le16_to_cpu(((acx_ie_generic_t *)buffer)->type) : -1);
|
|
+
|
|
+ if (!(adev->dev_state_mask & ACX_STATE_FW_LOADED)) {
|
|
+ printk("%s: "FUNC"(): firmware is not loaded yet, "
|
|
+ "cannot execute commands!\n", devname);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ if ((acx_debug & L_DEBUG) && (cmd != ACX1xx_CMD_INTERROGATE)) {
|
|
+ printk("input buffer (len=%u):\n", buflen);
|
|
+ acx_dump_bytes(buffer, buflen);
|
|
+ }
|
|
+
|
|
+ /* wait for firmware to become idle for our command submission */
|
|
+ timeout = HZ/5;
|
|
+ counter = (timeout * 1000 / HZ) - 1; /* in ms */
|
|
+ timeout += jiffies;
|
|
+ do {
|
|
+ cmd_status = acxmem_read_cmd_type_status(adev);
|
|
+ /* Test for IDLE state */
|
|
+ if (!cmd_status)
|
|
+ break;
|
|
+ if (counter % 8 == 0) {
|
|
+ if (time_after(jiffies, timeout)) {
|
|
+ counter = 0;
|
|
+ break;
|
|
+ }
|
|
+ /* we waited 8 iterations, no luck. Sleep 8 ms */
|
|
+ acx_s_msleep(8);
|
|
+ }
|
|
+ } while (likely(--counter));
|
|
+
|
|
+ if (!counter) {
|
|
+ /* the card doesn't get idle, we're in trouble */
|
|
+ printk("%s: "FUNC"(): cmd_status is not IDLE: 0x%04X!=0\n",
|
|
+ devname, cmd_status);
|
|
+#if DUMP_IF_SLOW > 0
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ panic ("not idle");
|
|
+#endif
|
|
+ goto bad;
|
|
+ } else if (counter < 190) { /* if waited >10ms... */
|
|
+ log(L_CTL|L_DEBUG, FUNC"(): waited for IDLE %dms. "
|
|
+ "Please report\n", 199 - counter);
|
|
+ }
|
|
+
|
|
+ /* now write the parameters of the command if needed */
|
|
+ if (buffer && buflen) {
|
|
+ /* if it's an INTERROGATE command, just pass the length
|
|
+ * of parameters to read, as data */
|
|
+#if CMD_DISCOVERY
|
|
+ if (cmd == ACX1xx_CMD_INTERROGATE)
|
|
+ memset_io(adev->cmd_area + 4, 0xAA, buflen);
|
|
+#endif
|
|
+ /*
|
|
+ * slave memory version
|
|
+ */
|
|
+ copy_to_slavemem (adev, (u32) (adev->cmd_area + 4), buffer,
|
|
+ (cmd == ACX1xx_CMD_INTERROGATE) ? 4 : buflen);
|
|
+ }
|
|
+ /* now write the actual command type */
|
|
+ acxmem_write_cmd_type_status(adev, cmd, 0);
|
|
+
|
|
+ /* clear CMD_COMPLETE bit. can be set only by IRQ handler: */
|
|
+ adev->irq_status &= ~HOST_INT_CMD_COMPLETE;
|
|
+
|
|
+ /* execute command */
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_CMD);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* wait for firmware to process command */
|
|
+
|
|
+ /* Ensure nonzero and not too large timeout.
|
|
+ ** Also converts e.g. 100->99, 200->199
|
|
+ ** which is nice but not essential */
|
|
+ cmd_timeout = (cmd_timeout-1) | 1;
|
|
+ if (unlikely(cmd_timeout > 1199))
|
|
+ cmd_timeout = 1199;
|
|
+
|
|
+ /* we schedule away sometimes (timeout can be large) */
|
|
+ counter = cmd_timeout;
|
|
+ timeout = jiffies + cmd_timeout * HZ / 1000;
|
|
+ do {
|
|
+ if (!adev->irqs_active) { /* IRQ disabled: poll */
|
|
+ irqtype = read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES);
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE) {
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK,
|
|
+ HOST_INT_CMD_COMPLETE);
|
|
+ break;
|
|
+ }
|
|
+ } else { /* Wait when IRQ will set the bit */
|
|
+ irqtype = adev->irq_status;
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (counter % 8 == 0) {
|
|
+ if (time_after(jiffies, timeout)) {
|
|
+ counter = 0;
|
|
+ break;
|
|
+ }
|
|
+ /* we waited 8 iterations, no luck. Sleep 8 ms */
|
|
+ acx_s_msleep(8);
|
|
+ }
|
|
+ } while (likely(--counter));
|
|
+
|
|
+ /* save state for debugging */
|
|
+ cmd_status = acxmem_read_cmd_type_status(adev);
|
|
+
|
|
+ /* put the card in IDLE state */
|
|
+ acxmem_write_cmd_type_status(adev, ACX1xx_CMD_RESET, 0);
|
|
+
|
|
+ if (!counter) { /* timed out! */
|
|
+ printk("%s: "FUNC"(): timed out %s for CMD_COMPLETE. "
|
|
+ "irq bits:0x%04X irq_status:0x%04X timeout:%dms "
|
|
+ "cmd_status:%d (%s)\n",
|
|
+ devname, (adev->irqs_active) ? "waiting" : "polling",
|
|
+ irqtype, adev->irq_status, cmd_timeout,
|
|
+ cmd_status, acx_cmd_status_str(cmd_status));
|
|
+ printk("%s: "FUNC"(): device irq status 0x%04x\n",
|
|
+ devname, read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES));
|
|
+ printk("%s: "FUNC"(): IO_ACX_IRQ_MASK 0x%04x IO_ACX_FEMR 0x%04x\n",
|
|
+ devname,
|
|
+ read_reg16 (adev, IO_ACX_IRQ_MASK),
|
|
+ read_reg16 (adev, IO_ACX_FEMR));
|
|
+ if (read_reg16 (adev, IO_ACX_IRQ_MASK) == 0xffff) {
|
|
+ printk ("acxmem: firmware probably hosed - reloading\n");
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+ {
|
|
+ pm_message_t state;
|
|
+ /* acxmem_e_suspend (resume_pdev, state); */
|
|
+ acxmem_e_suspend (adev->ndev , state);
|
|
+ }
|
|
+#else
|
|
+ acxmem_e_suspend (adev, 0);
|
|
+#endif
|
|
+ {
|
|
+ resume_ndev = adev->ndev;
|
|
+ fw_resumer (NULL);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ goto bad;
|
|
+ } else if (cmd_timeout - counter > 30) { /* if waited >30ms... */
|
|
+ log(L_CTL|L_DEBUG, FUNC"(): %s for CMD_COMPLETE %dms. "
|
|
+ "count:%d. Please report\n",
|
|
+ (adev->irqs_active) ? "waited" : "polled",
|
|
+ cmd_timeout - counter, counter);
|
|
+ }
|
|
+
|
|
+ if (1 != cmd_status) { /* it is not a 'Success' */
|
|
+ printk("%s: "FUNC"(): cmd_status is not SUCCESS: %d (%s). "
|
|
+ "Took %dms of %d\n",
|
|
+ devname, cmd_status, acx_cmd_status_str(cmd_status),
|
|
+ cmd_timeout - counter, cmd_timeout);
|
|
+ /* zero out result buffer
|
|
+ * WARNING: this will trash stack in case of illegally large input
|
|
+ * length! */
|
|
+ if (buflen > 388) {
|
|
+ /*
|
|
+ * 388 is maximum command length
|
|
+ */
|
|
+ printk ("invalid length 0x%08x\n", buflen);
|
|
+ buflen = 388;
|
|
+ }
|
|
+ p = (u8 *) buffer;
|
|
+ for (i = 0; i < buflen; i+= 16) {
|
|
+ printk ("%04x:", i);
|
|
+ for (j = 0; (j < 16) && (i+j < buflen); j++) {
|
|
+ printk (" %02x", *p++);
|
|
+ }
|
|
+ printk ("\n");
|
|
+ }
|
|
+ if (buffer && buflen)
|
|
+ memset(buffer, 0, buflen);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* read in result parameters if needed */
|
|
+ if (buffer && buflen && (cmd == ACX1xx_CMD_INTERROGATE)) {
|
|
+ copy_from_slavemem (adev, buffer, (u32) (adev->cmd_area + 4), buflen);
|
|
+ if (acx_debug & L_DEBUG) {
|
|
+ printk("output buffer (len=%u): ", buflen);
|
|
+ acx_dump_bytes(buffer, buflen);
|
|
+ }
|
|
+ }
|
|
+
|
|
+/* ok: */
|
|
+ log(L_CTL, FUNC"(%s): took %ld jiffies to complete\n",
|
|
+ cmdstr, jiffies - start);
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+
|
|
+bad:
|
|
+ /* Give enough info so that callers can avoid
|
|
+ ** printing their own diagnostic messages */
|
|
+#if ACX_DEBUG
|
|
+ printk("%s: "FUNC"(cmd:%s) FAILED\n", devname, cmdstr);
|
|
+#else
|
|
+ printk("%s: "FUNC"(cmd:0x%04X) FAILED\n", devname, cmd);
|
|
+#endif
|
|
+ dump_stack();
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#if defined(NONESSENTIAL_FEATURES)
|
|
+typedef struct device_id {
|
|
+ unsigned char id[6];
|
|
+ char *descr;
|
|
+ char *type;
|
|
+} device_id_t;
|
|
+
|
|
+static const device_id_t
|
|
+device_ids[] =
|
|
+{
|
|
+ {
|
|
+ {'G', 'l', 'o', 'b', 'a', 'l'},
|
|
+ NULL,
|
|
+ NULL,
|
|
+ },
|
|
+ {
|
|
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
+ "uninitialized",
|
|
+ "SpeedStream SS1021 or Gigafast WF721-AEX"
|
|
+ },
|
|
+ {
|
|
+ {0x80, 0x81, 0x82, 0x83, 0x84, 0x85},
|
|
+ "non-standard",
|
|
+ "DrayTek Vigor 520"
|
|
+ },
|
|
+ {
|
|
+ {'?', '?', '?', '?', '?', '?'},
|
|
+ "non-standard",
|
|
+ "Level One WPC-0200"
|
|
+ },
|
|
+ {
|
|
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
+ "empty",
|
|
+ "DWL-650+ variant"
|
|
+ }
|
|
+};
|
|
+
|
|
+static void
|
|
+acx_show_card_eeprom_id(acx_device_t *adev)
|
|
+{
|
|
+ unsigned char buffer[CARD_EEPROM_ID_SIZE];
|
|
+ int i;
|
|
+
|
|
+ memset(&buffer, 0, CARD_EEPROM_ID_SIZE);
|
|
+ /* use direct EEPROM access */
|
|
+ for (i = 0; i < CARD_EEPROM_ID_SIZE; i++) {
|
|
+ if (OK != acxmem_read_eeprom_byte(adev,
|
|
+ ACX100_EEPROM_ID_OFFSET + i,
|
|
+ &buffer[i])) {
|
|
+ printk("acx: reading EEPROM FAILED\n");
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(device_ids); i++) {
|
|
+ if (!memcmp(&buffer, device_ids[i].id, CARD_EEPROM_ID_SIZE)) {
|
|
+ if (device_ids[i].descr) {
|
|
+ printk("acx: EEPROM card ID string check "
|
|
+ "found %s card ID: is this %s?\n",
|
|
+ device_ids[i].descr, device_ids[i].type);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (i == VEC_SIZE(device_ids)) {
|
|
+ printk("acx: EEPROM card ID string check found "
|
|
+ "unknown card: expected 'Global', got '%.*s\'. "
|
|
+ "Please report\n", CARD_EEPROM_ID_SIZE, buffer);
|
|
+ }
|
|
+}
|
|
+#endif /* NONESSENTIAL_FEATURES */
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_free_desc_queues
|
|
+**
|
|
+** Releases the queues that have been allocated, the
|
|
+** others have been initialised to NULL so this
|
|
+** function can be used if only part of the queues were allocated.
|
|
+*/
|
|
+
|
|
+void
|
|
+acxmem_free_desc_queues(acx_device_t *adev)
|
|
+{
|
|
+#define ACX_FREE_QUEUE(size, ptr, phyaddr) \
|
|
+ if (ptr) { \
|
|
+ kfree(ptr); \
|
|
+ ptr = NULL; \
|
|
+ size = 0; \
|
|
+ }
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ACX_FREE_QUEUE(adev->txhostdesc_area_size, adev->txhostdesc_start, adev->txhostdesc_startphy);
|
|
+ ACX_FREE_QUEUE(adev->txbuf_area_size, adev->txbuf_start, adev->txbuf_startphy);
|
|
+
|
|
+ adev->txdesc_start = NULL;
|
|
+
|
|
+ ACX_FREE_QUEUE(adev->rxhostdesc_area_size, adev->rxhostdesc_start, adev->rxhostdesc_startphy);
|
|
+ ACX_FREE_QUEUE(adev->rxbuf_area_size, adev->rxbuf_start, adev->rxbuf_startphy);
|
|
+
|
|
+ adev->rxdesc_start = NULL;
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_delete_dma_regions
|
|
+*/
|
|
+static void
|
|
+acxmem_s_delete_dma_regions(acx_device_t *adev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* disable radio Tx/Rx. Shouldn't we use the firmware commands
|
|
+ * here instead? Or are we that much down the road that it's no
|
|
+ * longer possible here? */
|
|
+ /*
|
|
+ * slave memory interface really doesn't like this.
|
|
+ */
|
|
+ /*
|
|
+ write_reg16(adev, IO_ACX_ENABLE, 0);
|
|
+ */
|
|
+
|
|
+ acx_s_msleep(100);
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ acxmem_free_desc_queues(adev);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_probe
|
|
+**
|
|
+** Probe routine called when a PCI device w/ matching ID is found.
|
|
+** Here's the sequence:
|
|
+** - Allocate the PCI resources.
|
|
+** - Read the PCMCIA attribute memory to make sure we have a WLAN card
|
|
+** - Reset the MAC
|
|
+** - Initialize the dev and wlan data
|
|
+** - Initialize the MAC
|
|
+**
|
|
+** pdev - ptr to pci device structure containing info about pci configuration
|
|
+** id - ptr to the device id entry that matched this device
|
|
+*/
|
|
+static const u16
|
|
+IO_ACX100[] =
|
|
+{
|
|
+ 0x0000, /* IO_ACX_SOFT_RESET */
|
|
+
|
|
+ 0x0014, /* IO_ACX_SLV_MEM_ADDR */
|
|
+ 0x0018, /* IO_ACX_SLV_MEM_DATA */
|
|
+ 0x001c, /* IO_ACX_SLV_MEM_CTL */
|
|
+ 0x0020, /* IO_ACX_SLV_END_CTL */
|
|
+
|
|
+ 0x0034, /* IO_ACX_FEMR */
|
|
+
|
|
+ 0x007c, /* IO_ACX_INT_TRIG */
|
|
+ 0x0098, /* IO_ACX_IRQ_MASK */
|
|
+ 0x00a4, /* IO_ACX_IRQ_STATUS_NON_DES */
|
|
+ 0x00a8, /* IO_ACX_IRQ_STATUS_CLEAR */
|
|
+ 0x00ac, /* IO_ACX_IRQ_ACK */
|
|
+ 0x00b0, /* IO_ACX_HINT_TRIG */
|
|
+
|
|
+ 0x0104, /* IO_ACX_ENABLE */
|
|
+
|
|
+ 0x0250, /* IO_ACX_EEPROM_CTL */
|
|
+ 0x0254, /* IO_ACX_EEPROM_ADDR */
|
|
+ 0x0258, /* IO_ACX_EEPROM_DATA */
|
|
+ 0x025c, /* IO_ACX_EEPROM_CFG */
|
|
+
|
|
+ 0x0268, /* IO_ACX_PHY_ADDR */
|
|
+ 0x026c, /* IO_ACX_PHY_DATA */
|
|
+ 0x0270, /* IO_ACX_PHY_CTL */
|
|
+
|
|
+ 0x0290, /* IO_ACX_GPIO_OE */
|
|
+
|
|
+ 0x0298, /* IO_ACX_GPIO_OUT */
|
|
+
|
|
+ 0x02a4, /* IO_ACX_CMD_MAILBOX_OFFS */
|
|
+ 0x02a8, /* IO_ACX_INFO_MAILBOX_OFFS */
|
|
+ 0x02ac, /* IO_ACX_EEPROM_INFORMATION */
|
|
+
|
|
+ 0x02d0, /* IO_ACX_EE_START */
|
|
+ 0x02d4, /* IO_ACX_SOR_CFG */
|
|
+ 0x02d8 /* IO_ACX_ECPU_CTRL */
|
|
+};
|
|
+
|
|
+static const u16
|
|
+IO_ACX111[] =
|
|
+{
|
|
+ 0x0000, /* IO_ACX_SOFT_RESET */
|
|
+
|
|
+ 0x0014, /* IO_ACX_SLV_MEM_ADDR */
|
|
+ 0x0018, /* IO_ACX_SLV_MEM_DATA */
|
|
+ 0x001c, /* IO_ACX_SLV_MEM_CTL */
|
|
+ 0x0020, /* IO_ACX_SLV_MEM_CP */
|
|
+
|
|
+ 0x0034, /* IO_ACX_FEMR */
|
|
+
|
|
+ 0x00b4, /* IO_ACX_INT_TRIG */
|
|
+ 0x00d4, /* IO_ACX_IRQ_MASK */
|
|
+ /* we do mean NON_DES (0xf0), not NON_DES_MASK which is at 0xe0: */
|
|
+ 0x00f0, /* IO_ACX_IRQ_STATUS_NON_DES */
|
|
+ 0x00e4, /* IO_ACX_IRQ_STATUS_CLEAR */
|
|
+ 0x00e8, /* IO_ACX_IRQ_ACK */
|
|
+ 0x00ec, /* IO_ACX_HINT_TRIG */
|
|
+
|
|
+ 0x01d0, /* IO_ACX_ENABLE */
|
|
+
|
|
+ 0x0338, /* IO_ACX_EEPROM_CTL */
|
|
+ 0x033c, /* IO_ACX_EEPROM_ADDR */
|
|
+ 0x0340, /* IO_ACX_EEPROM_DATA */
|
|
+ 0x0344, /* IO_ACX_EEPROM_CFG */
|
|
+
|
|
+ 0x0350, /* IO_ACX_PHY_ADDR */
|
|
+ 0x0354, /* IO_ACX_PHY_DATA */
|
|
+ 0x0358, /* IO_ACX_PHY_CTL */
|
|
+
|
|
+ 0x0374, /* IO_ACX_GPIO_OE */
|
|
+
|
|
+ 0x037c, /* IO_ACX_GPIO_OUT */
|
|
+
|
|
+ 0x0388, /* IO_ACX_CMD_MAILBOX_OFFS */
|
|
+ 0x038c, /* IO_ACX_INFO_MAILBOX_OFFS */
|
|
+ 0x0390, /* IO_ACX_EEPROM_INFORMATION */
|
|
+
|
|
+ 0x0100, /* IO_ACX_EE_START */
|
|
+ 0x0104, /* IO_ACX_SOR_CFG */
|
|
+ 0x0108, /* IO_ACX_ECPU_CTRL */
|
|
+};
|
|
+
|
|
+static void
|
|
+dummy_netdev_init(struct net_device *ndev) {}
|
|
+
|
|
+/*
|
|
+ * Most of the acx specific pieces of hardware reset.
|
|
+ */
|
|
+static int
|
|
+acxmem_complete_hw_reset (acx_device_t *adev)
|
|
+{
|
|
+ acx111_ie_configoption_t co;
|
|
+
|
|
+ /* NB: read_reg() reads may return bogus data before reset_dev(),
|
|
+ * since the firmware which directly controls large parts of the I/O
|
|
+ * registers isn't initialized yet.
|
|
+ * acx100 seems to be more affected than acx111 */
|
|
+ if (OK != acxmem_s_reset_dev (adev))
|
|
+ return -1;
|
|
+
|
|
+ if (IS_ACX100(adev)) {
|
|
+ /* ACX100: configopt struct in cmd mailbox - directly after reset */
|
|
+ copy_from_slavemem (adev, (u8*) &co, (u32) adev->cmd_area, sizeof (co));
|
|
+ }
|
|
+
|
|
+ if (OK != acx_s_init_mac(adev))
|
|
+ return -3;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111: configopt struct needs to be queried after full init */
|
|
+ acx_s_interrogate(adev, &co, ACX111_IE_CONFIG_OPTIONS);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Set up transmit buffer administration
|
|
+ */
|
|
+ init_acx_txbuf (adev);
|
|
+
|
|
+ /*
|
|
+ * Windows driver writes 0x01000000 to register 0x288, RADIO_CTL, if the form factor
|
|
+ * is 3. It also write protects the EEPROM by writing 1<<9 to GPIO_OUT
|
|
+ */
|
|
+ if (adev->form_factor == 3) {
|
|
+ set_regbits (adev, 0x288, 0x01000000);
|
|
+ set_regbits (adev, 0x298, 1<<9);
|
|
+ }
|
|
+
|
|
+/* TODO: merge them into one function, they are called just once and are the same for pci & usb */
|
|
+ if (OK != acxmem_read_eeprom_byte(adev, 0x05, &adev->eeprom_version))
|
|
+ return -2;
|
|
+
|
|
+ acx_s_parse_configoption(adev, &co);
|
|
+ acx_s_get_firmware_version(adev); /* needs to be after acx_s_init_mac() */
|
|
+ acx_display_hardware_details(adev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int acx_init_netdev(struct net_device *ndev, struct device *dev, int base_addr, int addr_size, int irq)
|
|
+{
|
|
+ const char *chip_name;
|
|
+ int result = -EIO;
|
|
+ int err;
|
|
+ u8 chip_type;
|
|
+ acx_device_t *adev = NULL;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* FIXME: prism54 calls pci_set_mwi() here,
|
|
+ * should we do/support the same? */
|
|
+
|
|
+ /* chiptype is u8 but id->driver_data is ulong
|
|
+ ** Works for now (possible values are 1 and 2) */
|
|
+ chip_type = CHIPTYPE_ACX100;
|
|
+ /* acx100 and acx111 have different PCI memory regions */
|
|
+ if (chip_type == CHIPTYPE_ACX100) {
|
|
+ chip_name = "ACX100";
|
|
+ } else if (chip_type == CHIPTYPE_ACX111) {
|
|
+ chip_name = "ACX111";
|
|
+ } else {
|
|
+ printk("acx: unknown chip type 0x%04X\n", chip_type);
|
|
+ goto fail_unknown_chiptype;
|
|
+ }
|
|
+
|
|
+ printk("acx: found %s-based wireless network card\n", chip_name);
|
|
+ log(L_ANY, "initial debug setting is 0x%04X\n", acx_debug);
|
|
+
|
|
+
|
|
+ dev_set_drvdata(dev, ndev);
|
|
+
|
|
+ ether_setup(ndev);
|
|
+
|
|
+ ndev->irq = irq;
|
|
+
|
|
+ ndev->base_addr = base_addr;
|
|
+printk (KERN_INFO "memwinbase=%lx memwinsize=%u\n",memwin.Base,memwin.Size);
|
|
+ if (addr_size == 0 || ndev->irq == 0)
|
|
+ goto fail_hw_params;
|
|
+ ndev->open = &acxmem_e_open;
|
|
+ ndev->stop = &acxmem_e_close;
|
|
+ //pdev->dev.release = &acxmem_e_release;
|
|
+ ndev->hard_start_xmit = &acx_i_start_xmit;
|
|
+ ndev->get_stats = &acx_e_get_stats;
|
|
+#if IW_HANDLER_VERSION <= 5
|
|
+ ndev->get_wireless_stats = &acx_e_get_wireless_stats;
|
|
+#endif
|
|
+ ndev->wireless_handlers = (struct iw_handler_def *)&acx_ioctl_handler_def;
|
|
+ ndev->set_multicast_list = &acxmem_i_set_multicast_list;
|
|
+ ndev->tx_timeout = &acxmem_i_tx_timeout;
|
|
+ ndev->change_mtu = &acx_e_change_mtu;
|
|
+ ndev->watchdog_timeo = 4 * HZ;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ spin_lock_init(&adev->lock); /* initial state: unlocked */
|
|
+ spin_lock_init(&adev->txbuf_lock);
|
|
+ /* We do not start with downed sem: we want PARANOID_LOCKING to work */
|
|
+ sema_init(&adev->sem, 1); /* initial state: 1 (upped) */
|
|
+ /* since nobody can see new netdev yet, we can as well
|
|
+ ** just _presume_ that we're under sem (instead of actually taking it): */
|
|
+ /* acx_sem_lock(adev); */
|
|
+ adev->dev = dev;
|
|
+ adev->ndev = ndev;
|
|
+ adev->dev_type = DEVTYPE_MEM;
|
|
+ adev->chip_type = chip_type;
|
|
+ adev->chip_name = chip_name;
|
|
+ adev->io = (CHIPTYPE_ACX100 == chip_type) ? IO_ACX100 : IO_ACX111;
|
|
+ adev->membase = (volatile u32 *) ndev->base_addr;
|
|
+ adev->iobase = (volatile u32 *) ioremap_nocache (ndev->base_addr, addr_size);
|
|
+ /* to find crashes due to weird driver access
|
|
+ * to unconfigured interface (ifup) */
|
|
+ adev->mgmt_timer.function = (void (*)(unsigned long))0x0000dead;
|
|
+
|
|
+#if defined(NONESSENTIAL_FEATURES)
|
|
+ acx_show_card_eeprom_id(adev);
|
|
+#endif /* NONESSENTIAL_FEATURES */
|
|
+
|
|
+#ifdef SET_MODULE_OWNER
|
|
+ SET_MODULE_OWNER(ndev);
|
|
+#endif
|
|
+ // need to fix that @@
|
|
+ SET_NETDEV_DEV(ndev, dev);
|
|
+
|
|
+ log(L_IRQ|L_INIT, "using IRQ %d\n", ndev->irq);
|
|
+
|
|
+ /* ok, pci setup is finished, now start initializing the card */
|
|
+
|
|
+ if (OK != acxmem_complete_hw_reset (adev))
|
|
+ goto fail_reset;
|
|
+
|
|
+ /*
|
|
+ * Set up default things for most of the card settings.
|
|
+ */
|
|
+ acx_s_set_defaults(adev);
|
|
+
|
|
+ /* Register the card, AFTER everything else has been set up,
|
|
+ * since otherwise an ioctl could step on our feet due to
|
|
+ * firmware operations happening in parallel or uninitialized data */
|
|
+ err = register_netdev(ndev);
|
|
+ if (OK != err) {
|
|
+ printk("acx: register_netdev() FAILED: %d\n", err);
|
|
+ goto fail_register_netdev;
|
|
+ }
|
|
+
|
|
+ acx_proc_register_entries(ndev);
|
|
+
|
|
+ /* Now we have our device, so make sure the kernel doesn't try
|
|
+ * to send packets even though we're not associated to a network yet */
|
|
+ acx_stop_queue(ndev, "on probe");
|
|
+ acx_carrier_off(ndev, "on probe");
|
|
+
|
|
+ /*
|
|
+ * Set up a default monitor type so that poor combinations of initialization
|
|
+ * sequences in monitor mode don't end up destroying the hardware type.
|
|
+ */
|
|
+ adev->monitor_type = ARPHRD_ETHER;
|
|
+
|
|
+ /*
|
|
+ * Register to receive inetaddr notifier changes. This will allow us to
|
|
+ * catch if the user changes the MAC address of the interface.
|
|
+ */
|
|
+ register_netdevice_notifier(&acx_netdev_notifier);
|
|
+
|
|
+ /* after register_netdev() userspace may start working with dev
|
|
+ * (in particular, on other CPUs), we only need to up the sem */
|
|
+ /* acx_sem_unlock(adev); */
|
|
+
|
|
+ printk("acx "ACX_RELEASE": net device %s, driver compiled "
|
|
+ "against wireless extensions %d and Linux %s\n",
|
|
+ ndev->name, WIRELESS_EXT, UTS_RELEASE);
|
|
+
|
|
+#if CMD_DISCOVERY
|
|
+ great_inquisitor(adev);
|
|
+#endif
|
|
+
|
|
+ result = OK;
|
|
+ goto done;
|
|
+
|
|
+ /* error paths: undo everything in reverse order... */
|
|
+
|
|
+fail_register_netdev:
|
|
+
|
|
+ acxmem_s_delete_dma_regions(adev);
|
|
+
|
|
+fail_reset:
|
|
+fail_hw_params:
|
|
+ free_netdev(ndev);
|
|
+fail_unknown_chiptype:
|
|
+
|
|
+
|
|
+done:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_remove
|
|
+**
|
|
+** Shut device down (if not hot unplugged)
|
|
+** and deallocate PCI resources for the acx chip.
|
|
+**
|
|
+** pdev - ptr to PCI device structure containing info about pci configuration
|
|
+*/
|
|
+static int __devexit
|
|
+acxmem_e_remove(struct pcmcia_device *link)
|
|
+{
|
|
+ struct net_device *ndev;
|
|
+ acx_device_t *adev;
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ndev = ((local_info_t*)link->priv)->ndev;
|
|
+ if (!ndev) {
|
|
+ log(L_DEBUG, "%s: card is unused. Skipping any release code\n",
|
|
+ __func__);
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+
|
|
+ /* If device wasn't hot unplugged... */
|
|
+ if (adev_present(adev)) {
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* disable both Tx and Rx to shut radio down properly */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0);
|
|
+
|
|
+#ifdef REDUNDANT
|
|
+ /* put the eCPU to sleep to save power
|
|
+ * Halting is not possible currently,
|
|
+ * since not supported by all firmware versions */
|
|
+ acx_s_issue_cmd(adev, ACX100_CMD_SLEEP, NULL, 0);
|
|
+#endif
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* disable power LED to save power :-) */
|
|
+ log(L_INIT, "switching off power LED to save power\n");
|
|
+ acxmem_l_power_led(adev, 0);
|
|
+
|
|
+ /* stop our eCPU */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* FIXME: does this actually keep halting the eCPU?
|
|
+ * I don't think so...
|
|
+ */
|
|
+ acxmem_l_reset_mac(adev);
|
|
+ } else {
|
|
+ u16 temp;
|
|
+
|
|
+ /* halt eCPU */
|
|
+ temp = read_reg16(adev, IO_ACX_ECPU_CTRL) | 0x1;
|
|
+ write_reg16(adev, IO_ACX_ECPU_CTRL, temp);
|
|
+ write_flush(adev);
|
|
+ }
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+ }
|
|
+
|
|
+
|
|
+ /*
|
|
+ * Unregister the notifier chain
|
|
+ */
|
|
+ unregister_netdevice_notifier(&acx_netdev_notifier);
|
|
+
|
|
+ /* unregister the device to not let the kernel
|
|
+ * (e.g. ioctls) access a half-deconfigured device
|
|
+ * NB: this will cause acxmem_e_close() to be called,
|
|
+ * thus we shouldn't call it under sem! */
|
|
+ log(L_INIT, "removing device %s\n", ndev->name);
|
|
+ unregister_netdev(ndev);
|
|
+
|
|
+ /* unregister_netdev ensures that no references to us left.
|
|
+ * For paranoid reasons we continue to follow the rules */
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (adev->dev_state_mask & ACX_STATE_IFACE_UP) {
|
|
+ acxmem_s_down(ndev);
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ }
|
|
+
|
|
+ acx_proc_unregister_entries(ndev);
|
|
+
|
|
+ acxmem_s_delete_dma_regions(adev);
|
|
+
|
|
+ /* finally, clean up PCI bus state */
|
|
+ if (adev->iobase) iounmap((void *)adev->iobase);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ /* Free netdev (quite late,
|
|
+ * since otherwise we might get caught off-guard
|
|
+ * by a netdev timeout handler execution
|
|
+ * expecting to see a working dev...) */
|
|
+ free_netdev(ndev);
|
|
+
|
|
+ printk ("e_remove done\n");
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** TODO: PM code needs to be fixed / debugged / tested.
|
|
+*/
|
|
+#ifdef CONFIG_PM
|
|
+static int
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+acxmem_e_suspend( struct net_device *ndev, pm_message_t state)
|
|
+#else
|
|
+acxmem_e_suspend( struct net_device *ndev, u32 state)
|
|
+#endif
|
|
+{
|
|
+ FN_ENTER;
|
|
+ acx_device_t *adev;
|
|
+ printk("acx: suspend handler is experimental!\n");
|
|
+ printk("sus: dev %p\n", ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ goto end;
|
|
+ // @@ need to get it from link or something like that
|
|
+ adev = ndev2adev(ndev);
|
|
+ printk("sus: adev %p\n", adev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ netif_device_detach(adev->ndev); /* this one cannot sleep */
|
|
+ acxmem_s_down(adev->ndev);
|
|
+ /* down() does not set it to 0xffff, but here we really want that */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ acxmem_s_delete_dma_regions(adev);
|
|
+
|
|
+ /*
|
|
+ * Turn the ACX chip off.
|
|
+ */
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+fw_resumer(struct work_struct *notused)
|
|
+{
|
|
+ acx_device_t *adev;
|
|
+ struct net_device *ndev = resume_ndev;
|
|
+
|
|
+ printk("acx: resume handler is experimental!\n");
|
|
+ printk("rsm: got dev %p\n", ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ return;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ printk("rsm: got adev %p\n", adev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /*
|
|
+ * Turn on the ACX.
|
|
+ */
|
|
+
|
|
+ acxmem_complete_hw_reset (adev);
|
|
+
|
|
+ /*
|
|
+ * done by acx_s_set_defaults for initial startup
|
|
+ */
|
|
+ acxmem_set_interrupt_mask(adev);
|
|
+
|
|
+ printk ("rsm: bringing up interface\n");
|
|
+ SET_BIT (adev->set_mask, GETSET_ALL);
|
|
+ acxmem_s_up(ndev);
|
|
+ printk("rsm: acx up done\n");
|
|
+
|
|
+ /* now even reload all card parameters as they were before suspend,
|
|
+ * and possibly be back in the network again already :-)
|
|
+ */
|
|
+ /* - most settings updated in acxmem_s_up()
|
|
+ if (ACX_STATE_IFACE_UP & adev->dev_state_mask) {
|
|
+ adev->set_mask = GETSET_ALL;
|
|
+ acx_s_update_card_settings(adev);
|
|
+ printk("rsm: settings updated\n");
|
|
+ }
|
|
+ */
|
|
+ netif_device_attach(ndev);
|
|
+ printk("rsm: device attached\n");
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+}
|
|
+
|
|
+DECLARE_WORK( fw_resume_work, fw_resumer );
|
|
+
|
|
+static int
|
|
+acxmem_e_resume(struct pcmcia_device *link)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ //resume_pdev = pdev;
|
|
+ schedule_work( &fw_resume_work );
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_up
|
|
+**
|
|
+** This function is called by acxmem_e_open (when ifconfig sets the device as up)
|
|
+**
|
|
+** Side effects:
|
|
+** - Enables on-card interrupt requests
|
|
+** - calls acx_s_start
|
|
+*/
|
|
+
|
|
+static void
|
|
+enable_acx_irq(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x8000);
|
|
+ adev->irqs_active = 1;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+static void
|
|
+acxmem_s_up(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ enable_acx_irq(adev);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* acx fw < 1.9.3.e has a hardware timer, and older drivers
|
|
+ ** used to use it. But we don't do that anymore, our OS
|
|
+ ** has reliable software timers */
|
|
+ init_timer(&adev->mgmt_timer);
|
|
+ adev->mgmt_timer.function = acx_i_timer;
|
|
+ adev->mgmt_timer.data = (unsigned long)adev;
|
|
+
|
|
+ /* Need to set ACX_STATE_IFACE_UP first, or else
|
|
+ ** timer won't be started by acx_set_status() */
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ /* actual scan cmd will happen in start() */
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING); break;
|
|
+ case ACX_MODE_3_AP:
|
|
+ case ACX_MODE_MONITOR:
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED); break;
|
|
+ }
|
|
+
|
|
+ acx_s_start(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_down
|
|
+**
|
|
+** This disables the netdevice
|
|
+**
|
|
+** Side effects:
|
|
+** - disables on-card interrupt request
|
|
+*/
|
|
+
|
|
+static void
|
|
+disable_acx_irq(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* I guess mask is not 0xffff because acx100 won't signal
|
|
+ ** cmd completion then (needed for ifup).
|
|
+ ** Someone with acx100 please confirm */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask_off);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ adev->irqs_active = 0;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+static void
|
|
+acxmem_s_down(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* Disable IRQs first, so that IRQs cannot race with us */
|
|
+ /* then wait until interrupts have finished executing on other CPUs */
|
|
+ acx_lock(adev, flags);
|
|
+ disable_acx_irq(adev);
|
|
+ synchronize_irq(adev->pdev->irq);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* we really don't want to have an asynchronous tasklet disturb us
|
|
+ ** after something vital for its job has been shut down, so
|
|
+ ** end all remaining work now.
|
|
+ **
|
|
+ ** NB: carrier_off (done by set_status below) would lead to
|
|
+ ** not yet fully understood deadlock in FLUSH_SCHEDULED_WORK().
|
|
+ ** That's why we do FLUSH first.
|
|
+ **
|
|
+ ** NB2: we have a bad locking bug here: FLUSH_SCHEDULED_WORK()
|
|
+ ** waits for acx_e_after_interrupt_task to complete if it is running
|
|
+ ** on another CPU, but acx_e_after_interrupt_task
|
|
+ ** will sleep on sem forever, because it is taken by us!
|
|
+ ** Work around that by temporary sem unlock.
|
|
+ ** This will fail miserably if we'll be hit by concurrent
|
|
+ ** iwconfig or something in between. TODO! */
|
|
+ acx_sem_unlock(adev);
|
|
+ FLUSH_SCHEDULED_WORK();
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* This is possible:
|
|
+ ** FLUSH_SCHEDULED_WORK -> acx_e_after_interrupt_task ->
|
|
+ ** -> set_status(ASSOCIATED) -> wake_queue()
|
|
+ ** That's why we stop queue _after_ FLUSH_SCHEDULED_WORK
|
|
+ ** lock/unlock is just paranoia, maybe not needed */
|
|
+ acx_lock(adev, flags);
|
|
+ acx_stop_queue(ndev, "on ifdown");
|
|
+ acx_set_status(adev, ACX_STATUS_0_STOPPED);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* kernel/timer.c says it's illegal to del_timer_sync()
|
|
+ ** a timer which restarts itself. We guarantee this cannot
|
|
+ ** ever happen because acx_i_timer() never does this if
|
|
+ ** status is ACX_STATUS_0_STOPPED */
|
|
+ del_timer_sync(&adev->mgmt_timer);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_open
|
|
+**
|
|
+** Called as a result of SIOCSIFFLAGS ioctl changing the flags bit IFF_UP
|
|
+** from clear to set. In other words: ifconfig up.
|
|
+**
|
|
+** Returns:
|
|
+** 0 success
|
|
+** >0 f/w reported error
|
|
+** <0 driver reported error
|
|
+*/
|
|
+static int
|
|
+acxmem_e_open(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ acx_init_task_scheduler(adev);
|
|
+
|
|
+/* TODO: pci_set_power_state(pdev, PCI_D0); ? */
|
|
+
|
|
+#if 0
|
|
+ /* request shared IRQ handler */
|
|
+ if (request_irq(ndev->irq, acxmem_i_interrupt, SA_INTERRUPT, ndev->name, ndev)) {
|
|
+ printk("%s: request_irq FAILED\n", ndev->name);
|
|
+ result = -EAGAIN;
|
|
+ goto done;
|
|
+ }
|
|
+ set_irq_type (ndev->irq, IRQT_FALLING);
|
|
+ log(L_DEBUG|L_IRQ, "request_irq %d successful\n", ndev->irq);
|
|
+#endif
|
|
+
|
|
+ /* ifup device */
|
|
+ acxmem_s_up(ndev);
|
|
+
|
|
+ /* We don't currently have to do anything else.
|
|
+ * The setup of the MAC should be subsequently completed via
|
|
+ * the mlme commands.
|
|
+ * Higher layers know we're ready from dev->start==1 and
|
|
+ * dev->tbusy==0. Our rx path knows to pass up received/
|
|
+ * frames because of dev->flags&IFF_UP is true.
|
|
+ */
|
|
+done:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_close
|
|
+**
|
|
+** Called as a result of SIOCSIIFFLAGS ioctl changing the flags bit IFF_UP
|
|
+** from set to clear. I.e. called by "ifconfig DEV down"
|
|
+**
|
|
+** Returns:
|
|
+** 0 success
|
|
+** >0 f/w reported error
|
|
+** <0 driver reported error
|
|
+*/
|
|
+static int
|
|
+acxmem_e_close(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* ifdown device */
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ if (netif_device_present(ndev)) {
|
|
+ acxmem_s_down(ndev);
|
|
+ }
|
|
+
|
|
+ /* disable all IRQs, release shared IRQ handler */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ free_irq(ndev->irq, ndev);
|
|
+
|
|
+/* TODO: pci_set_power_state(pdev, PCI_D3hot); ? */
|
|
+
|
|
+ /* We currently don't have to do anything else.
|
|
+ * Higher layers know we're not ready from dev->start==0 and
|
|
+ * dev->tbusy==1. Our rx path knows to not pass up received
|
|
+ * frames because of dev->flags&IFF_UP is false.
|
|
+ */
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ log(L_INIT, "closed device\n");
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_i_tx_timeout
|
|
+**
|
|
+** Called from network core. Must not sleep!
|
|
+*/
|
|
+static void
|
|
+acxmem_i_tx_timeout(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ unsigned int tx_num_cleaned;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* clean processed tx descs, they may have been completely full */
|
|
+ tx_num_cleaned = acxmem_l_clean_txdesc(adev);
|
|
+
|
|
+ /* nothing cleaned, yet (almost) no free buffers available?
|
|
+ * --> clean all tx descs, no matter which status!!
|
|
+ * Note that I strongly suspect that doing emergency cleaning
|
|
+ * may confuse the firmware. This is a last ditch effort to get
|
|
+ * ANYTHING to work again...
|
|
+ *
|
|
+ * TODO: it's best to simply reset & reinit hw from scratch...
|
|
+ */
|
|
+ if ((adev->tx_free <= TX_EMERG_CLEAN) && (tx_num_cleaned == 0)) {
|
|
+ printk("%s: FAILED to free any of the many full tx buffers. "
|
|
+ "Switching to emergency freeing. "
|
|
+ "Please report!\n", ndev->name);
|
|
+ acxmem_l_clean_txdesc_emergency(adev);
|
|
+ }
|
|
+
|
|
+ if (acx_queue_stopped(ndev) && (ACX_STATUS_4_ASSOCIATED == adev->status))
|
|
+ acx_wake_queue(ndev, "after tx timeout");
|
|
+
|
|
+ /* stall may have happened due to radio drift, so recalib radio */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+
|
|
+ /* do unimportant work last */
|
|
+ printk("%s: tx timeout!\n", ndev->name);
|
|
+ adev->stats.tx_errors++;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_i_set_multicast_list
|
|
+** FIXME: most likely needs refinement
|
|
+*/
|
|
+static void
|
|
+acxmem_i_set_multicast_list(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* firmwares don't have allmulti capability,
|
|
+ * so just use promiscuous mode instead in this case. */
|
|
+ if (ndev->flags & (IFF_PROMISC|IFF_ALLMULTI)) {
|
|
+ SET_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS);
|
|
+ CLEAR_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ /* let kernel know in case *we* needed to set promiscuous */
|
|
+ ndev->flags |= (IFF_PROMISC|IFF_ALLMULTI);
|
|
+ } else {
|
|
+ CLEAR_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS);
|
|
+ SET_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ ndev->flags &= ~(IFF_PROMISC|IFF_ALLMULTI);
|
|
+ }
|
|
+
|
|
+ /* cannot update card settings directly here, atomic context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_UPDATE_CARD_CFG);
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_l_process_rxdesc
|
|
+**
|
|
+** Called directly and only from the IRQ handler
|
|
+*/
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline void log_rxbuffer(const acx_device_t *adev) {}
|
|
+#else
|
|
+static void
|
|
+log_rxbuffer(const acx_device_t *adev)
|
|
+{
|
|
+ register const struct rxhostdesc *rxhostdesc;
|
|
+ int i;
|
|
+ /* no FN_ENTER here, we don't want that */
|
|
+
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+ if (unlikely(!rxhostdesc)) return;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ if ((rxhostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN))
|
|
+ && (rxhostdesc->Status & cpu_to_le32(DESC_STATUS_FULL)))
|
|
+ printk("rx: buf %d full\n", i);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void
|
|
+acxmem_l_process_rxdesc(acx_device_t *adev)
|
|
+{
|
|
+ register rxhostdesc_t *hostdesc;
|
|
+ register rxdesc_t *rxdesc;
|
|
+ unsigned count, tail;
|
|
+ u32 addr;
|
|
+ u8 Ctl_8;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(acx_debug & L_BUFR))
|
|
+ log_rxbuffer(adev);
|
|
+
|
|
+ /* First, have a loop to determine the first descriptor that's
|
|
+ * full, just in case there's a mismatch between our current
|
|
+ * rx_tail and the full descriptor we're supposed to handle. */
|
|
+ tail = adev->rx_tail;
|
|
+ count = RX_CNT;
|
|
+ while (1) {
|
|
+ hostdesc = &adev->rxhostdesc_start[tail];
|
|
+ rxdesc = &adev->rxdesc_start[tail];
|
|
+ /* advance tail regardless of outcome of the below test */
|
|
+ tail = (tail + 1) % RX_CNT;
|
|
+
|
|
+ /*
|
|
+ * Unlike the PCI interface, where the ACX can write directly to
|
|
+ * the host descriptors, on the slave memory interface we have to
|
|
+ * pull these. All we really need to do is check the Ctl_8 field
|
|
+ * in the rx descriptor on the ACX, which should be 0x11000000 if
|
|
+ * we should process it.
|
|
+ */
|
|
+ Ctl_8 = hostdesc->Ctl_16 = read_slavemem8 (adev, (u32) &(rxdesc->Ctl_8));
|
|
+ if ((Ctl_8 & DESC_CTL_HOSTOWN) &&
|
|
+ (Ctl_8 & DESC_CTL_ACXDONE))
|
|
+ break; /* found it! */
|
|
+
|
|
+ if (unlikely(!--count)) /* hmm, no luck: all descs empty, bail out */
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* now process descriptors, starting with the first we figured out */
|
|
+ while (1) {
|
|
+ log(L_BUFR, "rx: tail=%u Ctl_8=%02X\n", tail, Ctl_8);
|
|
+ /*
|
|
+ * If the ACX has CTL_RECLAIM set on this descriptor there
|
|
+ * is no buffer associated; it just wants us to tell it to
|
|
+ * reclaim the memory.
|
|
+ */
|
|
+ if (!(Ctl_8 & DESC_CTL_RECLAIM)) {
|
|
+
|
|
+ /*
|
|
+ * slave interface - pull data now
|
|
+ */
|
|
+ hostdesc->length = read_slavemem16 (adev, (u32) &(rxdesc->total_length));
|
|
+
|
|
+ /*
|
|
+ * hostdesc->data is an rxbuffer_t, which includes header information,
|
|
+ * but the length in the data packet doesn't. The header information
|
|
+ * takes up an additional 12 bytes, so add that to the length we copy.
|
|
+ */
|
|
+ addr = read_slavemem32 (adev, (u32) &(rxdesc->ACXMemPtr));
|
|
+ if (addr) {
|
|
+ /*
|
|
+ * How can &(rxdesc->ACXMemPtr) above ever be zero? Looks like we
|
|
+ * get that now and then - try to trap it for debug.
|
|
+ */
|
|
+ if (addr & 0xffff0000) {
|
|
+ printk("rxdesc 0x%08x\n", (u32) rxdesc);
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ panic ("Bad access!");
|
|
+ }
|
|
+ chaincopy_from_slavemem (adev, (u8 *) hostdesc->data, addr,
|
|
+ hostdesc->length +
|
|
+ (u32) &((rxbuffer_t *)0)->hdr_a3);
|
|
+ acx_l_process_rxbuf(adev, hostdesc->data);
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ printk ("rx reclaim only!\n");
|
|
+ }
|
|
+
|
|
+ hostdesc->Status = 0;
|
|
+
|
|
+ /*
|
|
+ * Let the ACX know we're done.
|
|
+ */
|
|
+ CLEAR_BIT (Ctl_8, DESC_CTL_HOSTOWN);
|
|
+ SET_BIT (Ctl_8, DESC_CTL_HOSTDONE);
|
|
+ SET_BIT (Ctl_8, DESC_CTL_RECLAIM);
|
|
+ write_slavemem8 (adev, (u32) &rxdesc->Ctl_8, Ctl_8);
|
|
+
|
|
+ /*
|
|
+ * Now tell the ACX we've finished with the receive buffer so
|
|
+ * it can finish the reclaim.
|
|
+ */
|
|
+ write_reg16 (adev, IO_ACX_INT_TRIG, INT_TRIG_RXPRC);
|
|
+
|
|
+ /* ok, descriptor is handled, now check the next descriptor */
|
|
+ hostdesc = &adev->rxhostdesc_start[tail];
|
|
+ rxdesc = &adev->rxdesc_start[tail];
|
|
+
|
|
+ Ctl_8 = hostdesc->Ctl_16 = read_slavemem8 (adev, (u32) &(rxdesc->Ctl_8));
|
|
+
|
|
+ /* if next descriptor is empty, then bail out */
|
|
+ if (!(Ctl_8 & DESC_CTL_HOSTOWN) || !(Ctl_8 & DESC_CTL_ACXDONE))
|
|
+ break;
|
|
+
|
|
+ tail = (tail + 1) % RX_CNT;
|
|
+ }
|
|
+end:
|
|
+ adev->rx_tail = tail;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_i_interrupt
|
|
+**
|
|
+** IRQ handler (atomic context, must not sleep, blah, blah)
|
|
+*/
|
|
+
|
|
+/* scan is complete. all frames now on the receive queue are valid */
|
|
+#define INFO_SCAN_COMPLETE 0x0001
|
|
+#define INFO_WEP_KEY_NOT_FOUND 0x0002
|
|
+/* hw has been reset as the result of a watchdog timer timeout */
|
|
+#define INFO_WATCH_DOG_RESET 0x0003
|
|
+/* failed to send out NULL frame from PS mode notification to AP */
|
|
+/* recommended action: try entering 802.11 PS mode again */
|
|
+#define INFO_PS_FAIL 0x0004
|
|
+/* encryption/decryption process on a packet failed */
|
|
+#define INFO_IV_ICV_FAILURE 0x0005
|
|
+
|
|
+/* Info mailbox format:
|
|
+2 bytes: type
|
|
+2 bytes: status
|
|
+more bytes may follow
|
|
+ rumors say about status:
|
|
+ 0x0000 info available (set by hw)
|
|
+ 0x0001 information received (must be set by host)
|
|
+ 0x1000 info available, mailbox overflowed (messages lost) (set by hw)
|
|
+ but in practice we've seen:
|
|
+ 0x9000 when we did not set status to 0x0001 on prev message
|
|
+ 0x1001 when we did set it
|
|
+ 0x0000 was never seen
|
|
+ conclusion: this is really a bitfield:
|
|
+ 0x1000 is 'info available' bit
|
|
+ 'mailbox overflowed' bit is 0x8000, not 0x1000
|
|
+ value of 0x0000 probably means that there are no messages at all
|
|
+ P.S. I dunno how in hell hw is supposed to notice that messages are lost -
|
|
+ it does NOT clear bit 0x0001, and this bit will probably stay forever set
|
|
+ after we set it once. Let's hope this will be fixed in firmware someday
|
|
+*/
|
|
+
|
|
+static void
|
|
+handle_info_irq(acx_device_t *adev)
|
|
+{
|
|
+#if ACX_DEBUG
|
|
+ static const char * const info_type_msg[] = {
|
|
+ "(unknown)",
|
|
+ "scan complete",
|
|
+ "WEP key not found",
|
|
+ "internal watchdog reset was done",
|
|
+ "failed to send powersave (NULL frame) notification to AP",
|
|
+ "encrypt/decrypt on a packet has failed",
|
|
+ "TKIP tx keys disabled",
|
|
+ "TKIP rx keys disabled",
|
|
+ "TKIP rx: key ID not found",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "TKIP IV value exceeds thresh"
|
|
+ };
|
|
+#endif
|
|
+ u32 info_type, info_status;
|
|
+
|
|
+ info_type = read_slavemem32 (adev, (u32) adev->info_area);
|
|
+
|
|
+ info_status = (info_type >> 16);
|
|
+ info_type = (u16)info_type;
|
|
+
|
|
+ /* inform fw that we have read this info message */
|
|
+ write_slavemem32(adev, (u32) adev->info_area, info_type | 0x00010000);
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_INFOACK);
|
|
+ write_flush(adev);
|
|
+
|
|
+ log(L_CTL, "info_type:%04X info_status:%04X\n",
|
|
+ info_type, info_status);
|
|
+
|
|
+ log(L_IRQ, "got Info IRQ: status %04X type %04X: %s\n",
|
|
+ info_status, info_type,
|
|
+ info_type_msg[(info_type >= VEC_SIZE(info_type_msg)) ?
|
|
+ 0 : info_type]
|
|
+ );
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+log_unusual_irq(u16 irqtype) {
|
|
+ /*
|
|
+ if (!printk_ratelimit())
|
|
+ return;
|
|
+ */
|
|
+
|
|
+ printk("acx: got");
|
|
+ if (irqtype & HOST_INT_TX_XFER) {
|
|
+ printk(" Tx_Xfer");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_RX_COMPLETE) {
|
|
+ printk(" Rx_Complete");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_DTIM) {
|
|
+ printk(" DTIM");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_BEACON) {
|
|
+ printk(" Beacon");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_TIMER) {
|
|
+ log(L_IRQ, " Timer");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_KEY_NOT_FOUND) {
|
|
+ printk(" Key_Not_Found");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_IV_ICV_FAILURE) {
|
|
+ printk(" IV_ICV_Failure (crypto)");
|
|
+ }
|
|
+ /* HOST_INT_CMD_COMPLETE */
|
|
+ /* HOST_INT_INFO */
|
|
+ if (irqtype & HOST_INT_OVERFLOW) {
|
|
+ printk(" Overflow");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_PROCESS_ERROR) {
|
|
+ printk(" Process_Error");
|
|
+ }
|
|
+ /* HOST_INT_SCAN_COMPLETE */
|
|
+ if (irqtype & HOST_INT_FCS_THRESHOLD) {
|
|
+ printk(" FCS_Threshold");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_UNKNOWN) {
|
|
+ printk(" Unknown");
|
|
+ }
|
|
+ printk(" IRQ(s)\n");
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+update_link_quality_led(acx_device_t *adev)
|
|
+{
|
|
+ int qual;
|
|
+
|
|
+ qual = acx_signal_determine_quality(adev->wstats.qual.level, adev->wstats.qual.noise);
|
|
+ if (qual > adev->brange_max_quality)
|
|
+ qual = adev->brange_max_quality;
|
|
+
|
|
+ if (time_after(jiffies, adev->brange_time_last_state_change +
|
|
+ (HZ/2 - HZ/2 * (unsigned long)qual / adev->brange_max_quality ) )) {
|
|
+ acxmem_l_power_led(adev, (adev->brange_last_state == 0));
|
|
+ adev->brange_last_state ^= 1; /* toggle */
|
|
+ adev->brange_time_last_state_change = jiffies;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+#define MAX_IRQLOOPS_PER_JIFFY (20000/HZ) /* a la orinoco.c */
|
|
+
|
|
+static irqreturn_t
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+acxmem_i_interrupt(int irq, void *dev_id)
|
|
+#else
|
|
+acxmwm_i_interrupt(int irq, void *dev_id, struct pt_regs *regs)
|
|
+#endif
|
|
+{
|
|
+ acx_device_t *adev;
|
|
+ unsigned long flags;
|
|
+ unsigned int irqcount = MAX_IRQLOOPS_PER_JIFFY;
|
|
+ register u16 irqtype;
|
|
+ u16 unmasked;
|
|
+
|
|
+ adev = ndev2adev((struct net_device*)dev_id);
|
|
+
|
|
+ /* LOCKING: can just spin_lock() since IRQs are disabled anyway.
|
|
+ * I am paranoid */
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR);
|
|
+ if (unlikely(0xffff == unmasked)) {
|
|
+ /* 0xffff value hints at missing hardware,
|
|
+ * so don't do anything.
|
|
+ * Not very clean, but other drivers do the same... */
|
|
+ log(L_IRQ, "IRQ type:FFFF - device removed? IRQ_NONE\n");
|
|
+ goto none;
|
|
+ }
|
|
+
|
|
+ /* We will check only "interesting" IRQ types */
|
|
+ irqtype = unmasked & ~adev->irq_mask;
|
|
+ if (!irqtype) {
|
|
+ /* We are on a shared IRQ line and it wasn't our IRQ */
|
|
+ log(L_IRQ, "IRQ type:%04X, mask:%04X - all are masked, IRQ_NONE\n",
|
|
+ unmasked, adev->irq_mask);
|
|
+ goto none;
|
|
+ }
|
|
+
|
|
+ /* Done here because IRQ_NONEs taking three lines of log
|
|
+ ** drive me crazy */
|
|
+ FN_ENTER;
|
|
+
|
|
+#define IRQ_ITERATE 1
|
|
+#if IRQ_ITERATE
|
|
+if (jiffies != adev->irq_last_jiffies) {
|
|
+ adev->irq_loops_this_jiffy = 0;
|
|
+ adev->irq_last_jiffies = jiffies;
|
|
+}
|
|
+
|
|
+/* safety condition; we'll normally abort loop below
|
|
+ * in case no IRQ type occurred */
|
|
+while (likely(--irqcount)) {
|
|
+#endif
|
|
+ /* ACK all IRQs ASAP */
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK, 0xffff);
|
|
+
|
|
+ log(L_IRQ, "IRQ type:%04X, mask:%04X, type & ~mask:%04X\n",
|
|
+ unmasked, adev->irq_mask, irqtype);
|
|
+
|
|
+ /* Handle most important IRQ types first */
|
|
+ if (irqtype & HOST_INT_RX_DATA) {
|
|
+ log(L_IRQ, "got Rx_Data IRQ\n");
|
|
+ acxmem_l_process_rxdesc(adev);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_TX_COMPLETE) {
|
|
+ log(L_IRQ, "got Tx_Complete IRQ\n");
|
|
+ /* don't clean up on each Tx complete, wait a bit
|
|
+ * unless we're going towards full, in which case
|
|
+ * we do it immediately, too (otherwise we might lockup
|
|
+ * with a full Tx buffer if we go into
|
|
+ * acxmem_l_clean_txdesc() at a time when we won't wakeup
|
|
+ * the net queue in there for some reason...) */
|
|
+ if (adev->tx_free <= TX_START_CLEAN) {
|
|
+#if TX_CLEANUP_IN_SOFTIRQ
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_TX_CLEANUP);
|
|
+#else
|
|
+ acxmem_l_clean_txdesc(adev);
|
|
+#endif
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Less frequent ones */
|
|
+ if (irqtype & (0
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ )) {
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE) {
|
|
+ log(L_IRQ, "got Command_Complete IRQ\n");
|
|
+ /* save the state for the running issue_cmd() */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_CMD_COMPLETE);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_INFO) {
|
|
+ handle_info_irq(adev);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_SCAN_COMPLETE) {
|
|
+ log(L_IRQ, "got Scan_Complete IRQ\n");
|
|
+ /* need to do that in process context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_COMPLETE_SCAN);
|
|
+ /* remember that fw is not scanning anymore */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* These we just log, but either they happen rarely
|
|
+ * or we keep them masked out */
|
|
+ if (irqtype & (0
|
|
+ /* | HOST_INT_RX_DATA */
|
|
+ /* | HOST_INT_TX_COMPLETE */
|
|
+ | HOST_INT_TX_XFER
|
|
+ | HOST_INT_RX_COMPLETE
|
|
+ | HOST_INT_DTIM
|
|
+ | HOST_INT_BEACON
|
|
+ | HOST_INT_TIMER
|
|
+ | HOST_INT_KEY_NOT_FOUND
|
|
+ | HOST_INT_IV_ICV_FAILURE
|
|
+ /* | HOST_INT_CMD_COMPLETE */
|
|
+ /* | HOST_INT_INFO */
|
|
+ | HOST_INT_OVERFLOW
|
|
+ | HOST_INT_PROCESS_ERROR
|
|
+ /* | HOST_INT_SCAN_COMPLETE */
|
|
+ | HOST_INT_FCS_THRESHOLD
|
|
+ | HOST_INT_UNKNOWN
|
|
+ )) {
|
|
+ log_unusual_irq(irqtype);
|
|
+ }
|
|
+
|
|
+#if IRQ_ITERATE
|
|
+ unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR);
|
|
+ irqtype = unmasked & ~adev->irq_mask;
|
|
+ /* Bail out if no new IRQ bits or if all are masked out */
|
|
+ if (!irqtype)
|
|
+ break;
|
|
+
|
|
+ if (unlikely(++adev->irq_loops_this_jiffy > MAX_IRQLOOPS_PER_JIFFY)) {
|
|
+ printk(KERN_ERR "acx: too many interrupts per jiffy!\n");
|
|
+ /* Looks like card floods us with IRQs! Try to stop that */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ /* This will short-circuit all future attempts to handle IRQ.
|
|
+ * We cant do much more... */
|
|
+ adev->irq_mask = 0;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+ /* Routine to perform blink with range */
|
|
+ if (unlikely(adev->led_power == 2))
|
|
+ update_link_quality_led(adev);
|
|
+
|
|
+/* handled: */
|
|
+ /* write_flush(adev); - not needed, last op was read anyway */
|
|
+ acx_unlock(adev, flags);
|
|
+ FN_EXIT0;
|
|
+ return IRQ_HANDLED;
|
|
+
|
|
+none:
|
|
+ acx_unlock(adev, flags);
|
|
+ return IRQ_NONE;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_power_led
|
|
+*/
|
|
+void
|
|
+acxmem_l_power_led(acx_device_t *adev, int enable)
|
|
+{
|
|
+ u16 gpio_pled = IS_ACX111(adev) ? 0x0040 : 0x0800;
|
|
+
|
|
+ /* A hack. Not moving message rate limiting to adev->xxx
|
|
+ * (it's only a debug message after all) */
|
|
+ static int rate_limit = 0;
|
|
+
|
|
+ if (rate_limit++ < 3)
|
|
+ log(L_IOCTL, "Please report in case toggling the power "
|
|
+ "LED doesn't work for your card!\n");
|
|
+ if (enable)
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT,
|
|
+ read_reg16(adev, IO_ACX_GPIO_OUT) & ~gpio_pled);
|
|
+ else
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT,
|
|
+ read_reg16(adev, IO_ACX_GPIO_OUT) | gpio_pled);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Ioctls
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx111pci_ioctl_info(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+#if ACX_DEBUG > 1
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ rxdesc_t *rxdesc;
|
|
+ txdesc_t *txdesc;
|
|
+ rxhostdesc_t *rxhostdesc;
|
|
+ txhostdesc_t *txhostdesc;
|
|
+ struct acx111_ie_memoryconfig memconf;
|
|
+ struct acx111_ie_queueconfig queueconf;
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+ char memmap[0x34];
|
|
+ char rxconfig[0x8];
|
|
+ char fcserror[0x8];
|
|
+ char ratefallback[0x5];
|
|
+
|
|
+ if ( !(acx_debug & (L_IOCTL|L_DEBUG)) )
|
|
+ return OK;
|
|
+ /* using printk() since we checked debug flag already */
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (!IS_ACX111(adev)) {
|
|
+ printk("acx111-specific function called "
|
|
+ "with non-acx111 chip, aborting\n");
|
|
+ goto end_ok;
|
|
+ }
|
|
+
|
|
+ /* get Acx111 Memory Configuration */
|
|
+ memset(&memconf, 0, sizeof(memconf));
|
|
+ /* BTW, fails with 12 (Write only) error code.
|
|
+ ** Retained for easy testing of issue_cmd error handling :) */
|
|
+ printk ("Interrogating queue config\n");
|
|
+ acx_s_interrogate(adev, &memconf, ACX1xx_IE_QUEUE_CONFIG);
|
|
+ printk ("done with queue config\n");
|
|
+
|
|
+ /* get Acx111 Queue Configuration */
|
|
+ memset(&queueconf, 0, sizeof(queueconf));
|
|
+ printk ("Interrogating mem config options\n");
|
|
+ acx_s_interrogate(adev, &queueconf, ACX1xx_IE_MEMORY_CONFIG_OPTIONS);
|
|
+ printk ("done with mem config options\n");
|
|
+
|
|
+ /* get Acx111 Memory Map */
|
|
+ memset(memmap, 0, sizeof(memmap));
|
|
+ printk ("Interrogating mem map\n");
|
|
+ acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP);
|
|
+ printk ("done with mem map\n");
|
|
+
|
|
+ /* get Acx111 Rx Config */
|
|
+ memset(rxconfig, 0, sizeof(rxconfig));
|
|
+ printk ("Interrogating rxconfig\n");
|
|
+ acx_s_interrogate(adev, &rxconfig, ACX1xx_IE_RXCONFIG);
|
|
+ printk ("done with queue rxconfig\n");
|
|
+
|
|
+ /* get Acx111 fcs error count */
|
|
+ memset(fcserror, 0, sizeof(fcserror));
|
|
+ printk ("Interrogating fcs err count\n");
|
|
+ acx_s_interrogate(adev, &fcserror, ACX1xx_IE_FCS_ERROR_COUNT);
|
|
+ printk ("done with err count\n");
|
|
+
|
|
+ /* get Acx111 rate fallback */
|
|
+ memset(ratefallback, 0, sizeof(ratefallback));
|
|
+ printk ("Interrogating rate fallback\n");
|
|
+ acx_s_interrogate(adev, &ratefallback, ACX1xx_IE_RATE_FALLBACK);
|
|
+ printk ("done with rate fallback\n");
|
|
+
|
|
+ /* force occurrence of a beacon interrupt */
|
|
+ /* TODO: comment why is this necessary */
|
|
+ write_reg16(adev, IO_ACX_HINT_TRIG, HOST_INT_BEACON);
|
|
+
|
|
+ /* dump Acx111 Mem Configuration */
|
|
+ printk("dump mem config:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "Number of stations: %1X\n"
|
|
+ "Memory block size: %1X\n"
|
|
+ "tx/rx memory block allocation: %1X\n"
|
|
+ "count rx: %X / tx: %X queues\n"
|
|
+ "options %1X\n"
|
|
+ "fragmentation %1X\n"
|
|
+ "Rx Queue 1 Count Descriptors: %X\n"
|
|
+ "Rx Queue 1 Host Memory Start: %X\n"
|
|
+ "Tx Queue 1 Count Descriptors: %X\n"
|
|
+ "Tx Queue 1 Attributes: %X\n",
|
|
+ memconf.len, (int) sizeof(memconf),
|
|
+ memconf.no_of_stations,
|
|
+ memconf.memory_block_size,
|
|
+ memconf.tx_rx_memory_block_allocation,
|
|
+ memconf.count_rx_queues, memconf.count_tx_queues,
|
|
+ memconf.options,
|
|
+ memconf.fragmentation,
|
|
+ memconf.rx_queue1_count_descs,
|
|
+ acx2cpu(memconf.rx_queue1_host_rx_start),
|
|
+ memconf.tx_queue1_count_descs,
|
|
+ memconf.tx_queue1_attributes);
|
|
+
|
|
+ /* dump Acx111 Queue Configuration */
|
|
+ printk("dump queue head:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "tx_memory_block_address (from card): %X\n"
|
|
+ "rx_memory_block_address (from card): %X\n"
|
|
+ "rx1_queue address (from card): %X\n"
|
|
+ "tx1_queue address (from card): %X\n"
|
|
+ "tx1_queue attributes (from card): %X\n",
|
|
+ queueconf.len, (int) sizeof(queueconf),
|
|
+ queueconf.tx_memory_block_address,
|
|
+ queueconf.rx_memory_block_address,
|
|
+ queueconf.rx1_queue_address,
|
|
+ queueconf.tx1_queue_address,
|
|
+ queueconf.tx1_attributes);
|
|
+
|
|
+ /* dump Acx111 Mem Map */
|
|
+ printk("dump mem map:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "Code start: %X\n"
|
|
+ "Code end: %X\n"
|
|
+ "WEP default key start: %X\n"
|
|
+ "WEP default key end: %X\n"
|
|
+ "STA table start: %X\n"
|
|
+ "STA table end: %X\n"
|
|
+ "Packet template start: %X\n"
|
|
+ "Packet template end: %X\n"
|
|
+ "Queue memory start: %X\n"
|
|
+ "Queue memory end: %X\n"
|
|
+ "Packet memory pool start: %X\n"
|
|
+ "Packet memory pool end: %X\n"
|
|
+ "iobase: %p\n"
|
|
+ "iobase2: %p\n",
|
|
+ *((u16 *)&memmap[0x02]), (int) sizeof(memmap),
|
|
+ *((u32 *)&memmap[0x04]),
|
|
+ *((u32 *)&memmap[0x08]),
|
|
+ *((u32 *)&memmap[0x0C]),
|
|
+ *((u32 *)&memmap[0x10]),
|
|
+ *((u32 *)&memmap[0x14]),
|
|
+ *((u32 *)&memmap[0x18]),
|
|
+ *((u32 *)&memmap[0x1C]),
|
|
+ *((u32 *)&memmap[0x20]),
|
|
+ *((u32 *)&memmap[0x24]),
|
|
+ *((u32 *)&memmap[0x28]),
|
|
+ *((u32 *)&memmap[0x2C]),
|
|
+ *((u32 *)&memmap[0x30]),
|
|
+ adev->iobase,
|
|
+ adev->iobase2);
|
|
+
|
|
+ /* dump Acx111 Rx Config */
|
|
+ printk("dump rx config:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "rx config: %X\n"
|
|
+ "rx filter config: %X\n",
|
|
+ *((u16 *)&rxconfig[0x02]), (int) sizeof(rxconfig),
|
|
+ *((u16 *)&rxconfig[0x04]),
|
|
+ *((u16 *)&rxconfig[0x06]));
|
|
+
|
|
+ /* dump Acx111 fcs error */
|
|
+ printk("dump fcserror:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "fcserrors: %X\n",
|
|
+ *((u16 *)&fcserror[0x02]), (int) sizeof(fcserror),
|
|
+ *((u32 *)&fcserror[0x04]));
|
|
+
|
|
+ /* dump Acx111 rate fallback */
|
|
+ printk("dump rate fallback:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "ratefallback: %X\n",
|
|
+ *((u16 *)&ratefallback[0x02]), (int) sizeof(ratefallback),
|
|
+ *((u8 *)&ratefallback[0x04]));
|
|
+
|
|
+ /* protect against IRQ */
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* dump acx111 internal rx descriptor ring buffer */
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+
|
|
+ /* loop over complete receive pool */
|
|
+ if (rxdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ printk("\ndump internal rxdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "next 0x%X\n"
|
|
+ "acx mem pointer (dynamic) 0x%X\n"
|
|
+ "CTL (dynamic) 0x%X\n"
|
|
+ "Rate (dynamic) 0x%X\n"
|
|
+ "RxStatus (dynamic) 0x%X\n"
|
|
+ "Mod/Pre (dynamic) 0x%X\n",
|
|
+ i,
|
|
+ rxdesc,
|
|
+ acx2cpu(rxdesc->pNextDesc),
|
|
+ acx2cpu(rxdesc->ACXMemPtr),
|
|
+ rxdesc->Ctl_8,
|
|
+ rxdesc->rate,
|
|
+ rxdesc->error,
|
|
+ rxdesc->SNR);
|
|
+ rxdesc++;
|
|
+ }
|
|
+
|
|
+ /* dump host rx descriptor ring buffer */
|
|
+
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+
|
|
+ /* loop over complete receive pool */
|
|
+ if (rxhostdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ printk("\ndump host rxdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "buffer mem pos 0x%X\n"
|
|
+ "buffer mem offset 0x%X\n"
|
|
+ "CTL 0x%X\n"
|
|
+ "Length 0x%X\n"
|
|
+ "next 0x%X\n"
|
|
+ "Status 0x%X\n",
|
|
+ i,
|
|
+ rxhostdesc,
|
|
+ acx2cpu(rxhostdesc->data_phy),
|
|
+ rxhostdesc->data_offset,
|
|
+ le16_to_cpu(rxhostdesc->Ctl_16),
|
|
+ le16_to_cpu(rxhostdesc->length),
|
|
+ acx2cpu(rxhostdesc->desc_phy_next),
|
|
+ rxhostdesc->Status);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+
|
|
+ /* dump acx111 internal tx descriptor ring buffer */
|
|
+ txdesc = adev->txdesc_start;
|
|
+
|
|
+ /* loop over complete transmit pool */
|
|
+ if (txdesc) for (i = 0; i < TX_CNT; i++) {
|
|
+ printk("\ndump internal txdesc %d:\n"
|
|
+ "size 0x%X\n"
|
|
+ "mem pos %p\n"
|
|
+ "next 0x%X\n"
|
|
+ "acx mem pointer (dynamic) 0x%X\n"
|
|
+ "host mem pointer (dynamic) 0x%X\n"
|
|
+ "length (dynamic) 0x%X\n"
|
|
+ "CTL (dynamic) 0x%X\n"
|
|
+ "CTL2 (dynamic) 0x%X\n"
|
|
+ "Status (dynamic) 0x%X\n"
|
|
+ "Rate (dynamic) 0x%X\n",
|
|
+ i,
|
|
+ (int) sizeof(struct txdesc),
|
|
+ txdesc,
|
|
+ acx2cpu(txdesc->pNextDesc),
|
|
+ acx2cpu(txdesc->AcxMemPtr),
|
|
+ acx2cpu(txdesc->HostMemPtr),
|
|
+ le16_to_cpu(txdesc->total_length),
|
|
+ txdesc->Ctl_8,
|
|
+ txdesc->Ctl2_8, txdesc->error,
|
|
+ txdesc->u.r1.rate);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+
|
|
+ /* dump host tx descriptor ring buffer */
|
|
+
|
|
+ txhostdesc = adev->txhostdesc_start;
|
|
+
|
|
+ /* loop over complete host send pool */
|
|
+ if (txhostdesc) for (i = 0; i < TX_CNT * 2; i++) {
|
|
+ printk("\ndump host txdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "buffer mem pos 0x%X\n"
|
|
+ "buffer mem offset 0x%X\n"
|
|
+ "CTL 0x%X\n"
|
|
+ "Length 0x%X\n"
|
|
+ "next 0x%X\n"
|
|
+ "Status 0x%X\n",
|
|
+ i,
|
|
+ txhostdesc,
|
|
+ acx2cpu(txhostdesc->data_phy),
|
|
+ txhostdesc->data_offset,
|
|
+ le16_to_cpu(txhostdesc->Ctl_16),
|
|
+ le16_to_cpu(txhostdesc->length),
|
|
+ acx2cpu(txhostdesc->desc_phy_next),
|
|
+ le32_to_cpu(txhostdesc->Status));
|
|
+ txhostdesc++;
|
|
+ }
|
|
+
|
|
+ /* write_reg16(adev, 0xb4, 0x4); */
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+end_ok:
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+#endif /* ACX_DEBUG */
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx100mem_ioctl_set_phy_amp_bias(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ u16 gpio_old;
|
|
+
|
|
+ if (!IS_ACX100(adev)) {
|
|
+ /* WARNING!!!
|
|
+ * Removing this check *might* damage
|
|
+ * hardware, since we're tweaking GPIOs here after all!!!
|
|
+ * You've been warned...
|
|
+ * WARNING!!! */
|
|
+ printk("acx: sorry, setting bias level for non-acx100 "
|
|
+ "is not supported yet\n");
|
|
+ return OK;
|
|
+ }
|
|
+
|
|
+ if (*extra > 7) {
|
|
+ printk("acx: invalid bias parameter, range is 0-7\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* Need to lock accesses to [IO_ACX_GPIO_OUT]:
|
|
+ * IRQ handler uses it to update LED */
|
|
+ acx_lock(adev, flags);
|
|
+ gpio_old = read_reg16(adev, IO_ACX_GPIO_OUT);
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT, (gpio_old & 0xf8ff) | ((u16)*extra << 8));
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ log(L_DEBUG, "gpio_old: 0x%04X\n", gpio_old);
|
|
+ printk("%s: PHY power amplifier bias: old:%d, new:%d\n",
|
|
+ ndev->name,
|
|
+ (gpio_old & 0x0700) >> 8, (unsigned char)*extra);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_l_alloc_tx
|
|
+** Actually returns a txdesc_t* ptr
|
|
+**
|
|
+** FIXME: in case of fragments, should allocate multiple descrs
|
|
+** after figuring out how many we need and whether we still have
|
|
+** sufficiently many.
|
|
+*/
|
|
+tx_t*
|
|
+acxmem_l_alloc_tx(acx_device_t *adev)
|
|
+{
|
|
+ struct txdesc *txdesc;
|
|
+ unsigned head;
|
|
+ u8 ctl8;
|
|
+ static int txattempts = 0;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(!adev->tx_free)) {
|
|
+ printk("acx: BUG: no free txdesc left\n");
|
|
+ /*
|
|
+ * Probably the ACX ignored a transmit attempt and now there's a packet
|
|
+ * sitting in the queue we think should be transmitting but the ACX doesn't
|
|
+ * know about.
|
|
+ * On the first pass, send the ACX a TxProc interrupt to try moving
|
|
+ * things along, and if that doesn't work (ie, we get called again) completely
|
|
+ * flush the transmit queue.
|
|
+ */
|
|
+ if (txattempts < 10) {
|
|
+ txattempts++;
|
|
+ printk ("acx: trying to wake up ACX\n");
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_TXPRC);
|
|
+ write_flush(adev); }
|
|
+ else {
|
|
+ txattempts = 0;
|
|
+ printk ("acx: flushing transmit queue.\n");
|
|
+ acxmem_l_clean_txdesc_emergency (adev);
|
|
+ }
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Make a quick check to see if there is transmit buffer space on
|
|
+ * the ACX. This can't guarantee there is enough space for the packet
|
|
+ * since we don't yet know how big it is, but it will prevent at least some
|
|
+ * annoyances.
|
|
+ */
|
|
+ if (!adev->acx_txbuf_blocks_free) {
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ head = adev->tx_head;
|
|
+ /*
|
|
+ * txdesc points to ACX memory
|
|
+ */
|
|
+ txdesc = get_txdesc(adev, head);
|
|
+ ctl8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+
|
|
+ /*
|
|
+ * If we don't own the buffer (HOSTOWN) it is certainly not free; however,
|
|
+ * we may have previously thought we had enough memory to send
|
|
+ * a packet, allocated the buffer then gave up when we found not enough
|
|
+ * transmit buffer space on the ACX. In that case, HOSTOWN and
|
|
+ * ACXDONE will both be set.
|
|
+ */
|
|
+ if (unlikely(DESC_CTL_HOSTOWN != (ctl8 & DESC_CTL_HOSTOWN))) {
|
|
+ /* whoops, descr at current index is not free, so probably
|
|
+ * ring buffer already full */
|
|
+ printk("acx: BUG: tx_head:%d Ctl8:0x%02X - failed to find "
|
|
+ "free txdesc\n", head, ctl8);
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* Needed in case txdesc won't be eventually submitted for tx */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), DESC_CTL_ACXDONE_HOSTOWN);
|
|
+
|
|
+ adev->tx_free--;
|
|
+ log(L_BUFT, "tx: got desc %u, %u remain\n",
|
|
+ head, adev->tx_free);
|
|
+ /* Keep a few free descs between head and tail of tx ring.
|
|
+ ** It is not absolutely needed, just feels safer */
|
|
+ if (adev->tx_free < TX_STOP_QUEUE) {
|
|
+ log(L_BUF, "stop queue (%u tx desc left)\n",
|
|
+ adev->tx_free);
|
|
+ acx_stop_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+ /* returning current descriptor, so advance to next free one */
|
|
+ adev->tx_head = (head + 1) % TX_CNT;
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+
|
|
+ return (tx_t*)txdesc;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_l_dealloc_tx
|
|
+** Clears out a previously allocatedvoid acxmem_l_dealloc_tx(tx_t *tx_opaque);
|
|
+ transmit descriptor. The ACX
|
|
+** can get confused if we skip transmit descriptors in the queue,
|
|
+** so when we don't need a descriptor return it to its original
|
|
+** state and move the queue head pointer back.
|
|
+**
|
|
+*/
|
|
+void
|
|
+acxmem_l_dealloc_tx(acx_device_t *adev, tx_t *tx_opaque)
|
|
+{
|
|
+ /*
|
|
+ * txdesc is the address of the descriptor on the ACX.
|
|
+ */
|
|
+ txdesc_t *txdesc = (txdesc_t*)tx_opaque;
|
|
+ txdesc_t tmptxdesc;
|
|
+ int index;
|
|
+
|
|
+ memset (&tmptxdesc, 0, sizeof(tmptxdesc));
|
|
+ tmptxdesc.Ctl_8 = DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG;
|
|
+ tmptxdesc.u.r1.rate = 0x0a;
|
|
+
|
|
+ /*
|
|
+ * Clear out all of the transmit descriptor except for the next pointer
|
|
+ */
|
|
+ copy_to_slavemem (adev, (u32) &(txdesc->HostMemPtr),
|
|
+ (u8 *) &(tmptxdesc.HostMemPtr),
|
|
+ sizeof (tmptxdesc) - sizeof(tmptxdesc.pNextDesc));
|
|
+
|
|
+ /*
|
|
+ * This is only called immediately after we've allocated, so we should
|
|
+ * be able to set the head back to this descriptor.
|
|
+ */
|
|
+ index = ((u8*) txdesc - (u8*)adev->txdesc_start) / adev->txdesc_size;
|
|
+ printk ("acx_dealloc: moving head from %d to %d\n", adev->tx_head, index);
|
|
+ adev->tx_head = index;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void*
|
|
+acxmem_l_get_txbuf(acx_device_t *adev, tx_t* tx_opaque)
|
|
+{
|
|
+ return get_txhostdesc(adev, (txdesc_t*)tx_opaque)->data;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_tx_data
|
|
+**
|
|
+** Can be called from IRQ (rx -> (AP bridging or mgmt response) -> tx).
|
|
+** Can be called from acx_i_start_xmit (data frames from net core).
|
|
+**
|
|
+** FIXME: in case of fragments, should loop over the number of
|
|
+** pre-allocated tx descrs, properly setting up transfer data and
|
|
+** CTL_xxx flags according to fragment number.
|
|
+*/
|
|
+void
|
|
+acxmem_update_queue_indicator (acx_device_t *adev, int txqueue)
|
|
+{
|
|
+#ifdef USING_MORE_THAN_ONE_TRANSMIT_QUEUE
|
|
+ u32 indicator;
|
|
+ unsigned long flags;
|
|
+ int count;
|
|
+
|
|
+ /*
|
|
+ * Can't handle an interrupt while we're fiddling with the ACX's lock,
|
|
+ * according to TI. The ACX is supposed to hold fw_lock for at most
|
|
+ * 500ns.
|
|
+ */
|
|
+ local_irq_save (flags);
|
|
+
|
|
+ /*
|
|
+ * Wait for ACX to release the lock (at most 500ns).
|
|
+ */
|
|
+ count = 0;
|
|
+ while (read_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->fw_lock))
|
|
+ && (count++ < 50)) {
|
|
+ ndelay (10);
|
|
+ }
|
|
+ if (count < 50) {
|
|
+
|
|
+ /*
|
|
+ * Take out the host lock - anything non-zero will work, so don't worry about
|
|
+ * be/le
|
|
+ */
|
|
+ write_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->host_lock), 1);
|
|
+
|
|
+ /*
|
|
+ * Avoid a race condition
|
|
+ */
|
|
+ count = 0;
|
|
+ while (read_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->fw_lock))
|
|
+ && (count++ < 50)) {
|
|
+ ndelay (10);
|
|
+ }
|
|
+
|
|
+ if (count < 50) {
|
|
+ /*
|
|
+ * Mark the queue active
|
|
+ */
|
|
+ indicator = read_slavemem32 (adev, (u32) &(adev->acx_queue_indicator->indicator));
|
|
+ indicator |= cpu_to_le32 (1 << txqueue);
|
|
+ write_slavemem32 (adev, (u32) &(adev->acx_queue_indicator->indicator), indicator);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Release the host lock
|
|
+ */
|
|
+ write_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->host_lock), 0);
|
|
+
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Restore interrupts
|
|
+ */
|
|
+ local_irq_restore (flags);
|
|
+#endif
|
|
+}
|
|
+
|
|
+void
|
|
+acxmem_l_tx_data(acx_device_t *adev, tx_t* tx_opaque, int len)
|
|
+{
|
|
+ /*
|
|
+ * txdesc is the address on the ACX
|
|
+ */
|
|
+ txdesc_t *txdesc = (txdesc_t*)tx_opaque;
|
|
+ txhostdesc_t *hostdesc1, *hostdesc2;
|
|
+ client_t *clt;
|
|
+ u16 rate_cur;
|
|
+ u8 Ctl_8, Ctl2_8;
|
|
+ u32 addr;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* fw doesn't tx such packets anyhow */
|
|
+ if (unlikely(len < WLAN_HDR_A3_LEN))
|
|
+ goto end;
|
|
+
|
|
+ hostdesc1 = get_txhostdesc(adev, txdesc);
|
|
+ /* modify flag status in separate variable to be able to write it back
|
|
+ * in one big swoop later (also in order to have less device memory
|
|
+ * accesses) */
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ Ctl2_8 = 0; /* really need to init it to 0, not txdesc->Ctl2_8, it seems */
|
|
+
|
|
+ hostdesc2 = hostdesc1 + 1;
|
|
+
|
|
+ /* DON'T simply set Ctl field to 0 here globally,
|
|
+ * it needs to maintain a consistent flag status (those are state flags!!),
|
|
+ * otherwise it may lead to severe disruption. Only set or reset particular
|
|
+ * flags at the exact moment this is needed... */
|
|
+
|
|
+ /* let chip do RTS/CTS handshaking before sending
|
|
+ * in case packet size exceeds threshold */
|
|
+ if (len > adev->rts_threshold)
|
|
+ SET_BIT(Ctl2_8, DESC_CTL2_RTS);
|
|
+ else
|
|
+ CLEAR_BIT(Ctl2_8, DESC_CTL2_RTS);
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_3_AP:
|
|
+ clt = acx_l_sta_list_get(adev, ((wlan_hdr_t*)hostdesc1->data)->a1);
|
|
+ break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ clt = adev->ap_client;
|
|
+ break;
|
|
+#if 0
|
|
+/* testing was done on acx111: */
|
|
+ case ACX_MODE_MONITOR:
|
|
+ SET_BIT(Ctl2_8, 0
|
|
+/* sends CTS to self before packet */
|
|
+ + DESC_CTL2_SEQ /* don't increase sequence field */
|
|
+/* not working (looks like good fcs is still added) */
|
|
+ + DESC_CTL2_FCS /* don't add the FCS */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_MORE_FRAG
|
|
+/* not tested */
|
|
+ + DESC_CTL2_RETRY /* don't increase retry field */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_POWER /* don't increase power mgmt. field */
|
|
+/* no effect */
|
|
+ + DESC_CTL2_WEP /* encrypt this frame */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_DUR /* don't increase duration field */
|
|
+ );
|
|
+ /* fallthrough */
|
|
+#endif
|
|
+ default: /* ACX_MODE_OFF, ACX_MODE_MONITOR */
|
|
+ clt = NULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ rate_cur = clt ? clt->rate_cur : adev->rate_bcast;
|
|
+ if (unlikely(!rate_cur)) {
|
|
+ printk("acx: driver bug! bad ratemask\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* used in tx cleanup routine for auto rate and accounting: */
|
|
+ put_txcr(adev, txdesc, clt, rate_cur);
|
|
+
|
|
+ write_slavemem16 (adev, (u32) &(txdesc->total_length), cpu_to_le16(len));
|
|
+ hostdesc2->length = cpu_to_le16(len - WLAN_HDR_A3_LEN);
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* note that if !txdesc->do_auto, txrate->cur
|
|
+ ** has only one nonzero bit */
|
|
+ txdesc->u.r2.rate111 = cpu_to_le16(
|
|
+ rate_cur
|
|
+ /* WARNING: I was never able to make it work with prism54 AP.
|
|
+ ** It was falling down to 1Mbit where shortpre is not applicable,
|
|
+ ** and not working at all at "5,11 basic rates only" setting.
|
|
+ ** I even didn't see tx packets in radio packet capture.
|
|
+ ** Disabled for now --vda */
|
|
+ /*| ((clt->shortpre && clt->cur!=RATE111_1) ? RATE111_SHORTPRE : 0) */
|
|
+ );
|
|
+#ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS
|
|
+ /* should add this to rate111 above as necessary */
|
|
+ | (clt->pbcc511 ? RATE111_PBCC511 : 0)
|
|
+#endif
|
|
+ hostdesc1->length = cpu_to_le16(len);
|
|
+ } else { /* ACX100 */
|
|
+ u8 rate_100 = clt ? clt->rate_100 : adev->rate_bcast100;
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->u.r1.rate), rate_100);
|
|
+#ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS
|
|
+ if (clt->pbcc511) {
|
|
+ if (n == RATE100_5 || n == RATE100_11)
|
|
+ n |= RATE100_PBCC511;
|
|
+ }
|
|
+
|
|
+ if (clt->shortpre && (clt->cur != RATE111_1))
|
|
+ SET_BIT(Ctl_8, DESC_CTL_SHORT_PREAMBLE); /* set Short Preamble */
|
|
+#endif
|
|
+ /* set autodma and reclaim and 1st mpdu */
|
|
+ SET_BIT(Ctl_8, DESC_CTL_FIRSTFRAG);
|
|
+
|
|
+#if ACX_FRAGMENTATION
|
|
+ /* SET_BIT(Ctl2_8, DESC_CTL2_MORE_FRAG); cannot set it unconditionally, needs to be set for all non-last fragments */
|
|
+#endif
|
|
+ hostdesc1->length = cpu_to_le16(WLAN_HDR_A3_LEN);
|
|
+
|
|
+ /*
|
|
+ * Since we're not using autodma copy the packet data to the acx now.
|
|
+ * Even host descriptors point to the packet header, and the odd indexed
|
|
+ * descriptor following points to the packet data.
|
|
+ *
|
|
+ * The first step is to find free memory in the ACX transmit buffers.
|
|
+ * They don't necessarily map one to one with the transmit queue entries,
|
|
+ * so search through them starting just after the last one used.
|
|
+ */
|
|
+ addr = allocate_acx_txbuf_space (adev, len);
|
|
+ if (addr) {
|
|
+ chaincopy_to_slavemem (adev, addr, hostdesc1->data, len);
|
|
+ }
|
|
+ else {
|
|
+ /*
|
|
+ * Bummer. We thought we might have enough room in the transmit
|
|
+ * buffers to send this packet, but it turns out we don't. alloc_tx
|
|
+ * has already marked this transmit descriptor as HOSTOWN and ACXDONE,
|
|
+ * which means the ACX will hang when it gets to this descriptor unless
|
|
+ * we do something about it. Having a bubble in the transmit queue just
|
|
+ * doesn't seem to work, so we have to reset this transmit queue entry's
|
|
+ * state to its original value and back up our head pointer to point
|
|
+ * back to this entry.
|
|
+ */
|
|
+ hostdesc1->length = 0;
|
|
+ hostdesc2->length = 0;
|
|
+ write_slavemem16 (adev, (u32) &(txdesc->total_length), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG);
|
|
+ adev->tx_head = ((u8*) txdesc - (u8*) adev->txdesc_start) / adev->txdesc_size;
|
|
+ goto end;
|
|
+ }
|
|
+ /*
|
|
+ * Tell the ACX where the packet is.
|
|
+ */
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr), addr);
|
|
+
|
|
+ }
|
|
+ /* don't need to clean ack/rts statistics here, already
|
|
+ * done on descr cleanup */
|
|
+
|
|
+ /* clears HOSTOWN and ACXDONE bits, thus telling that the descriptors
|
|
+ * are now owned by the acx100; do this as LAST operation */
|
|
+ CLEAR_BIT(Ctl_8, DESC_CTL_ACXDONE_HOSTOWN);
|
|
+ /* flush writes before we release hostdesc to the adapter here */
|
|
+ //wmb();
|
|
+
|
|
+ /* write back modified flags */
|
|
+ /*
|
|
+ * At this point Ctl_8 should just be FIRSTFRAG
|
|
+ */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl2_8),Ctl2_8);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), Ctl_8);
|
|
+ /* unused: txdesc->tx_time = cpu_to_le32(jiffies); */
|
|
+
|
|
+ /*
|
|
+ * Update the queue indicator to say there's data on the first queue.
|
|
+ */
|
|
+ acxmem_update_queue_indicator (adev, 0);
|
|
+
|
|
+ /* flush writes before we tell the adapter that it's its turn now */
|
|
+ mmiowb();
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_TXPRC);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* log the packet content AFTER sending it,
|
|
+ * in order to not delay sending any further than absolutely needed
|
|
+ * Do separate logs for acx100/111 to have human-readable rates */
|
|
+ if (unlikely(acx_debug & (L_XFER|L_DATA))) {
|
|
+ u16 fc = ((wlan_hdr_t*)hostdesc1->data)->fc;
|
|
+ if (IS_ACX111(adev))
|
|
+ printk("tx: pkt (%s): len %d "
|
|
+ "rate %04X%s status %u\n",
|
|
+ acx_get_packet_type_string(le16_to_cpu(fc)), len,
|
|
+ le16_to_cpu(txdesc->u.r2.rate111),
|
|
+ (le16_to_cpu(txdesc->u.r2.rate111) & RATE111_SHORTPRE) ? "(SPr)" : "",
|
|
+ adev->status);
|
|
+ else
|
|
+ printk("tx: pkt (%s): len %d rate %03u%s status %u\n",
|
|
+ acx_get_packet_type_string(fc), len,
|
|
+ read_slavemem8 (adev, (u32) &(txdesc->u.r1.rate)),
|
|
+ (Ctl_8 & DESC_CTL_SHORT_PREAMBLE) ? "(SPr)" : "",
|
|
+ adev->status);
|
|
+
|
|
+ if (acx_debug & L_DATA) {
|
|
+ printk("tx: 802.11 [%d]: ", len);
|
|
+ acx_dump_bytes(hostdesc1->data, len);
|
|
+ }
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_clean_txdesc
|
|
+**
|
|
+** This function resets the txdescs' status when the ACX100
|
|
+** signals the TX done IRQ (txdescs have been processed), starting with
|
|
+** the pool index of the descriptor which we would use next,
|
|
+** in order to make sure that we can be as fast as possible
|
|
+** in filling new txdescs.
|
|
+** Everytime we get called we know where the next packet to be cleaned is.
|
|
+*/
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline void log_txbuffer(const acx_device_t *adev) {}
|
|
+#else
|
|
+static void
|
|
+log_txbuffer(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+ u8 Ctl_8;
|
|
+
|
|
+ /* no FN_ENTER here, we don't want that */
|
|
+ /* no locks here, since it's entirely non-critical code */
|
|
+ txdesc = adev->txdesc_start;
|
|
+ if (unlikely(!txdesc)) return;
|
|
+ printk("tx: desc->Ctl8's:");
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ printk(" %02X", Ctl_8);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ printk("\n");
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+static void
|
|
+handle_tx_error(acx_device_t *adev, u8 error, unsigned int finger)
|
|
+{
|
|
+ const char *err = "unknown error";
|
|
+
|
|
+ /* hmm, should we handle this as a mask
|
|
+ * of *several* bits?
|
|
+ * For now I think only caring about
|
|
+ * individual bits is ok... */
|
|
+ switch (error) {
|
|
+ case 0x01:
|
|
+ err = "no Tx due to error in other fragment";
|
|
+ adev->wstats.discard.fragment++;
|
|
+ break;
|
|
+ case 0x02:
|
|
+ err = "Tx aborted";
|
|
+ adev->stats.tx_aborted_errors++;
|
|
+ break;
|
|
+ case 0x04:
|
|
+ err = "Tx desc wrong parameters";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x08:
|
|
+ err = "WEP key not found";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x10:
|
|
+ err = "MSDU lifetime timeout? - try changing "
|
|
+ "'iwconfig retry lifetime XXX'";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x20:
|
|
+ err = "excessive Tx retries due to either distance "
|
|
+ "too high or unable to Tx or Tx frame error - "
|
|
+ "try changing 'iwconfig txpower XXX' or "
|
|
+ "'sens'itivity or 'retry'";
|
|
+ adev->wstats.discard.retries++;
|
|
+ /* Tx error 0x20 also seems to occur on
|
|
+ * overheating, so I'm not sure whether we
|
|
+ * actually want to do aggressive radio recalibration,
|
|
+ * since people maybe won't notice then that their hardware
|
|
+ * is slowly getting cooked...
|
|
+ * Or is it still a safe long distance from utter
|
|
+ * radio non-functionality despite many radio recalibs
|
|
+ * to final destructive overheating of the hardware?
|
|
+ * In this case we really should do recalib here...
|
|
+ * I guess the only way to find out is to do a
|
|
+ * potentially fatal self-experiment :-\
|
|
+ * Or maybe only recalib in case we're using Tx
|
|
+ * rate auto (on errors switching to lower speed
|
|
+ * --> less heat?) or 802.11 power save mode?
|
|
+ *
|
|
+ * ok, just do it. */
|
|
+ if (++adev->retry_errors_msg_ratelimit % 4 == 0) {
|
|
+ if (adev->retry_errors_msg_ratelimit <= 20) {
|
|
+ printk("%s: several excessive Tx "
|
|
+ "retry errors occurred, attempting "
|
|
+ "to recalibrate radio. Radio "
|
|
+ "drift might be caused by increasing "
|
|
+ "card temperature, please check the card "
|
|
+ "before it's too late!\n",
|
|
+ adev->ndev->name);
|
|
+ if (adev->retry_errors_msg_ratelimit == 20)
|
|
+ printk("disabling above message\n");
|
|
+ }
|
|
+
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+ }
|
|
+ break;
|
|
+ case 0x40:
|
|
+ err = "Tx buffer overflow";
|
|
+ adev->stats.tx_fifo_errors++;
|
|
+ break;
|
|
+ case 0x80:
|
|
+ err = "DMA error";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ }
|
|
+ adev->stats.tx_errors++;
|
|
+ if (adev->stats.tx_errors <= 20)
|
|
+ printk("%s: tx error 0x%02X, buf %02u! (%s)\n",
|
|
+ adev->ndev->name, error, finger, err);
|
|
+ else
|
|
+ printk("%s: tx error 0x%02X, buf %02u!\n",
|
|
+ adev->ndev->name, error, finger);
|
|
+}
|
|
+
|
|
+
|
|
+unsigned int
|
|
+acxmem_l_clean_txdesc(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ unsigned finger;
|
|
+ int num_cleaned;
|
|
+ u16 r111;
|
|
+ u8 error, ack_failures, rts_failures, rts_ok, r100, Ctl_8;
|
|
+ u32 acxmem;
|
|
+ txdesc_t tmptxdesc;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /*
|
|
+ * Set up a template descriptor for re-initialization. The only
|
|
+ * things that get set are Ctl_8 and the rate, and the rate defaults
|
|
+ * to 1Mbps.
|
|
+ */
|
|
+ memset (&tmptxdesc, 0, sizeof (tmptxdesc));
|
|
+ tmptxdesc.Ctl_8 = DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG;
|
|
+ tmptxdesc.u.r1.rate = 0x0a;
|
|
+
|
|
+ if (unlikely(acx_debug & L_DEBUG))
|
|
+ log_txbuffer(adev);
|
|
+
|
|
+ log(L_BUFT, "tx: cleaning up bufs from %u\n", adev->tx_tail);
|
|
+
|
|
+ /* We know first descr which is not free yet. We advance it as far
|
|
+ ** as we see correct bits set in following descs (if next desc
|
|
+ ** is NOT free, we shouldn't advance at all). We know that in
|
|
+ ** front of tx_tail may be "holes" with isolated free descs.
|
|
+ ** We will catch up when all intermediate descs will be freed also */
|
|
+
|
|
+ finger = adev->tx_tail;
|
|
+ num_cleaned = 0;
|
|
+ while (likely(finger != adev->tx_head)) {
|
|
+ txdesc = get_txdesc(adev, finger);
|
|
+
|
|
+ /* If we allocated txdesc on tx path but then decided
|
|
+ ** to NOT use it, then it will be left as a free "bubble"
|
|
+ ** in the "allocated for tx" part of the ring.
|
|
+ ** We may meet it on the next ring pass here. */
|
|
+
|
|
+ /* stop if not marked as "tx finished" and "host owned" */
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ if ((Ctl_8 & DESC_CTL_ACXDONE_HOSTOWN)
|
|
+ != DESC_CTL_ACXDONE_HOSTOWN) {
|
|
+ if (unlikely(!num_cleaned)) { /* maybe remove completely */
|
|
+ log(L_BUFT, "clean_txdesc: tail isn't free. "
|
|
+ "tail:%d head:%d\n",
|
|
+ adev->tx_tail, adev->tx_head);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* remember desc values... */
|
|
+ error = read_slavemem8 (adev, (u32) &(txdesc->error));
|
|
+ ack_failures = read_slavemem8 (adev, (u32) &(txdesc->ack_failures));
|
|
+ rts_failures = read_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_failures));
|
|
+ rts_ok = read_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_ok));
|
|
+ r100 = read_slavemem8 (adev, (u32) &(txdesc->u.r1.rate));
|
|
+ r111 = le16_to_cpu(read_slavemem16 (adev, (u32) &(txdesc->u.r2.rate111)));
|
|
+
|
|
+ /* need to check for certain error conditions before we
|
|
+ * clean the descriptor: we still need valid descr data here */
|
|
+ if (unlikely(0x30 & error)) {
|
|
+ /* only send IWEVTXDROP in case of retry or lifetime exceeded;
|
|
+ * all other errors mean we screwed up locally */
|
|
+ union iwreq_data wrqu;
|
|
+ wlan_hdr_t *hdr;
|
|
+ txhostdesc_t *hostdesc;
|
|
+
|
|
+ hostdesc = get_txhostdesc(adev, txdesc);
|
|
+ hdr = (wlan_hdr_t *)hostdesc->data;
|
|
+ MAC_COPY(wrqu.addr.sa_data, hdr->a1);
|
|
+ wireless_send_event(adev->ndev, IWEVTXDROP, &wrqu, NULL);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Free up the transmit data buffers
|
|
+ */
|
|
+ acxmem = read_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr));
|
|
+ if (acxmem) {
|
|
+ reclaim_acx_txbuf_space (adev, acxmem);
|
|
+ }
|
|
+
|
|
+ /* ...and free the desc by clearing all the fields
|
|
+ except the next pointer */
|
|
+ copy_to_slavemem (adev,
|
|
+ (u32) &(txdesc->HostMemPtr),
|
|
+ (u8 *) &(tmptxdesc.HostMemPtr),
|
|
+ sizeof (tmptxdesc) - sizeof(tmptxdesc.pNextDesc)
|
|
+ );
|
|
+
|
|
+ adev->tx_free++;
|
|
+ num_cleaned++;
|
|
+
|
|
+ if ((adev->tx_free >= TX_START_QUEUE)
|
|
+ && (adev->status == ACX_STATUS_4_ASSOCIATED)
|
|
+ && (acx_queue_stopped(adev->ndev))
|
|
+ ) {
|
|
+ log(L_BUF, "tx: wake queue (avail. Tx desc %u)\n",
|
|
+ adev->tx_free);
|
|
+ acx_wake_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+ /* do error checking, rate handling and logging
|
|
+ * AFTER having done the work, it's faster */
|
|
+
|
|
+ /* do rate handling */
|
|
+ if (adev->rate_auto) {
|
|
+ struct client *clt = get_txc(adev, txdesc);
|
|
+ if (clt) {
|
|
+ u16 cur = get_txr(adev, txdesc);
|
|
+ if (clt->rate_cur == cur) {
|
|
+ acx_l_handle_txrate_auto(adev, clt,
|
|
+ cur, /* intended rate */
|
|
+ r100, r111, /* actually used rate */
|
|
+ (error & 0x30), /* was there an error? */
|
|
+ TX_CNT + TX_CLEAN_BACKLOG - adev->tx_free);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (unlikely(error))
|
|
+ handle_tx_error(adev, error, finger);
|
|
+
|
|
+ if (IS_ACX111(adev))
|
|
+ log(L_BUFT, "tx: cleaned %u: !ACK=%u !RTS=%u RTS=%u r111=%04X\n",
|
|
+ finger, ack_failures, rts_failures, rts_ok, r111);
|
|
+ else
|
|
+ log(L_BUFT, "tx: cleaned %u: !ACK=%u !RTS=%u RTS=%u rate=%u\n",
|
|
+ finger, ack_failures, rts_failures, rts_ok, r100);
|
|
+
|
|
+ /* update pointer for descr to be cleaned next */
|
|
+ finger = (finger + 1) % TX_CNT;
|
|
+ }
|
|
+
|
|
+ /* remember last position */
|
|
+ adev->tx_tail = finger;
|
|
+/* end: */
|
|
+ FN_EXIT1(num_cleaned);
|
|
+ return num_cleaned;
|
|
+}
|
|
+
|
|
+/* clean *all* Tx descriptors, and regardless of their previous state.
|
|
+ * Used for brute-force reset handling. */
|
|
+void
|
|
+acxmem_l_clean_txdesc_emergency(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+ u32 acxmem;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ txdesc = get_txdesc(adev, i);
|
|
+
|
|
+ /* free it */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->ack_failures), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_failures), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_ok), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->error), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), DESC_CTL_HOSTOWN);
|
|
+
|
|
+ /*
|
|
+ * Clean up the memory allocated on the ACX for this transmit descriptor.
|
|
+ */
|
|
+ acxmem = read_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr));
|
|
+ if (acxmem) {
|
|
+ reclaim_acx_txbuf_space (adev, acxmem);
|
|
+ }
|
|
+
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr), 0);
|
|
+ }
|
|
+
|
|
+ adev->tx_free = TX_CNT;
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_create_tx_host_desc_queue
|
|
+*/
|
|
+
|
|
+static void*
|
|
+allocate(acx_device_t *adev, size_t size, dma_addr_t *phy, const char *msg)
|
|
+{
|
|
+ void *ptr;
|
|
+ ptr = kmalloc (size, GFP_KERNEL);
|
|
+ /*
|
|
+ * The ACX can't use the physical address, so we'll have to fake it
|
|
+ * later and it might be handy to have the virtual address.
|
|
+ */
|
|
+ *phy = (dma_addr_t) NULL;
|
|
+
|
|
+ if (ptr) {
|
|
+ log(L_DEBUG, "%s sz=%d adr=0x%p phy=0x%08llx\n",
|
|
+ msg, (int)size, ptr, (unsigned long long)*phy);
|
|
+ memset(ptr, 0, size);
|
|
+ return ptr;
|
|
+ }
|
|
+ printk(KERN_ERR "acx: %s allocation FAILED (%d bytes)\n",
|
|
+ msg, (int)size);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * In the generic slave memory access mode, most of the stuff in
|
|
+ * the txhostdesc_t is unused. It's only here because the rest of
|
|
+ * the ACX driver expects it to be since the PCI version uses indirect
|
|
+ * host memory organization with DMA. Since we're not using DMA the
|
|
+ * only use we have for the host descriptors is to store the packets
|
|
+ * on the way out.
|
|
+ */
|
|
+static int
|
|
+acxmem_s_create_tx_host_desc_queue(acx_device_t *adev)
|
|
+{
|
|
+ txhostdesc_t *hostdesc;
|
|
+ u8 *txbuf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* allocate TX buffer */
|
|
+ adev->txbuf_area_size = TX_CNT * WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+
|
|
+ adev->txbuf_start = allocate(adev, adev->txbuf_area_size,
|
|
+ &adev->txbuf_startphy, "txbuf_start");
|
|
+ if (!adev->txbuf_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* allocate the TX host descriptor queue pool */
|
|
+ adev->txhostdesc_area_size = TX_CNT * 2*sizeof(*hostdesc);
|
|
+
|
|
+ adev->txhostdesc_start = allocate(adev, adev->txhostdesc_area_size,
|
|
+ &adev->txhostdesc_startphy, "txhostdesc_start");
|
|
+ if (!adev->txhostdesc_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* check for proper alignment of TX host descriptor pool */
|
|
+ if ((long) adev->txhostdesc_start & 3) {
|
|
+ printk("acx: driver bug: dma alloc returns unaligned address\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ hostdesc = adev->txhostdesc_start;
|
|
+ txbuf = adev->txbuf_start;
|
|
+
|
|
+#if 0
|
|
+/* Each tx buffer is accessed by hardware via
|
|
+** txdesc -> txhostdesc(s) -> txbuffer(s).
|
|
+** We use only one txhostdesc per txdesc, but it looks like
|
|
+** acx111 is buggy: it accesses second txhostdesc
|
|
+** (via hostdesc.desc_phy_next field) even if
|
|
+** txdesc->length == hostdesc->length and thus
|
|
+** entire packet was placed into first txhostdesc.
|
|
+** Due to this bug acx111 hangs unless second txhostdesc
|
|
+** has le16_to_cpu(hostdesc.length) = 3 (or larger)
|
|
+** Storing NULL into hostdesc.desc_phy_next
|
|
+** doesn't seem to help.
|
|
+**
|
|
+** Update: although it worked on Xterasys XN-2522g
|
|
+** with len=3 trick, WG311v2 is even more bogus, doesn't work.
|
|
+** Keeping this code (#ifdef'ed out) for documentational purposes.
|
|
+*/
|
|
+ for (i = 0; i < TX_CNT*2; i++) {
|
|
+ hostdesc_phy += sizeof(*hostdesc);
|
|
+ if (!(i & 1)) {
|
|
+ hostdesc->data_phy = cpu2acx(txbuf_phy);
|
|
+ /* hostdesc->data_offset = ... */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ hostdesc->Ctl_16 = cpu_to_le16(DESC_CTL_HOSTOWN);
|
|
+ /* hostdesc->length = ... */
|
|
+ hostdesc->desc_phy_next = cpu2acx(hostdesc_phy);
|
|
+ hostdesc->pNext = ptr2acx(NULL);
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* below: non-hardware fields */
|
|
+ hostdesc->data = txbuf;
|
|
+
|
|
+ txbuf += WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ txbuf_phy += WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ } else {
|
|
+ /* hostdesc->data_phy = ... */
|
|
+ /* hostdesc->data_offset = ... */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ /* hostdesc->Ctl_16 = ... */
|
|
+ hostdesc->length = cpu_to_le16(3); /* bug workaround */
|
|
+ /* hostdesc->desc_phy_next = ... */
|
|
+ /* hostdesc->pNext = ... */
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* below: non-hardware fields */
|
|
+ /* hostdesc->data = ... */
|
|
+ }
|
|
+ hostdesc++;
|
|
+ }
|
|
+#endif
|
|
+/* We initialize two hostdescs so that they point to adjacent
|
|
+** memory areas. Thus txbuf is really just a contiguous memory area */
|
|
+ for (i = 0; i < TX_CNT*2; i++) {
|
|
+ /* ->data is a non-hardware field: */
|
|
+ hostdesc->data = txbuf;
|
|
+
|
|
+ if (!(i & 1)) {
|
|
+ txbuf += WLAN_HDR_A3_LEN;
|
|
+ } else {
|
|
+ txbuf += WLAN_A4FR_MAXLEN_WEP_FCS - WLAN_HDR_A3_LEN;
|
|
+ }
|
|
+ hostdesc++;
|
|
+ }
|
|
+ hostdesc--;
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+ printk("acx: create_tx_host_desc_queue FAILED\n");
|
|
+ /* dealloc will be done by free function on error case */
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_s_create_rx_host_desc_queue
|
|
+*/
|
|
+/* the whole size of a data buffer (header plus data body)
|
|
+ * plus 32 bytes safety offset at the end */
|
|
+#define RX_BUFFER_SIZE (sizeof(rxbuffer_t) + 32)
|
|
+
|
|
+static int
|
|
+acxmem_s_create_rx_host_desc_queue(acx_device_t *adev)
|
|
+{
|
|
+ rxhostdesc_t *hostdesc;
|
|
+ rxbuffer_t *rxbuf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* allocate the RX host descriptor queue pool */
|
|
+ adev->rxhostdesc_area_size = RX_CNT * sizeof(*hostdesc);
|
|
+
|
|
+ adev->rxhostdesc_start = allocate(adev, adev->rxhostdesc_area_size,
|
|
+ &adev->rxhostdesc_startphy, "rxhostdesc_start");
|
|
+ if (!adev->rxhostdesc_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* check for proper alignment of RX host descriptor pool */
|
|
+ if ((long) adev->rxhostdesc_start & 3) {
|
|
+ printk("acx: driver bug: dma alloc returns unaligned address\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* allocate Rx buffer pool which will be used by the acx
|
|
+ * to store the whole content of the received frames in it */
|
|
+ adev->rxbuf_area_size = RX_CNT * RX_BUFFER_SIZE;
|
|
+
|
|
+ adev->rxbuf_start = allocate(adev, adev->rxbuf_area_size,
|
|
+ &adev->rxbuf_startphy, "rxbuf_start");
|
|
+ if (!adev->rxbuf_start)
|
|
+ goto fail;
|
|
+
|
|
+ rxbuf = adev->rxbuf_start;
|
|
+ hostdesc = adev->rxhostdesc_start;
|
|
+
|
|
+ /* don't make any popular C programming pointer arithmetic mistakes
|
|
+ * here, otherwise I'll kill you...
|
|
+ * (and don't dare asking me why I'm warning you about that...) */
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ hostdesc->data = rxbuf;
|
|
+ hostdesc->length = cpu_to_le16(RX_BUFFER_SIZE);
|
|
+ rxbuf++;
|
|
+ hostdesc++;
|
|
+ }
|
|
+ hostdesc--;
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+ printk("acx: create_rx_host_desc_queue FAILED\n");
|
|
+ /* dealloc will be done by free function on error case */
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_s_create_hostdesc_queues
|
|
+*/
|
|
+int
|
|
+acxmem_s_create_hostdesc_queues(acx_device_t *adev)
|
|
+{
|
|
+ int result;
|
|
+ result = acxmem_s_create_tx_host_desc_queue(adev);
|
|
+ if (OK != result) return result;
|
|
+ result = acxmem_s_create_rx_host_desc_queue(adev);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_create_tx_desc_queue
|
|
+*/
|
|
+static void
|
|
+acxmem_create_tx_desc_queue(acx_device_t *adev, u32 tx_queue_start)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ u32 clr;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (IS_ACX100(adev))
|
|
+ adev->txdesc_size = sizeof(*txdesc);
|
|
+ else
|
|
+ /* the acx111 txdesc is 4 bytes larger */
|
|
+ adev->txdesc_size = sizeof(*txdesc) + 4;
|
|
+
|
|
+ /*
|
|
+ * This refers to an ACX address, not one of ours
|
|
+ */
|
|
+ adev->txdesc_start = (txdesc_t *) tx_queue_start;
|
|
+
|
|
+ log(L_DEBUG, "adev->txdesc_start=%p\n",
|
|
+ adev->txdesc_start);
|
|
+
|
|
+ adev->tx_free = TX_CNT;
|
|
+ /* done by memset: adev->tx_head = 0; */
|
|
+ /* done by memset: adev->tx_tail = 0; */
|
|
+ txdesc = adev->txdesc_start;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111 has a preinitialized Tx buffer! */
|
|
+ /* loop over whole send pool */
|
|
+ /* FIXME: do we have to do the hostmemptr stuff here?? */
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ txdesc->Ctl_8 = DESC_CTL_HOSTOWN;
|
|
+ /* reserve two (hdr desc and payload desc) */
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ } else {
|
|
+ /* ACX100 Tx buffer needs to be initialized by us */
|
|
+ /* clear whole send pool. sizeof is safe here (we are acx100) */
|
|
+
|
|
+ /*
|
|
+ * adev->txdesc_start refers to device memory, so we can't write
|
|
+ * directly to it.
|
|
+ */
|
|
+ clr = (u32) adev->txdesc_start;
|
|
+ while (clr < (u32) adev->txdesc_start + (TX_CNT * sizeof(*txdesc))) {
|
|
+ write_slavemem32 (adev, clr, 0);
|
|
+ clr += 4;
|
|
+ }
|
|
+
|
|
+ /* loop over whole send pool */
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ log(L_DEBUG, "configure card tx descriptor: 0x%p, "
|
|
+ "size: 0x%X\n", txdesc, adev->txdesc_size);
|
|
+
|
|
+ /* initialise ctl */
|
|
+ /*
|
|
+ * No auto DMA here
|
|
+ */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8),
|
|
+ (u8) (DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG));
|
|
+ /* done by memset(0): txdesc->Ctl2_8 = 0; */
|
|
+
|
|
+ /* point to next txdesc */
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 ((u8 *) txdesc + adev->txdesc_size));
|
|
+
|
|
+ /* go to the next one */
|
|
+ /* ++ is safe here (we are acx100) */
|
|
+ txdesc++;
|
|
+ }
|
|
+ /* go back to the last one */
|
|
+ txdesc--;
|
|
+ /* and point to the first making it a ring buffer */
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 (tx_queue_start));
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_create_rx_desc_queue
|
|
+*/
|
|
+static void
|
|
+acxmem_create_rx_desc_queue(acx_device_t *adev, u32 rx_queue_start)
|
|
+{
|
|
+ rxdesc_t *rxdesc;
|
|
+ u32 mem_offs;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* done by memset: adev->rx_tail = 0; */
|
|
+
|
|
+ /* ACX111 doesn't need any further config: preconfigures itself.
|
|
+ * Simply print ring buffer for debugging */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* rxdesc_start already set here */
|
|
+
|
|
+ adev->rxdesc_start = (rxdesc_t *) rx_queue_start;
|
|
+
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ log(L_DEBUG, "rx descriptor %d @ 0x%p\n", i, rxdesc);
|
|
+ rxdesc = adev->rxdesc_start = (rxdesc_t *)
|
|
+ acx2cpu(rxdesc->pNextDesc);
|
|
+ }
|
|
+ } else {
|
|
+ /* we didn't pre-calculate rxdesc_start in case of ACX100 */
|
|
+ /* rxdesc_start should be right AFTER Tx pool */
|
|
+ adev->rxdesc_start = (rxdesc_t *)
|
|
+ ((u8 *) adev->txdesc_start + (TX_CNT * sizeof(txdesc_t)));
|
|
+ /* NB: sizeof(txdesc_t) above is valid because we know
|
|
+ ** we are in if (acx100) block. Beware of cut-n-pasting elsewhere!
|
|
+ ** acx111's txdesc is larger! */
|
|
+
|
|
+ mem_offs = (u32) adev->rxdesc_start;
|
|
+ while (mem_offs < (u32) adev->rxdesc_start + (RX_CNT * sizeof (*rxdesc))) {
|
|
+ write_slavemem32 (adev, mem_offs, 0);
|
|
+ mem_offs += 4;
|
|
+ }
|
|
+
|
|
+ /* loop over whole receive pool */
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ log(L_DEBUG, "rx descriptor @ 0x%p\n", rxdesc);
|
|
+ /* point to next rxdesc */
|
|
+ write_slavemem32 (adev, (u32) &(rxdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 ((u8 *) rxdesc + sizeof(*rxdesc)));
|
|
+ /* go to the next one */
|
|
+ rxdesc++;
|
|
+ }
|
|
+ /* go to the last one */
|
|
+ rxdesc--;
|
|
+
|
|
+ /* and point to the first making it a ring buffer */
|
|
+ write_slavemem32 (adev, (u32) &(rxdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 (rx_queue_start));
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_create_desc_queues
|
|
+*/
|
|
+void
|
|
+acxmem_create_desc_queues(acx_device_t *adev, u32 tx_queue_start, u32 rx_queue_start)
|
|
+{
|
|
+ u32 *p;
|
|
+ int i;
|
|
+
|
|
+ acxmem_create_tx_desc_queue(adev, tx_queue_start);
|
|
+ acxmem_create_rx_desc_queue(adev, rx_queue_start);
|
|
+ p = (u32 *) adev->acx_queue_indicator;
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ write_slavemem32 (adev, (u32) p, 0);
|
|
+ p++;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_s_proc_diag_output
|
|
+*/
|
|
+char*
|
|
+acxmem_s_proc_diag_output(char *p, acx_device_t *adev)
|
|
+{
|
|
+ const char *rtl, *thd, *ttl;
|
|
+ txdesc_t *txdesc;
|
|
+ u8 Ctl_8;
|
|
+ rxdesc_t *rxdesc;
|
|
+ int i;
|
|
+ u32 tmp;
|
|
+ txdesc_t txd;
|
|
+ u8 buf[0x200];
|
|
+ int j, k;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+#if DUMP_MEM_DURING_DIAG > 0
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ panic ("dump finished");
|
|
+#endif
|
|
+
|
|
+ p += sprintf(p, "** Rx buf **\n");
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ if (rxdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ rtl = (i == adev->rx_tail) ? " [tail]" : "";
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(rxdesc->Ctl_8));
|
|
+ if (Ctl_8 & DESC_CTL_HOSTOWN)
|
|
+ p += sprintf(p, "%02u (%02x) FULL%s\n", i, Ctl_8, rtl);
|
|
+ else
|
|
+ p += sprintf(p, "%02u (%02x) empty%s\n", i, Ctl_8, rtl);
|
|
+ rxdesc++;
|
|
+ }
|
|
+ p += sprintf(p, "** Tx buf (free %d, Linux netqueue %s) **\n", adev->tx_free,
|
|
+ acx_queue_stopped(adev->ndev) ? "STOPPED" : "running");
|
|
+
|
|
+ p += sprintf(p, "** Tx buf %d blocks total, %d available, free list head %04x\n",
|
|
+ adev->acx_txbuf_numblocks, adev->acx_txbuf_blocks_free, adev->acx_txbuf_free);
|
|
+ txdesc = adev->txdesc_start;
|
|
+ if (txdesc) {
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ thd = (i == adev->tx_head) ? " [head]" : "";
|
|
+ ttl = (i == adev->tx_tail) ? " [tail]" : "";
|
|
+ copy_from_slavemem (adev, (u8 *) &txd, (u32) txdesc, sizeof (txd));
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ if (Ctl_8 & DESC_CTL_ACXDONE)
|
|
+ p += sprintf(p, "%02u ready to free (%02X)%s%s", i, Ctl_8, thd, ttl);
|
|
+ else if (Ctl_8 & DESC_CTL_HOSTOWN)
|
|
+ p += sprintf(p, "%02u available (%02X)%s%s", i, Ctl_8, thd, ttl);
|
|
+ else
|
|
+ p += sprintf(p, "%02u busy (%02X)%s%s", i, Ctl_8, thd, ttl);
|
|
+ tmp = read_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr));
|
|
+ if (tmp) {
|
|
+ p += sprintf (p, " %04x", tmp);
|
|
+ while ((tmp = read_slavemem32 (adev, (u32) tmp)) != 0x02000000) {
|
|
+ tmp <<= 5;
|
|
+ p += sprintf (p, " %04x", tmp);
|
|
+ }
|
|
+ }
|
|
+ p += sprintf (p, "\n");
|
|
+ p += sprintf (p, " %04x: %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %02x %02x %02x %02x\n"
|
|
+ "%02x %02x %02x %02x %04x\n",
|
|
+ (u32) txdesc,
|
|
+ txd.pNextDesc.v, txd.HostMemPtr.v, txd.AcxMemPtr.v, txd.tx_time,
|
|
+ txd.total_length, txd.Reserved,
|
|
+ txd.dummy[0], txd.dummy[1], txd.dummy[2], txd.dummy[3],
|
|
+ txd.Ctl_8, txd.Ctl2_8, txd.error, txd.ack_failures,
|
|
+ txd.u.rts.rts_failures, txd.u.rts.rts_ok, txd.u.r1.rate, txd.u.r1.queue_ctrl,
|
|
+ txd.queue_info
|
|
+ );
|
|
+ if (txd.AcxMemPtr.v) {
|
|
+ copy_from_slavemem (adev, buf, txd.AcxMemPtr.v, sizeof (buf));
|
|
+ for (j = 0; (j < txd.total_length) && (j<(sizeof(buf)-4)); j+=16) {
|
|
+ p += sprintf (p, " ");
|
|
+ for (k = 0; (k < 16) && (j+k < txd.total_length); k++) {
|
|
+ p += sprintf (p, " %02x", buf[j+k+4]);
|
|
+ }
|
|
+ p += sprintf (p, "\n");
|
|
+ }
|
|
+ }
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "\n"
|
|
+ "** Generic slave data **\n"
|
|
+ "irq_mask 0x%04x irq_status 0x%04x irq on acx 0x%04x\n"
|
|
+ "txbuf_start 0x%p, txbuf_area_size %u\n"
|
|
+ "txdesc_size %u, txdesc_start 0x%p\n"
|
|
+ "txhostdesc_start 0x%p, txhostdesc_area_size %u\n"
|
|
+ "txbuf start 0x%04x, txbuf size %d\n"
|
|
+ "rxdesc_start 0x%p\n"
|
|
+ "rxhostdesc_start 0x%p, rxhostdesc_area_size %u\n"
|
|
+ "rxbuf_start 0x%p, rxbuf_area_size %u\n",
|
|
+ adev->irq_mask, adev->irq_status, read_reg32(adev, IO_ACX_IRQ_STATUS_NON_DES),
|
|
+ adev->txbuf_start, adev->txbuf_area_size,
|
|
+ adev->txdesc_size, adev->txdesc_start,
|
|
+ adev->txhostdesc_start, adev->txhostdesc_area_size,
|
|
+ adev->acx_txbuf_start, adev->acx_txbuf_numblocks * adev->memblocksize,
|
|
+ adev->rxdesc_start,
|
|
+ adev->rxhostdesc_start, adev->rxhostdesc_area_size,
|
|
+ adev->rxbuf_start, adev->rxbuf_area_size);
|
|
+ FN_EXIT0;
|
|
+ return p;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxmem_proc_eeprom_output(char *buf, acx_device_t *adev)
|
|
+{
|
|
+ char *p = buf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ for (i = 0; i < 0x400; i++) {
|
|
+ acxmem_read_eeprom_byte(adev, i, p++);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(p - buf);
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void
|
|
+acxmem_set_interrupt_mask(acx_device_t *adev)
|
|
+{
|
|
+ if (IS_ACX111(adev)) {
|
|
+ adev->irq_mask = (u16) ~(0
|
|
+ | HOST_INT_RX_DATA
|
|
+ | HOST_INT_TX_COMPLETE
|
|
+ /* | HOST_INT_TX_XFER */
|
|
+ /* | HOST_INT_RX_COMPLETE */
|
|
+ /* | HOST_INT_DTIM */
|
|
+ /* | HOST_INT_BEACON */
|
|
+ /* | HOST_INT_TIMER */
|
|
+ /* | HOST_INT_KEY_NOT_FOUND */
|
|
+ | HOST_INT_IV_ICV_FAILURE
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ | HOST_INT_OVERFLOW
|
|
+ /* | HOST_INT_PROCESS_ERROR */
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ | HOST_INT_FCS_THRESHOLD
|
|
+ | HOST_INT_UNKNOWN
|
|
+ );
|
|
+ /* Or else acx100 won't signal cmd completion, right? */
|
|
+ adev->irq_mask_off = (u16)~( HOST_INT_CMD_COMPLETE ); /* 0xfdff */
|
|
+ } else {
|
|
+ adev->irq_mask = (u16) ~(0
|
|
+ | HOST_INT_RX_DATA
|
|
+ | HOST_INT_TX_COMPLETE
|
|
+ /* | HOST_INT_TX_XFER */
|
|
+ /* | HOST_INT_RX_COMPLETE */
|
|
+ /* | HOST_INT_DTIM */
|
|
+ /* | HOST_INT_BEACON */
|
|
+ /* | HOST_INT_TIMER */
|
|
+ /* | HOST_INT_KEY_NOT_FOUND */
|
|
+ /* | HOST_INT_IV_ICV_FAILURE */
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ /* | HOST_INT_OVERFLOW */
|
|
+ /* | HOST_INT_PROCESS_ERROR */
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ /* | HOST_INT_FCS_THRESHOLD */
|
|
+ /* | HOST_INT_BEACON_MISSED */
|
|
+ );
|
|
+ adev->irq_mask_off = (u16)~( HOST_INT_UNKNOWN ); /* 0x7fff */
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx100mem_s_set_tx_level(acx_device_t *adev, u8 level_dbm)
|
|
+{
|
|
+ struct acx111_ie_tx_level tx_level;
|
|
+
|
|
+ /* since it can be assumed that at least the Maxim radio has a
|
|
+ * maximum power output of 20dBm and since it also can be
|
|
+ * assumed that these values drive the DAC responsible for
|
|
+ * setting the linear Tx level, I'd guess that these values
|
|
+ * should be the corresponding linear values for a dBm value,
|
|
+ * in other words: calculate the values from that formula:
|
|
+ * Y [dBm] = 10 * log (X [mW])
|
|
+ * then scale the 0..63 value range onto the 1..100mW range (0..20 dBm)
|
|
+ * and you're done...
|
|
+ * Hopefully that's ok, but you never know if we're actually
|
|
+ * right... (especially since Windows XP doesn't seem to show
|
|
+ * actual Tx dBm values :-P) */
|
|
+
|
|
+ /* NOTE: on Maxim, value 30 IS 30mW, and value 10 IS 10mW - so the
|
|
+ * values are EXACTLY mW!!! Not sure about RFMD and others,
|
|
+ * though... */
|
|
+ static const u8 dbm2val_maxim[21] = {
|
|
+ 63, 63, 63, 62,
|
|
+ 61, 61, 60, 60,
|
|
+ 59, 58, 57, 55,
|
|
+ 53, 50, 47, 43,
|
|
+ 38, 31, 23, 13,
|
|
+ 0
|
|
+ };
|
|
+ static const u8 dbm2val_rfmd[21] = {
|
|
+ 0, 0, 0, 1,
|
|
+ 2, 2, 3, 3,
|
|
+ 4, 5, 6, 8,
|
|
+ 10, 13, 16, 20,
|
|
+ 25, 32, 41, 50,
|
|
+ 63
|
|
+ };
|
|
+ const u8 *table;
|
|
+
|
|
+ switch (adev->radio_type) {
|
|
+ case RADIO_MAXIM_0D:
|
|
+ table = &dbm2val_maxim[0];
|
|
+ break;
|
|
+ case RADIO_RFMD_11:
|
|
+ case RADIO_RALINK_15:
|
|
+ table = &dbm2val_rfmd[0];
|
|
+ break;
|
|
+ default:
|
|
+ printk("%s: unknown/unsupported radio type, "
|
|
+ "cannot modify tx power level yet!\n",
|
|
+ adev->ndev->name);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+ /*
|
|
+ * The hx4700 EEPROM, at least, only supports 1 power setting. The configure
|
|
+ * routine matches the PA bias with the gain, so just use its default value.
|
|
+ * The values are: 0x2b for the gain and 0x03 for the PA bias. The firmware
|
|
+ * writes the gain level to the Tx gain control DAC and the PA bias to the Maxim
|
|
+ * radio's PA bias register. The firmware limits itself to 0 - 64 when writing to the
|
|
+ * gain control DAC.
|
|
+ *
|
|
+ * Physically between the ACX and the radio, higher Tx gain control DAC values result
|
|
+ * in less power output; 0 volts to the Maxim radio results in the highest output power
|
|
+ * level, which I'm assuming matches up with 0 in the Tx Gain DAC register.
|
|
+ *
|
|
+ * Although there is only the 1 power setting, one of the radio firmware functions adjusts
|
|
+ * the transmit power level up and down. That function is called by the ACX FIQ handler
|
|
+ * under certain conditions.
|
|
+ */
|
|
+ tx_level.level = 1;
|
|
+ //return acx_s_configure(adev, &tx_level, ACX1xx_IE_DOT11_TX_POWER_LEVEL);
|
|
+
|
|
+ printk("%s: changing radio power level to %u dBm (%u)\n",
|
|
+ adev->ndev->name, level_dbm, table[level_dbm]);
|
|
+ acxmem_s_write_phy_reg(adev, 0x11, table[level_dbm]);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+void acxmem_e_release(struct device *dev) {
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_cs part
|
|
+**
|
|
+** called by pcmcia card service
|
|
+*/
|
|
+
|
|
+/*
|
|
+ The event() function is this driver's Card Services event handler.
|
|
+ It will be called by Card Services when an appropriate card status
|
|
+ event is received. The config() and release() entry points are
|
|
+ used to configure or release a socket, in response to card
|
|
+ insertion and ejection events. They are invoked from the acx_cs
|
|
+ event handler.
|
|
+*/
|
|
+
|
|
+static int acx_cs_config(struct pcmcia_device *link);
|
|
+static void acx_cs_release(struct pcmcia_device *link);
|
|
+
|
|
+/*
|
|
+ The attach() and detach() entry points are used to create and destroy
|
|
+ "instances" of the driver, where each instance represents everything
|
|
+ needed to manage one actual PCMCIA card.
|
|
+*/
|
|
+
|
|
+static void acx_cs_detach(struct pcmcia_device *p_dev);
|
|
+
|
|
+/*
|
|
+ You'll also need to prototype all the functions that will actually
|
|
+ be used to talk to your device. See 'pcmem_cs' for a good example
|
|
+ of a fully self-sufficient driver; the other drivers rely more or
|
|
+ less on other parts of the kernel.
|
|
+*/
|
|
+
|
|
+/*
|
|
+ A linked list of "instances" of the acxnet device. Each actual
|
|
+ PCMCIA card corresponds to one device instance, and is described
|
|
+ by one struct pcmcia_device structure (defined in ds.h).
|
|
+
|
|
+ You may not want to use a linked list for this -- for example, the
|
|
+ memory card driver uses an array of struct pcmcia_device pointers, where minor
|
|
+ device numbers are used to derive the corresponding array index.
|
|
+*/
|
|
+
|
|
+/*
|
|
+ A driver needs to provide a dev_node_t structure for each device
|
|
+ on a card. In some cases, there is only one device per card (for
|
|
+ example, ethernet cards, modems). In other cases, there may be
|
|
+ many actual or logical devices (SCSI adapters, memory cards with
|
|
+ multiple partitions). The dev_node_t structures need to be kept
|
|
+ in a linked list starting at the 'dev' field of a struct pcmcia_device
|
|
+ structure. We allocate them in the card's private data structure,
|
|
+ because they generally shouldn't be allocated dynamically.
|
|
+
|
|
+ In this case, we also provide a flag to indicate if a device is
|
|
+ "stopped" due to a power management event, or card ejection. The
|
|
+ device IO routines can use a flag like this to throttle IO to a
|
|
+ card that is not ready to accept it.
|
|
+*/
|
|
+
|
|
+
|
|
+/*======================================================================
|
|
+
|
|
+ acx_attach() creates an "instance" of the driver, allocating
|
|
+ local data structures for one device. The device is registered
|
|
+ with Card Services.
|
|
+
|
|
+ The dev_link structure is initialized, but we don't actually
|
|
+ configure the card at this point -- we wait until we receive a
|
|
+ card insertion event.
|
|
+
|
|
+ ======================================================================*/
|
|
+
|
|
+static int acx_cs_probe(struct pcmcia_device *link)
|
|
+{
|
|
+ local_info_t *local;
|
|
+ struct net_device *ndev;
|
|
+
|
|
+ DEBUG(0, "acx_attach()\n");
|
|
+
|
|
+ ndev = alloc_netdev(sizeof(acx_device_t), "wlan%d", dummy_netdev_init);
|
|
+ if (!ndev) {
|
|
+ printk("acx: no memory for netdevice struct\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+
|
|
+ /* Interrupt setup */
|
|
+ link->irq.Attributes = IRQ_TYPE_EXCLUSIVE | IRQ_HANDLE_PRESENT;
|
|
+ link->irq.IRQInfo1 = IRQ_LEVEL_ID;
|
|
+ link->irq.Handler = acxmem_i_interrupt;
|
|
+ link->irq.Instance = ndev;
|
|
+
|
|
+ /*
|
|
+ General socket configuration defaults can go here. In this
|
|
+ client, we assume very little, and rely on the CIS for almost
|
|
+ everything. In most clients, many details (i.e., number, sizes,
|
|
+ and attributes of IO windows) are fixed by the nature of the
|
|
+ device, and can be hard-wired here.
|
|
+ */
|
|
+ link->conf.Attributes = CONF_ENABLE_IRQ;
|
|
+ link->conf.IntType = INT_MEMORY_AND_IO;
|
|
+ link->conf.Present = PRESENT_OPTION | PRESENT_COPY;
|
|
+
|
|
+ /* Allocate space for private device-specific data */
|
|
+ local = kzalloc(sizeof(local_info_t), GFP_KERNEL);
|
|
+ if (!local) {
|
|
+ printk(KERN_ERR "acx_cs: no memory for new device\n");
|
|
+ return -ENOMEM;
|
|
+ }
|
|
+ local->ndev = ndev;
|
|
+
|
|
+ link->priv = local;
|
|
+
|
|
+ return acx_cs_config(link);
|
|
+} /* acx_attach */
|
|
+
|
|
+/*======================================================================
|
|
+
|
|
+ This deletes a driver "instance". The device is de-registered
|
|
+ with Card Services. If it has been released, all local data
|
|
+ structures are freed. Otherwise, the structures will be freed
|
|
+ when the device is released.
|
|
+
|
|
+ ======================================================================*/
|
|
+
|
|
+static void acx_cs_detach(struct pcmcia_device *link)
|
|
+{
|
|
+ DEBUG(0, "acx_detach(0x%p)\n", link);
|
|
+
|
|
+
|
|
+ if ( ((local_info_t*)link->priv)->ndev ) {
|
|
+ acxmem_e_close( ((local_info_t*)link->priv)->ndev );
|
|
+ }
|
|
+
|
|
+ acx_cs_release(link);
|
|
+
|
|
+ ((local_info_t*)link->priv)->ndev = NULL;
|
|
+
|
|
+ kfree(link->priv);
|
|
+} /* acx_detach */
|
|
+
|
|
+/*======================================================================
|
|
+
|
|
+ acx_config() is scheduled to run after a CARD_INSERTION event
|
|
+ is received, to configure the PCMCIA socket, and to make the
|
|
+ device available to the system.
|
|
+
|
|
+ ======================================================================*/
|
|
+
|
|
+#define CS_CHECK(fn, ret) \
|
|
+do { last_fn = (fn); if ((last_ret = (ret)) != 0) goto cs_failed; } while (0)
|
|
+
|
|
+static int acx_cs_config(struct pcmcia_device *link)
|
|
+{
|
|
+ tuple_t tuple;
|
|
+ cisparse_t parse;
|
|
+ local_info_t *local = link->priv;
|
|
+ int last_fn, last_ret;
|
|
+ u_char buf[64];
|
|
+ win_req_t req;
|
|
+ memreq_t map;
|
|
+// int i;
|
|
+// acx_device_t *adev;
|
|
+
|
|
+// adev = (acx_device_t *)link->priv;
|
|
+
|
|
+ DEBUG(0, "acx_cs_config(0x%p)\n", link);
|
|
+
|
|
+ /*
|
|
+ In this loop, we scan the CIS for configuration table entries,
|
|
+ each of which describes a valid card configuration, including
|
|
+ voltage, IO window, memory window, and interrupt settings.
|
|
+
|
|
+ We make no assumptions about the card to be configured: we use
|
|
+ just the information available in the CIS. In an ideal world,
|
|
+ this would work for any PCMCIA card, but it requires a complete
|
|
+ and accurate CIS. In practice, a driver usually "knows" most of
|
|
+ these things without consulting the CIS, and most client drivers
|
|
+ will only use the CIS to fill in implementation-defined details.
|
|
+ */
|
|
+ tuple.Attributes = 0;
|
|
+ tuple.TupleData = (cisdata_t *)buf;
|
|
+ tuple.TupleDataMax = sizeof(buf);
|
|
+ tuple.TupleOffset = 0;
|
|
+ tuple.DesiredTuple = CISTPL_CFTABLE_ENTRY;
|
|
+
|
|
+ /* don't trust the CIS on this; Linksys got it wrong */
|
|
+ //link->conf.Present = 0x63;
|
|
+
|
|
+ CS_CHECK(GetFirstTuple, pcmcia_get_first_tuple(link, &tuple));
|
|
+ while (1) {
|
|
+ cistpl_cftable_entry_t dflt = { 0 };
|
|
+ cistpl_cftable_entry_t *cfg = &(parse.cftable_entry);
|
|
+ if (pcmcia_get_tuple_data(link, &tuple) != 0 ||
|
|
+ pcmcia_parse_tuple(link, &tuple, &parse) != 0)
|
|
+ goto next_entry;
|
|
+
|
|
+ if (cfg->flags & CISTPL_CFTABLE_DEFAULT) dflt = *cfg;
|
|
+ if (cfg->index == 0) goto next_entry;
|
|
+ link->conf.ConfigIndex = cfg->index;
|
|
+
|
|
+ /* Does this card need audio output? */
|
|
+ if (cfg->flags & CISTPL_CFTABLE_AUDIO) {
|
|
+ link->conf.Attributes |= CONF_ENABLE_SPKR;
|
|
+ link->conf.Status = CCSR_AUDIO_ENA;
|
|
+ }
|
|
+
|
|
+ /* Use power settings for Vcc and Vpp if present */
|
|
+ /* Note that the CIS values need to be rescaled */
|
|
+ if (cfg->vpp1.present & (1<<CISTPL_POWER_VNOM))
|
|
+ link->conf.Vpp =
|
|
+ cfg->vpp1.param[CISTPL_POWER_VNOM]/10000;
|
|
+ else if (dflt.vpp1.present & (1<<CISTPL_POWER_VNOM))
|
|
+ link->conf.Vpp =
|
|
+ dflt.vpp1.param[CISTPL_POWER_VNOM]/10000;
|
|
+
|
|
+ /* Do we need to allocate an interrupt? */
|
|
+ if (cfg->irq.IRQInfo1 || dflt.irq.IRQInfo1)
|
|
+ link->conf.Attributes |= CONF_ENABLE_IRQ;
|
|
+ if ((cfg->mem.nwin > 0) || (dflt.mem.nwin > 0)) {
|
|
+ cistpl_mem_t *mem =
|
|
+ (cfg->mem.nwin) ? &cfg->mem : &dflt.mem;
|
|
+// req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_AM|WIN_ENABLE|WIN_USE_WAIT;
|
|
+ req.Attributes = WIN_DATA_WIDTH_16|WIN_MEMORY_TYPE_CM|WIN_ENABLE|WIN_USE_WAIT;
|
|
+ req.Base = mem->win[0].host_addr;
|
|
+ req.Size = mem->win[0].len;
|
|
+ req.Size=0x1000;
|
|
+ req.AccessSpeed = 0;
|
|
+ if (pcmcia_request_window(&link, &req, &link->win) != 0)
|
|
+ goto next_entry;
|
|
+ map.Page = 0; map.CardOffset = mem->win[0].card_addr;
|
|
+ if (pcmcia_map_mem_page(link->win, &map) != 0)
|
|
+ goto next_entry;
|
|
+ else
|
|
+ printk(KERN_INFO "MEMORY WINDOW FOUND!!!\n");
|
|
+ }
|
|
+ /* If we got this far, we're cool! */
|
|
+ break;
|
|
+
|
|
+ next_entry:
|
|
+ CS_CHECK(GetNextTuple, pcmcia_get_next_tuple(link, &tuple));
|
|
+ }
|
|
+
|
|
+ if (link->conf.Attributes & CONF_ENABLE_IRQ) {
|
|
+ printk(KERN_INFO "requesting Irq...\n");
|
|
+ CS_CHECK(RequestIRQ, pcmcia_request_irq(link, &link->irq));
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ This actually configures the PCMCIA socket -- setting up
|
|
+ the I/O windows and the interrupt mapping, and putting the
|
|
+ card and host interface into "Memory and IO" mode.
|
|
+ */
|
|
+ CS_CHECK(RequestConfiguration, pcmcia_request_configuration(link, &link->conf));
|
|
+ DEBUG(0,"RequestConfiguration OK\n");
|
|
+
|
|
+
|
|
+ memwin.Base=req.Base;
|
|
+ memwin.Size=req.Size;
|
|
+
|
|
+ acx_init_netdev(local->ndev, &link->dev, memwin.Base, memwin.Size, link->irq.AssignedIRQ);
|
|
+
|
|
+#if 1
|
|
+ /*
|
|
+ At this point, the dev_node_t structure(s) need to be
|
|
+ initialized and arranged in a linked list at link->dev_node.
|
|
+ */
|
|
+ strcpy(local->node.dev_name, local->ndev->name );
|
|
+ local->node.major = local->node.minor = 0;
|
|
+ link->dev_node = &local->node;
|
|
+
|
|
+ /* Finally, report what we've done */
|
|
+ printk(KERN_INFO "%s: index 0x%02x: ",
|
|
+ local->ndev->name, link->conf.ConfigIndex);
|
|
+#endif
|
|
+ if (link->conf.Attributes & CONF_ENABLE_IRQ)
|
|
+ printk("irq %d", link->irq.AssignedIRQ);
|
|
+ if (link->io.NumPorts1)
|
|
+ printk(", io 0x%04x-0x%04x", link->io.BasePort1,
|
|
+ link->io.BasePort1+link->io.NumPorts1-1);
|
|
+ if (link->io.NumPorts2)
|
|
+ printk(" & 0x%04x-0x%04x", link->io.BasePort2,
|
|
+ link->io.BasePort2+link->io.NumPorts2-1);
|
|
+ if (link->win)
|
|
+ printk(", mem 0x%06lx-0x%06lx\n", req.Base,
|
|
+ req.Base+req.Size-1);
|
|
+ return 0;
|
|
+
|
|
+ cs_failed:
|
|
+ cs_error(link, last_fn, last_ret);
|
|
+ acx_cs_release(link);
|
|
+ return -ENODEV;
|
|
+} /* acx_config */
|
|
+
|
|
+/*======================================================================
|
|
+
|
|
+ After a card is removed, acx_release() will unregister the
|
|
+ device, and release the PCMCIA configuration. If the device is
|
|
+ still open, this will be postponed until it is closed.
|
|
+
|
|
+ ======================================================================*/
|
|
+
|
|
+static void acx_cs_release(struct pcmcia_device *link)
|
|
+{
|
|
+ DEBUG(0, "acx_release(0x%p)\n", link);
|
|
+ acxmem_e_remove(link);
|
|
+ pcmcia_disable_device(link);
|
|
+}
|
|
+
|
|
+static int acx_cs_suspend(struct pcmcia_device *link)
|
|
+{
|
|
+ local_info_t *local = link->priv;
|
|
+
|
|
+ pm_message_t state;
|
|
+ acxmem_e_suspend ( local->ndev, state);
|
|
+ /* Already done in suspend
|
|
+ * netif_device_detach(local->ndev); */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int acx_cs_resume(struct pcmcia_device *link)
|
|
+{
|
|
+ local_info_t *local = link->priv;
|
|
+
|
|
+ FN_ENTER;
|
|
+ resume_ndev = local->ndev;
|
|
+
|
|
+ schedule_work( &fw_resume_work );
|
|
+
|
|
+ /* Already done in suspend
|
|
+ if (link->open) {
|
|
+ // do we need reset for ACX, if so what function nane is ?
|
|
+ //reset_acx_card(local->eth_dev);
|
|
+ netif_device_attach(local->ndev);
|
|
+ } */
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct pcmcia_device_id acx_ids[] = {
|
|
+ PCMCIA_DEVICE_MANF_CARD(0x0097, 0x8402),
|
|
+ PCMCIA_DEVICE_MANF_CARD(0x0250, 0xb001),
|
|
+ PCMCIA_DEVICE_NULL,
|
|
+};
|
|
+MODULE_DEVICE_TABLE(pcmcia, acx_ids);
|
|
+
|
|
+static struct pcmcia_driver acx_driver = {
|
|
+ .owner = THIS_MODULE,
|
|
+ .drv = {
|
|
+ .name = "acx_cs",
|
|
+ },
|
|
+ .probe = acx_cs_probe,
|
|
+ .remove = acx_cs_detach,
|
|
+ .id_table = acx_ids,
|
|
+ .suspend = acx_cs_suspend,
|
|
+ .resume = acx_cs_resume,
|
|
+};
|
|
+
|
|
+int acx_cs_init(void)
|
|
+{
|
|
+ /* return success if at least one succeeded */
|
|
+ DEBUG(0, "acxcs_init()\n");
|
|
+ return pcmcia_register_driver(&acx_driver);
|
|
+}
|
|
+
|
|
+void acx_cs_cleanup(void)
|
|
+{
|
|
+ pcmcia_unregister_driver(&acx_driver);
|
|
+}
|
|
+
|
|
+/*
|
|
+ This program is free software; you can redistribute it and/or
|
|
+ modify it under the terms of the GNU General Public License
|
|
+ as published by the Free Software Foundation; either version 2
|
|
+ of the License, or (at your option) any later version.
|
|
+
|
|
+ This program is distributed in the hope that it will be useful,
|
|
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
+ GNU General Public License for more details.
|
|
+
|
|
+ In addition:
|
|
+
|
|
+ Redistribution and use in source and binary forms, with or without
|
|
+ modification, are permitted provided that the following conditions
|
|
+ are met:
|
|
+
|
|
+ 1. Redistributions of source code must retain the above copyright
|
|
+ notice, this list of conditions and the following disclaimer.
|
|
+ 2. Redistributions in binary form must reproduce the above copyright
|
|
+ notice, this list of conditions and the following disclaimer in the
|
|
+ documentation and/or other materials provided with the distribution.
|
|
+ 3. The name of the author may not be used to endorse or promote
|
|
+ products derived from this software without specific prior written
|
|
+ permission.
|
|
+
|
|
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
|
+ IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
+ ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
|
|
+ INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
+ HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
+ STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
|
|
+ IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
+ POSSIBILITY OF SUCH DAMAGE.
|
|
+*/
|
|
+
|
|
+MODULE_DESCRIPTION( "ACX Cardbus Driver" );
|
|
+MODULE_LICENSE( "GPL" );
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/htcsable_acx.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/htcsable_acx.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,118 @@
|
|
+/*
|
|
+ * WLAN (TI TNETW1100B) support in the HTC Sable
|
|
+ *
|
|
+ * Copyright (c) 2006 SDG Systems, LLC
|
|
+ *
|
|
+ * This file is subject to the terms and conditions of the GNU General Public
|
|
+ * License. See the file COPYING in the main directory of this archive for
|
|
+ * more details.
|
|
+ *
|
|
+ * 28-March-2006 Todd Blumer <todd@sdgsystems.com>
|
|
+ */
|
|
+
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#include <asm/hardware.h>
|
|
+
|
|
+#include <asm/arch/pxa-regs.h>
|
|
+#include <linux/mfd/asic3_base.h>
|
|
+#include <asm/arch/htcsable-gpio.h>
|
|
+#include <asm/arch/htcsable-asic.h>
|
|
+#include <asm/io.h>
|
|
+
|
|
+#include "acx_hw.h"
|
|
+
|
|
+#define WLAN_BASE PXA_CS2_PHYS
|
|
+
|
|
+/*
|
|
+off: b15 c8 d3
|
|
+on: d3 c8 b5 b5-
|
|
+*/
|
|
+
|
|
+#define GPIO_NR_HTCSABLE_ACX111 111
|
|
+
|
|
+static int
|
|
+htcsable_wlan_stop( void );
|
|
+
|
|
+static int
|
|
+htcsable_wlan_start( void )
|
|
+{
|
|
+ printk( "htcsable_wlan_start\n" );
|
|
+
|
|
+ /*asic3_set_gpio_out_c(&htcsable_asic3.dev, 1<<GPIOC_ACX_RESET, 0);*/
|
|
+ asic3_set_gpio_out_c(&htcsable_asic3.dev, 1<<GPIOC_ACX_PWR_3, 1<<GPIOC_ACX_PWR_3); /* related to acx */
|
|
+ SET_HTCSABLE_GPIO(ACX111, 1);
|
|
+ asic3_set_gpio_out_b(&htcsable_asic3.dev, 1<<GPIOB_ACX_PWR_1, 1<<GPIOB_ACX_PWR_1);
|
|
+ asic3_set_gpio_out_d(&htcsable_asic3.dev, 1<<GPIOD_ACX_PWR_2, 1<<GPIOD_ACX_PWR_2);
|
|
+ mdelay(260);
|
|
+ asic3_set_gpio_out_c(&htcsable_asic3.dev, 1<<GPIOC_ACX_RESET, 1<<GPIOC_ACX_RESET);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+htcsable_wlan_stop( void )
|
|
+{
|
|
+ printk( "htcsable_wlan_stop\n" );
|
|
+ asic3_set_gpio_out_b(&htcsable_asic3.dev, 1<<GPIOB_ACX_PWR_1, 0);
|
|
+ asic3_set_gpio_out_c(&htcsable_asic3.dev, 1<<GPIOC_ACX_RESET, 0);
|
|
+ asic3_set_gpio_out_d(&htcsable_asic3.dev, 1<<GPIOD_ACX_PWR_2, 0);
|
|
+ SET_HTCSABLE_GPIO(ACX111, 0); /* not necessary to power down this one? */
|
|
+ asic3_set_gpio_out_c(&htcsable_asic3.dev, 1<<GPIOC_ACX_PWR_3, 0); /* not necessary to power down this one? */
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct resource acx_resources[] = {
|
|
+ [0] = {
|
|
+ .start = WLAN_BASE,
|
|
+ .end = WLAN_BASE + 0x20,
|
|
+ .flags = IORESOURCE_MEM,
|
|
+ },
|
|
+ [1] = {
|
|
+// .start = asic3_irq_base(&htcsable_asic3.dev) + ASIC3_GPIOC_IRQ_BASE+GPIOC_WIFI_IRQ_N,
|
|
+// .end = asic3_irq_base(&htcsable_asic3.dev) + ASIC3_GPIOC_IRQ_BASE+GPIOC_WIFI_IRQ_N,
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct acx_hardware_data acx_data = {
|
|
+ .start_hw = htcsable_wlan_start,
|
|
+ .stop_hw = htcsable_wlan_stop,
|
|
+};
|
|
+
|
|
+static struct platform_device acx_device = {
|
|
+ .name = "acx-mem",
|
|
+ .dev = {
|
|
+ .platform_data = &acx_data,
|
|
+ },
|
|
+ .num_resources = ARRAY_SIZE( acx_resources ),
|
|
+ .resource = acx_resources,
|
|
+};
|
|
+
|
|
+static int __init
|
|
+htcsable_wlan_init( void )
|
|
+{
|
|
+ printk( "htcsable_wlan_init: acx-mem platform_device_register\n" );
|
|
+ acx_device.resource[1].start = asic3_irq_base(&htcsable_asic3.dev) + ASIC3_GPIOB_IRQ_BASE+GPIOB_ACX_IRQ_N;
|
|
+ acx_device.resource[1].end = asic3_irq_base(&htcsable_asic3.dev) + ASIC3_GPIOB_IRQ_BASE+GPIOB_ACX_IRQ_N;
|
|
+ return platform_device_register( &acx_device );
|
|
+}
|
|
+
|
|
+
|
|
+static void __exit
|
|
+htcsable_wlan_exit( void )
|
|
+{
|
|
+ platform_device_unregister( &acx_device );
|
|
+}
|
|
+
|
|
+module_init( htcsable_wlan_init );
|
|
+module_exit( htcsable_wlan_exit );
|
|
+
|
|
+MODULE_AUTHOR( "Todd Blumer <todd@sdgsystems.com>" );
|
|
+MODULE_DESCRIPTION( "WLAN driver for HTC Sable" );
|
|
+MODULE_LICENSE( "GPL" );
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/htcuniversal_acx.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/htcuniversal_acx.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,108 @@
|
|
+/*
|
|
+ * WLAN (TI TNETW1100B) support in the HTC Universal
|
|
+ *
|
|
+ * Copyright (c) 2006 SDG Systems, LLC
|
|
+ *
|
|
+ * This file is subject to the terms and conditions of the GNU General Public
|
|
+ * License. See the file COPYING in the main directory of this archive for
|
|
+ * more details.
|
|
+ *
|
|
+ * 28-March-2006 Todd Blumer <todd@sdgsystems.com>
|
|
+ */
|
|
+
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/delay.h>
|
|
+
|
|
+#include <asm/hardware.h>
|
|
+
|
|
+#include <asm/arch/pxa-regs.h>
|
|
+#include <linux/soc/asic3_base.h>
|
|
+#include <asm/arch/htcuniversal-gpio.h>
|
|
+#include <asm/arch/htcuniversal-asic.h>
|
|
+#include <asm/io.h>
|
|
+
|
|
+#include "acx_hw.h"
|
|
+
|
|
+#define WLAN_BASE PXA_CS2_PHYS
|
|
+
|
|
+
|
|
+static int
|
|
+htcuniversal_wlan_start( void )
|
|
+{
|
|
+ htcuniversal_egpio_enable(1<<EGPIO6_WIFI_ON);
|
|
+ asic3_set_gpio_out_c(&htcuniversal_asic3.dev, 1<<GPIOC_WIFI_PWR1_ON, 1<<GPIOC_WIFI_PWR1_ON);
|
|
+ asic3_set_gpio_out_d(&htcuniversal_asic3.dev, 1<<GPIOD_WIFI_PWR3_ON, 1<<GPIOD_WIFI_PWR3_ON);
|
|
+ asic3_set_gpio_out_d(&htcuniversal_asic3.dev, 1<<GPIOD_WIFI_PWR2_ON, 1<<GPIOD_WIFI_PWR2_ON);
|
|
+ mdelay(100);
|
|
+
|
|
+ asic3_set_gpio_out_c(&htcuniversal_asic3.dev, 1<<GPIOC_WIFI_RESET, 0);
|
|
+ mdelay(100);
|
|
+ asic3_set_gpio_out_c(&htcuniversal_asic3.dev, 1<<GPIOC_WIFI_RESET, 1<<GPIOC_WIFI_RESET);
|
|
+ mdelay(100);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+htcuniversal_wlan_stop( void )
|
|
+{
|
|
+ asic3_set_gpio_out_c(&htcuniversal_asic3.dev, 1<<GPIOC_WIFI_RESET, 0);
|
|
+
|
|
+ htcuniversal_egpio_disable(1<<EGPIO6_WIFI_ON);
|
|
+ asic3_set_gpio_out_c(&htcuniversal_asic3.dev, 1<<GPIOC_WIFI_PWR1_ON, 0);
|
|
+ asic3_set_gpio_out_d(&htcuniversal_asic3.dev, 1<<GPIOD_WIFI_PWR2_ON, 0);
|
|
+ asic3_set_gpio_out_d(&htcuniversal_asic3.dev, 1<<GPIOD_WIFI_PWR3_ON, 0);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct resource acx_resources[] = {
|
|
+ [0] = {
|
|
+ .start = WLAN_BASE,
|
|
+ .end = WLAN_BASE + 0x20,
|
|
+ .flags = IORESOURCE_MEM,
|
|
+ },
|
|
+ [1] = {
|
|
+// .start = asic3_irq_base(&htcuniversal_asic3.dev) + ASIC3_GPIOC_IRQ_BASE+GPIOC_WIFI_IRQ_N,
|
|
+// .end = asic3_irq_base(&htcuniversal_asic3.dev) + ASIC3_GPIOC_IRQ_BASE+GPIOC_WIFI_IRQ_N,
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct acx_hardware_data acx_data = {
|
|
+ .start_hw = htcuniversal_wlan_start,
|
|
+ .stop_hw = htcuniversal_wlan_stop,
|
|
+};
|
|
+
|
|
+static struct platform_device acx_device = {
|
|
+ .name = "acx-mem",
|
|
+ .dev = {
|
|
+ .platform_data = &acx_data,
|
|
+ },
|
|
+ .num_resources = ARRAY_SIZE( acx_resources ),
|
|
+ .resource = acx_resources,
|
|
+};
|
|
+
|
|
+static int __init
|
|
+htcuniversal_wlan_init( void )
|
|
+{
|
|
+ printk( "htcuniversal_wlan_init: acx-mem platform_device_register\n" );
|
|
+ acx_device.resource[1].start = asic3_irq_base(&htcuniversal_asic3.dev) + ASIC3_GPIOC_IRQ_BASE+GPIOC_WIFI_IRQ_N;
|
|
+ acx_device.resource[1].end = asic3_irq_base(&htcuniversal_asic3.dev) + ASIC3_GPIOC_IRQ_BASE+GPIOC_WIFI_IRQ_N;
|
|
+ return platform_device_register( &acx_device );
|
|
+}
|
|
+
|
|
+
|
|
+static void __exit
|
|
+htcuniversal_wlan_exit( void )
|
|
+{
|
|
+ platform_device_unregister( &acx_device );
|
|
+}
|
|
+
|
|
+module_init( htcuniversal_wlan_init );
|
|
+module_exit( htcuniversal_wlan_exit );
|
|
+
|
|
+MODULE_AUTHOR( "Todd Blumer <todd@sdgsystems.com>" );
|
|
+MODULE_DESCRIPTION( "WLAN driver for HTC Universal" );
|
|
+MODULE_LICENSE( "GPL" );
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/hx4700_acx.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/hx4700_acx.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,108 @@
|
|
+/*
|
|
+ * WLAN (TI TNETW1100B) support in the hx470x.
|
|
+ *
|
|
+ * Copyright (c) 2006 SDG Systems, LLC
|
|
+ *
|
|
+ * This file is subject to the terms and conditions of the GNU General Public
|
|
+ * License. See the file COPYING in the main directory of this archive for
|
|
+ * more details.
|
|
+ *
|
|
+ * 28-March-2006 Todd Blumer <todd@sdgsystems.com>
|
|
+ */
|
|
+
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/leds.h>
|
|
+
|
|
+#include <asm/hardware.h>
|
|
+
|
|
+#include <asm/arch/pxa-regs.h>
|
|
+#include <asm/arch/hx4700-gpio.h>
|
|
+#include <asm/arch/hx4700-core.h>
|
|
+#include <asm/io.h>
|
|
+
|
|
+#include "acx_hw.h"
|
|
+
|
|
+#define WLAN_OFFSET 0x1000000
|
|
+#define WLAN_BASE (PXA_CS5_PHYS+WLAN_OFFSET)
|
|
+
|
|
+
|
|
+static int
|
|
+hx4700_wlan_start( void )
|
|
+{
|
|
+ SET_HX4700_GPIO( WLAN_RESET_N, 0 );
|
|
+ mdelay(5);
|
|
+ hx4700_egpio_enable( EGPIO0_VCC_3V3_EN );
|
|
+ mdelay(100);
|
|
+ hx4700_egpio_enable( EGPIO7_VCC_3V3_WL_EN );
|
|
+ mdelay(150);
|
|
+ hx4700_egpio_enable( EGPIO1_WL_VREG_EN | EGPIO2_VCC_2V1_WL_EN |
|
|
+ EGPIO6_WL1V8_EN );
|
|
+ mdelay(10);
|
|
+ SET_HX4700_GPIO( WLAN_RESET_N, 1 );
|
|
+ mdelay(50);
|
|
+ led_trigger_event_shared(hx4700_radio_trig, LED_FULL);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+hx4700_wlan_stop( void )
|
|
+{
|
|
+ hx4700_egpio_disable( EGPIO0_VCC_3V3_EN | EGPIO1_WL_VREG_EN |
|
|
+ EGPIO7_VCC_3V3_WL_EN | EGPIO2_VCC_2V1_WL_EN |
|
|
+ EGPIO6_WL1V8_EN );
|
|
+ SET_HX4700_GPIO( WLAN_RESET_N, 0 );
|
|
+ led_trigger_event_shared(hx4700_radio_trig, LED_OFF);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct resource acx_resources[] = {
|
|
+ [0] = {
|
|
+ .start = WLAN_BASE,
|
|
+ .end = WLAN_BASE + 0x20,
|
|
+ .flags = IORESOURCE_MEM,
|
|
+ },
|
|
+ [1] = {
|
|
+ .start = HX4700_IRQ(WLAN_IRQ_N),
|
|
+ .end = HX4700_IRQ(WLAN_IRQ_N),
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct acx_hardware_data acx_data = {
|
|
+ .start_hw = hx4700_wlan_start,
|
|
+ .stop_hw = hx4700_wlan_stop,
|
|
+};
|
|
+
|
|
+static struct platform_device acx_device = {
|
|
+ .name = "acx-mem",
|
|
+ .dev = {
|
|
+ .platform_data = &acx_data,
|
|
+ },
|
|
+ .num_resources = ARRAY_SIZE( acx_resources ),
|
|
+ .resource = acx_resources,
|
|
+};
|
|
+
|
|
+static int __init
|
|
+hx4700_wlan_init( void )
|
|
+{
|
|
+ printk( "hx4700_wlan_init: acx-mem platform_device_register\n" );
|
|
+ return platform_device_register( &acx_device );
|
|
+}
|
|
+
|
|
+
|
|
+static void __exit
|
|
+hx4700_wlan_exit( void )
|
|
+{
|
|
+ platform_device_unregister( &acx_device );
|
|
+}
|
|
+
|
|
+module_init( hx4700_wlan_init );
|
|
+module_exit( hx4700_wlan_exit );
|
|
+
|
|
+MODULE_AUTHOR( "Todd Blumer <todd@sdgsystems.com>" );
|
|
+MODULE_DESCRIPTION( "WLAN driver for iPAQ hx4700" );
|
|
+MODULE_LICENSE( "GPL" );
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/ioctl.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/ioctl.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,2748 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/types.h>
|
|
+#include <asm/io.h>
|
|
+/* #include <asm/uaccess.h> */ /* required for 2.4.x kernels; verify_write() */
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+
|
|
+#include "acx.h"
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+
|
|
+/* channel frequencies
|
|
+ * TODO: Currently, every other 802.11 driver keeps its own copy of this. In
|
|
+ * the long run this should be integrated into ieee802_11.h or wireless.h or
|
|
+ * whatever IEEE802.11x framework evolves */
|
|
+static const u16 acx_channel_freq[] = {
|
|
+ 2412, 2417, 2422, 2427, 2432, 2437, 2442,
|
|
+ 2447, 2452, 2457, 2462, 2467, 2472, 2484,
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_commit
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_commit(struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ if (ACX_STATE_IFACE_UP & adev->dev_state_mask)
|
|
+ acx_s_update_card_settings(adev);
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_name(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ static const char * const names[] = { "IEEE 802.11b+/g+", "IEEE 802.11b+" };
|
|
+
|
|
+ strcpy(wrqu->name, names[IS_ACX111(adev) ? 0 : 1]);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_freq
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_freq(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int channel = -1;
|
|
+ unsigned int mult = 1;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (wrqu->freq.e == 0 && wrqu->freq.m <= 1000) {
|
|
+ /* Setting by channel number */
|
|
+ channel = wrqu->freq.m;
|
|
+ } else {
|
|
+ /* If setting by frequency, convert to a channel */
|
|
+ int i;
|
|
+
|
|
+ for (i = 0; i < (6 - wrqu->freq.e); i++)
|
|
+ mult *= 10;
|
|
+
|
|
+ for (i = 1; i <= 14; i++)
|
|
+ if (wrqu->freq.m == acx_channel_freq[i - 1] * mult)
|
|
+ channel = i;
|
|
+ }
|
|
+
|
|
+ if (channel > 14) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ adev->channel = channel;
|
|
+ /* hmm, the following code part is strange, but this is how
|
|
+ * it was being done before... */
|
|
+ log(L_IOCTL, "Changing to channel %d\n", channel);
|
|
+ SET_BIT(adev->set_mask, GETSET_CHANNEL);
|
|
+
|
|
+ result = -EINPROGRESS; /* need to call commit handler */
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static inline int
|
|
+acx_ioctl_get_freq(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ wrqu->freq.e = 0;
|
|
+ wrqu->freq.m = adev->channel;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_mode
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_mode(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ switch (wrqu->mode) {
|
|
+ case IW_MODE_AUTO:
|
|
+ adev->mode = ACX_MODE_OFF;
|
|
+ break;
|
|
+ case IW_MODE_MONITOR:
|
|
+ adev->mode = ACX_MODE_MONITOR;
|
|
+ break;
|
|
+ case IW_MODE_ADHOC:
|
|
+ adev->mode = ACX_MODE_0_ADHOC;
|
|
+ break;
|
|
+ case IW_MODE_INFRA:
|
|
+ adev->mode = ACX_MODE_2_STA;
|
|
+ break;
|
|
+ case IW_MODE_MASTER:
|
|
+ printk("acx: master mode (HostAP) is very, very "
|
|
+ "experimental! It might work partially, but "
|
|
+ "better get prepared for nasty surprises "
|
|
+ "at any time\n");
|
|
+ adev->mode = ACX_MODE_3_AP;
|
|
+ break;
|
|
+ case IW_MODE_REPEAT:
|
|
+ case IW_MODE_SECOND:
|
|
+ default:
|
|
+ result = -EOPNOTSUPP;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ log(L_ASSOC, "new adev->mode=%d\n", adev->mode);
|
|
+ SET_BIT(adev->set_mask, GETSET_MODE);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_mode(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result = 0;
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_OFF:
|
|
+ wrqu->mode = IW_MODE_AUTO; break;
|
|
+ case ACX_MODE_MONITOR:
|
|
+ wrqu->mode = IW_MODE_MONITOR; break;
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ wrqu->mode = IW_MODE_ADHOC; break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ wrqu->mode = IW_MODE_INFRA; break;
|
|
+ case ACX_MODE_3_AP:
|
|
+ wrqu->mode = IW_MODE_MASTER; break;
|
|
+ default:
|
|
+ result = -EOPNOTSUPP;
|
|
+ }
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_sens(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->sens;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ adev->sensitivity = (1 == vwrq->disabled) ? 0 : vwrq->value;
|
|
+ SET_BIT(adev->set_mask, GETSET_SENSITIVITY);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return -EINPROGRESS;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_sens(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->sens;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ if (IS_USB(adev))
|
|
+ /* setting the PHY reg via fw cmd doesn't work yet */
|
|
+ return -EOPNOTSUPP;
|
|
+
|
|
+ /* acx_sem_lock(adev); */
|
|
+
|
|
+ vwrq->value = adev->sensitivity;
|
|
+ vwrq->disabled = (vwrq->value == 0);
|
|
+ vwrq->fixed = 1;
|
|
+
|
|
+ /* acx_sem_unlock(adev); */
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_ap
|
|
+**
|
|
+** Sets the MAC address of the AP to associate with
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_ap(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct sockaddr *awrq = &wrqu->ap_addr;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result = 0;
|
|
+ const u8 *ap;
|
|
+
|
|
+ FN_ENTER;
|
|
+ if (NULL == awrq) {
|
|
+ result = -EFAULT;
|
|
+ goto end;
|
|
+ }
|
|
+ if (ARPHRD_ETHER != awrq->sa_family) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ ap = awrq->sa_data;
|
|
+ acxlog_mac(L_IOCTL, "set AP=", ap, "\n");
|
|
+
|
|
+ MAC_COPY(adev->ap, ap);
|
|
+
|
|
+ /* We want to start rescan in managed or ad-hoc mode,
|
|
+ ** otherwise just set adev->ap.
|
|
+ ** "iwconfig <if> ap <mac> mode managed": we must be able
|
|
+ ** to set ap _first_ and _then_ set mode */
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ /* FIXME: if there is a convention on what zero AP means,
|
|
+ ** please add a comment about that. I don't know of any --vda */
|
|
+ if (mac_is_zero(ap)) {
|
|
+ /* "off" == 00:00:00:00:00:00 */
|
|
+ MAC_BCAST(adev->ap);
|
|
+ log(L_IOCTL, "Not reassociating\n");
|
|
+ } else {
|
|
+ log(L_IOCTL, "Forcing reassociation\n");
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ result = -EINPROGRESS;
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_ap(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct sockaddr *awrq = &wrqu->ap_addr;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ if (ACX_STATUS_4_ASSOCIATED == adev->status) {
|
|
+ /* as seen in Aironet driver, airo.c */
|
|
+ MAC_COPY(awrq->sa_data, adev->bssid);
|
|
+ } else {
|
|
+ MAC_ZERO(awrq->sa_data);
|
|
+ }
|
|
+ awrq->sa_family = ARPHRD_ETHER;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_aplist
|
|
+**
|
|
+** Deprecated in favor of iwscan.
|
|
+** We simply return the list of currently available stations in range,
|
|
+** don't do a new scan.
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_aplist(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->data;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ struct sockaddr *address = (struct sockaddr *) extra;
|
|
+ struct iw_quality qual[IW_MAX_AP];
|
|
+ int i, cur;
|
|
+ int result = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* we have AP list only in STA mode */
|
|
+ if (ACX_MODE_2_STA != adev->mode) {
|
|
+ result = -EOPNOTSUPP;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ cur = 0;
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ struct client *bss = &adev->sta_list[i];
|
|
+ if (!bss->used) continue;
|
|
+ MAC_COPY(address[cur].sa_data, bss->bssid);
|
|
+ address[cur].sa_family = ARPHRD_ETHER;
|
|
+ qual[cur].level = bss->sir;
|
|
+ qual[cur].noise = bss->snr;
|
|
+#ifndef OLD_QUALITY
|
|
+ qual[cur].qual = acx_signal_determine_quality(qual[cur].level,
|
|
+ qual[cur].noise);
|
|
+#else
|
|
+ qual[cur].qual = (qual[cur].noise <= 100) ?
|
|
+ 100 - qual[cur].noise : 0;
|
|
+#endif
|
|
+ /* no scan: level/noise/qual not updated: */
|
|
+ qual[cur].updated = 0;
|
|
+ cur++;
|
|
+ }
|
|
+ if (cur) {
|
|
+ dwrq->flags = 1;
|
|
+ memcpy(extra + sizeof(struct sockaddr)*cur, &qual,
|
|
+ sizeof(struct iw_quality)*cur);
|
|
+ }
|
|
+ dwrq->length = cur;
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_scan(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* don't start scan if device is not up yet */
|
|
+ if (!(adev->dev_state_mask & ACX_STATE_IFACE_UP)) {
|
|
+ result = -EAGAIN;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ /* This is NOT a rescan for new AP!
|
|
+ ** Do not use SET_BIT(GETSET_RESCAN); */
|
|
+ acx_s_cmd_start_scan(adev);
|
|
+ result = OK;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+/* end: */
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_s_scan_add_station
|
|
+*/
|
|
+/* helper. not sure whether it's really a _s_leeping fn */
|
|
+static char*
|
|
+acx_s_scan_add_station(
|
|
+ acx_device_t *adev,
|
|
+ char *ptr,
|
|
+ char *end_buf,
|
|
+ struct client *bss)
|
|
+{
|
|
+ struct iw_event iwe;
|
|
+ char *ptr_rate;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* MAC address has to be added first */
|
|
+ iwe.cmd = SIOCGIWAP;
|
|
+ iwe.u.ap_addr.sa_family = ARPHRD_ETHER;
|
|
+ MAC_COPY(iwe.u.ap_addr.sa_data, bss->bssid);
|
|
+ acxlog_mac(L_IOCTL, "scan, station address: ", bss->bssid, "\n");
|
|
+ ptr = iwe_stream_add_event(ptr, end_buf, &iwe, IW_EV_ADDR_LEN);
|
|
+
|
|
+ /* Add ESSID */
|
|
+ iwe.cmd = SIOCGIWESSID;
|
|
+ iwe.u.data.length = bss->essid_len;
|
|
+ iwe.u.data.flags = 1;
|
|
+ log(L_IOCTL, "scan, essid: %s\n", bss->essid);
|
|
+ ptr = iwe_stream_add_point(ptr, end_buf, &iwe, bss->essid);
|
|
+
|
|
+ /* Add mode */
|
|
+ iwe.cmd = SIOCGIWMODE;
|
|
+ if (bss->cap_info & (WF_MGMT_CAP_ESS | WF_MGMT_CAP_IBSS)) {
|
|
+ if (bss->cap_info & WF_MGMT_CAP_ESS)
|
|
+ iwe.u.mode = IW_MODE_MASTER;
|
|
+ else
|
|
+ iwe.u.mode = IW_MODE_ADHOC;
|
|
+ log(L_IOCTL, "scan, mode: %d\n", iwe.u.mode);
|
|
+ ptr = iwe_stream_add_event(ptr, end_buf, &iwe, IW_EV_UINT_LEN);
|
|
+ }
|
|
+
|
|
+ /* Add frequency */
|
|
+ iwe.cmd = SIOCGIWFREQ;
|
|
+ iwe.u.freq.m = acx_channel_freq[bss->channel - 1] * 100000;
|
|
+ iwe.u.freq.e = 1;
|
|
+ log(L_IOCTL, "scan, frequency: %d\n", iwe.u.freq.m);
|
|
+ ptr = iwe_stream_add_event(ptr, end_buf, &iwe, IW_EV_FREQ_LEN);
|
|
+
|
|
+ /* Add link quality */
|
|
+ iwe.cmd = IWEVQUAL;
|
|
+ /* FIXME: these values should be expressed in dBm, but we don't know
|
|
+ * how to calibrate it yet */
|
|
+ iwe.u.qual.level = bss->sir;
|
|
+ iwe.u.qual.noise = bss->snr;
|
|
+#ifndef OLD_QUALITY
|
|
+ iwe.u.qual.qual = acx_signal_determine_quality(iwe.u.qual.level,
|
|
+ iwe.u.qual.noise);
|
|
+#else
|
|
+ iwe.u.qual.qual = (iwe.u.qual.noise <= 100) ?
|
|
+ 100 - iwe.u.qual.noise : 0;
|
|
+#endif
|
|
+ iwe.u.qual.updated = 7;
|
|
+ log(L_IOCTL, "scan, link quality: %d/%d/%d\n",
|
|
+ iwe.u.qual.level, iwe.u.qual.noise, iwe.u.qual.qual);
|
|
+ ptr = iwe_stream_add_event(ptr, end_buf, &iwe, IW_EV_QUAL_LEN);
|
|
+
|
|
+ /* Add encryption */
|
|
+ iwe.cmd = SIOCGIWENCODE;
|
|
+ if (bss->cap_info & WF_MGMT_CAP_PRIVACY)
|
|
+ iwe.u.data.flags = IW_ENCODE_ENABLED | IW_ENCODE_NOKEY;
|
|
+ else
|
|
+ iwe.u.data.flags = IW_ENCODE_DISABLED;
|
|
+ iwe.u.data.length = 0;
|
|
+ log(L_IOCTL, "scan, encryption flags: %X\n", iwe.u.data.flags);
|
|
+ ptr = iwe_stream_add_point(ptr, end_buf, &iwe, bss->essid);
|
|
+
|
|
+ /* add rates */
|
|
+ iwe.cmd = SIOCGIWRATE;
|
|
+ iwe.u.bitrate.fixed = iwe.u.bitrate.disabled = 0;
|
|
+ ptr_rate = ptr + IW_EV_LCP_LEN;
|
|
+
|
|
+ {
|
|
+ u16 rate = bss->rate_cap;
|
|
+ const u8* p = acx_bitpos2ratebyte;
|
|
+ while (rate) {
|
|
+ if (rate & 1) {
|
|
+ iwe.u.bitrate.value = *p * 500000; /* units of 500kb/s */
|
|
+ log(L_IOCTL, "scan, rate: %d\n", iwe.u.bitrate.value);
|
|
+ ptr_rate = iwe_stream_add_value(ptr, ptr_rate, end_buf,
|
|
+ &iwe, IW_EV_PARAM_LEN);
|
|
+ }
|
|
+ rate >>= 1;
|
|
+ p++;
|
|
+ }}
|
|
+
|
|
+ if ((ptr_rate - ptr) > (ptrdiff_t)IW_EV_LCP_LEN)
|
|
+ ptr = ptr_rate;
|
|
+
|
|
+ /* drop remaining station data items for now */
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return ptr;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+ * acx_ioctl_get_scan
|
|
+ */
|
|
+static int
|
|
+acx_ioctl_get_scan(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->data;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ char *ptr = extra;
|
|
+ int i;
|
|
+ int result = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* no scan available if device is not up yet */
|
|
+ if (!(adev->dev_state_mask & ACX_STATE_IFACE_UP)) {
|
|
+ log(L_IOCTL, "iface not up yet\n");
|
|
+ result = -EAGAIN;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+#ifdef ENODATA_TO_BE_USED_AFTER_SCAN_ERROR_ONLY
|
|
+ if (adev->bss_table_count == 0) {
|
|
+ /* no stations found */
|
|
+ result = -ENODATA;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ struct client *bss = &adev->sta_list[i];
|
|
+ if (!bss->used) continue;
|
|
+ ptr = acx_s_scan_add_station(adev, ptr,
|
|
+ extra + IW_SCAN_MAX_DATA, bss);
|
|
+ }
|
|
+ dwrq->length = ptr - extra;
|
|
+ dwrq->flags = 0;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+/* end: */
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_essid
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_essid(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->essid;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int len = dwrq->length;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (len < 0) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ log(L_IOCTL, "set ESSID '%*s', length %d, flags 0x%04X\n",
|
|
+ len, extra, len, dwrq->flags);
|
|
+
|
|
+#if WIRELESS_EXT >= 21
|
|
+ /* WE 21 gives real ESSID strlen, not +1 (trailing zero):
|
|
+ * see LKML "[patch] drivers/net/wireless: correct reported ssid lengths" */
|
|
+ len += 1;
|
|
+#endif
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* ESSID disabled? */
|
|
+ if (0 == dwrq->flags) {
|
|
+ adev->essid_active = 0;
|
|
+
|
|
+ } else {
|
|
+ if (len > IW_ESSID_MAX_SIZE) {
|
|
+ result = -E2BIG;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ if (len >= sizeof(adev->essid))
|
|
+ len = sizeof(adev->essid) - 1;
|
|
+ memcpy(adev->essid, extra, len);
|
|
+ adev->essid[len] = '\0';
|
|
+ /* Paranoia: just in case there is a '\0'... */
|
|
+ adev->essid_len = strlen(adev->essid);
|
|
+ adev->essid_active = 1;
|
|
+ }
|
|
+
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_essid(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->essid;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ dwrq->flags = adev->essid_active;
|
|
+ if (adev->essid_active) {
|
|
+ memcpy(extra, adev->essid, adev->essid_len);
|
|
+ extra[adev->essid_len] = '\0';
|
|
+ dwrq->length = adev->essid_len + 1;
|
|
+ dwrq->flags = 1;
|
|
+ }
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_l_update_client_rates
|
|
+*/
|
|
+static void
|
|
+acx_l_update_client_rates(acx_device_t *adev, u16 rate)
|
|
+{
|
|
+ int i;
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ client_t *clt = &adev->sta_list[i];
|
|
+ if (!clt->used) continue;
|
|
+ clt->rate_cfg = (clt->rate_cap & rate);
|
|
+ if (!clt->rate_cfg) {
|
|
+ /* no compatible rates left: kick client */
|
|
+ acxlog_mac(L_ASSOC, "client ",clt->address," kicked: "
|
|
+ "rates are not compatible anymore\n");
|
|
+ acx_l_sta_list_del(adev, clt);
|
|
+ continue;
|
|
+ }
|
|
+ clt->rate_cur &= clt->rate_cfg;
|
|
+ if (!clt->rate_cur) {
|
|
+ /* current rate become invalid, choose a valid one */
|
|
+ clt->rate_cur = 1 << lowest_bit(clt->rate_cfg);
|
|
+ }
|
|
+ if (IS_ACX100(adev))
|
|
+ clt->rate_100 = acx_bitpos2rate100[highest_bit(clt->rate_cur)];
|
|
+ clt->fallback_count = clt->stepup_count = 0;
|
|
+ clt->ignore_count = 16;
|
|
+ }
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_2_STA:
|
|
+ if (adev->ap_client && !adev->ap_client->used) {
|
|
+ /* Owwww... we kicked our AP!! :) */
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+/* maps bits from acx111 rate to rate in Mbits */
|
|
+static const unsigned int
|
|
+acx111_rate_tbl[] = {
|
|
+ 1000000, /* 0 */
|
|
+ 2000000, /* 1 */
|
|
+ 5500000, /* 2 */
|
|
+ 6000000, /* 3 */
|
|
+ 9000000, /* 4 */
|
|
+ 11000000, /* 5 */
|
|
+ 12000000, /* 6 */
|
|
+ 18000000, /* 7 */
|
|
+ 22000000, /* 8 */
|
|
+ 24000000, /* 9 */
|
|
+ 36000000, /* 10 */
|
|
+ 48000000, /* 11 */
|
|
+ 54000000, /* 12 */
|
|
+ 500000, /* 13, should not happen */
|
|
+ 500000, /* 14, should not happen */
|
|
+ 500000, /* 15, should not happen */
|
|
+};
|
|
+
|
|
+/***********************************************************************
|
|
+ * acx_ioctl_set_rate
|
|
+ */
|
|
+static int
|
|
+acx_ioctl_set_rate(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->param;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ u16 txrate_cfg = 1;
|
|
+ unsigned long flags;
|
|
+ int autorate;
|
|
+ int result = -EINVAL;
|
|
+
|
|
+ FN_ENTER;
|
|
+ log(L_IOCTL, "rate %d fixed 0x%X disabled 0x%X flags 0x%X\n",
|
|
+ vwrq->value, vwrq->fixed, vwrq->disabled, vwrq->flags);
|
|
+
|
|
+ if ((0 == vwrq->fixed) || (1 == vwrq->fixed)) {
|
|
+ int i = VEC_SIZE(acx111_rate_tbl)-1;
|
|
+ if (vwrq->value == -1)
|
|
+ /* "iwconfig rate auto" --> choose highest */
|
|
+ vwrq->value = IS_ACX100(adev) ? 22000000 : 54000000;
|
|
+ while (i >= 0) {
|
|
+ if (vwrq->value == acx111_rate_tbl[i]) {
|
|
+ txrate_cfg <<= i;
|
|
+ i = 0;
|
|
+ break;
|
|
+ }
|
|
+ i--;
|
|
+ }
|
|
+ if (i == -1) { /* no matching rate */
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+ } else { /* rate N, N<1000 (driver specific): we don't use this */
|
|
+ result = -EOPNOTSUPP;
|
|
+ goto end;
|
|
+ }
|
|
+ /* now: only one bit is set in txrate_cfg, corresponding to
|
|
+ ** indicated rate */
|
|
+
|
|
+ autorate = (vwrq->fixed == 0) && (RATE111_1 != txrate_cfg);
|
|
+ if (autorate) {
|
|
+ /* convert 00100000 -> 00111111 */
|
|
+ txrate_cfg = (txrate_cfg<<1)-1;
|
|
+ }
|
|
+
|
|
+ if (IS_ACX100(adev)) {
|
|
+ txrate_cfg &= RATE111_ACX100_COMPAT;
|
|
+ if (!txrate_cfg) {
|
|
+ result = -ENOTSUPP; /* rate is not supported by acx100 */
|
|
+ goto end;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ adev->rate_auto = autorate;
|
|
+ adev->rate_oper = txrate_cfg;
|
|
+ adev->rate_basic = txrate_cfg;
|
|
+ /* only do that in auto mode, non-auto will be able to use
|
|
+ * one specific Tx rate only anyway */
|
|
+ if (autorate) {
|
|
+ /* only use 802.11b base rates, for standard 802.11b H/W
|
|
+ * compatibility */
|
|
+ adev->rate_basic &= RATE111_80211B_COMPAT;
|
|
+ }
|
|
+ adev->rate_bcast = 1 << lowest_bit(txrate_cfg);
|
|
+ if (IS_ACX100(adev))
|
|
+ adev->rate_bcast100 = acx_rate111to100(adev->rate_bcast);
|
|
+ acx_l_update_ratevector(adev);
|
|
+ acx_l_update_client_rates(adev, txrate_cfg);
|
|
+
|
|
+ /* Do/don't do tx rate fallback; beacon contents and rate */
|
|
+ SET_BIT(adev->set_mask, SET_RATE_FALLBACK|SET_TEMPLATES);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_rate
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_rate(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->param;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ u16 rate;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ rate = adev->rate_oper;
|
|
+ if (adev->ap_client)
|
|
+ rate = adev->ap_client->rate_cur;
|
|
+ vwrq->value = acx111_rate_tbl[highest_bit(rate)];
|
|
+ vwrq->fixed = !adev->rate_auto;
|
|
+ vwrq->disabled = 0;
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_ioctl_set_rts(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->rts;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int val = vwrq->value;
|
|
+
|
|
+ if (vwrq->disabled)
|
|
+ val = 2312;
|
|
+ if ((val < 0) || (val > 2312))
|
|
+ return -EINVAL;
|
|
+
|
|
+ adev->rts_threshold = val;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_ioctl_get_rts(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->rts;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ vwrq->value = adev->rts_threshold;
|
|
+ vwrq->disabled = (vwrq->value >= 2312);
|
|
+ vwrq->fixed = 1;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+#if ACX_FRAGMENTATION
|
|
+static int
|
|
+acx_ioctl_set_frag(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int val = vwrq->value;
|
|
+
|
|
+ if (vwrq->disabled)
|
|
+ val = 32767;
|
|
+ else
|
|
+ if ((val < 256) || (val > 2347))
|
|
+ return -EINVAL;
|
|
+
|
|
+ adev->frag_threshold = val;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static inline int
|
|
+acx_ioctl_get_frag(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->frag;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ vwrq->value = adev->frag_threshold;
|
|
+ vwrq->disabled = (vwrq->value >= 2347);
|
|
+ vwrq->fixed = 1;
|
|
+ return OK;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_encode
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_encode(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->encoding;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int index;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_IOCTL, "set encoding flags=0x%04X, size=%d, key: %s\n",
|
|
+ dwrq->flags, dwrq->length, extra ? "set" : "No key");
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
|
|
+
|
|
+ if (dwrq->length > 0) {
|
|
+ /* if index is 0 or invalid, use default key */
|
|
+ if ((index < 0) || (index > 3))
|
|
+ index = (int)adev->wep_current_index;
|
|
+
|
|
+ if (0 == (dwrq->flags & IW_ENCODE_NOKEY)) {
|
|
+ if (dwrq->length > 29)
|
|
+ dwrq->length = 29; /* restrict it */
|
|
+
|
|
+ if (dwrq->length > 13) {
|
|
+ /* 29*8 == 232, WEP256 */
|
|
+ adev->wep_keys[index].size = 29;
|
|
+ } else if (dwrq->length > 5) {
|
|
+ /* 13*8 == 104bit, WEP128 */
|
|
+ adev->wep_keys[index].size = 13;
|
|
+ } else if (dwrq->length > 0) {
|
|
+ /* 5*8 == 40bit, WEP64 */
|
|
+ adev->wep_keys[index].size = 5;
|
|
+ } else {
|
|
+ /* disable key */
|
|
+ adev->wep_keys[index].size = 0;
|
|
+ }
|
|
+
|
|
+ memset(adev->wep_keys[index].key, 0,
|
|
+ sizeof(adev->wep_keys[index].key));
|
|
+ memcpy(adev->wep_keys[index].key, extra, dwrq->length);
|
|
+ }
|
|
+ } else {
|
|
+ /* set transmit key */
|
|
+ if ((index >= 0) && (index <= 3))
|
|
+ adev->wep_current_index = index;
|
|
+ else if (0 == (dwrq->flags & IW_ENCODE_MODE)) {
|
|
+ /* complain if we were not just setting
|
|
+ * the key mode */
|
|
+ result = -EINVAL;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ adev->wep_enabled = !(dwrq->flags & IW_ENCODE_DISABLED);
|
|
+
|
|
+ if (dwrq->flags & IW_ENCODE_OPEN) {
|
|
+ adev->auth_alg = WLAN_AUTH_ALG_OPENSYSTEM;
|
|
+ adev->wep_restricted = 0;
|
|
+
|
|
+ } else if (dwrq->flags & IW_ENCODE_RESTRICTED) {
|
|
+ adev->auth_alg = WLAN_AUTH_ALG_SHAREDKEY;
|
|
+ adev->wep_restricted = 1;
|
|
+ }
|
|
+
|
|
+ /* set flag to make sure the card WEP settings get updated */
|
|
+ SET_BIT(adev->set_mask, GETSET_WEP);
|
|
+
|
|
+ log(L_IOCTL, "len=%d, key at 0x%p, flags=0x%X\n",
|
|
+ dwrq->length, extra, dwrq->flags);
|
|
+
|
|
+ for (index = 0; index <= 3; index++) {
|
|
+ if (adev->wep_keys[index].size) {
|
|
+ log(L_IOCTL, "index=%d, size=%d, key at 0x%p\n",
|
|
+ adev->wep_keys[index].index,
|
|
+ (int) adev->wep_keys[index].size,
|
|
+ adev->wep_keys[index].key);
|
|
+ }
|
|
+ }
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_encode
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_encode(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->encoding;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int index = (dwrq->flags & IW_ENCODE_INDEX) - 1;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (adev->wep_enabled == 0) {
|
|
+ dwrq->flags = IW_ENCODE_DISABLED;
|
|
+ } else {
|
|
+ if ((index < 0) || (index > 3))
|
|
+ index = (int)adev->wep_current_index;
|
|
+
|
|
+ dwrq->flags = (adev->wep_restricted == 1) ?
|
|
+ IW_ENCODE_RESTRICTED : IW_ENCODE_OPEN;
|
|
+ dwrq->length = adev->wep_keys[index].size;
|
|
+
|
|
+ memcpy(extra, adev->wep_keys[index].key,
|
|
+ adev->wep_keys[index].size);
|
|
+ }
|
|
+
|
|
+ /* set the current index */
|
|
+ SET_BIT(dwrq->flags, index + 1);
|
|
+
|
|
+ log(L_IOCTL, "len=%d, key=%p, flags=0x%X\n",
|
|
+ dwrq->length, dwrq->pointer,
|
|
+ dwrq->flags);
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_power(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->power;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result = -EINPROGRESS;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_IOCTL, "set 802.11 powersave flags=0x%04X\n", vwrq->flags);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (vwrq->disabled) {
|
|
+ CLEAR_BIT(adev->ps_wakeup_cfg, PS_CFG_ENABLE);
|
|
+ SET_BIT(adev->set_mask, GETSET_POWER_80211);
|
|
+ goto end;
|
|
+ }
|
|
+ if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_TIMEOUT) {
|
|
+ u16 ps_timeout = (vwrq->value * 1024) / 1000;
|
|
+
|
|
+ if (ps_timeout > 255)
|
|
+ ps_timeout = 255;
|
|
+ log(L_IOCTL, "setting PS timeout value to %d time units "
|
|
+ "due to %dus\n", ps_timeout, vwrq->value);
|
|
+ adev->ps_hangover_period = ps_timeout;
|
|
+ } else if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_PERIOD) {
|
|
+ u16 ps_periods = vwrq->value / 1000000;
|
|
+
|
|
+ if (ps_periods > 255)
|
|
+ ps_periods = 255;
|
|
+ log(L_IOCTL, "setting PS period value to %d periods "
|
|
+ "due to %dus\n", ps_periods, vwrq->value);
|
|
+ adev->ps_listen_interval = ps_periods;
|
|
+ CLEAR_BIT(adev->ps_wakeup_cfg, PS_CFG_WAKEUP_MODE_MASK);
|
|
+ SET_BIT(adev->ps_wakeup_cfg, PS_CFG_WAKEUP_EACH_ITVL);
|
|
+ }
|
|
+
|
|
+ switch (vwrq->flags & IW_POWER_MODE) {
|
|
+ /* FIXME: are we doing the right thing here? */
|
|
+ case IW_POWER_UNICAST_R:
|
|
+ CLEAR_BIT(adev->ps_options, PS_OPT_STILL_RCV_BCASTS);
|
|
+ break;
|
|
+ case IW_POWER_MULTICAST_R:
|
|
+ SET_BIT(adev->ps_options, PS_OPT_STILL_RCV_BCASTS);
|
|
+ break;
|
|
+ case IW_POWER_ALL_R:
|
|
+ SET_BIT(adev->ps_options, PS_OPT_STILL_RCV_BCASTS);
|
|
+ break;
|
|
+ case IW_POWER_ON:
|
|
+ break;
|
|
+ default:
|
|
+ log(L_IOCTL, "unknown PS mode\n");
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ SET_BIT(adev->ps_wakeup_cfg, PS_CFG_ENABLE);
|
|
+ SET_BIT(adev->set_mask, GETSET_POWER_80211);
|
|
+end:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_power(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->power;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_IOCTL, "Get 802.11 Power Save flags = 0x%04X\n", vwrq->flags);
|
|
+ vwrq->disabled = ((adev->ps_wakeup_cfg & PS_CFG_ENABLE) == 0);
|
|
+ if (vwrq->disabled)
|
|
+ goto end;
|
|
+
|
|
+ if ((vwrq->flags & IW_POWER_TYPE) == IW_POWER_TIMEOUT) {
|
|
+ vwrq->value = adev->ps_hangover_period * 1000 / 1024;
|
|
+ vwrq->flags = IW_POWER_TIMEOUT;
|
|
+ } else {
|
|
+ vwrq->value = adev->ps_listen_interval * 1000000;
|
|
+ vwrq->flags = IW_POWER_PERIOD|IW_POWER_RELATIVE;
|
|
+ }
|
|
+ if (adev->ps_options & PS_OPT_STILL_RCV_BCASTS)
|
|
+ SET_BIT(vwrq->flags, IW_POWER_ALL_R);
|
|
+ else
|
|
+ SET_BIT(vwrq->flags, IW_POWER_UNICAST_R);
|
|
+end:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_txpow
|
|
+*/
|
|
+static inline int
|
|
+acx_ioctl_get_txpow(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->power;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ vwrq->flags = IW_TXPOW_DBM;
|
|
+ vwrq->disabled = 0;
|
|
+ vwrq->fixed = 1;
|
|
+ vwrq->value = adev->tx_level_dbm;
|
|
+
|
|
+ log(L_IOCTL, "get txpower:%d dBm\n", adev->tx_level_dbm);
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_txpow
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_txpow(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->power;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_IOCTL, "set txpower:%d, disabled:%d, flags:0x%04X\n",
|
|
+ vwrq->value, vwrq->disabled, vwrq->flags);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (vwrq->disabled != adev->tx_disabled) {
|
|
+ SET_BIT(adev->set_mask, GETSET_TX);
|
|
+ }
|
|
+
|
|
+ adev->tx_disabled = vwrq->disabled;
|
|
+ if (vwrq->value == -1) {
|
|
+ if (vwrq->disabled) {
|
|
+ adev->tx_level_dbm = 0;
|
|
+ log(L_IOCTL, "disable radio tx\n");
|
|
+ } else {
|
|
+ /* adev->tx_level_auto = 1; */
|
|
+ log(L_IOCTL, "set tx power auto (NIY)\n");
|
|
+ }
|
|
+ } else {
|
|
+ adev->tx_level_dbm = vwrq->value <= 20 ? vwrq->value : 20;
|
|
+ /* adev->tx_level_auto = 0; */
|
|
+ log(L_IOCTL, "set txpower=%d dBm\n", adev->tx_level_dbm);
|
|
+ }
|
|
+ SET_BIT(adev->set_mask, GETSET_TXPOWER);
|
|
+
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_range
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_range(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->data;
|
|
+ struct iw_range *range = (struct iw_range *)extra;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int i,n;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (!dwrq->pointer)
|
|
+ goto end;
|
|
+
|
|
+ dwrq->length = sizeof(struct iw_range);
|
|
+ memset(range, 0, sizeof(struct iw_range));
|
|
+ n = 0;
|
|
+ for (i = 1; i <= 14; i++) {
|
|
+ if (adev->reg_dom_chanmask & (1 << (i - 1))) {
|
|
+ range->freq[n].i = i;
|
|
+ range->freq[n].m = acx_channel_freq[i - 1] * 100000;
|
|
+ range->freq[n].e = 1; /* units are MHz */
|
|
+ n++;
|
|
+ }
|
|
+ }
|
|
+ range->num_channels = n;
|
|
+ range->num_frequency = n;
|
|
+
|
|
+ range->min_rts = 0;
|
|
+ range->max_rts = 2312;
|
|
+
|
|
+#if ACX_FRAGMENTATION
|
|
+ range->min_frag = 256;
|
|
+ range->max_frag = 2312;
|
|
+#endif
|
|
+
|
|
+ range->encoding_size[0] = 5;
|
|
+ range->encoding_size[1] = 13;
|
|
+ range->encoding_size[2] = 29;
|
|
+ range->num_encoding_sizes = 3;
|
|
+ range->max_encoding_tokens = 4;
|
|
+
|
|
+ range->min_pmp = 0;
|
|
+ range->max_pmp = 5000000;
|
|
+ range->min_pmt = 0;
|
|
+ range->max_pmt = 65535 * 1000;
|
|
+ range->pmp_flags = IW_POWER_PERIOD;
|
|
+ range->pmt_flags = IW_POWER_TIMEOUT;
|
|
+ range->pm_capa = IW_POWER_PERIOD | IW_POWER_TIMEOUT | IW_POWER_ALL_R;
|
|
+
|
|
+ if (IS_ACX100(adev)) { /* ACX100 has direct radio programming - arbitrary levels, so offer a lot */
|
|
+ for (i = 0; i <= IW_MAX_TXPOWER - 1; i++)
|
|
+ range->txpower[i] = 20 * i / (IW_MAX_TXPOWER - 1);
|
|
+ range->num_txpower = IW_MAX_TXPOWER;
|
|
+ range->txpower_capa = IW_TXPOW_DBM;
|
|
+ }
|
|
+ else {
|
|
+ int count = min(IW_MAX_TXPOWER, (int)adev->cfgopt_power_levels.len);
|
|
+ for (i = 0; i <= count; i++)
|
|
+ range->txpower[i] = adev->cfgopt_power_levels.list[i];
|
|
+ range->num_txpower = count;
|
|
+ /* this list is given in mW */
|
|
+ range->txpower_capa = IW_TXPOW_MWATT;
|
|
+ }
|
|
+
|
|
+ range->we_version_compiled = WIRELESS_EXT;
|
|
+ range->we_version_source = 0x9;
|
|
+
|
|
+ range->retry_capa = IW_RETRY_LIMIT;
|
|
+ range->retry_flags = IW_RETRY_LIMIT;
|
|
+ range->min_retry = 1;
|
|
+ range->max_retry = 255;
|
|
+
|
|
+ range->r_time_flags = IW_RETRY_LIFETIME;
|
|
+ range->min_r_time = 0;
|
|
+ /* FIXME: lifetime ranges and orders of magnitude are strange?? */
|
|
+ range->max_r_time = 65535;
|
|
+
|
|
+ if (IS_USB(adev))
|
|
+ range->sensitivity = 0;
|
|
+ else if (IS_ACX111(adev))
|
|
+ range->sensitivity = 3;
|
|
+ else
|
|
+ range->sensitivity = 255;
|
|
+
|
|
+ for (i=0; i < adev->rate_supported_len; i++) {
|
|
+ range->bitrate[i] = (adev->rate_supported[i] & ~0x80) * 500000;
|
|
+ /* never happens, but keep it, to be safe: */
|
|
+ if (range->bitrate[i] == 0)
|
|
+ break;
|
|
+ }
|
|
+ range->num_bitrates = i;
|
|
+
|
|
+ range->max_qual.qual = 100;
|
|
+ range->max_qual.level = 100;
|
|
+ range->max_qual.noise = 100;
|
|
+ /* TODO: better values */
|
|
+ range->avg_qual.qual = 90;
|
|
+ range->avg_qual.level = 80;
|
|
+ range->avg_qual.noise = 2;
|
|
+
|
|
+end:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Private functions
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_nick
|
|
+*/
|
|
+static inline int
|
|
+acx_ioctl_get_nick(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->data;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ strcpy(extra, adev->nick);
|
|
+ dwrq->length = strlen(extra) + 1;
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_nick
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_nick(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_point *dwrq = &wrqu->data;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (dwrq->length > IW_ESSID_MAX_SIZE + 1) {
|
|
+ result = -E2BIG;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ /* extra includes trailing \0, so it's ok */
|
|
+ strcpy(adev->nick, extra);
|
|
+ result = OK;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_retry
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_retry(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->retry;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned int type = vwrq->flags & IW_RETRY_TYPE;
|
|
+ unsigned int modifier = vwrq->flags & IW_RETRY_MODIFIER;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* return the short retry number by default */
|
|
+ if (type == IW_RETRY_LIFETIME) {
|
|
+ vwrq->flags = IW_RETRY_LIFETIME;
|
|
+ vwrq->value = adev->msdu_lifetime;
|
|
+ } else if (modifier == IW_RETRY_MAX) {
|
|
+ vwrq->flags = IW_RETRY_LIMIT | IW_RETRY_MAX;
|
|
+ vwrq->value = adev->long_retry;
|
|
+ } else {
|
|
+ vwrq->flags = IW_RETRY_LIMIT;
|
|
+ if (adev->long_retry != adev->short_retry)
|
|
+ SET_BIT(vwrq->flags, IW_RETRY_MIN);
|
|
+ vwrq->value = adev->short_retry;
|
|
+ }
|
|
+
|
|
+ /* can't be disabled */
|
|
+ vwrq->disabled = (u8)0;
|
|
+ result = OK;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_retry
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_retry(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->retry;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (!vwrq) {
|
|
+ result = -EFAULT;
|
|
+ goto end;
|
|
+ }
|
|
+ if (vwrq->disabled) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ result = -EINVAL;
|
|
+ if (IW_RETRY_LIMIT == (vwrq->flags & IW_RETRY_TYPE)) {
|
|
+ printk("old retry limits: short %d long %d\n",
|
|
+ adev->short_retry, adev->long_retry);
|
|
+ if (vwrq->flags & IW_RETRY_MAX) {
|
|
+ adev->long_retry = vwrq->value;
|
|
+ } else if (vwrq->flags & IW_RETRY_MIN) {
|
|
+ adev->short_retry = vwrq->value;
|
|
+ } else {
|
|
+ /* no modifier: set both */
|
|
+ adev->long_retry = vwrq->value;
|
|
+ adev->short_retry = vwrq->value;
|
|
+ }
|
|
+ printk("new retry limits: short %d long %d\n",
|
|
+ adev->short_retry, adev->long_retry);
|
|
+ SET_BIT(adev->set_mask, GETSET_RETRY);
|
|
+ result = -EINPROGRESS;
|
|
+ }
|
|
+ else if (vwrq->flags & IW_RETRY_LIFETIME) {
|
|
+ adev->msdu_lifetime = vwrq->value;
|
|
+ printk("new MSDU lifetime: %d\n", adev->msdu_lifetime);
|
|
+ SET_BIT(adev->set_mask, SET_MSDU_LIFETIME);
|
|
+ result = -EINPROGRESS;
|
|
+ }
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/************************ private ioctls ******************************/
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_debug
|
|
+*/
|
|
+#if ACX_DEBUG
|
|
+static int
|
|
+acx_ioctl_set_debug(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ unsigned int debug_new = *((unsigned int *)extra);
|
|
+ int result = -EINVAL;
|
|
+
|
|
+ log(L_ANY, "setting debug from %04X to %04X\n", acx_debug, debug_new);
|
|
+ acx_debug = debug_new;
|
|
+
|
|
+ result = OK;
|
|
+ return result;
|
|
+
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_list_reg_domain
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_list_reg_domain(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ int i = 1;
|
|
+ const char * const *entry = acx_reg_domain_strings;
|
|
+
|
|
+ printk("dom# chan# domain/country\n");
|
|
+ while (*entry)
|
|
+ printk("%4d %s\n", i++, *entry++);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_reg_domain
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_reg_domain(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if ((*extra < 1) || ((size_t)*extra > acx_reg_domain_ids_len)) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ adev->reg_dom_id = acx_reg_domain_ids[*extra - 1];
|
|
+ SET_BIT(adev->set_mask, GETSET_REG_DOMAIN);
|
|
+
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_reg_domain
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_reg_domain(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int dom,i;
|
|
+
|
|
+ /* no locking */
|
|
+ dom = adev->reg_dom_id;
|
|
+
|
|
+ for (i = 1; i <= acx_reg_domain_ids_len; i++) {
|
|
+ if (acx_reg_domain_ids[i-1] == dom) {
|
|
+ log(L_IOCTL, "regulatory domain is currently set "
|
|
+ "to %d (0x%X): %s\n", i, dom,
|
|
+ acx_reg_domain_strings[i-1]);
|
|
+ *extra = i;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_short_preamble
|
|
+*/
|
|
+static const char * const
|
|
+preamble_modes[] = {
|
|
+ "off",
|
|
+ "on",
|
|
+ "auto (peer capability dependent)",
|
|
+ "unknown mode, error"
|
|
+};
|
|
+
|
|
+static int
|
|
+acx_ioctl_set_short_preamble(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int i;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if ((unsigned char)*extra > 2) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ adev->preamble_mode = (u8)*extra;
|
|
+ switch (adev->preamble_mode) {
|
|
+ case 0: /* long */
|
|
+ adev->preamble_cur = 0;
|
|
+ break;
|
|
+ case 1:
|
|
+ /* short, kick incapable peers */
|
|
+ adev->preamble_cur = 1;
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ client_t *clt = &adev->sta_list[i];
|
|
+ if (!clt->used) continue;
|
|
+ if (!(clt->cap_info & WF_MGMT_CAP_SHORT)) {
|
|
+ clt->used = CLIENT_EMPTY_SLOT_0;
|
|
+ }
|
|
+ }
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_2_STA:
|
|
+ if (adev->ap_client && !adev->ap_client->used) {
|
|
+ /* We kicked our AP :) */
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ case 2: /* auto. short only if all peers are short-capable */
|
|
+ adev->preamble_cur = 1;
|
|
+ for (i = 0; i < VEC_SIZE(adev->sta_list); i++) {
|
|
+ client_t *clt = &adev->sta_list[i];
|
|
+ if (!clt->used) continue;
|
|
+ if (!(clt->cap_info & WF_MGMT_CAP_SHORT)) {
|
|
+ adev->preamble_cur = 0;
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ printk("new short preamble setting: configured %s, active %s\n",
|
|
+ preamble_modes[adev->preamble_mode],
|
|
+ preamble_modes[adev->preamble_cur]);
|
|
+ result = OK;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_short_preamble
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_short_preamble(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ printk("current short preamble setting: configured %s, active %s\n",
|
|
+ preamble_modes[adev->preamble_mode],
|
|
+ preamble_modes[adev->preamble_cur]);
|
|
+
|
|
+ *extra = (char)adev->preamble_mode;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_antenna
|
|
+**
|
|
+** TX and RX antenna can be set separately but this function good
|
|
+** for testing 0-4 bits
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_antenna(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ printk("old antenna value: 0x%02X (COMBINED bit mask)\n"
|
|
+ "Rx antenna selection:\n"
|
|
+ "0x00 ant. 1\n"
|
|
+ "0x40 ant. 2\n"
|
|
+ "0x80 full diversity\n"
|
|
+ "0xc0 partial diversity\n"
|
|
+ "0x0f dwell time mask (in units of us)\n"
|
|
+ "Tx antenna selection:\n"
|
|
+ "0x00 ant. 2\n" /* yep, those ARE reversed! */
|
|
+ "0x20 ant. 1\n"
|
|
+ "new antenna value: 0x%02X\n",
|
|
+ adev->antenna, (u8)*extra);
|
|
+
|
|
+ adev->antenna = (u8)*extra;
|
|
+ SET_BIT(adev->set_mask, GETSET_ANTENNA);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return -EINPROGRESS;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_antenna
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_antenna(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ /* no locking. it's pointless to lock a single load */
|
|
+ printk("current antenna value: 0x%02X (COMBINED bit mask)\n"
|
|
+ "Rx antenna selection:\n"
|
|
+ "0x00 ant. 1\n"
|
|
+ "0x40 ant. 2\n"
|
|
+ "0x80 full diversity\n"
|
|
+ "0xc0 partial diversity\n"
|
|
+ "Tx antenna selection:\n"
|
|
+ "0x00 ant. 2\n" /* yep, those ARE reversed! */
|
|
+ "0x20 ant. 1\n", adev->antenna);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_rx_antenna
|
|
+**
|
|
+** 0 = antenna1; 1 = antenna2; 2 = full diversity; 3 = partial diversity
|
|
+** Could anybody test which antenna is the external one?
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_rx_antenna(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (*extra > 3) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ printk("old antenna value: 0x%02X\n", adev->antenna);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ adev->antenna &= 0x3f;
|
|
+ SET_BIT(adev->antenna, (*extra << 6));
|
|
+ SET_BIT(adev->set_mask, GETSET_ANTENNA);
|
|
+ printk("new antenna value: 0x%02X\n", adev->antenna);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_tx_antenna
|
|
+**
|
|
+** Arguments: 0 == antenna2; 1 == antenna1;
|
|
+** Could anybody test which antenna is the external one?
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_set_tx_antenna(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (*extra > 1) {
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ printk("old antenna value: 0x%02X\n", adev->antenna);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ adev->antenna &= ~0x30;
|
|
+ SET_BIT(adev->antenna, ((*extra & 0x01) << 5));
|
|
+ SET_BIT(adev->set_mask, GETSET_ANTENNA);
|
|
+ printk("new antenna value: 0x%02X\n", adev->antenna);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_wlansniff
|
|
+**
|
|
+** can we just remove this in favor of monitor mode? --vda
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_wlansniff(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned int *params = (unsigned int*)extra;
|
|
+ unsigned int enable = (unsigned int)(params[0] > 0);
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* not using printk() here, since it distorts kismet display
|
|
+ * when printk messages activated */
|
|
+ log(L_IOCTL, "setting monitor to: 0x%02X\n", params[0]);
|
|
+
|
|
+ switch (params[0]) {
|
|
+ case 0:
|
|
+ /* no monitor mode. hmm, should we simply ignore it
|
|
+ * or go back to enabling adev->netdev->type ARPHRD_ETHER? */
|
|
+ break;
|
|
+ case 1:
|
|
+ adev->monitor_type = ARPHRD_IEEE80211_PRISM;
|
|
+ break;
|
|
+ case 2:
|
|
+ adev->monitor_type = ARPHRD_IEEE80211;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (params[0]) {
|
|
+ adev->mode = ACX_MODE_MONITOR;
|
|
+ SET_BIT(adev->set_mask, GETSET_MODE);
|
|
+ }
|
|
+
|
|
+ if (enable) {
|
|
+ adev->channel = params[1];
|
|
+ SET_BIT(adev->set_mask, GETSET_RX);
|
|
+ }
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_unknown11
|
|
+** FIXME: looks like some sort of "iwpriv kick_sta MAC" but it's broken
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_unknown11(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+#ifdef BROKEN
|
|
+ struct iw_param *vwrq = &wrqu->param;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ client_t client;
|
|
+ int result;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ acx_l_transmit_disassoc(adev, &client);
|
|
+ result = OK;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+#endif
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** debug helper function to be able to debug various issues relatively easily
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_dbg_set_masks(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ const unsigned int *params = (unsigned int*)extra;
|
|
+ int result;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ log(L_IOCTL, "setting flags in settings mask: "
|
|
+ "get_mask %08X set_mask %08X\n"
|
|
+ "before: get_mask %08X set_mask %08X\n",
|
|
+ params[0], params[1],
|
|
+ adev->get_mask, adev->set_mask);
|
|
+ SET_BIT(adev->get_mask, params[0]);
|
|
+ SET_BIT(adev->set_mask, params[1]);
|
|
+ log(L_IOCTL, "after: get_mask %08X set_mask %08X\n",
|
|
+ adev->get_mask, adev->set_mask);
|
|
+ result = -EINPROGRESS; /* immediately call commit handler */
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+* acx_ioctl_set_rates
|
|
+*
|
|
+* This ioctl takes string parameter. Examples:
|
|
+* iwpriv wlan0 SetRates "1,2"
|
|
+* use 1 and 2 Mbit rates, both are in basic rate set
|
|
+* iwpriv wlan0 SetRates "1,2 5,11"
|
|
+* use 1,2,5.5,11 Mbit rates. 1 and 2 are basic
|
|
+* iwpriv wlan0 SetRates "1,2 5c,11c"
|
|
+* same ('c' means 'CCK modulation' and it is a default for 5 and 11)
|
|
+* iwpriv wlan0 SetRates "1,2 5p,11p"
|
|
+* use 1,2,5.5,11 Mbit, 1,2 are basic. 5 and 11 are using PBCC
|
|
+* iwpriv wlan0 SetRates "1,2,5,11 22p"
|
|
+* use 1,2,5.5,11,22 Mbit. 1,2,5.5 and 11 are basic. 22 is using PBCC
|
|
+* (this is the maximum acx100 can do (modulo x4 mode))
|
|
+* iwpriv wlan0 SetRates "1,2,5,11 22"
|
|
+* same. 802.11 defines only PBCC modulation
|
|
+* for 22 and 33 Mbit rates, so there is no ambiguity
|
|
+* iwpriv wlan0 SetRates "1,2,5,11 6o,9o,12o,18o,24o,36o,48o,54o"
|
|
+* 1,2,5.5 and 11 are basic. 11g OFDM rates are enabled but
|
|
+* they are not in basic rate set. 22 Mbit is disabled.
|
|
+* iwpriv wlan0 SetRates "1,2,5,11 6,9,12,18,24,36,48,54"
|
|
+* same. OFDM is default for 11g rates except 22 and 33 Mbit,
|
|
+* thus 'o' is optional
|
|
+* iwpriv wlan0 SetRates "1,2,5,11 6d,9d,12d,18d,24d,36d,48d,54d"
|
|
+* 1,2,5.5 and 11 are basic. 11g CCK-OFDM rates are enabled
|
|
+* (acx111 does not support CCK-OFDM, driver will reject this cmd)
|
|
+* iwpriv wlan0 SetRates "6,9,12 18,24,36,48,54"
|
|
+* 6,9,12 are basic, rest of 11g rates is enabled. Using OFDM
|
|
+*/
|
|
+#include "setrate.c"
|
|
+
|
|
+/* disallow: 33Mbit (unsupported by hw) */
|
|
+/* disallow: CCKOFDM (unsupported by hw) */
|
|
+static int
|
|
+acx111_supported(int mbit, int modulation, void *opaque)
|
|
+{
|
|
+ if (mbit==33) return -ENOTSUPP;
|
|
+ if (modulation==DOT11_MOD_CCKOFDM) return -ENOTSUPP;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+static const u16
|
|
+acx111mask[] = {
|
|
+ [DOT11_RATE_1 ] = RATE111_1 ,
|
|
+ [DOT11_RATE_2 ] = RATE111_2 ,
|
|
+ [DOT11_RATE_5 ] = RATE111_5 ,
|
|
+ [DOT11_RATE_11] = RATE111_11,
|
|
+ [DOT11_RATE_22] = RATE111_22,
|
|
+ /* [DOT11_RATE_33] = */
|
|
+ [DOT11_RATE_6 ] = RATE111_6 ,
|
|
+ [DOT11_RATE_9 ] = RATE111_9 ,
|
|
+ [DOT11_RATE_12] = RATE111_12,
|
|
+ [DOT11_RATE_18] = RATE111_18,
|
|
+ [DOT11_RATE_24] = RATE111_24,
|
|
+ [DOT11_RATE_36] = RATE111_36,
|
|
+ [DOT11_RATE_48] = RATE111_48,
|
|
+ [DOT11_RATE_54] = RATE111_54,
|
|
+};
|
|
+
|
|
+static u32
|
|
+acx111_gen_mask(int mbit, int modulation, void *opaque)
|
|
+{
|
|
+ /* lower 16 bits show selected 1, 2, CCK and OFDM rates */
|
|
+ /* upper 16 bits show selected PBCC rates */
|
|
+ u32 m = acx111mask[rate_mbit2enum(mbit)];
|
|
+ if (modulation==DOT11_MOD_PBCC)
|
|
+ return m<<16;
|
|
+ return m;
|
|
+}
|
|
+
|
|
+static int
|
|
+verify_rate(u32 rate, int chip_type)
|
|
+{
|
|
+ /* never happens. be paranoid */
|
|
+ if (!rate) return -EINVAL;
|
|
+
|
|
+ /* disallow: mixing PBCC and CCK at 5 and 11Mbit
|
|
+ ** (can be supported, but needs complicated handling in tx code) */
|
|
+ if (( rate & ((RATE111_11+RATE111_5)<<16) )
|
|
+ && ( rate & (RATE111_11+RATE111_5) )
|
|
+ ) {
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+ if (CHIPTYPE_ACX100 == chip_type) {
|
|
+ if ( rate & ~(RATE111_ACX100_COMPAT+(RATE111_ACX100_COMPAT<<16)) )
|
|
+ return -ENOTSUPP;
|
|
+ }
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_ioctl_set_rates(struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ int result;
|
|
+ u32 brate = 0, orate = 0; /* basic, operational rate set */
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ log(L_IOCTL, "set_rates %s\n", extra);
|
|
+ result = fill_ratemasks(extra, &brate, &orate,
|
|
+ acx111_supported, acx111_gen_mask, 0);
|
|
+ if (result) goto end;
|
|
+ SET_BIT(orate, brate);
|
|
+ log(L_IOCTL, "brate %08X orate %08X\n", brate, orate);
|
|
+
|
|
+ result = verify_rate(brate, adev->chip_type);
|
|
+ if (result) goto end;
|
|
+ result = verify_rate(orate, adev->chip_type);
|
|
+ if (result) goto end;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ adev->rate_basic = brate;
|
|
+ adev->rate_oper = orate;
|
|
+ /* TODO: ideally, we shall monitor highest basic rate
|
|
+ ** which was successfully sent to every peer
|
|
+ ** (say, last we checked, everybody could hear 5.5 Mbits)
|
|
+ ** and use that for bcasts when we want to reach all peers.
|
|
+ ** For beacons, we probably shall use lowest basic rate
|
|
+ ** because we want to reach all *potential* new peers too */
|
|
+ adev->rate_bcast = 1 << lowest_bit(brate);
|
|
+ if (IS_ACX100(adev))
|
|
+ adev->rate_bcast100 = acx_rate111to100(adev->rate_bcast);
|
|
+ adev->rate_auto = !has_only_one_bit(orate);
|
|
+ acx_l_update_client_rates(adev, orate);
|
|
+ /* TODO: get rid of ratevector, build it only when needed */
|
|
+ acx_l_update_ratevector(adev);
|
|
+
|
|
+ /* Do/don't do tx rate fallback; beacon contents and rate */
|
|
+ SET_BIT(adev->set_mask, SET_RATE_FALLBACK|SET_TEMPLATES);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_get_phy_chan_busy_percentage
|
|
+*/
|
|
+static int
|
|
+acx_ioctl_get_phy_chan_busy_percentage(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ struct {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ u32 busytime;
|
|
+ u32 totaltime;
|
|
+ } ACX_PACKED usage;
|
|
+ int result;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (OK != acx_s_interrogate(adev, &usage, ACX1xx_IE_MEDIUM_USAGE)) {
|
|
+ result = NOT_OK;
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ usage.busytime = le32_to_cpu(usage.busytime);
|
|
+ usage.totaltime = le32_to_cpu(usage.totaltime);
|
|
+
|
|
+ /* yes, this is supposed to be "Medium" (singular of media),
|
|
+ not "average"! OK, reword the message to make it obvious... */
|
|
+ printk("%s: busy percentage of medium (since last invocation): %d%% "
|
|
+ "(%u of %u microseconds)\n",
|
|
+ ndev->name,
|
|
+ usage.busytime / ((usage.totaltime / 100) + 1),
|
|
+ usage.busytime, usage.totaltime);
|
|
+
|
|
+ result = OK;
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_ed_threshold
|
|
+*/
|
|
+static inline int
|
|
+acx_ioctl_set_ed_threshold(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ printk("old ED threshold value: %d\n", adev->ed_threshold);
|
|
+ adev->ed_threshold = (unsigned char)*extra;
|
|
+ printk("new ED threshold value: %d\n", (unsigned char)*extra);
|
|
+ SET_BIT(adev->set_mask, GETSET_ED_THRESH);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return -EINPROGRESS;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acx_ioctl_set_cca
|
|
+*/
|
|
+static inline int
|
|
+acx_ioctl_set_cca(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ printk("old CCA value: 0x%02X\n", adev->cca);
|
|
+ adev->cca = (unsigned char)*extra;
|
|
+ printk("new CCA value: 0x%02X\n", (unsigned char)*extra);
|
|
+ SET_BIT(adev->set_mask, GETSET_CCA);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static const char * const
|
|
+scan_modes[] = { "active", "passive", "background" };
|
|
+
|
|
+static void
|
|
+acx_print_scan_params(acx_device_t *adev, const char* head)
|
|
+{
|
|
+ printk("%s: %smode %d (%s), min chan time %dTU, "
|
|
+ "max chan time %dTU, max scan rate byte: %d\n",
|
|
+ adev->ndev->name, head,
|
|
+ adev->scan_mode, scan_modes[adev->scan_mode],
|
|
+ adev->scan_probe_delay, adev->scan_duration, adev->scan_rate);
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_ioctl_set_scan_params(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+ const int *params = (int *)extra;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ acx_print_scan_params(adev, "old scan parameters: ");
|
|
+ if ((params[0] != -1) && (params[0] >= 0) && (params[0] <= 2))
|
|
+ adev->scan_mode = params[0];
|
|
+ if (params[1] != -1)
|
|
+ adev->scan_probe_delay = params[1];
|
|
+ if (params[2] != -1)
|
|
+ adev->scan_duration = params[2];
|
|
+ if ((params[3] != -1) && (params[3] <= 255))
|
|
+ adev->scan_rate = params[3];
|
|
+ acx_print_scan_params(adev, "new scan parameters: ");
|
|
+ SET_BIT(adev->set_mask, GETSET_RESCAN);
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+static int
|
|
+acx_ioctl_get_scan_params(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+ int *params = (int *)extra;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ acx_print_scan_params(adev, "current scan parameters: ");
|
|
+ params[0] = adev->scan_mode;
|
|
+ params[1] = adev->scan_probe_delay;
|
|
+ params[2] = adev->scan_duration;
|
|
+ params[3] = adev->scan_rate;
|
|
+ result = OK;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx100_ioctl_set_led_power(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ static const char * const led_modes[] = { "off", "on", "LinkQuality" };
|
|
+
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ printk("%s: power LED status: old %d (%s), ",
|
|
+ ndev->name,
|
|
+ adev->led_power,
|
|
+ led_modes[adev->led_power]);
|
|
+ adev->led_power = extra[0];
|
|
+ if (adev->led_power > 2) adev->led_power = 2;
|
|
+ printk("new %d (%s)\n",
|
|
+ adev->led_power,
|
|
+ led_modes[adev->led_power]);
|
|
+
|
|
+ if (adev->led_power == 2) {
|
|
+ printk("%s: max link quality setting: old %d, ",
|
|
+ ndev->name, adev->brange_max_quality);
|
|
+ if (extra[1])
|
|
+ adev->brange_max_quality = extra[1];
|
|
+ printk("new %d\n", adev->brange_max_quality);
|
|
+ }
|
|
+
|
|
+ SET_BIT(adev->set_mask, GETSET_LED_POWER);
|
|
+
|
|
+ result = -EINPROGRESS;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static inline int
|
|
+acx100_ioctl_get_led_power(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ extra[0] = adev->led_power;
|
|
+ if (adev->led_power == 2)
|
|
+ extra[1] = adev->brange_max_quality;
|
|
+ else
|
|
+ extra[1] = -1;
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx111_ioctl_info(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->param;
|
|
+ if (!IS_PCI(ndev2adev(ndev)))
|
|
+ return OK;
|
|
+ return acx111pci_ioctl_info(ndev, info, vwrq, extra);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static int
|
|
+acx100_ioctl_set_phy_amp_bias(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ union iwreq_data *wrqu,
|
|
+ char *extra)
|
|
+{
|
|
+ struct iw_param *vwrq = &wrqu->param;
|
|
+ if (IS_USB(ndev2adev(ndev))) {
|
|
+ printk("acx: set_phy_amp_bias() is not supported on USB\n");
|
|
+ return OK;
|
|
+ }
|
|
+#ifdef ACX_MEM
|
|
+ return acx100mem_ioctl_set_phy_amp_bias(ndev, info, vwrq, extra);
|
|
+#else
|
|
+ return acx100pci_ioctl_set_phy_amp_bias(ndev, info, vwrq, extra);
|
|
+#endif
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static const iw_handler acx_ioctl_handler[] =
|
|
+{
|
|
+ acx_ioctl_commit, /* SIOCSIWCOMMIT */
|
|
+ acx_ioctl_get_name, /* SIOCGIWNAME */
|
|
+ NULL, /* SIOCSIWNWID */
|
|
+ NULL, /* SIOCGIWNWID */
|
|
+ acx_ioctl_set_freq, /* SIOCSIWFREQ */
|
|
+ acx_ioctl_get_freq, /* SIOCGIWFREQ */
|
|
+ acx_ioctl_set_mode, /* SIOCSIWMODE */
|
|
+ acx_ioctl_get_mode, /* SIOCGIWMODE */
|
|
+ acx_ioctl_set_sens, /* SIOCSIWSENS */
|
|
+ acx_ioctl_get_sens, /* SIOCGIWSENS */
|
|
+ NULL, /* SIOCSIWRANGE */
|
|
+ acx_ioctl_get_range, /* SIOCGIWRANGE */
|
|
+ NULL, /* SIOCSIWPRIV */
|
|
+ NULL, /* SIOCGIWPRIV */
|
|
+ NULL, /* SIOCSIWSTATS */
|
|
+ NULL, /* SIOCGIWSTATS */
|
|
+#if IW_HANDLER_VERSION > 4
|
|
+ iw_handler_set_spy, /* SIOCSIWSPY */
|
|
+ iw_handler_get_spy, /* SIOCGIWSPY */
|
|
+ iw_handler_set_thrspy, /* SIOCSIWTHRSPY */
|
|
+ iw_handler_get_thrspy, /* SIOCGIWTHRSPY */
|
|
+#else /* IW_HANDLER_VERSION > 4 */
|
|
+#ifdef WIRELESS_SPY
|
|
+ NULL /* acx_ioctl_set_spy FIXME */, /* SIOCSIWSPY */
|
|
+ NULL /* acx_ioctl_get_spy */, /* SIOCGIWSPY */
|
|
+#else /* WSPY */
|
|
+ NULL, /* SIOCSIWSPY */
|
|
+ NULL, /* SIOCGIWSPY */
|
|
+#endif /* WSPY */
|
|
+ NULL, /* [nothing] */
|
|
+ NULL, /* [nothing] */
|
|
+#endif /* IW_HANDLER_VERSION > 4 */
|
|
+ acx_ioctl_set_ap, /* SIOCSIWAP */
|
|
+ acx_ioctl_get_ap, /* SIOCGIWAP */
|
|
+ NULL, /* [nothing] */
|
|
+ acx_ioctl_get_aplist, /* SIOCGIWAPLIST */
|
|
+ acx_ioctl_set_scan, /* SIOCSIWSCAN */
|
|
+ acx_ioctl_get_scan, /* SIOCGIWSCAN */
|
|
+ acx_ioctl_set_essid, /* SIOCSIWESSID */
|
|
+ acx_ioctl_get_essid, /* SIOCGIWESSID */
|
|
+ acx_ioctl_set_nick, /* SIOCSIWNICKN */
|
|
+ acx_ioctl_get_nick, /* SIOCGIWNICKN */
|
|
+ NULL, /* [nothing] */
|
|
+ NULL, /* [nothing] */
|
|
+ acx_ioctl_set_rate, /* SIOCSIWRATE */
|
|
+ acx_ioctl_get_rate, /* SIOCGIWRATE */
|
|
+ acx_ioctl_set_rts, /* SIOCSIWRTS */
|
|
+ acx_ioctl_get_rts, /* SIOCGIWRTS */
|
|
+#if ACX_FRAGMENTATION
|
|
+ acx_ioctl_set_frag, /* SIOCSIWFRAG */
|
|
+ acx_ioctl_get_frag, /* SIOCGIWFRAG */
|
|
+#else
|
|
+ NULL, /* SIOCSIWFRAG */
|
|
+ NULL, /* SIOCGIWFRAG */
|
|
+#endif
|
|
+ acx_ioctl_set_txpow, /* SIOCSIWTXPOW */
|
|
+ acx_ioctl_get_txpow, /* SIOCGIWTXPOW */
|
|
+ acx_ioctl_set_retry, /* SIOCSIWRETRY */
|
|
+ acx_ioctl_get_retry, /* SIOCGIWRETRY */
|
|
+ acx_ioctl_set_encode, /* SIOCSIWENCODE */
|
|
+ acx_ioctl_get_encode, /* SIOCGIWENCODE */
|
|
+ acx_ioctl_set_power, /* SIOCSIWPOWER */
|
|
+ acx_ioctl_get_power, /* SIOCGIWPOWER */
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+
|
|
+/* if you plan to reorder something, make sure to reorder all other places
|
|
+ * accordingly! */
|
|
+/* SET/GET convention: SETs must have even position, GETs odd */
|
|
+#define ACX100_IOCTL SIOCIWFIRSTPRIV
|
|
+enum {
|
|
+ ACX100_IOCTL_DEBUG = ACX100_IOCTL,
|
|
+ ACX100_IOCTL_GET__________UNUSED1,
|
|
+ ACX100_IOCTL_SET_PLED,
|
|
+ ACX100_IOCTL_GET_PLED,
|
|
+ ACX100_IOCTL_SET_RATES,
|
|
+ ACX100_IOCTL_LIST_DOM,
|
|
+ ACX100_IOCTL_SET_DOM,
|
|
+ ACX100_IOCTL_GET_DOM,
|
|
+ ACX100_IOCTL_SET_SCAN_PARAMS,
|
|
+ ACX100_IOCTL_GET_SCAN_PARAMS,
|
|
+ ACX100_IOCTL_SET_PREAMB,
|
|
+ ACX100_IOCTL_GET_PREAMB,
|
|
+ ACX100_IOCTL_SET_ANT,
|
|
+ ACX100_IOCTL_GET_ANT,
|
|
+ ACX100_IOCTL_RX_ANT,
|
|
+ ACX100_IOCTL_TX_ANT,
|
|
+ ACX100_IOCTL_SET_PHY_AMP_BIAS,
|
|
+ ACX100_IOCTL_GET_PHY_CHAN_BUSY,
|
|
+ ACX100_IOCTL_SET_ED,
|
|
+ ACX100_IOCTL_GET__________UNUSED3,
|
|
+ ACX100_IOCTL_SET_CCA,
|
|
+ ACX100_IOCTL_GET__________UNUSED4,
|
|
+ ACX100_IOCTL_MONITOR,
|
|
+ ACX100_IOCTL_TEST,
|
|
+ ACX100_IOCTL_DBG_SET_MASKS,
|
|
+ ACX111_IOCTL_INFO,
|
|
+ ACX100_IOCTL_DBG_SET_IO,
|
|
+ ACX100_IOCTL_DBG_GET_IO
|
|
+};
|
|
+
|
|
+
|
|
+static const iw_handler acx_ioctl_private_handler[] =
|
|
+{
|
|
+#if ACX_DEBUG
|
|
+[ACX100_IOCTL_DEBUG - ACX100_IOCTL] = acx_ioctl_set_debug,
|
|
+#endif
|
|
+[ACX100_IOCTL_SET_PLED - ACX100_IOCTL] = acx100_ioctl_set_led_power,
|
|
+[ACX100_IOCTL_GET_PLED - ACX100_IOCTL] = acx100_ioctl_get_led_power,
|
|
+[ACX100_IOCTL_SET_RATES - ACX100_IOCTL] = acx_ioctl_set_rates,
|
|
+[ACX100_IOCTL_LIST_DOM - ACX100_IOCTL] = acx_ioctl_list_reg_domain,
|
|
+[ACX100_IOCTL_SET_DOM - ACX100_IOCTL] = acx_ioctl_set_reg_domain,
|
|
+[ACX100_IOCTL_GET_DOM - ACX100_IOCTL] = acx_ioctl_get_reg_domain,
|
|
+[ACX100_IOCTL_SET_SCAN_PARAMS - ACX100_IOCTL] = acx_ioctl_set_scan_params,
|
|
+[ACX100_IOCTL_GET_SCAN_PARAMS - ACX100_IOCTL] = acx_ioctl_get_scan_params,
|
|
+[ACX100_IOCTL_SET_PREAMB - ACX100_IOCTL] = acx_ioctl_set_short_preamble,
|
|
+[ACX100_IOCTL_GET_PREAMB - ACX100_IOCTL] = acx_ioctl_get_short_preamble,
|
|
+[ACX100_IOCTL_SET_ANT - ACX100_IOCTL] = acx_ioctl_set_antenna,
|
|
+[ACX100_IOCTL_GET_ANT - ACX100_IOCTL] = acx_ioctl_get_antenna,
|
|
+[ACX100_IOCTL_RX_ANT - ACX100_IOCTL] = acx_ioctl_set_rx_antenna,
|
|
+[ACX100_IOCTL_TX_ANT - ACX100_IOCTL] = acx_ioctl_set_tx_antenna,
|
|
+[ACX100_IOCTL_SET_PHY_AMP_BIAS - ACX100_IOCTL] = acx100_ioctl_set_phy_amp_bias,
|
|
+[ACX100_IOCTL_GET_PHY_CHAN_BUSY - ACX100_IOCTL] = acx_ioctl_get_phy_chan_busy_percentage,
|
|
+[ACX100_IOCTL_SET_ED - ACX100_IOCTL] = acx_ioctl_set_ed_threshold,
|
|
+[ACX100_IOCTL_SET_CCA - ACX100_IOCTL] = acx_ioctl_set_cca,
|
|
+[ACX100_IOCTL_MONITOR - ACX100_IOCTL] = acx_ioctl_wlansniff,
|
|
+[ACX100_IOCTL_TEST - ACX100_IOCTL] = acx_ioctl_unknown11,
|
|
+[ACX100_IOCTL_DBG_SET_MASKS - ACX100_IOCTL] = acx_ioctl_dbg_set_masks,
|
|
+[ACX111_IOCTL_INFO - ACX100_IOCTL] = acx111_ioctl_info,
|
|
+};
|
|
+
|
|
+
|
|
+static const struct iw_priv_args acx_ioctl_private_args[] = {
|
|
+#if ACX_DEBUG
|
|
+{ cmd : ACX100_IOCTL_DEBUG,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetDebug" },
|
|
+#endif
|
|
+{ cmd : ACX100_IOCTL_SET_PLED,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | 2,
|
|
+ get_args : 0,
|
|
+ name : "SetLEDPower" },
|
|
+{ cmd : ACX100_IOCTL_GET_PLED,
|
|
+ set_args : 0,
|
|
+ get_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 2,
|
|
+ name : "GetLEDPower" },
|
|
+{ cmd : ACX100_IOCTL_SET_RATES,
|
|
+ set_args : IW_PRIV_TYPE_CHAR | 256,
|
|
+ get_args : 0,
|
|
+ name : "SetRates" },
|
|
+{ cmd : ACX100_IOCTL_LIST_DOM,
|
|
+ set_args : 0,
|
|
+ get_args : 0,
|
|
+ name : "ListRegDomain" },
|
|
+{ cmd : ACX100_IOCTL_SET_DOM,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetRegDomain" },
|
|
+{ cmd : ACX100_IOCTL_GET_DOM,
|
|
+ set_args : 0,
|
|
+ get_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ name : "GetRegDomain" },
|
|
+{ cmd : ACX100_IOCTL_SET_SCAN_PARAMS,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 4,
|
|
+ get_args : 0,
|
|
+ name : "SetScanParams" },
|
|
+{ cmd : ACX100_IOCTL_GET_SCAN_PARAMS,
|
|
+ set_args : 0,
|
|
+ get_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 4,
|
|
+ name : "GetScanParams" },
|
|
+{ cmd : ACX100_IOCTL_SET_PREAMB,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetSPreamble" },
|
|
+{ cmd : ACX100_IOCTL_GET_PREAMB,
|
|
+ set_args : 0,
|
|
+ get_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ name : "GetSPreamble" },
|
|
+{ cmd : ACX100_IOCTL_SET_ANT,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetAntenna" },
|
|
+{ cmd : ACX100_IOCTL_GET_ANT,
|
|
+ set_args : 0,
|
|
+ get_args : 0,
|
|
+ name : "GetAntenna" },
|
|
+{ cmd : ACX100_IOCTL_RX_ANT,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetRxAnt" },
|
|
+{ cmd : ACX100_IOCTL_TX_ANT,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetTxAnt" },
|
|
+{ cmd : ACX100_IOCTL_SET_PHY_AMP_BIAS,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetPhyAmpBias"},
|
|
+{ cmd : ACX100_IOCTL_GET_PHY_CHAN_BUSY,
|
|
+ set_args : 0,
|
|
+ get_args : 0,
|
|
+ name : "GetPhyChanBusy" },
|
|
+{ cmd : ACX100_IOCTL_SET_ED,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetED" },
|
|
+{ cmd : ACX100_IOCTL_SET_CCA,
|
|
+ set_args : IW_PRIV_TYPE_BYTE | IW_PRIV_SIZE_FIXED | 1,
|
|
+ get_args : 0,
|
|
+ name : "SetCCA" },
|
|
+{ cmd : ACX100_IOCTL_MONITOR,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
|
|
+ get_args : 0,
|
|
+ name : "monitor" },
|
|
+{ cmd : ACX100_IOCTL_TEST,
|
|
+ set_args : 0,
|
|
+ get_args : 0,
|
|
+ name : "Test" },
|
|
+{ cmd : ACX100_IOCTL_DBG_SET_MASKS,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 2,
|
|
+ get_args : 0,
|
|
+ name : "DbgSetMasks" },
|
|
+{ cmd : ACX111_IOCTL_INFO,
|
|
+ set_args : 0,
|
|
+ get_args : 0,
|
|
+ name : "GetAcx111Info" },
|
|
+{ cmd : ACX100_IOCTL_DBG_SET_IO,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 4,
|
|
+ get_args : 0,
|
|
+ name : "DbgSetIO" },
|
|
+{ cmd : ACX100_IOCTL_DBG_GET_IO,
|
|
+ set_args : IW_PRIV_TYPE_INT | IW_PRIV_SIZE_FIXED | 3,
|
|
+ get_args : 0,
|
|
+ name : "DbgGetIO" },
|
|
+};
|
|
+
|
|
+
|
|
+const struct iw_handler_def acx_ioctl_handler_def =
|
|
+{
|
|
+ .num_standard = VEC_SIZE(acx_ioctl_handler),
|
|
+ .num_private = VEC_SIZE(acx_ioctl_private_handler),
|
|
+ .num_private_args = VEC_SIZE(acx_ioctl_private_args),
|
|
+ .standard = (iw_handler *) acx_ioctl_handler,
|
|
+ .private = (iw_handler *) acx_ioctl_private_handler,
|
|
+ .private_args = (struct iw_priv_args *) acx_ioctl_private_args,
|
|
+#if IW_HANDLER_VERSION > 5
|
|
+ .get_wireless_stats = acx_e_get_wireless_stats
|
|
+#endif /* IW > 5 */
|
|
+};
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/Kconfig
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/Kconfig 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,113 @@
|
|
+config ACX
|
|
+ tristate "TI acx100/acx111 802.11b/g wireless chipsets"
|
|
+ depends on NET_RADIO && EXPERIMENTAL
|
|
+ select FW_LOADER
|
|
+ ---help---
|
|
+ A driver for 802.11b/g wireless cards based on
|
|
+ Texas Instruments acx100 and acx111 chipsets.
|
|
+
|
|
+ This driver supports Host AP mode that allows
|
|
+ your computer to act as an IEEE 802.11 access point.
|
|
+ This driver is new and experimental.
|
|
+
|
|
+ Texas Instruments did not take part in development of this driver
|
|
+ in any way, shape or form.
|
|
+
|
|
+ The driver can be compiled as a module and will be named "acx".
|
|
+
|
|
+config ACX_PCI
|
|
+ bool "TI acx100/acx111 802.11b/g PCI"
|
|
+ depends on ACX && PCI
|
|
+ ---help---
|
|
+ Include PCI and CardBus support in acx.
|
|
+
|
|
+ acx chipsets need their firmware loaded at startup.
|
|
+ You will need to provide a firmware image via hotplug.
|
|
+
|
|
+ Firmware may be in a form of single image 40-100kb in size
|
|
+ (a 'combined' firmware) or two images - main image
|
|
+ (again 40-100kb) and radio image (~10kb or less).
|
|
+
|
|
+ Firmware images are requested from hotplug using following names:
|
|
+
|
|
+ tiacx100 - main firmware image for acx100 chipset
|
|
+ tiacx100rNN - radio acx100 firmware for radio type NN
|
|
+ tiacx100cNN - combined acx100 firmware for radio type NN
|
|
+ tiacx111 - main acx111 firmware
|
|
+ tiacx111rNN - radio acx111 firmware for radio type NN
|
|
+ tiacx111cNN - combined acx111 firmware for radio type NN
|
|
+
|
|
+ Driver will attempt to load combined image first.
|
|
+ If no such image is found, it will try to load main image
|
|
+ and radio image instead.
|
|
+
|
|
+ Firmware files are not covered by GPL and are not distributed
|
|
+ with this driver for legal reasons.
|
|
+
|
|
+config ACX_USB
|
|
+ bool "TI acx100/acx111 802.11b/g USB"
|
|
+ depends on ACX && (USB=y || USB=ACX)
|
|
+ ---help---
|
|
+ Include USB support in acx.
|
|
+
|
|
+ There is only one currently known device in this category,
|
|
+ D-Link DWL-120+, but newer devices seem to be on the horizon.
|
|
+
|
|
+ acx chipsets need their firmware loaded at startup.
|
|
+ You will need to provide a firmware image via hotplug.
|
|
+
|
|
+ Firmware for USB device is requested from hotplug
|
|
+ by the 'tiacx100usb' name.
|
|
+
|
|
+ Firmware files are not covered by GPL and are not distributed
|
|
+ with this driver for legal reasons.
|
|
+
|
|
+config ACX_MEM
|
|
+ bool "TI acx100/acx111 802.11b/g memory mapped slave 16 interface"
|
|
+ depends on ACX
|
|
+ ---help---
|
|
+ acx chipsets need their firmware loaded at startup.
|
|
+ You will need to provide a firmware image via hotplug.
|
|
+
|
|
+ Firmware for USB device is requested from hotplug
|
|
+ by the 'tiacx100usb' name.
|
|
+
|
|
+ Firmware files are not covered by GPL and are not distributed
|
|
+ with this driver for legal reasons.
|
|
+
|
|
+config ACX_CS
|
|
+ bool "TI acx100/acx111 802.11b/g cardbus interface"
|
|
+ depends on ACX
|
|
+ ---help---
|
|
+ acx chipsets need their firmware loaded at startup.
|
|
+ You will need to provide a firmware image via hotplug.
|
|
+
|
|
+ This driver is based on memory mapped driver.
|
|
+
|
|
+ Firmware files are not covered by GPL and are not distributed
|
|
+ with this driver for legal reasons.
|
|
+
|
|
+config ACX_HX4700
|
|
+ tristate "ACX support for the iPAQ hx4700 using ACX_MEM"
|
|
+ depends on HX4700_CORE && ACX_MEM
|
|
+ ---help---
|
|
+ Include memory interface support in acx for the iPAQ hx4700.
|
|
+
|
|
+config ACX_HTCUNIVERSAL
|
|
+ tristate "ACX support for the HTC Universal using ACX_MEM"
|
|
+ depends on HTCUNIVERSAL_CORE && HTC_ASIC3 && ACX_MEM
|
|
+ ---help---
|
|
+ Include memory interface support in acx for the HTC Universal.
|
|
+
|
|
+config ACX_HTCSABLE
|
|
+ tristate "ACX support for the HTC Sable (IPAQ hw6915) using ACX_MEM"
|
|
+ depends on MACH_HW6900 && HTC_ASIC3 && ACX_MEM
|
|
+ ---help---
|
|
+ Include memory interface support in acx for the HTC Sable (IPAQ hw6915).
|
|
+
|
|
+config ACX_RX3000
|
|
+ tristate "ACX support for the iPAQ RX3000 using ACX_MEM"
|
|
+ depends on MACH_RX3715 && ACX_MEM && LEDS_ASIC3
|
|
+ ---help---
|
|
+ Include memory interface support in acx for the IPAQ RX3000.
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/Makefile
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/Makefile 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,21 @@
|
|
+#obj-m += acx.o
|
|
+
|
|
+#acx-obj-y += pci.o
|
|
+#acx-obj-y += usb.o
|
|
+
|
|
+#acx-objs := wlan.o conv.o ioctl.o common.o $(acx-obj-y)
|
|
+
|
|
+# Use this if you have proper Kconfig integration:
|
|
+
|
|
+obj-$(CONFIG_ACX) += acx.o
|
|
+obj-$(CONFIG_ACX_HX4700) += hx4700_acx.o
|
|
+obj-$(CONFIG_ACX_HTCUNIVERSAL) += htcuniversal_acx.o
|
|
+obj-$(CONFIG_ACX_HTCSABLE) += htcsable_acx.o
|
|
+obj-$(CONFIG_ACX_RX3000) += rx3000_acx.o
|
|
+#
|
|
+acx-obj-$(CONFIG_ACX_PCI) += pci.o
|
|
+acx-obj-$(CONFIG_ACX_USB) += usb.o
|
|
+acx-obj-$(CONFIG_ACX_MEM) += mem.o
|
|
+acx-obj-$(CONFIG_ACX_CS) += cs.o
|
|
+#
|
|
+acx-objs := wlan.o conv.o ioctl.o common.o $(acx-obj-y)
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/mem.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/mem.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,5363 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+**
|
|
+** Slave memory interface support:
|
|
+**
|
|
+** Todd Blumer - SDG Systems
|
|
+** Bill Reese - HP
|
|
+** Eric McCorkle - Shadowsun
|
|
+*/
|
|
+#define ACX_MEM 1
|
|
+
|
|
+/*
|
|
+ * non-zero makes it dump the ACX memory to the console then
|
|
+ * panic when you cat /proc/driver/acx_wlan0_diag
|
|
+ */
|
|
+#define DUMP_MEM_DEFINED 1
|
|
+
|
|
+#define DUMP_MEM_DURING_DIAG 0
|
|
+#define DUMP_IF_SLOW 0
|
|
+
|
|
+#define PATCH_AROUND_BAD_SPOTS 1
|
|
+#define HX4700_FIRMWARE_CHECKSUM 0x0036862e
|
|
+#define HX4700_ALTERNATE_FIRMWARE_CHECKSUM 0x00368a75
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+
|
|
+/* Linux 2.6.18+ uses <linux/utsrelease.h> */
|
|
+#ifndef UTS_RELEASE
|
|
+#include <linux/utsrelease.h>
|
|
+#endif
|
|
+
|
|
+#include <linux/compiler.h> /* required for Lx 2.6.8 ?? */
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/moduleparam.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/irq.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/workqueue.h>
|
|
+#include <linux/inetdevice.h>
|
|
+
|
|
+#include "acx.h"
|
|
+#include "acx_hw.h"
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+
|
|
+#define CARD_EEPROM_ID_SIZE 6
|
|
+
|
|
+#include <asm/io.h>
|
|
+
|
|
+#define REG_ACX_VENDOR_ID 0x900
|
|
+/*
|
|
+ * This is the vendor id on the HX4700, anyway
|
|
+ */
|
|
+#define ACX_VENDOR_ID 0x8400104c
|
|
+
|
|
+typedef enum {
|
|
+ ACX_SOFT_RESET = 0,
|
|
+
|
|
+ ACX_SLV_REG_ADDR,
|
|
+ ACX_SLV_REG_DATA,
|
|
+ ACX_SLV_REG_ADATA,
|
|
+
|
|
+ ACX_SLV_MEM_CP,
|
|
+ ACX_SLV_MEM_ADDR,
|
|
+ ACX_SLV_MEM_DATA,
|
|
+ ACX_SLV_MEM_CTL,
|
|
+} acxreg_t;
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static void acxmem_i_tx_timeout(struct net_device *ndev);
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+static irqreturn_t acxmem_i_interrupt(int irq, void *dev_id);
|
|
+#else
|
|
+static irqreturn_t acxmem_i_interrupt(int irq, void *dev_id, struct pt_regs *regs);
|
|
+#endif
|
|
+static void acxmem_i_set_multicast_list(struct net_device *ndev);
|
|
+
|
|
+static int acxmem_e_open(struct net_device *ndev);
|
|
+static int acxmem_e_close(struct net_device *ndev);
|
|
+static void acxmem_s_up(struct net_device *ndev);
|
|
+static void acxmem_s_down(struct net_device *ndev);
|
|
+
|
|
+static void dump_acxmem (acx_device_t *adev, u32 start, int length);
|
|
+static int acxmem_complete_hw_reset (acx_device_t *adev);
|
|
+static void acxmem_s_delete_dma_regions(acx_device_t *adev);
|
|
+
|
|
+static struct platform_device *resume_pdev;
|
|
+
|
|
+static int
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+acxmem_e_suspend(struct platform_device *pdev, pm_message_t state);
|
|
+#else
|
|
+acxmem_e_suspend(struct device *pdev, u32 state);
|
|
+#endif
|
|
+static void
|
|
+fw_resumer(struct work_struct *notused);
|
|
+//fw_resumer( void *data );
|
|
+
|
|
+static int acx_netdev_event(struct notifier_block *this, unsigned long event, void *ptr)
|
|
+{
|
|
+ struct net_device *ndev = ptr;
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ /*
|
|
+ * Upper level ioctl() handlers send a NETDEV_CHANGEADDR if the MAC address changes.
|
|
+ */
|
|
+
|
|
+ if (NETDEV_CHANGEADDR == event) {
|
|
+ /*
|
|
+ * the upper layers put the new MAC address in ndev->dev_addr; we just copy
|
|
+ * it over and update the ACX with it.
|
|
+ */
|
|
+ MAC_COPY(adev->dev_addr, adev->ndev->dev_addr);
|
|
+ adev->set_mask |= GETSET_STATION_ID;
|
|
+ acx_s_update_card_settings (adev);
|
|
+ }
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct notifier_block acx_netdev_notifier = {
|
|
+ .notifier_call = acx_netdev_event,
|
|
+};
|
|
+
|
|
+/***********************************************************************
|
|
+** Register access
|
|
+*/
|
|
+
|
|
+/* Pick one */
|
|
+/* #define INLINE_IO static */
|
|
+#define INLINE_IO static inline
|
|
+
|
|
+INLINE_IO u32
|
|
+read_id_register (acx_device_t *adev)
|
|
+{
|
|
+ writel (0x24, &adev->iobase[ACX_SLV_REG_ADDR]);
|
|
+ return readl (&adev->iobase[ACX_SLV_REG_DATA]);
|
|
+}
|
|
+
|
|
+INLINE_IO u32
|
|
+read_reg32(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ return readl(((u8*)adev->iobase) + addr);
|
|
+ }
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ val = readl( &adev->iobase[ACX_SLV_REG_DATA] );
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+INLINE_IO u16
|
|
+read_reg16(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ u16 lo;
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ return readw(((u8 *) adev->iobase) + addr);
|
|
+ }
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ lo = readw( (u16 *)&adev->iobase[ACX_SLV_REG_DATA] );
|
|
+
|
|
+ return lo;
|
|
+}
|
|
+
|
|
+INLINE_IO u8
|
|
+read_reg8(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ u8 lo;
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20)
|
|
+ return readb(((u8 *)adev->iobase) + addr);
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ lo = readw( (u8 *)&adev->iobase[ACX_SLV_REG_DATA] );
|
|
+
|
|
+ return (u8)lo;
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg32(acx_device_t *adev, unsigned int offset, u32 val)
|
|
+{
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ writel(val, ((u8*)adev->iobase) + addr);
|
|
+ return;
|
|
+ }
|
|
+
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ writel( val, &adev->iobase[ACX_SLV_REG_DATA] );
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg16(acx_device_t *adev, unsigned int offset, u16 val)
|
|
+{
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ writew(val, ((u8 *)adev->iobase) + addr);
|
|
+ return;
|
|
+ }
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ writew( val, (u16 *) &adev->iobase[ACX_SLV_REG_DATA] );
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg8(acx_device_t *adev, unsigned int offset, u8 val)
|
|
+{
|
|
+ u32 addr;
|
|
+
|
|
+ if (offset > IO_ACX_ECPU_CTRL)
|
|
+ addr = offset;
|
|
+ else
|
|
+ addr = adev->io[offset];
|
|
+
|
|
+ if (addr < 0x20) {
|
|
+ writeb(val, ((u8 *) adev->iobase) + addr);
|
|
+ return;
|
|
+ }
|
|
+ writel( addr, &adev->iobase[ACX_SLV_REG_ADDR] );
|
|
+ writeb( val, (u8 *)&adev->iobase[ACX_SLV_REG_DATA] );
|
|
+}
|
|
+
|
|
+/* Handle PCI posting properly:
|
|
+ * Make sure that writes reach the adapter in case they require to be executed
|
|
+ * *before* the next write, by reading a random (and safely accessible) register.
|
|
+ * This call has to be made if there is no read following (which would flush the data
|
|
+ * to the adapter), yet the written data has to reach the adapter immediately. */
|
|
+INLINE_IO void
|
|
+write_flush(acx_device_t *adev)
|
|
+{
|
|
+ /* readb(adev->iobase + adev->io[IO_ACX_INFO_MAILBOX_OFFS]); */
|
|
+ /* faster version (accesses the first register, IO_ACX_SOFT_RESET,
|
|
+ * which should also be safe): */
|
|
+ (void) readl(adev->iobase);
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+set_regbits (acx_device_t *adev, unsigned int offset, u32 bits) {
|
|
+ u32 tmp;
|
|
+
|
|
+ tmp = read_reg32 (adev, offset);
|
|
+ tmp = tmp | bits;
|
|
+ write_reg32 (adev, offset, tmp);
|
|
+ write_flush (adev);
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+clear_regbits (acx_device_t *adev, unsigned int offset, u32 bits) {
|
|
+ u32 tmp;
|
|
+
|
|
+ tmp = read_reg32 (adev, offset);
|
|
+ tmp = tmp & ~bits;
|
|
+ write_reg32 (adev, offset, tmp);
|
|
+ write_flush (adev);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Copy from PXA memory to the ACX memory. This assumes both the PXA and ACX
|
|
+ * addresses are 32 bit aligned. Count is in bytes.
|
|
+ */
|
|
+INLINE_IO void
|
|
+write_slavemem32 (acx_device_t *adev, u32 slave_address, u32 val)
|
|
+{
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_CTL, 0x0);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, slave_address);
|
|
+ udelay (10);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_DATA, val);
|
|
+}
|
|
+
|
|
+INLINE_IO u32
|
|
+read_slavemem32 (acx_device_t *adev, u32 slave_address)
|
|
+{
|
|
+ u32 val;
|
|
+
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_CTL, 0x0);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, slave_address);
|
|
+ udelay (10);
|
|
+ val = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_slavemem8 (acx_device_t *adev, u32 slave_address, u8 val)
|
|
+{
|
|
+ u32 data;
|
|
+ u32 base;
|
|
+ int offset;
|
|
+
|
|
+ /*
|
|
+ * Get the word containing the target address and the byte offset in that word.
|
|
+ */
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+ data &= ~(0xff << offset);
|
|
+ data |= val << offset;
|
|
+ write_slavemem32 (adev, base, data);
|
|
+}
|
|
+
|
|
+INLINE_IO u8
|
|
+read_slavemem8 (acx_device_t *adev, u32 slave_address)
|
|
+{
|
|
+ u8 val;
|
|
+ u32 base;
|
|
+ u32 data;
|
|
+ int offset;
|
|
+
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+
|
|
+ val = (data >> offset) & 0xff;
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * doesn't split across word boundaries
|
|
+ */
|
|
+INLINE_IO void
|
|
+write_slavemem16 (acx_device_t *adev, u32 slave_address, u16 val)
|
|
+{
|
|
+ u32 data;
|
|
+ u32 base;
|
|
+ int offset;
|
|
+
|
|
+ /*
|
|
+ * Get the word containing the target address and the byte offset in that word.
|
|
+ */
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+ data &= ~(0xffff << offset);
|
|
+ data |= val << offset;
|
|
+ write_slavemem32 (adev, base, data);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * doesn't split across word boundaries
|
|
+ */
|
|
+INLINE_IO u16
|
|
+read_slavemem16 (acx_device_t *adev, u32 slave_address)
|
|
+{
|
|
+ u16 val;
|
|
+ u32 base;
|
|
+ u32 data;
|
|
+ int offset;
|
|
+
|
|
+ base = slave_address & ~3;
|
|
+ offset = (slave_address & 3) * 8;
|
|
+
|
|
+ data = read_slavemem32 (adev, base);
|
|
+
|
|
+ val = (data >> offset) & 0xffff;
|
|
+
|
|
+ return val;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Copy from slave memory
|
|
+ *
|
|
+ * TODO - rewrite using address autoincrement, handle partial words
|
|
+ */
|
|
+void
|
|
+copy_from_slavemem (acx_device_t *adev, u8 *destination, u32 source, int count) {
|
|
+ u32 tmp = 0;
|
|
+ u8 *ptmp = (u8 *) &tmp;
|
|
+
|
|
+ /*
|
|
+ * Right now I'm making the assumption that the destination is aligned, but
|
|
+ * I'd better check.
|
|
+ */
|
|
+ if ((u32) destination & 3) {
|
|
+ printk ("acx copy_from_slavemem: warning! destination not word-aligned!\n");
|
|
+ }
|
|
+
|
|
+ while (count >= 4) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, source);
|
|
+ udelay (10);
|
|
+ *((u32 *) destination) = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+ count -= 4;
|
|
+ source += 4;
|
|
+ destination += 4;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If the word reads above didn't satisfy the count, read one more word
|
|
+ * and transfer a byte at a time until the request is satisfied.
|
|
+ */
|
|
+ if (count) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, source);
|
|
+ udelay (10);
|
|
+ tmp = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+ while (count--) {
|
|
+ *destination++ = *ptmp++;
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Copy to slave memory
|
|
+ *
|
|
+ * TODO - rewrite using autoincrement, handle partial words
|
|
+ */
|
|
+void
|
|
+copy_to_slavemem (acx_device_t *adev, u32 destination, u8 *source, int count)
|
|
+{
|
|
+ u32 tmp = 0;
|
|
+ u8* ptmp = (u8 *) &tmp;
|
|
+ static u8 src[512]; /* make static to avoid huge stack objects */
|
|
+
|
|
+ /*
|
|
+ * For now, make sure the source is word-aligned by copying it to a word-aligned
|
|
+ * buffer. Someday rewrite to avoid the extra copy.
|
|
+ */
|
|
+ if (count > sizeof (src)) {
|
|
+ printk ("acx copy_to_slavemem: Warning! buffer overflow!\n");
|
|
+ count = sizeof (src);
|
|
+ }
|
|
+ memcpy (src, source, count);
|
|
+ source = src;
|
|
+
|
|
+ while (count >= 4) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, destination);
|
|
+ udelay (10);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_DATA, *((u32 *) source));
|
|
+ count -= 4;
|
|
+ source += 4;
|
|
+ destination += 4;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If there are leftovers read the next word from the acx and merge in
|
|
+ * what they want to write.
|
|
+ */
|
|
+ if (count) {
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, destination);
|
|
+ udelay (10);
|
|
+ tmp = read_reg32 (adev, IO_ACX_SLV_MEM_DATA);
|
|
+ while (count--) {
|
|
+ *ptmp++ = *source++;
|
|
+ }
|
|
+ /*
|
|
+ * reset address in case we're currently in auto-increment mode
|
|
+ */
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_ADDR, destination);
|
|
+ udelay (10);
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_DATA, tmp);
|
|
+ udelay (10);
|
|
+ }
|
|
+
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Block copy to slave buffers using memory block chain mode. Copies to the ACX
|
|
+ * transmit buffer structure with minimal intervention on our part.
|
|
+ * Interrupts should be disabled when calling this.
|
|
+ */
|
|
+void
|
|
+chaincopy_to_slavemem (acx_device_t *adev, u32 destination, u8 *source, int count)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 *data = (u32 *) source;
|
|
+ static u8 aligned_source[WLAN_A4FR_MAXLEN_WEP_FCS];
|
|
+
|
|
+ /*
|
|
+ * Warn if the pointers don't look right. Destination must fit in [23:5] with
|
|
+ * zero elsewhere and source should be 32 bit aligned.
|
|
+ * This should never happen since we're in control of both, but I want to know about
|
|
+ * it if it does.
|
|
+ */
|
|
+ if ((destination & 0x00ffffe0) != destination) {
|
|
+ printk ("acx chaincopy: destination block 0x%04x not aligned!\n", destination);
|
|
+ }
|
|
+ if (count > sizeof aligned_source) {
|
|
+ printk( KERN_ERR "chaincopy_to_slavemem overflow!\n" );
|
|
+ count = sizeof aligned_source;
|
|
+ }
|
|
+ if ((u32) source & 3) {
|
|
+ memcpy (aligned_source, source, count);
|
|
+ data = (u32 *) aligned_source;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CTL[17:16] = memory block chain mode with auto-increment
|
|
+ * SLV_MEM_CTL[5:2] = offset to data portion = 1 word
|
|
+ */
|
|
+ val = 2 << 16 | 1 << 2;
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CTL]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CP[23:5] = start of 1st block
|
|
+ * SLV_MEM_CP[3:2] = offset to memblkptr = 0
|
|
+ */
|
|
+ val = destination & 0x00ffffe0;
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CP]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_ADDR[23:2] = SLV_MEM_CTL[5:2] + SLV_MEM_CP[23:5]
|
|
+ */
|
|
+ val = (destination & 0x00ffffe0) + (1<<2);
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_ADDR]);
|
|
+
|
|
+ /*
|
|
+ * Write the data to the slave data register, rounding up to the end
|
|
+ * of the word containing the last byte (hence the > 0)
|
|
+ */
|
|
+ while (count > 0) {
|
|
+ writel (*data++, &adev->iobase[ACX_SLV_MEM_DATA]);
|
|
+ count -= 4;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * Block copy from slave buffers using memory block chain mode. Copies from the ACX
|
|
+ * receive buffer structures with minimal intervention on our part.
|
|
+ * Interrupts should be disabled when calling this.
|
|
+ */
|
|
+void
|
|
+chaincopy_from_slavemem (acx_device_t *adev, u8 *destination, u32 source, int count)
|
|
+{
|
|
+ u32 val;
|
|
+ u32 *data = (u32 *) destination;
|
|
+ static u8 aligned_destination[WLAN_A4FR_MAXLEN_WEP_FCS];
|
|
+ int saved_count = count;
|
|
+
|
|
+ /*
|
|
+ * Warn if the pointers don't look right. Destination must fit in [23:5] with
|
|
+ * zero elsewhere and source should be 32 bit aligned.
|
|
+ * Turns out the network stack sends unaligned things, so fix them before
|
|
+ * copying to the ACX.
|
|
+ */
|
|
+ if ((source & 0x00ffffe0) != source) {
|
|
+ printk ("acx chaincopy: source block 0x%04x not aligned!\n", source);
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ }
|
|
+ if ((u32) destination & 3) {
|
|
+ //printk ("acx chaincopy: data destination not word aligned!\n");
|
|
+ data = (u32 *) aligned_destination;
|
|
+ if (count > sizeof aligned_destination) {
|
|
+ printk( KERN_ERR "chaincopy_from_slavemem overflow!\n" );
|
|
+ count = sizeof aligned_destination;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CTL[17:16] = memory block chain mode with auto-increment
|
|
+ * SLV_MEM_CTL[5:2] = offset to data portion = 1 word
|
|
+ */
|
|
+ val = (2 << 16) | (1 << 2);
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CTL]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_CP[23:5] = start of 1st block
|
|
+ * SLV_MEM_CP[3:2] = offset to memblkptr = 0
|
|
+ */
|
|
+ val = source & 0x00ffffe0;
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_CP]);
|
|
+
|
|
+ /*
|
|
+ * SLV_MEM_ADDR[23:2] = SLV_MEM_CTL[5:2] + SLV_MEM_CP[23:5]
|
|
+ */
|
|
+ val = (source & 0x00ffffe0) + (1<<2);
|
|
+ writel (val, &adev->iobase[ACX_SLV_MEM_ADDR]);
|
|
+
|
|
+ /*
|
|
+ * Read the data from the slave data register, rounding up to the end
|
|
+ * of the word containing the last byte (hence the > 0)
|
|
+ */
|
|
+ while (count > 0) {
|
|
+ *data++ = readl (&adev->iobase[ACX_SLV_MEM_DATA]);
|
|
+ count -= 4;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * If the destination wasn't aligned, we would have saved it in
|
|
+ * the aligned buffer, so copy it where it should go.
|
|
+ */
|
|
+ if ((u32) destination & 3) {
|
|
+ memcpy (destination, aligned_destination, saved_count);
|
|
+ }
|
|
+}
|
|
+
|
|
+char
|
|
+printable (char c)
|
|
+{
|
|
+ return ((c >= 20) && (c < 127)) ? c : '.';
|
|
+}
|
|
+
|
|
+#if DUMP_MEM_DEFINED > 0
|
|
+static void
|
|
+dump_acxmem (acx_device_t *adev, u32 start, int length)
|
|
+{
|
|
+ int i;
|
|
+ u8 buf[16];
|
|
+
|
|
+ while (length > 0) {
|
|
+ printk ("%04x ", start);
|
|
+ copy_from_slavemem (adev, buf, start, 16);
|
|
+ for (i = 0; (i < 16) && (i < length); i++) {
|
|
+ printk ("%02x ", buf[i]);
|
|
+ }
|
|
+ for (i = 0; (i < 16) && (i < length); i++) {
|
|
+ printk ("%c", printable (buf[i]));
|
|
+ }
|
|
+ printk ("\n");
|
|
+ start += 16;
|
|
+ length -= 16;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void
|
|
+enable_acx_irq(acx_device_t *adev);
|
|
+static void
|
|
+disable_acx_irq(acx_device_t *adev);
|
|
+
|
|
+/*
|
|
+ * Return an acx pointer to the next transmit data block.
|
|
+ */
|
|
+u32
|
|
+allocate_acx_txbuf_space (acx_device_t *adev, int count) {
|
|
+ u32 block, next, last_block;
|
|
+ int blocks_needed;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave(&adev->txbuf_lock, flags);
|
|
+ /*
|
|
+ * Take 4 off the memory block size to account for the reserved word at the start of
|
|
+ * the block.
|
|
+ */
|
|
+ blocks_needed = count / (adev->memblocksize - 4);
|
|
+ if (count % (adev->memblocksize - 4))
|
|
+ blocks_needed++;
|
|
+
|
|
+ if (blocks_needed <= adev->acx_txbuf_blocks_free) {
|
|
+ /*
|
|
+ * Take blocks at the head of the free list.
|
|
+ */
|
|
+ last_block = block = adev->acx_txbuf_free;
|
|
+
|
|
+ /*
|
|
+ * Follow block pointers through the requested number of blocks both to
|
|
+ * find the new head of the free list and to set the flags for the blocks
|
|
+ * appropriately.
|
|
+ */
|
|
+ while (blocks_needed--) {
|
|
+ /*
|
|
+ * Keep track of the last block of the allocation
|
|
+ */
|
|
+ last_block = adev->acx_txbuf_free;
|
|
+
|
|
+ /*
|
|
+ * Make sure the end control flag is not set.
|
|
+ */
|
|
+ next = read_slavemem32 (adev, adev->acx_txbuf_free) & 0x7ffff;
|
|
+ write_slavemem32 (adev, adev->acx_txbuf_free, next);
|
|
+
|
|
+ /*
|
|
+ * Update the new head of the free list
|
|
+ */
|
|
+ adev->acx_txbuf_free = next << 5;
|
|
+ adev->acx_txbuf_blocks_free--;
|
|
+
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Flag the last block both by clearing out the next pointer
|
|
+ * and marking the control field.
|
|
+ */
|
|
+ write_slavemem32 (adev, last_block, 0x02000000);
|
|
+
|
|
+ /*
|
|
+ * If we're out of buffers make sure the free list pointer is NULL
|
|
+ */
|
|
+ if (!adev->acx_txbuf_blocks_free) {
|
|
+ adev->acx_txbuf_free = 0;
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ block = 0;
|
|
+ }
|
|
+ spin_unlock_irqrestore (&adev->txbuf_lock, flags);
|
|
+ return block;
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Return buffer space back to the pool by following the next pointers until we find
|
|
+ * the block marked as the end. Point the last block to the head of the free list,
|
|
+ * then update the head of the free list to point to the newly freed memory.
|
|
+ * This routine gets called in interrupt context, so it shouldn't block to protect
|
|
+ * the integrity of the linked list. The ISR already holds the lock.
|
|
+ */
|
|
+void
|
|
+reclaim_acx_txbuf_space (acx_device_t *adev, u32 blockptr) {
|
|
+ u32 cur, last, next;
|
|
+ unsigned long flags;
|
|
+
|
|
+ spin_lock_irqsave (&adev->txbuf_lock, flags);
|
|
+ if ((blockptr >= adev->acx_txbuf_start) &&
|
|
+ (blockptr <= adev->acx_txbuf_start +
|
|
+ (adev->acx_txbuf_numblocks - 1) * adev->memblocksize)) {
|
|
+ cur = blockptr;
|
|
+ do {
|
|
+ last = cur;
|
|
+ next = read_slavemem32 (adev, cur);
|
|
+
|
|
+ /*
|
|
+ * Advance to the next block in this allocation
|
|
+ */
|
|
+ cur = (next & 0x7ffff) << 5;
|
|
+
|
|
+ /*
|
|
+ * This block now counts as free.
|
|
+ */
|
|
+ adev->acx_txbuf_blocks_free++;
|
|
+ } while (!(next & 0x02000000));
|
|
+
|
|
+ /*
|
|
+ * last now points to the last block of that allocation. Update the pointer
|
|
+ * in that block to point to the free list and reset the free list to the
|
|
+ * first block of the free call. If there were no free blocks, make sure
|
|
+ * the new end of the list marks itself as truly the end.
|
|
+ */
|
|
+ if (adev->acx_txbuf_free) {
|
|
+ write_slavemem32 (adev, last, adev->acx_txbuf_free >> 5);
|
|
+ }
|
|
+ else {
|
|
+ write_slavemem32 (adev, last, 0x02000000);
|
|
+ }
|
|
+ adev->acx_txbuf_free = blockptr;
|
|
+ }
|
|
+ spin_unlock_irqrestore(&adev->txbuf_lock, flags);
|
|
+}
|
|
+
|
|
+/*
|
|
+ * Initialize the pieces managing the transmit buffer pool on the ACX. The transmit
|
|
+ * buffer is a circular queue with one 32 bit word reserved at the beginning of each
|
|
+ * block. The upper 13 bits are a control field, of which only 0x02000000 has any
|
|
+ * meaning. The lower 19 bits are the address of the next block divided by 32.
|
|
+ */
|
|
+void
|
|
+init_acx_txbuf (acx_device_t *adev) {
|
|
+
|
|
+ /*
|
|
+ * acx100_s_init_memory_pools set up txbuf_start and txbuf_numblocks for us.
|
|
+ * All we need to do is reset the rest of the bookeeping.
|
|
+ */
|
|
+
|
|
+ adev->acx_txbuf_free = adev->acx_txbuf_start;
|
|
+ adev->acx_txbuf_blocks_free = adev->acx_txbuf_numblocks;
|
|
+
|
|
+ /*
|
|
+ * Initialization leaves the last transmit pool block without a pointer back to
|
|
+ * the head of the list, but marked as the end of the list. That's how we want
|
|
+ * to see it, too, so leave it alone. This is only ever called after a firmware
|
|
+ * reset, so the ACX memory is in the state we want.
|
|
+ */
|
|
+
|
|
+}
|
|
+
|
|
+INLINE_IO int
|
|
+adev_present(acx_device_t *adev)
|
|
+{
|
|
+ /* fast version (accesses the first register, IO_ACX_SOFT_RESET,
|
|
+ * which should be safe): */
|
|
+ return readl(adev->iobase) != 0xffffffff;
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static inline txdesc_t*
|
|
+get_txdesc(acx_device_t *adev, int index)
|
|
+{
|
|
+ return (txdesc_t*) (((u8*)adev->txdesc_start) + index * adev->txdesc_size);
|
|
+}
|
|
+
|
|
+static inline txdesc_t*
|
|
+advance_txdesc(acx_device_t *adev, txdesc_t* txdesc, int inc)
|
|
+{
|
|
+ return (txdesc_t*) (((u8*)txdesc) + inc * adev->txdesc_size);
|
|
+}
|
|
+
|
|
+static txhostdesc_t*
|
|
+get_txhostdesc(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ return &adev->txhostdesc_start[index*2];
|
|
+}
|
|
+
|
|
+static inline client_t*
|
|
+get_txc(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ return adev->txc[index];
|
|
+}
|
|
+
|
|
+static inline u16
|
|
+get_txr(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ index /= adev->txdesc_size;
|
|
+ return adev->txr[index];
|
|
+}
|
|
+
|
|
+static inline void
|
|
+put_txcr(acx_device_t *adev, txdesc_t* txdesc, client_t* c, u16 r111)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return;
|
|
+ }
|
|
+ adev->txc[index] = c;
|
|
+ adev->txr[index] = r111;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** EEPROM and PHY read/write helpers
|
|
+*/
|
|
+/***********************************************************************
|
|
+** acxmem_read_eeprom_byte
|
|
+**
|
|
+** Function called to read an octet in the EEPROM.
|
|
+**
|
|
+** This function is used by acxmem_e_probe to check if the
|
|
+** connected card is a legal one or not.
|
|
+**
|
|
+** Arguments:
|
|
+** adev ptr to acx_device structure
|
|
+** addr address to read in the EEPROM
|
|
+** charbuf ptr to a char. This is where the read octet
|
|
+** will be stored
|
|
+*/
|
|
+int
|
|
+acxmem_read_eeprom_byte(acx_device_t *adev, u32 addr, u8 *charbuf)
|
|
+{
|
|
+ int result;
|
|
+ int count;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for EEPROM read\n",
|
|
+ adev->ndev->name);
|
|
+ result = NOT_OK;
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ *charbuf = read_reg8(adev, IO_ACX_EEPROM_DATA);
|
|
+ log(L_DEBUG, "EEPROM at 0x%04X = 0x%02X\n", addr, *charbuf);
|
|
+ result = OK;
|
|
+
|
|
+fail:
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** We don't lock hw accesses here since we never r/w eeprom in IRQ
|
|
+** Note: this function sleeps only because of GFP_KERNEL alloc
|
|
+*/
|
|
+#ifdef UNUSED
|
|
+int
|
|
+acxmem_s_write_eeprom(acx_device_t *adev, u32 addr, u32 len, const u8 *charbuf)
|
|
+{
|
|
+ u8 *data_verify = NULL;
|
|
+ unsigned long flags;
|
|
+ int count, i;
|
|
+ int result = NOT_OK;
|
|
+ u16 gpio_orig;
|
|
+
|
|
+ printk("acx: WARNING! I would write to EEPROM now. "
|
|
+ "Since I really DON'T want to unless you know "
|
|
+ "what you're doing (THIS CODE WILL PROBABLY "
|
|
+ "NOT WORK YET!), I will abort that now. And "
|
|
+ "definitely make sure to make a "
|
|
+ "/proc/driver/acx_wlan0_eeprom backup copy first!!! "
|
|
+ "(the EEPROM content includes the PCI config header!! "
|
|
+ "If you kill important stuff, then you WILL "
|
|
+ "get in trouble and people DID get in trouble already)\n");
|
|
+ return OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ data_verify = kmalloc(len, GFP_KERNEL);
|
|
+ if (!data_verify) {
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* first we need to enable the OE (EEPROM Output Enable) GPIO line
|
|
+ * to be able to write to the EEPROM.
|
|
+ * NOTE: an EEPROM writing success has been reported,
|
|
+ * but you probably have to modify GPIO_OUT, too,
|
|
+ * and you probably need to activate a different GPIO
|
|
+ * line instead! */
|
|
+ gpio_orig = read_reg16(adev, IO_ACX_GPIO_OE);
|
|
+ write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig & ~1);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* ok, now start writing the data out */
|
|
+ for (i = 0; i < len; i++) {
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_DATA, *(charbuf + i));
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 1);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("WARNING, DANGER!!! "
|
|
+ "Timeout waiting for EEPROM write\n");
|
|
+ goto end;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* disable EEPROM writing */
|
|
+ write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* now start a verification run */
|
|
+ for (i = 0; i < len; i++) {
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("timeout waiting for EEPROM read\n");
|
|
+ goto end;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ data_verify[i] = read_reg16(adev, IO_ACX_EEPROM_DATA);
|
|
+ }
|
|
+
|
|
+ if (0 == memcmp(charbuf, data_verify, len))
|
|
+ result = OK; /* read data matches, success */
|
|
+
|
|
+end:
|
|
+ kfree(data_verify);
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+#endif /* UNUSED */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_read_phy_reg
|
|
+**
|
|
+** Messing with rx/tx disabling and enabling here
|
|
+** (write_reg32(adev, IO_ACX_ENABLE, 0b000000xx)) kills traffic
|
|
+*/
|
|
+int
|
|
+acxmem_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+ int count;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_PHY_ADDR, reg);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_PHY_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg32(adev, IO_ACX_PHY_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for phy read\n",
|
|
+ adev->ndev->name);
|
|
+ *charbuf = 0;
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "count was %u\n", count);
|
|
+ *charbuf = read_reg8(adev, IO_ACX_PHY_DATA);
|
|
+
|
|
+ log(L_DEBUG, "radio PHY at 0x%04X = 0x%02X\n", *charbuf, reg);
|
|
+ result = OK;
|
|
+ goto fail; /* silence compiler warning */
|
|
+fail:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxmem_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value)
|
|
+{
|
|
+ int count;
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* mprusko said that 32bit accesses result in distorted sensitivity
|
|
+ * on his card. Unconfirmed, looks like it's not true (most likely since we
|
|
+ * now properly flush writes). */
|
|
+ write_reg32(adev, IO_ACX_PHY_DATA, value);
|
|
+ write_reg32(adev, IO_ACX_PHY_ADDR, reg);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_PHY_CTL, 1);
|
|
+ write_flush(adev);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg32(adev, IO_ACX_PHY_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for phy read\n",
|
|
+ adev->ndev->name);
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "radio PHY write 0x%02X at 0x%04X\n", value, reg);
|
|
+ fail:
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+#define NO_AUTO_INCREMENT 1
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_write_fw
|
|
+**
|
|
+** Write the firmware image into the card.
|
|
+**
|
|
+** Arguments:
|
|
+** adev wlan device structure
|
|
+** fw_image firmware image.
|
|
+**
|
|
+** Returns:
|
|
+** 1 firmware image corrupted
|
|
+** 0 success
|
|
+*/
|
|
+static int
|
|
+acxmem_s_write_fw(acx_device_t *adev, const firmware_image_t *fw_image, u32 offset)
|
|
+{
|
|
+ int len, size, checkMismatch = -1;
|
|
+ u32 sum, v32, tmp, id;
|
|
+ /* we skip the first four bytes which contain the control sum */
|
|
+ const u8 *p = (u8*)fw_image + 4;
|
|
+
|
|
+ /* start the image checksum by adding the image size value */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+#ifdef NOPE
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */
|
|
+#else
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+#endif
|
|
+ len = 0;
|
|
+ size = le32_to_cpu(fw_image->size) & (~3);
|
|
+
|
|
+ while (likely(len < size)) {
|
|
+ v32 = be32_to_cpu(*(u32*)p);
|
|
+ sum += p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+ len += 4;
|
|
+
|
|
+#ifdef NOPE
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4);
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_DATA, v32);
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+ write_slavemem32 (adev, offset + len - 4, v32);
|
|
+
|
|
+ id = read_id_register (adev);
|
|
+
|
|
+ /*
|
|
+ * check the data written
|
|
+ */
|
|
+ tmp = read_slavemem32 (adev, offset + len - 4);
|
|
+ if (checkMismatch && (tmp != v32)) {
|
|
+ printk ("first data mismatch at 0x%08x good 0x%08x bad 0x%08x id 0x%08x\n",
|
|
+ offset + len - 4, v32, tmp, id);
|
|
+ checkMismatch = 0;
|
|
+ }
|
|
+ }
|
|
+ log(L_DEBUG, "firmware written, size:%d sum1:%x sum2:%x\n",
|
|
+ size, sum, le32_to_cpu(fw_image->chksum));
|
|
+
|
|
+ /* compare our checksum with the stored image checksum */
|
|
+ return (sum != le32_to_cpu(fw_image->chksum));
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_validate_fw
|
|
+**
|
|
+** Compare the firmware image given with
|
|
+** the firmware image written into the card.
|
|
+**
|
|
+** Arguments:
|
|
+** adev wlan device structure
|
|
+** fw_image firmware image.
|
|
+**
|
|
+** Returns:
|
|
+** NOT_OK firmware image corrupted or not correctly written
|
|
+** OK success
|
|
+*/
|
|
+static int
|
|
+acxmem_s_validate_fw(acx_device_t *adev, const firmware_image_t *fw_image,
|
|
+ u32 offset)
|
|
+{
|
|
+ u32 sum, v32, w32;
|
|
+ int len, size;
|
|
+ int result = OK;
|
|
+ /* we skip the first four bytes which contain the control sum */
|
|
+ const u8 *p = (u8*)fw_image + 4;
|
|
+
|
|
+ /* start the image checksum by adding the image size value */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_SLV_END_CTL, 0);
|
|
+
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */
|
|
+#else
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */
|
|
+#endif
|
|
+
|
|
+ len = 0;
|
|
+ size = le32_to_cpu(fw_image->size) & (~3);
|
|
+
|
|
+ while (likely(len < size)) {
|
|
+ v32 = be32_to_cpu(*(u32*)p);
|
|
+ p += 4;
|
|
+ len += 4;
|
|
+
|
|
+#ifdef NOPE
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4);
|
|
+#endif
|
|
+ udelay(10);
|
|
+ w32 = read_reg32(adev, IO_ACX_SLV_MEM_DATA);
|
|
+#endif
|
|
+ w32 = read_slavemem32 (adev, offset + len - 4);
|
|
+
|
|
+ if (unlikely(w32 != v32)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "data parts at offset %d don't match\n(0x%08X vs. 0x%08X)!\n"
|
|
+ "I/O timing issues or defective memory, with DWL-xx0+? "
|
|
+ "ACX_IO_WIDTH=16 may help. Please report\n",
|
|
+ len, v32, w32);
|
|
+ result = NOT_OK;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ sum += (u8)w32 + (u8)(w32>>8) + (u8)(w32>>16) + (u8)(w32>>24);
|
|
+ }
|
|
+
|
|
+ /* sum control verification */
|
|
+ if (result != NOT_OK) {
|
|
+ if (sum != le32_to_cpu(fw_image->chksum)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "checksums don't match!\n");
|
|
+ result = NOT_OK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_upload_fw
|
|
+**
|
|
+** Called from acx_reset_dev
|
|
+*/
|
|
+static int
|
|
+acxmem_s_upload_fw(acx_device_t *adev)
|
|
+{
|
|
+ firmware_image_t *fw_image = NULL;
|
|
+ int res = NOT_OK;
|
|
+ int try;
|
|
+ u32 file_size;
|
|
+ char *filename = "WLANGEN.BIN";
|
|
+#ifdef PATCH_AROUND_BAD_SPOTS
|
|
+ u32 offset;
|
|
+ int i;
|
|
+ /*
|
|
+ * arm-linux-objdump -d patch.bin, or
|
|
+ * od -Ax -t x4 patch.bin after finding the bounds
|
|
+ * of the .text section with arm-linux-objdump -s patch.bin
|
|
+ */
|
|
+ u32 patch[] = {
|
|
+ 0xe584c030, 0xe59fc008,
|
|
+ 0xe92d1000, 0xe59fc004, 0xe8bd8000, 0x0000080c,
|
|
+ 0x0000aa68, 0x605a2200, 0x2c0a689c, 0x2414d80a,
|
|
+ 0x2f00689f, 0x1c27d007, 0x06241e7c, 0x2f000e24,
|
|
+ 0xe000d1f6, 0x602e6018, 0x23036468, 0x480203db,
|
|
+ 0x60ca6003, 0xbdf0750a, 0xffff0808
|
|
+ };
|
|
+#endif
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* No combined image; tell common we need the radio firmware, too */
|
|
+ adev->need_radio_fw = 1;
|
|
+
|
|
+ fw_image = acx_s_read_fw(adev->dev, filename, &file_size);
|
|
+ if (!fw_image) {
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+
|
|
+ for (try = 1; try <= 5; try++) {
|
|
+ res = acxmem_s_write_fw(adev, fw_image, 0);
|
|
+ log(L_DEBUG|L_INIT, "acx_write_fw (main): %d\n", res);
|
|
+ if (OK == res) {
|
|
+ res = acxmem_s_validate_fw(adev, fw_image, 0);
|
|
+ log(L_DEBUG|L_INIT, "acx_validate_fw "
|
|
+ "(main): %d\n", res);
|
|
+ }
|
|
+
|
|
+ if (OK == res) {
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_FW_LOADED);
|
|
+ break;
|
|
+ }
|
|
+ printk("acx: firmware upload attempt #%d FAILED, "
|
|
+ "retrying...\n", try);
|
|
+ acx_s_msleep(1000); /* better wait for a while... */
|
|
+ }
|
|
+
|
|
+#ifdef PATCH_AROUND_BAD_SPOTS
|
|
+ /*
|
|
+ * Only want to do this if the firmware is exactly what we expect for an
|
|
+ * iPaq 4700; otherwise, bad things would ensue.
|
|
+ */
|
|
+ if ((HX4700_FIRMWARE_CHECKSUM == fw_image->chksum) ||
|
|
+ (HX4700_ALTERNATE_FIRMWARE_CHECKSUM == fw_image->chksum)) {
|
|
+ /*
|
|
+ * Put the patch after the main firmware image. 0x950c contains
|
|
+ * the ACX's idea of the end of the firmware. Use that location to
|
|
+ * load ours (which depends on that location being 0xab58) then
|
|
+ * update that location to point to after ours.
|
|
+ */
|
|
+
|
|
+ offset = read_slavemem32 (adev, 0x950c);
|
|
+
|
|
+ log (L_DEBUG, "acx: patching in at 0x%04x\n", offset);
|
|
+
|
|
+ for (i = 0; i < sizeof(patch) / sizeof(patch[0]); i++) {
|
|
+ write_slavemem32 (adev, offset, patch[i]);
|
|
+ offset += sizeof(u32);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Patch the instruction at 0x0804 to branch to our ARM patch at 0xab58
|
|
+ */
|
|
+ write_slavemem32 (adev, 0x0804, 0xea000000 + (0xab58-0x0804-8)/4);
|
|
+
|
|
+ /*
|
|
+ * Patch the instructions at 0x1f40 to branch to our Thumb patch at 0xab74
|
|
+ *
|
|
+ * 4a00 ldr r2, [pc, #0]
|
|
+ * 4710 bx r2
|
|
+ * .data 0xab74+1
|
|
+ */
|
|
+ write_slavemem32 (adev, 0x1f40, 0x47104a00);
|
|
+ write_slavemem32 (adev, 0x1f44, 0x0000ab74+1);
|
|
+
|
|
+ /*
|
|
+ * Bump the end of the firmware up to beyond our patch.
|
|
+ */
|
|
+ write_slavemem32 (adev, 0x950c, offset);
|
|
+
|
|
+ }
|
|
+#endif
|
|
+
|
|
+ vfree(fw_image);
|
|
+
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_upload_radio
|
|
+**
|
|
+** Uploads the appropriate radio module firmware into the card.
|
|
+*/
|
|
+int
|
|
+acxmem_s_upload_radio(acx_device_t *adev)
|
|
+{
|
|
+ acx_ie_memmap_t mm;
|
|
+ firmware_image_t *radio_image;
|
|
+ acx_cmd_radioinit_t radioinit;
|
|
+ int res = NOT_OK;
|
|
+ int try;
|
|
+ u32 offset;
|
|
+ u32 size;
|
|
+ char filename[sizeof("RADIONN.BIN")];
|
|
+
|
|
+ if (!adev->need_radio_fw) return OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP);
|
|
+ offset = le32_to_cpu(mm.CodeEnd);
|
|
+
|
|
+ snprintf(filename, sizeof(filename), "RADIO%02x.BIN",
|
|
+ adev->radio_type);
|
|
+ radio_image = acx_s_read_fw(adev->dev, filename, &size);
|
|
+ if (!radio_image) {
|
|
+ printk("acx: can't load radio module '%s'\n", filename);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_SLEEP, NULL, 0);
|
|
+
|
|
+ for (try = 1; try <= 5; try++) {
|
|
+ res = acxmem_s_write_fw(adev, radio_image, offset);
|
|
+ log(L_DEBUG|L_INIT, "acx_write_fw (radio): %d\n", res);
|
|
+ if (OK == res) {
|
|
+ res = acxmem_s_validate_fw(adev, radio_image, offset);
|
|
+ log(L_DEBUG|L_INIT, "acx_validate_fw (radio): %d\n", res);
|
|
+ }
|
|
+
|
|
+ if (OK == res)
|
|
+ break;
|
|
+ printk("acx: radio firmware upload attempt #%d FAILED, "
|
|
+ "retrying...\n", try);
|
|
+ acx_s_msleep(1000); /* better wait for a while... */
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0);
|
|
+ radioinit.offset = cpu_to_le32(offset);
|
|
+
|
|
+ /* no endian conversion needed, remains in card CPU area: */
|
|
+ radioinit.len = radio_image->size;
|
|
+
|
|
+ vfree(radio_image);
|
|
+
|
|
+ if (OK != res)
|
|
+ goto fail;
|
|
+
|
|
+ /* will take a moment so let's have a big timeout */
|
|
+ acx_s_issue_cmd_timeo(adev, ACX1xx_CMD_RADIOINIT,
|
|
+ &radioinit, sizeof(radioinit), CMD_TIMEOUT_MS(1000));
|
|
+
|
|
+ res = acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP);
|
|
+
|
|
+fail:
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_reset_mac
|
|
+**
|
|
+** MAC will be reset
|
|
+** Call context: reset_dev
|
|
+*/
|
|
+static void
|
|
+acxmem_l_reset_mac(acx_device_t *adev)
|
|
+{
|
|
+ int count;
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* halt eCPU */
|
|
+ set_regbits (adev, IO_ACX_ECPU_CTRL, 0x1);
|
|
+
|
|
+ /* now do soft reset of eCPU, set bit */
|
|
+ set_regbits (adev, IO_ACX_SOFT_RESET, 0x1);
|
|
+ log(L_DEBUG, "%s: enable soft reset...\n", __func__);
|
|
+
|
|
+ /* Windows driver sleeps here for a while with this sequence */
|
|
+ for (count = 0; count < 200; count++) {
|
|
+ udelay (50);
|
|
+ }
|
|
+
|
|
+ /* now clear bit again: deassert eCPU reset */
|
|
+ log(L_DEBUG, "%s: disable soft reset and go to init mode...\n", __func__);
|
|
+ clear_regbits (adev, IO_ACX_SOFT_RESET, 0x1);
|
|
+
|
|
+ /* now start a burst read from initial EEPROM */
|
|
+ set_regbits (adev, IO_ACX_EE_START, 0x1);
|
|
+
|
|
+ /*
|
|
+ * Windows driver sleeps here for a while with this sequence
|
|
+ */
|
|
+ for (count = 0; count < 200; count++) {
|
|
+ udelay (50);
|
|
+ }
|
|
+
|
|
+ /* Windows driver writes 0x10000 to register 0x808 here */
|
|
+
|
|
+ write_reg32 (adev, 0x808, 0x10000);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_verify_init
|
|
+*/
|
|
+static int
|
|
+acxmem_s_verify_init(acx_device_t *adev)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+ unsigned long timeout;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ timeout = jiffies + 2*HZ;
|
|
+ for (;;) {
|
|
+ u32 irqstat = read_reg32(adev, IO_ACX_IRQ_STATUS_NON_DES);
|
|
+ if ((irqstat != 0xFFFFFFFF) && (irqstat & HOST_INT_FCS_THRESHOLD)) {
|
|
+ result = OK;
|
|
+ write_reg32(adev, IO_ACX_IRQ_ACK, HOST_INT_FCS_THRESHOLD);
|
|
+ break;
|
|
+ }
|
|
+ if (time_after(jiffies, timeout))
|
|
+ break;
|
|
+ /* Init may take up to ~0.5 sec total */
|
|
+ acx_s_msleep(50);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** A few low-level helpers
|
|
+**
|
|
+** Note: these functions are not protected by lock
|
|
+** and thus are never allowed to be called from IRQ.
|
|
+** Also they must not race with fw upload which uses same hw regs
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_write_cmd_type_status
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+acxmem_write_cmd_type_status(acx_device_t *adev, u16 type, u16 status)
|
|
+{
|
|
+ write_slavemem32 (adev, (u32) adev->cmd_area, type | (status << 16));
|
|
+ write_flush(adev);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_read_cmd_type_status
|
|
+*/
|
|
+static u32
|
|
+acxmem_read_cmd_type_status(acx_device_t *adev)
|
|
+{
|
|
+ u32 cmd_type, cmd_status;
|
|
+
|
|
+ cmd_type = read_slavemem32 (adev, (u32) adev->cmd_area);
|
|
+
|
|
+ cmd_status = (cmd_type >> 16);
|
|
+ cmd_type = (u16)cmd_type;
|
|
+
|
|
+ log(L_CTL, "cmd_type:%04X cmd_status:%04X [%s]\n",
|
|
+ cmd_type, cmd_status,
|
|
+ acx_cmd_status_str(cmd_status));
|
|
+
|
|
+ return cmd_status;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_reset_dev
|
|
+**
|
|
+** Arguments:
|
|
+** netdevice that contains the adev variable
|
|
+** Returns:
|
|
+** NOT_OK on fail
|
|
+** OK on success
|
|
+** Side effects:
|
|
+** device is hard reset
|
|
+** Call context:
|
|
+** acxmem_e_probe
|
|
+** Comment:
|
|
+** This resets the device using low level hardware calls
|
|
+** as well as uploads and verifies the firmware to the card
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+init_mboxes(acx_device_t *adev)
|
|
+{
|
|
+ u32 cmd_offs, info_offs;
|
|
+
|
|
+ cmd_offs = read_reg32(adev, IO_ACX_CMD_MAILBOX_OFFS);
|
|
+ info_offs = read_reg32(adev, IO_ACX_INFO_MAILBOX_OFFS);
|
|
+ adev->cmd_area = (u8*) cmd_offs;
|
|
+ adev->info_area = (u8*) info_offs;
|
|
+ /*
|
|
+ log(L_DEBUG, "iobase2=%p\n"
|
|
+ */
|
|
+ log( L_DEBUG, "cmd_mbox_offset=%X cmd_area=%p\n"
|
|
+ "info_mbox_offset=%X info_area=%p\n",
|
|
+ cmd_offs, adev->cmd_area,
|
|
+ info_offs, adev->info_area);
|
|
+}
|
|
+
|
|
+
|
|
+static inline void
|
|
+read_eeprom_area(acx_device_t *adev)
|
|
+{
|
|
+#if ACX_DEBUG > 1
|
|
+ int offs;
|
|
+ u8 tmp;
|
|
+
|
|
+ for (offs = 0x8c; offs < 0xb9; offs++)
|
|
+ acxmem_read_eeprom_byte(adev, offs, &tmp);
|
|
+#endif
|
|
+}
|
|
+
|
|
+static int
|
|
+acxmem_s_reset_dev(acx_device_t *adev)
|
|
+{
|
|
+ const char* msg = "";
|
|
+ unsigned long flags;
|
|
+ int result = NOT_OK;
|
|
+ u16 hardware_info;
|
|
+ u16 ecpu_ctrl;
|
|
+ int count;
|
|
+ u32 tmp;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /*
|
|
+ write_reg32 (adev, IO_ACX_SLV_MEM_CP, 0);
|
|
+ */
|
|
+ /* reset the device to make sure the eCPU is stopped
|
|
+ * to upload the firmware correctly */
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* Windows driver does some funny things here */
|
|
+ /*
|
|
+ * clear bit 0x200 in register 0x2A0
|
|
+ */
|
|
+ clear_regbits (adev, 0x2A0, 0x200);
|
|
+
|
|
+ /*
|
|
+ * Set bit 0x200 in ACX_GPIO_OUT
|
|
+ */
|
|
+ set_regbits (adev, IO_ACX_GPIO_OUT, 0x200);
|
|
+
|
|
+ /*
|
|
+ * read register 0x900 until its value is 0x8400104C, sleeping
|
|
+ * in between reads if it's not immediate
|
|
+ */
|
|
+ tmp = read_reg32 (adev, REG_ACX_VENDOR_ID);
|
|
+ count = 500;
|
|
+ while (count-- && (tmp != ACX_VENDOR_ID)) {
|
|
+ mdelay (10);
|
|
+ tmp = read_reg32 (adev, REG_ACX_VENDOR_ID);
|
|
+ }
|
|
+
|
|
+ /* end what Windows driver does */
|
|
+
|
|
+ acxmem_l_reset_mac(adev);
|
|
+
|
|
+ ecpu_ctrl = read_reg32(adev, IO_ACX_ECPU_CTRL) & 1;
|
|
+ if (!ecpu_ctrl) {
|
|
+ msg = "eCPU is already running. ";
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+#ifdef WE_DONT_NEED_THAT_DO_WE
|
|
+ if (read_reg16(adev, IO_ACX_SOR_CFG) & 2) {
|
|
+ /* eCPU most likely means "embedded CPU" */
|
|
+ msg = "eCPU did not start after boot from flash. ";
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ /* check sense on reset flags */
|
|
+ if (read_reg16(adev, IO_ACX_SOR_CFG) & 0x10) {
|
|
+ printk("%s: eCPU did not start after boot (SOR), "
|
|
+ "is this fatal?\n", adev->ndev->name);
|
|
+ }
|
|
+#endif
|
|
+ /* scan, if any, is stopped now, setting corresponding IRQ bit */
|
|
+ adev->irq_status |= HOST_INT_SCAN_COMPLETE;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* need to know radio type before fw load */
|
|
+ /* Need to wait for arrival of this information in a loop,
|
|
+ * most probably since eCPU runs some init code from EEPROM
|
|
+ * (started burst read in reset_mac()) which also
|
|
+ * sets the radio type ID */
|
|
+
|
|
+ count = 0xffff;
|
|
+ do {
|
|
+ hardware_info = read_reg16(adev, IO_ACX_EEPROM_INFORMATION);
|
|
+ if (!--count) {
|
|
+ msg = "eCPU didn't indicate radio type";
|
|
+ goto end_fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ } while (!(hardware_info & 0xff00)); /* radio type still zero? */
|
|
+ printk("ACX radio type 0x%02x\n", (hardware_info >> 8) & 0xff);
|
|
+ /* printk("DEBUG: count %d\n", count); */
|
|
+ adev->form_factor = hardware_info & 0xff;
|
|
+ adev->radio_type = hardware_info >> 8;
|
|
+
|
|
+ /* load the firmware */
|
|
+ if (OK != acxmem_s_upload_fw(adev))
|
|
+ goto end_fail;
|
|
+
|
|
+ /* acx_s_msleep(10); this one really shouldn't be required */
|
|
+
|
|
+ /* now start eCPU by clearing bit */
|
|
+ clear_regbits (adev, IO_ACX_ECPU_CTRL, 0x1);
|
|
+ log(L_DEBUG, "booted eCPU up and waiting for completion...\n");
|
|
+
|
|
+ /* Windows driver clears bit 0x200 in register 0x2A0 here */
|
|
+ clear_regbits (adev, 0x2A0, 0x200);
|
|
+
|
|
+ /* Windows driver sets bit 0x200 in ACX_GPIO_OUT here */
|
|
+ set_regbits (adev, IO_ACX_GPIO_OUT, 0x200);
|
|
+ /* wait for eCPU bootup */
|
|
+ if (OK != acxmem_s_verify_init(adev)) {
|
|
+ msg = "timeout waiting for eCPU. ";
|
|
+ goto end_fail;
|
|
+ }
|
|
+ log(L_DEBUG, "eCPU has woken up, card is ready to be configured\n");
|
|
+ init_mboxes(adev);
|
|
+ acxmem_write_cmd_type_status(adev, ACX1xx_CMD_RESET, 0);
|
|
+
|
|
+ /* test that EEPROM is readable */
|
|
+ read_eeprom_area(adev);
|
|
+
|
|
+ result = OK;
|
|
+ goto end;
|
|
+
|
|
+/* Finish error message. Indicate which function failed */
|
|
+end_unlock:
|
|
+ acx_unlock(adev, flags);
|
|
+end_fail:
|
|
+ printk("acx: %sreset_dev() FAILED\n", msg);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_issue_cmd_timeo
|
|
+**
|
|
+** Sends command to fw, extract result
|
|
+**
|
|
+** NB: we do _not_ take lock inside, so be sure to not touch anything
|
|
+** which may interfere with IRQ handler operation
|
|
+**
|
|
+** TODO: busy wait is a bit silly, so:
|
|
+** 1) stop doing many iters - go to sleep after first
|
|
+** 2) go to waitqueue based approach: wait, not poll!
|
|
+*/
|
|
+#undef FUNC
|
|
+#define FUNC "issue_cmd"
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+int
|
|
+acxmem_s_issue_cmd_timeo(
|
|
+ acx_device_t *adev,
|
|
+ unsigned int cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned cmd_timeout)
|
|
+{
|
|
+#else
|
|
+int
|
|
+acxmem_s_issue_cmd_timeo_debug(
|
|
+ acx_device_t *adev,
|
|
+ unsigned cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned cmd_timeout,
|
|
+ const char* cmdstr)
|
|
+{
|
|
+ unsigned long start = jiffies;
|
|
+#endif
|
|
+ const char *devname;
|
|
+ unsigned counter;
|
|
+ u16 irqtype;
|
|
+ int i, j;
|
|
+ u8 *p;
|
|
+ u16 cmd_status;
|
|
+ unsigned long timeout;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ devname = adev->ndev->name;
|
|
+ if (!devname || !devname[0] || devname[4]=='%')
|
|
+ devname = "acx";
|
|
+
|
|
+ log(L_CTL, FUNC"(cmd:%s,buflen:%u,timeout:%ums,type:0x%04X)\n",
|
|
+ cmdstr, buflen, cmd_timeout,
|
|
+ buffer ? le16_to_cpu(((acx_ie_generic_t *)buffer)->type) : -1);
|
|
+
|
|
+ if (!(adev->dev_state_mask & ACX_STATE_FW_LOADED)) {
|
|
+ printk("%s: "FUNC"(): firmware is not loaded yet, "
|
|
+ "cannot execute commands!\n", devname);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ if ((acx_debug & L_DEBUG) && (cmd != ACX1xx_CMD_INTERROGATE)) {
|
|
+ printk("input buffer (len=%u):\n", buflen);
|
|
+ acx_dump_bytes(buffer, buflen);
|
|
+ }
|
|
+
|
|
+ /* wait for firmware to become idle for our command submission */
|
|
+ timeout = HZ/5;
|
|
+ counter = (timeout * 1000 / HZ) - 1; /* in ms */
|
|
+ timeout += jiffies;
|
|
+ do {
|
|
+ cmd_status = acxmem_read_cmd_type_status(adev);
|
|
+ /* Test for IDLE state */
|
|
+ if (!cmd_status)
|
|
+ break;
|
|
+ if (counter % 8 == 0) {
|
|
+ if (time_after(jiffies, timeout)) {
|
|
+ counter = 0;
|
|
+ break;
|
|
+ }
|
|
+ /* we waited 8 iterations, no luck. Sleep 8 ms */
|
|
+ acx_s_msleep(8);
|
|
+ }
|
|
+ } while (likely(--counter));
|
|
+
|
|
+ if (!counter) {
|
|
+ /* the card doesn't get idle, we're in trouble */
|
|
+ printk("%s: "FUNC"(): cmd_status is not IDLE: 0x%04X!=0\n",
|
|
+ devname, cmd_status);
|
|
+#if DUMP_IF_SLOW > 0
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ panic ("not idle");
|
|
+#endif
|
|
+ goto bad;
|
|
+ } else if (counter < 190) { /* if waited >10ms... */
|
|
+ log(L_CTL|L_DEBUG, FUNC"(): waited for IDLE %dms. "
|
|
+ "Please report\n", 199 - counter);
|
|
+ }
|
|
+
|
|
+ /* now write the parameters of the command if needed */
|
|
+ if (buffer && buflen) {
|
|
+ /* if it's an INTERROGATE command, just pass the length
|
|
+ * of parameters to read, as data */
|
|
+#if CMD_DISCOVERY
|
|
+ if (cmd == ACX1xx_CMD_INTERROGATE)
|
|
+ memset_io(adev->cmd_area + 4, 0xAA, buflen);
|
|
+#endif
|
|
+ /*
|
|
+ * slave memory version
|
|
+ */
|
|
+ copy_to_slavemem (adev, (u32) (adev->cmd_area + 4), buffer,
|
|
+ (cmd == ACX1xx_CMD_INTERROGATE) ? 4 : buflen);
|
|
+ }
|
|
+ /* now write the actual command type */
|
|
+ acxmem_write_cmd_type_status(adev, cmd, 0);
|
|
+
|
|
+ /* clear CMD_COMPLETE bit. can be set only by IRQ handler: */
|
|
+ adev->irq_status &= ~HOST_INT_CMD_COMPLETE;
|
|
+
|
|
+ /* execute command */
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_CMD);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* wait for firmware to process command */
|
|
+
|
|
+ /* Ensure nonzero and not too large timeout.
|
|
+ ** Also converts e.g. 100->99, 200->199
|
|
+ ** which is nice but not essential */
|
|
+ cmd_timeout = (cmd_timeout-1) | 1;
|
|
+ if (unlikely(cmd_timeout > 1199))
|
|
+ cmd_timeout = 1199;
|
|
+
|
|
+ /* we schedule away sometimes (timeout can be large) */
|
|
+ counter = cmd_timeout;
|
|
+ timeout = jiffies + cmd_timeout * HZ / 1000;
|
|
+ do {
|
|
+ if (!adev->irqs_active) { /* IRQ disabled: poll */
|
|
+ irqtype = read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES);
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE) {
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK,
|
|
+ HOST_INT_CMD_COMPLETE);
|
|
+ break;
|
|
+ }
|
|
+ } else { /* Wait when IRQ will set the bit */
|
|
+ irqtype = adev->irq_status;
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (counter % 8 == 0) {
|
|
+ if (time_after(jiffies, timeout)) {
|
|
+ counter = 0;
|
|
+ break;
|
|
+ }
|
|
+ /* we waited 8 iterations, no luck. Sleep 8 ms */
|
|
+ acx_s_msleep(8);
|
|
+ }
|
|
+ } while (likely(--counter));
|
|
+
|
|
+ /* save state for debugging */
|
|
+ cmd_status = acxmem_read_cmd_type_status(adev);
|
|
+
|
|
+ /* put the card in IDLE state */
|
|
+ acxmem_write_cmd_type_status(adev, ACX1xx_CMD_RESET, 0);
|
|
+
|
|
+ if (!counter) { /* timed out! */
|
|
+ printk("%s: "FUNC"(): timed out %s for CMD_COMPLETE. "
|
|
+ "irq bits:0x%04X irq_status:0x%04X timeout:%dms "
|
|
+ "cmd_status:%d (%s)\n",
|
|
+ devname, (adev->irqs_active) ? "waiting" : "polling",
|
|
+ irqtype, adev->irq_status, cmd_timeout,
|
|
+ cmd_status, acx_cmd_status_str(cmd_status));
|
|
+ printk("%s: "FUNC"(): device irq status 0x%04x\n",
|
|
+ devname, read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES));
|
|
+ printk("%s: "FUNC"(): IO_ACX_IRQ_MASK 0x%04x IO_ACX_FEMR 0x%04x\n",
|
|
+ devname,
|
|
+ read_reg16 (adev, IO_ACX_IRQ_MASK),
|
|
+ read_reg16 (adev, IO_ACX_FEMR));
|
|
+ if (read_reg16 (adev, IO_ACX_IRQ_MASK) == 0xffff) {
|
|
+ printk ("acxmem: firmware probably hosed - reloading\n");
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+ {
|
|
+ pm_message_t state;
|
|
+ acxmem_e_suspend (resume_pdev, state);
|
|
+ }
|
|
+#else
|
|
+ acxmem_e_suspend (adev->dev, 0);
|
|
+#endif
|
|
+ {
|
|
+ struct work_struct *notused;
|
|
+ fw_resumer (notused);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ goto bad;
|
|
+ } else if (cmd_timeout - counter > 30) { /* if waited >30ms... */
|
|
+ log(L_CTL|L_DEBUG, FUNC"(): %s for CMD_COMPLETE %dms. "
|
|
+ "count:%d. Please report\n",
|
|
+ (adev->irqs_active) ? "waited" : "polled",
|
|
+ cmd_timeout - counter, counter);
|
|
+ }
|
|
+
|
|
+ if (1 != cmd_status) { /* it is not a 'Success' */
|
|
+ printk("%s: "FUNC"(): cmd_status is not SUCCESS: %d (%s). "
|
|
+ "Took %dms of %d\n",
|
|
+ devname, cmd_status, acx_cmd_status_str(cmd_status),
|
|
+ cmd_timeout - counter, cmd_timeout);
|
|
+ /* zero out result buffer
|
|
+ * WARNING: this will trash stack in case of illegally large input
|
|
+ * length! */
|
|
+ if (buflen > 388) {
|
|
+ /*
|
|
+ * 388 is maximum command length
|
|
+ */
|
|
+ printk ("invalid length 0x%08x\n", buflen);
|
|
+ buflen = 388;
|
|
+ }
|
|
+ p = (u8 *) buffer;
|
|
+ for (i = 0; i < buflen; i+= 16) {
|
|
+ printk ("%04x:", i);
|
|
+ for (j = 0; (j < 16) && (i+j < buflen); j++) {
|
|
+ printk (" %02x", *p++);
|
|
+ }
|
|
+ printk ("\n");
|
|
+ }
|
|
+
|
|
+ if (buffer && buflen)
|
|
+ memset(buffer, 0, buflen);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* read in result parameters if needed */
|
|
+ if (buffer && buflen && (cmd == ACX1xx_CMD_INTERROGATE)) {
|
|
+ copy_from_slavemem (adev, buffer, (u32) (adev->cmd_area + 4), buflen);
|
|
+ if (acx_debug & L_DEBUG) {
|
|
+ printk("output buffer (len=%u): ", buflen);
|
|
+ acx_dump_bytes(buffer, buflen);
|
|
+ }
|
|
+ }
|
|
+
|
|
+/* ok: */
|
|
+ log(L_CTL, FUNC"(%s): took %ld jiffies to complete\n",
|
|
+ cmdstr, jiffies - start);
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+
|
|
+bad:
|
|
+ /* Give enough info so that callers can avoid
|
|
+ ** printing their own diagnostic messages */
|
|
+#if ACX_DEBUG
|
|
+ printk("%s: "FUNC"(cmd:%s) FAILED\n", devname, cmdstr);
|
|
+#else
|
|
+ printk("%s: "FUNC"(cmd:0x%04X) FAILED\n", devname, cmd);
|
|
+#endif
|
|
+ dump_stack();
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#if defined(NONESSENTIAL_FEATURES)
|
|
+typedef struct device_id {
|
|
+ unsigned char id[6];
|
|
+ char *descr;
|
|
+ char *type;
|
|
+} device_id_t;
|
|
+
|
|
+static const device_id_t
|
|
+device_ids[] =
|
|
+{
|
|
+ {
|
|
+ {'G', 'l', 'o', 'b', 'a', 'l'},
|
|
+ NULL,
|
|
+ NULL,
|
|
+ },
|
|
+ {
|
|
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
+ "uninitialized",
|
|
+ "SpeedStream SS1021 or Gigafast WF721-AEX"
|
|
+ },
|
|
+ {
|
|
+ {0x80, 0x81, 0x82, 0x83, 0x84, 0x85},
|
|
+ "non-standard",
|
|
+ "DrayTek Vigor 520"
|
|
+ },
|
|
+ {
|
|
+ {'?', '?', '?', '?', '?', '?'},
|
|
+ "non-standard",
|
|
+ "Level One WPC-0200"
|
|
+ },
|
|
+ {
|
|
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
+ "empty",
|
|
+ "DWL-650+ variant"
|
|
+ }
|
|
+};
|
|
+
|
|
+static void
|
|
+acx_show_card_eeprom_id(acx_device_t *adev)
|
|
+{
|
|
+ unsigned char buffer[CARD_EEPROM_ID_SIZE];
|
|
+ int i;
|
|
+
|
|
+ memset(&buffer, 0, CARD_EEPROM_ID_SIZE);
|
|
+ /* use direct EEPROM access */
|
|
+ for (i = 0; i < CARD_EEPROM_ID_SIZE; i++) {
|
|
+ if (OK != acxmem_read_eeprom_byte(adev,
|
|
+ ACX100_EEPROM_ID_OFFSET + i,
|
|
+ &buffer[i])) {
|
|
+ printk("acx: reading EEPROM FAILED\n");
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(device_ids); i++) {
|
|
+ if (!memcmp(&buffer, device_ids[i].id, CARD_EEPROM_ID_SIZE)) {
|
|
+ if (device_ids[i].descr) {
|
|
+ printk("acx: EEPROM card ID string check "
|
|
+ "found %s card ID: is this %s?\n",
|
|
+ device_ids[i].descr, device_ids[i].type);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (i == VEC_SIZE(device_ids)) {
|
|
+ printk("acx: EEPROM card ID string check found "
|
|
+ "unknown card: expected 'Global', got '%.*s\'. "
|
|
+ "Please report\n", CARD_EEPROM_ID_SIZE, buffer);
|
|
+ }
|
|
+}
|
|
+#endif /* NONESSENTIAL_FEATURES */
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_free_desc_queues
|
|
+**
|
|
+** Releases the queues that have been allocated, the
|
|
+** others have been initialised to NULL so this
|
|
+** function can be used if only part of the queues were allocated.
|
|
+*/
|
|
+
|
|
+void
|
|
+acxmem_free_desc_queues(acx_device_t *adev)
|
|
+{
|
|
+#define ACX_FREE_QUEUE(size, ptr, phyaddr) \
|
|
+ if (ptr) { \
|
|
+ kfree(ptr); \
|
|
+ ptr = NULL; \
|
|
+ size = 0; \
|
|
+ }
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ACX_FREE_QUEUE(adev->txhostdesc_area_size, adev->txhostdesc_start, adev->txhostdesc_startphy);
|
|
+ ACX_FREE_QUEUE(adev->txbuf_area_size, adev->txbuf_start, adev->txbuf_startphy);
|
|
+
|
|
+ adev->txdesc_start = NULL;
|
|
+
|
|
+ ACX_FREE_QUEUE(adev->rxhostdesc_area_size, adev->rxhostdesc_start, adev->rxhostdesc_startphy);
|
|
+ ACX_FREE_QUEUE(adev->rxbuf_area_size, adev->rxbuf_start, adev->rxbuf_startphy);
|
|
+
|
|
+ adev->rxdesc_start = NULL;
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_delete_dma_regions
|
|
+*/
|
|
+static void
|
|
+acxmem_s_delete_dma_regions(acx_device_t *adev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* disable radio Tx/Rx. Shouldn't we use the firmware commands
|
|
+ * here instead? Or are we that much down the road that it's no
|
|
+ * longer possible here? */
|
|
+ /*
|
|
+ * slave memory interface really doesn't like this.
|
|
+ */
|
|
+ /*
|
|
+ write_reg16(adev, IO_ACX_ENABLE, 0);
|
|
+ */
|
|
+
|
|
+ acx_s_msleep(100);
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ acxmem_free_desc_queues(adev);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_probe
|
|
+**
|
|
+** Probe routine called when a PCI device w/ matching ID is found.
|
|
+** Here's the sequence:
|
|
+** - Allocate the PCI resources.
|
|
+** - Read the PCMCIA attribute memory to make sure we have a WLAN card
|
|
+** - Reset the MAC
|
|
+** - Initialize the dev and wlan data
|
|
+** - Initialize the MAC
|
|
+**
|
|
+** pdev - ptr to pci device structure containing info about pci configuration
|
|
+** id - ptr to the device id entry that matched this device
|
|
+*/
|
|
+static const u16
|
|
+IO_ACX100[] =
|
|
+{
|
|
+ 0x0000, /* IO_ACX_SOFT_RESET */
|
|
+
|
|
+ 0x0014, /* IO_ACX_SLV_MEM_ADDR */
|
|
+ 0x0018, /* IO_ACX_SLV_MEM_DATA */
|
|
+ 0x001c, /* IO_ACX_SLV_MEM_CTL */
|
|
+ 0x0020, /* IO_ACX_SLV_END_CTL */
|
|
+
|
|
+ 0x0034, /* IO_ACX_FEMR */
|
|
+
|
|
+ 0x007c, /* IO_ACX_INT_TRIG */
|
|
+ 0x0098, /* IO_ACX_IRQ_MASK */
|
|
+ 0x00a4, /* IO_ACX_IRQ_STATUS_NON_DES */
|
|
+ 0x00a8, /* IO_ACX_IRQ_STATUS_CLEAR */
|
|
+ 0x00ac, /* IO_ACX_IRQ_ACK */
|
|
+ 0x00b0, /* IO_ACX_HINT_TRIG */
|
|
+
|
|
+ 0x0104, /* IO_ACX_ENABLE */
|
|
+
|
|
+ 0x0250, /* IO_ACX_EEPROM_CTL */
|
|
+ 0x0254, /* IO_ACX_EEPROM_ADDR */
|
|
+ 0x0258, /* IO_ACX_EEPROM_DATA */
|
|
+ 0x025c, /* IO_ACX_EEPROM_CFG */
|
|
+
|
|
+ 0x0268, /* IO_ACX_PHY_ADDR */
|
|
+ 0x026c, /* IO_ACX_PHY_DATA */
|
|
+ 0x0270, /* IO_ACX_PHY_CTL */
|
|
+
|
|
+ 0x0290, /* IO_ACX_GPIO_OE */
|
|
+
|
|
+ 0x0298, /* IO_ACX_GPIO_OUT */
|
|
+
|
|
+ 0x02a4, /* IO_ACX_CMD_MAILBOX_OFFS */
|
|
+ 0x02a8, /* IO_ACX_INFO_MAILBOX_OFFS */
|
|
+ 0x02ac, /* IO_ACX_EEPROM_INFORMATION */
|
|
+
|
|
+ 0x02d0, /* IO_ACX_EE_START */
|
|
+ 0x02d4, /* IO_ACX_SOR_CFG */
|
|
+ 0x02d8 /* IO_ACX_ECPU_CTRL */
|
|
+};
|
|
+
|
|
+static const u16
|
|
+IO_ACX111[] =
|
|
+{
|
|
+ 0x0000, /* IO_ACX_SOFT_RESET */
|
|
+
|
|
+ 0x0014, /* IO_ACX_SLV_MEM_ADDR */
|
|
+ 0x0018, /* IO_ACX_SLV_MEM_DATA */
|
|
+ 0x001c, /* IO_ACX_SLV_MEM_CTL */
|
|
+ 0x0020, /* IO_ACX_SLV_MEM_CP */
|
|
+
|
|
+ 0x0034, /* IO_ACX_FEMR */
|
|
+
|
|
+ 0x00b4, /* IO_ACX_INT_TRIG */
|
|
+ 0x00d4, /* IO_ACX_IRQ_MASK */
|
|
+ /* we do mean NON_DES (0xf0), not NON_DES_MASK which is at 0xe0: */
|
|
+ 0x00f0, /* IO_ACX_IRQ_STATUS_NON_DES */
|
|
+ 0x00e4, /* IO_ACX_IRQ_STATUS_CLEAR */
|
|
+ 0x00e8, /* IO_ACX_IRQ_ACK */
|
|
+ 0x00ec, /* IO_ACX_HINT_TRIG */
|
|
+
|
|
+ 0x01d0, /* IO_ACX_ENABLE */
|
|
+
|
|
+ 0x0338, /* IO_ACX_EEPROM_CTL */
|
|
+ 0x033c, /* IO_ACX_EEPROM_ADDR */
|
|
+ 0x0340, /* IO_ACX_EEPROM_DATA */
|
|
+ 0x0344, /* IO_ACX_EEPROM_CFG */
|
|
+
|
|
+ 0x0350, /* IO_ACX_PHY_ADDR */
|
|
+ 0x0354, /* IO_ACX_PHY_DATA */
|
|
+ 0x0358, /* IO_ACX_PHY_CTL */
|
|
+
|
|
+ 0x0374, /* IO_ACX_GPIO_OE */
|
|
+
|
|
+ 0x037c, /* IO_ACX_GPIO_OUT */
|
|
+
|
|
+ 0x0388, /* IO_ACX_CMD_MAILBOX_OFFS */
|
|
+ 0x038c, /* IO_ACX_INFO_MAILBOX_OFFS */
|
|
+ 0x0390, /* IO_ACX_EEPROM_INFORMATION */
|
|
+
|
|
+ 0x0100, /* IO_ACX_EE_START */
|
|
+ 0x0104, /* IO_ACX_SOR_CFG */
|
|
+ 0x0108, /* IO_ACX_ECPU_CTRL */
|
|
+};
|
|
+
|
|
+static void
|
|
+dummy_netdev_init(struct net_device *ndev) {}
|
|
+
|
|
+/*
|
|
+ * Most of the acx specific pieces of hardware reset.
|
|
+ */
|
|
+static int
|
|
+acxmem_complete_hw_reset (acx_device_t *adev)
|
|
+{
|
|
+ acx111_ie_configoption_t co;
|
|
+
|
|
+ /* NB: read_reg() reads may return bogus data before reset_dev(),
|
|
+ * since the firmware which directly controls large parts of the I/O
|
|
+ * registers isn't initialized yet.
|
|
+ * acx100 seems to be more affected than acx111 */
|
|
+ if (OK != acxmem_s_reset_dev (adev))
|
|
+ return -1;
|
|
+
|
|
+ if (IS_ACX100(adev)) {
|
|
+ /* ACX100: configopt struct in cmd mailbox - directly after reset */
|
|
+ copy_from_slavemem (adev, (u8*) &co, (u32) adev->cmd_area, sizeof (co));
|
|
+ }
|
|
+
|
|
+ if (OK != acx_s_init_mac(adev))
|
|
+ return -3;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111: configopt struct needs to be queried after full init */
|
|
+ acx_s_interrogate(adev, &co, ACX111_IE_CONFIG_OPTIONS);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Set up transmit buffer administration
|
|
+ */
|
|
+ init_acx_txbuf (adev);
|
|
+
|
|
+ /*
|
|
+ * Windows driver writes 0x01000000 to register 0x288, RADIO_CTL, if the form factor
|
|
+ * is 3. It also write protects the EEPROM by writing 1<<9 to GPIO_OUT
|
|
+ */
|
|
+ if (adev->form_factor == 3) {
|
|
+ set_regbits (adev, 0x288, 0x01000000);
|
|
+ set_regbits (adev, 0x298, 1<<9);
|
|
+ }
|
|
+
|
|
+/* TODO: merge them into one function, they are called just once and are the same for pci & usb */
|
|
+ if (OK != acxmem_read_eeprom_byte(adev, 0x05, &adev->eeprom_version))
|
|
+ return -2;
|
|
+
|
|
+ acx_s_parse_configoption(adev, &co);
|
|
+ acx_s_get_firmware_version(adev); /* needs to be after acx_s_init_mac() */
|
|
+ acx_display_hardware_details(adev);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int __devinit
|
|
+acxmem_e_probe(struct platform_device *pdev)
|
|
+{
|
|
+ struct acx_hardware_data *hwdata = pdev->dev.platform_data;
|
|
+ acx_device_t *adev = NULL;
|
|
+ struct net_device *ndev = NULL;
|
|
+ const char *chip_name;
|
|
+ int result = -EIO;
|
|
+ int err;
|
|
+ int i;
|
|
+ unsigned long addr_size=0;
|
|
+ u8 chip_type;
|
|
+
|
|
+ FN_ENTER;
|
|
+ (void) hwdata->start_hw();
|
|
+
|
|
+ /* FIXME: prism54 calls pci_set_mwi() here,
|
|
+ * should we do/support the same? */
|
|
+
|
|
+ /* chiptype is u8 but id->driver_data is ulong
|
|
+ ** Works for now (possible values are 1 and 2) */
|
|
+ chip_type = CHIPTYPE_ACX100;
|
|
+ /* acx100 and acx111 have different PCI memory regions */
|
|
+ if (chip_type == CHIPTYPE_ACX100) {
|
|
+ chip_name = "ACX100";
|
|
+ } else if (chip_type == CHIPTYPE_ACX111) {
|
|
+ chip_name = "ACX111";
|
|
+ } else {
|
|
+ printk("acx: unknown chip type 0x%04X\n", chip_type);
|
|
+ goto fail_unknown_chiptype;
|
|
+ }
|
|
+
|
|
+ printk("acx: found %s-based wireless network card\n", chip_name);
|
|
+ log(L_ANY, "initial debug setting is 0x%04X\n", acx_debug);
|
|
+
|
|
+ ndev = alloc_netdev(sizeof(*adev), "wlan%d", dummy_netdev_init);
|
|
+ /* (NB: memsets to 0 entire area) */
|
|
+ if (!ndev) {
|
|
+ printk("acx: no memory for netdevice struct\n");
|
|
+ goto fail_alloc_netdev;
|
|
+ }
|
|
+
|
|
+ platform_set_drvdata (pdev, ndev);
|
|
+
|
|
+ ether_setup(ndev);
|
|
+
|
|
+ /*
|
|
+ * use platform_data resources that were provided
|
|
+ */
|
|
+ ndev->irq = 0;
|
|
+ for (i=0; i<pdev->num_resources; i++) {
|
|
+ if (pdev->resource[i].flags == IORESOURCE_IRQ) {
|
|
+ ndev->irq = pdev->resource[i].start;
|
|
+ }
|
|
+ else if (pdev->resource[i].flags == IORESOURCE_MEM) {
|
|
+ ndev->base_addr = pdev->resource[i].start;
|
|
+ addr_size = pdev->resource[i].end - pdev->resource[i].start;
|
|
+ }
|
|
+ }
|
|
+ if (addr_size == 0 || ndev->irq == 0)
|
|
+ goto fail_hw_params;
|
|
+ ndev->open = &acxmem_e_open;
|
|
+ ndev->stop = &acxmem_e_close;
|
|
+ pdev->dev.release = &acxmem_e_release;
|
|
+ ndev->hard_start_xmit = &acx_i_start_xmit;
|
|
+ ndev->get_stats = &acx_e_get_stats;
|
|
+#if IW_HANDLER_VERSION <= 5
|
|
+ ndev->get_wireless_stats = &acx_e_get_wireless_stats;
|
|
+#endif
|
|
+ ndev->wireless_handlers = (struct iw_handler_def *)&acx_ioctl_handler_def;
|
|
+ ndev->set_multicast_list = &acxmem_i_set_multicast_list;
|
|
+ ndev->tx_timeout = &acxmem_i_tx_timeout;
|
|
+ ndev->change_mtu = &acx_e_change_mtu;
|
|
+ ndev->watchdog_timeo = 4 * HZ;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ spin_lock_init(&adev->lock); /* initial state: unlocked */
|
|
+ spin_lock_init(&adev->txbuf_lock);
|
|
+ /* We do not start with downed sem: we want PARANOID_LOCKING to work */
|
|
+ sema_init(&adev->sem, 1); /* initial state: 1 (upped) */
|
|
+ /* since nobody can see new netdev yet, we can as well
|
|
+ ** just _presume_ that we're under sem (instead of actually taking it): */
|
|
+ /* acx_sem_lock(adev); */
|
|
+ adev->dev = &pdev->dev;
|
|
+ adev->ndev = ndev;
|
|
+ adev->dev_type = DEVTYPE_MEM;
|
|
+ adev->chip_type = chip_type;
|
|
+ adev->chip_name = chip_name;
|
|
+ adev->io = (CHIPTYPE_ACX100 == chip_type) ? IO_ACX100 : IO_ACX111;
|
|
+ adev->membase = (volatile u32 *) ndev->base_addr;
|
|
+ adev->iobase = (volatile u32 *) ioremap_nocache (ndev->base_addr, addr_size);
|
|
+ /* to find crashes due to weird driver access
|
|
+ * to unconfigured interface (ifup) */
|
|
+ adev->mgmt_timer.function = (void (*)(unsigned long))0x0000dead;
|
|
+
|
|
+#if defined(NONESSENTIAL_FEATURES)
|
|
+ acx_show_card_eeprom_id(adev);
|
|
+#endif /* NONESSENTIAL_FEATURES */
|
|
+
|
|
+#ifdef SET_MODULE_OWNER
|
|
+ SET_MODULE_OWNER(ndev);
|
|
+#endif
|
|
+ SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
+
|
|
+ log(L_IRQ|L_INIT, "using IRQ %d\n", ndev->irq);
|
|
+
|
|
+ /* ok, pci setup is finished, now start initializing the card */
|
|
+
|
|
+ if (OK != acxmem_complete_hw_reset (adev))
|
|
+ goto fail_reset;
|
|
+
|
|
+ /*
|
|
+ * Set up default things for most of the card settings.
|
|
+ */
|
|
+ acx_s_set_defaults(adev);
|
|
+
|
|
+ /* Register the card, AFTER everything else has been set up,
|
|
+ * since otherwise an ioctl could step on our feet due to
|
|
+ * firmware operations happening in parallel or uninitialized data */
|
|
+ err = register_netdev(ndev);
|
|
+ if (OK != err) {
|
|
+ printk("acx: register_netdev() FAILED: %d\n", err);
|
|
+ goto fail_register_netdev;
|
|
+ }
|
|
+
|
|
+ acx_proc_register_entries(ndev);
|
|
+
|
|
+ /* Now we have our device, so make sure the kernel doesn't try
|
|
+ * to send packets even though we're not associated to a network yet */
|
|
+ acx_stop_queue(ndev, "on probe");
|
|
+ acx_carrier_off(ndev, "on probe");
|
|
+
|
|
+ /*
|
|
+ * Set up a default monitor type so that poor combinations of initialization
|
|
+ * sequences in monitor mode don't end up destroying the hardware type.
|
|
+ */
|
|
+ adev->monitor_type = ARPHRD_ETHER;
|
|
+
|
|
+ /*
|
|
+ * Register to receive inetaddr notifier changes. This will allow us to
|
|
+ * catch if the user changes the MAC address of the interface.
|
|
+ */
|
|
+ register_netdevice_notifier(&acx_netdev_notifier);
|
|
+
|
|
+ /* after register_netdev() userspace may start working with dev
|
|
+ * (in particular, on other CPUs), we only need to up the sem */
|
|
+ /* acx_sem_unlock(adev); */
|
|
+
|
|
+ printk("acx "ACX_RELEASE": net device %s, driver compiled "
|
|
+ "against wireless extensions %d and Linux %s\n",
|
|
+ ndev->name, WIRELESS_EXT, UTS_RELEASE);
|
|
+
|
|
+#if CMD_DISCOVERY
|
|
+ great_inquisitor(adev);
|
|
+#endif
|
|
+
|
|
+ result = OK;
|
|
+ goto done;
|
|
+
|
|
+ /* error paths: undo everything in reverse order... */
|
|
+
|
|
+fail_register_netdev:
|
|
+
|
|
+ acxmem_s_delete_dma_regions(adev);
|
|
+
|
|
+fail_reset:
|
|
+fail_hw_params:
|
|
+ free_netdev(ndev);
|
|
+fail_alloc_netdev:
|
|
+fail_unknown_chiptype:
|
|
+
|
|
+
|
|
+done:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_remove
|
|
+**
|
|
+** Shut device down (if not hot unplugged)
|
|
+** and deallocate PCI resources for the acx chip.
|
|
+**
|
|
+** pdev - ptr to PCI device structure containing info about pci configuration
|
|
+*/
|
|
+static int __devexit
|
|
+acxmem_e_remove(struct platform_device *pdev)
|
|
+{
|
|
+ struct acx_hardware_data *hwdata = pdev->dev.platform_data;
|
|
+ struct net_device *ndev;
|
|
+ acx_device_t *adev;
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ndev = (struct net_device*) platform_get_drvdata(pdev);
|
|
+ if (!ndev) {
|
|
+ log(L_DEBUG, "%s: card is unused. Skipping any release code\n",
|
|
+ __func__);
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+
|
|
+ /* If device wasn't hot unplugged... */
|
|
+ if (adev_present(adev)) {
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* disable both Tx and Rx to shut radio down properly */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0);
|
|
+
|
|
+#ifdef REDUNDANT
|
|
+ /* put the eCPU to sleep to save power
|
|
+ * Halting is not possible currently,
|
|
+ * since not supported by all firmware versions */
|
|
+ acx_s_issue_cmd(adev, ACX100_CMD_SLEEP, NULL, 0);
|
|
+#endif
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* disable power LED to save power :-) */
|
|
+ log(L_INIT, "switching off power LED to save power\n");
|
|
+ acxmem_l_power_led(adev, 0);
|
|
+
|
|
+ /* stop our eCPU */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* FIXME: does this actually keep halting the eCPU?
|
|
+ * I don't think so...
|
|
+ */
|
|
+ acxmem_l_reset_mac(adev);
|
|
+ } else {
|
|
+ u16 temp;
|
|
+
|
|
+ /* halt eCPU */
|
|
+ temp = read_reg16(adev, IO_ACX_ECPU_CTRL) | 0x1;
|
|
+ write_reg16(adev, IO_ACX_ECPU_CTRL, temp);
|
|
+ write_flush(adev);
|
|
+ }
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+ }
|
|
+
|
|
+
|
|
+ /*
|
|
+ * Unregister the notifier chain
|
|
+ */
|
|
+ unregister_netdevice_notifier(&acx_netdev_notifier);
|
|
+
|
|
+ /* unregister the device to not let the kernel
|
|
+ * (e.g. ioctls) access a half-deconfigured device
|
|
+ * NB: this will cause acxmem_e_close() to be called,
|
|
+ * thus we shouldn't call it under sem! */
|
|
+ log(L_INIT, "removing device %s\n", ndev->name);
|
|
+ unregister_netdev(ndev);
|
|
+
|
|
+ /* unregister_netdev ensures that no references to us left.
|
|
+ * For paranoid reasons we continue to follow the rules */
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (adev->dev_state_mask & ACX_STATE_IFACE_UP) {
|
|
+ acxmem_s_down(ndev);
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ }
|
|
+
|
|
+ acx_proc_unregister_entries(ndev);
|
|
+
|
|
+ acxmem_s_delete_dma_regions(adev);
|
|
+
|
|
+ /* finally, clean up PCI bus state */
|
|
+ if (adev->iobase) iounmap((void *)adev->iobase);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ /* Free netdev (quite late,
|
|
+ * since otherwise we might get caught off-guard
|
|
+ * by a netdev timeout handler execution
|
|
+ * expecting to see a working dev...) */
|
|
+ free_netdev(ndev);
|
|
+
|
|
+ (void) hwdata->stop_hw();
|
|
+
|
|
+ printk ("e_remove done\n");
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** TODO: PM code needs to be fixed / debugged / tested.
|
|
+*/
|
|
+#ifdef CONFIG_PM
|
|
+static int
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+acxmem_e_suspend(struct platform_device *pdev, pm_message_t state)
|
|
+#else
|
|
+acxmem_e_suspend(struct device *pdev, u32 state)
|
|
+#endif
|
|
+{
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ acx_device_t *adev;
|
|
+ struct acx_hardware_data *hwdata;
|
|
+
|
|
+ FN_ENTER;
|
|
+ printk("acx: suspend handler is experimental!\n");
|
|
+ printk("sus: dev %p\n", ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ goto end;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ printk("sus: adev %p\n", adev);
|
|
+
|
|
+ hwdata = adev->dev->platform_data;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ netif_device_detach(ndev); /* this one cannot sleep */
|
|
+ acxmem_s_down(ndev);
|
|
+ /* down() does not set it to 0xffff, but here we really want that */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ acxmem_s_delete_dma_regions(adev);
|
|
+
|
|
+ /*
|
|
+ * Turn the ACX chip off.
|
|
+ */
|
|
+ hwdata->stop_hw();
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+static void
|
|
+fw_resumer(struct work_struct *notused)
|
|
+{
|
|
+ struct platform_device *pdev = resume_pdev;
|
|
+ struct net_device *ndev = platform_get_drvdata(pdev);
|
|
+ acx_device_t *adev;
|
|
+ struct acx_hardware_data *hwdata;
|
|
+
|
|
+ printk("acx: resume handler is experimental!\n");
|
|
+ printk("rsm: got dev %p\n", ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ return;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ printk("rsm: got adev %p\n", adev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ hwdata = adev->dev->platform_data;
|
|
+
|
|
+ /*
|
|
+ * Turn on the ACX.
|
|
+ */
|
|
+ hwdata->start_hw();
|
|
+
|
|
+ acxmem_complete_hw_reset (adev);
|
|
+
|
|
+ /*
|
|
+ * done by acx_s_set_defaults for initial startup
|
|
+ */
|
|
+ acxmem_set_interrupt_mask(adev);
|
|
+
|
|
+ printk ("rsm: bringing up interface\n");
|
|
+ SET_BIT (adev->set_mask, GETSET_ALL);
|
|
+ acxmem_s_up(ndev);
|
|
+ printk("rsm: acx up done\n");
|
|
+
|
|
+ /* now even reload all card parameters as they were before suspend,
|
|
+ * and possibly be back in the network again already :-)
|
|
+ */
|
|
+ /* - most settings updated in acxmem_s_up()
|
|
+ if (ACX_STATE_IFACE_UP & adev->dev_state_mask) {
|
|
+ adev->set_mask = GETSET_ALL;
|
|
+ acx_s_update_card_settings(adev);
|
|
+ printk("rsm: settings updated\n");
|
|
+ }
|
|
+ */
|
|
+ netif_device_attach(ndev);
|
|
+ printk("rsm: device attached\n");
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+}
|
|
+
|
|
+DECLARE_WORK( fw_resume_work, fw_resumer );
|
|
+
|
|
+static int
|
|
+acxmem_e_resume(struct platform_device *pdev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ resume_pdev = pdev;
|
|
+ schedule_work( &fw_resume_work );
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_up
|
|
+**
|
|
+** This function is called by acxmem_e_open (when ifconfig sets the device as up)
|
|
+**
|
|
+** Side effects:
|
|
+** - Enables on-card interrupt requests
|
|
+** - calls acx_s_start
|
|
+*/
|
|
+
|
|
+static void
|
|
+enable_acx_irq(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x8000);
|
|
+ adev->irqs_active = 1;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+static void
|
|
+acxmem_s_up(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ enable_acx_irq(adev);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* acx fw < 1.9.3.e has a hardware timer, and older drivers
|
|
+ ** used to use it. But we don't do that anymore, our OS
|
|
+ ** has reliable software timers */
|
|
+ init_timer(&adev->mgmt_timer);
|
|
+ adev->mgmt_timer.function = acx_i_timer;
|
|
+ adev->mgmt_timer.data = (unsigned long)adev;
|
|
+
|
|
+ /* Need to set ACX_STATE_IFACE_UP first, or else
|
|
+ ** timer won't be started by acx_set_status() */
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ /* actual scan cmd will happen in start() */
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING); break;
|
|
+ case ACX_MODE_3_AP:
|
|
+ case ACX_MODE_MONITOR:
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED); break;
|
|
+ }
|
|
+
|
|
+ acx_s_start(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_down
|
|
+**
|
|
+** This disables the netdevice
|
|
+**
|
|
+** Side effects:
|
|
+** - disables on-card interrupt request
|
|
+*/
|
|
+
|
|
+static void
|
|
+disable_acx_irq(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* I guess mask is not 0xffff because acx100 won't signal
|
|
+ ** cmd completion then (needed for ifup).
|
|
+ ** Someone with acx100 please confirm */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask_off);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ adev->irqs_active = 0;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+static void
|
|
+acxmem_s_down(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* Disable IRQs first, so that IRQs cannot race with us */
|
|
+ /* then wait until interrupts have finished executing on other CPUs */
|
|
+ acx_lock(adev, flags);
|
|
+ disable_acx_irq(adev);
|
|
+ synchronize_irq(adev->pdev->irq);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* we really don't want to have an asynchronous tasklet disturb us
|
|
+ ** after something vital for its job has been shut down, so
|
|
+ ** end all remaining work now.
|
|
+ **
|
|
+ ** NB: carrier_off (done by set_status below) would lead to
|
|
+ ** not yet fully understood deadlock in FLUSH_SCHEDULED_WORK().
|
|
+ ** That's why we do FLUSH first.
|
|
+ **
|
|
+ ** NB2: we have a bad locking bug here: FLUSH_SCHEDULED_WORK()
|
|
+ ** waits for acx_e_after_interrupt_task to complete if it is running
|
|
+ ** on another CPU, but acx_e_after_interrupt_task
|
|
+ ** will sleep on sem forever, because it is taken by us!
|
|
+ ** Work around that by temporary sem unlock.
|
|
+ ** This will fail miserably if we'll be hit by concurrent
|
|
+ ** iwconfig or something in between. TODO! */
|
|
+ acx_sem_unlock(adev);
|
|
+ FLUSH_SCHEDULED_WORK();
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* This is possible:
|
|
+ ** FLUSH_SCHEDULED_WORK -> acx_e_after_interrupt_task ->
|
|
+ ** -> set_status(ASSOCIATED) -> wake_queue()
|
|
+ ** That's why we stop queue _after_ FLUSH_SCHEDULED_WORK
|
|
+ ** lock/unlock is just paranoia, maybe not needed */
|
|
+ acx_lock(adev, flags);
|
|
+ acx_stop_queue(ndev, "on ifdown");
|
|
+ acx_set_status(adev, ACX_STATUS_0_STOPPED);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* kernel/timer.c says it's illegal to del_timer_sync()
|
|
+ ** a timer which restarts itself. We guarantee this cannot
|
|
+ ** ever happen because acx_i_timer() never does this if
|
|
+ ** status is ACX_STATUS_0_STOPPED */
|
|
+ del_timer_sync(&adev->mgmt_timer);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_open
|
|
+**
|
|
+** Called as a result of SIOCSIFFLAGS ioctl changing the flags bit IFF_UP
|
|
+** from clear to set. In other words: ifconfig up.
|
|
+**
|
|
+** Returns:
|
|
+** 0 success
|
|
+** >0 f/w reported error
|
|
+** <0 driver reported error
|
|
+*/
|
|
+static int
|
|
+acxmem_e_open(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ acx_init_task_scheduler(adev);
|
|
+
|
|
+/* TODO: pci_set_power_state(pdev, PCI_D0); ? */
|
|
+
|
|
+ /* request shared IRQ handler */
|
|
+ if (request_irq(ndev->irq, acxmem_i_interrupt, SA_INTERRUPT, ndev->name, ndev)) {
|
|
+ printk("%s: request_irq FAILED\n", ndev->name);
|
|
+ result = -EAGAIN;
|
|
+ goto done;
|
|
+ }
|
|
+ set_irq_type (ndev->irq, IRQT_FALLING);
|
|
+ log(L_DEBUG|L_IRQ, "request_irq %d successful\n", ndev->irq);
|
|
+
|
|
+ /* ifup device */
|
|
+ acxmem_s_up(ndev);
|
|
+
|
|
+ /* We don't currently have to do anything else.
|
|
+ * The setup of the MAC should be subsequently completed via
|
|
+ * the mlme commands.
|
|
+ * Higher layers know we're ready from dev->start==1 and
|
|
+ * dev->tbusy==0. Our rx path knows to pass up received/
|
|
+ * frames because of dev->flags&IFF_UP is true.
|
|
+ */
|
|
+done:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_close
|
|
+**
|
|
+** Called as a result of SIOCSIIFFLAGS ioctl changing the flags bit IFF_UP
|
|
+** from set to clear. I.e. called by "ifconfig DEV down"
|
|
+**
|
|
+** Returns:
|
|
+** 0 success
|
|
+** >0 f/w reported error
|
|
+** <0 driver reported error
|
|
+*/
|
|
+static int
|
|
+acxmem_e_close(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* ifdown device */
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ if (netif_device_present(ndev)) {
|
|
+ acxmem_s_down(ndev);
|
|
+ }
|
|
+
|
|
+ /* disable all IRQs, release shared IRQ handler */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ free_irq(ndev->irq, ndev);
|
|
+
|
|
+/* TODO: pci_set_power_state(pdev, PCI_D3hot); ? */
|
|
+
|
|
+ /* We currently don't have to do anything else.
|
|
+ * Higher layers know we're not ready from dev->start==0 and
|
|
+ * dev->tbusy==1. Our rx path knows to not pass up received
|
|
+ * frames because of dev->flags&IFF_UP is false.
|
|
+ */
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ log(L_INIT, "closed device\n");
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_i_tx_timeout
|
|
+**
|
|
+** Called from network core. Must not sleep!
|
|
+*/
|
|
+static void
|
|
+acxmem_i_tx_timeout(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ unsigned int tx_num_cleaned;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* clean processed tx descs, they may have been completely full */
|
|
+ tx_num_cleaned = acxmem_l_clean_txdesc(adev);
|
|
+
|
|
+ /* nothing cleaned, yet (almost) no free buffers available?
|
|
+ * --> clean all tx descs, no matter which status!!
|
|
+ * Note that I strongly suspect that doing emergency cleaning
|
|
+ * may confuse the firmware. This is a last ditch effort to get
|
|
+ * ANYTHING to work again...
|
|
+ *
|
|
+ * TODO: it's best to simply reset & reinit hw from scratch...
|
|
+ */
|
|
+ if ((adev->tx_free <= TX_EMERG_CLEAN) && (tx_num_cleaned == 0)) {
|
|
+ printk("%s: FAILED to free any of the many full tx buffers. "
|
|
+ "Switching to emergency freeing. "
|
|
+ "Please report!\n", ndev->name);
|
|
+ acxmem_l_clean_txdesc_emergency(adev);
|
|
+ }
|
|
+
|
|
+ if (acx_queue_stopped(ndev) && (ACX_STATUS_4_ASSOCIATED == adev->status))
|
|
+ acx_wake_queue(ndev, "after tx timeout");
|
|
+
|
|
+ /* stall may have happened due to radio drift, so recalib radio */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+
|
|
+ /* do unimportant work last */
|
|
+ printk("%s: tx timeout!\n", ndev->name);
|
|
+ adev->stats.tx_errors++;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_i_set_multicast_list
|
|
+** FIXME: most likely needs refinement
|
|
+*/
|
|
+static void
|
|
+acxmem_i_set_multicast_list(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* firmwares don't have allmulti capability,
|
|
+ * so just use promiscuous mode instead in this case. */
|
|
+ if (ndev->flags & (IFF_PROMISC|IFF_ALLMULTI)) {
|
|
+ SET_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS);
|
|
+ CLEAR_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ /* let kernel know in case *we* needed to set promiscuous */
|
|
+ ndev->flags |= (IFF_PROMISC|IFF_ALLMULTI);
|
|
+ } else {
|
|
+ CLEAR_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS);
|
|
+ SET_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ ndev->flags &= ~(IFF_PROMISC|IFF_ALLMULTI);
|
|
+ }
|
|
+
|
|
+ /* cannot update card settings directly here, atomic context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_UPDATE_CARD_CFG);
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_l_process_rxdesc
|
|
+**
|
|
+** Called directly and only from the IRQ handler
|
|
+*/
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline void log_rxbuffer(const acx_device_t *adev) {}
|
|
+#else
|
|
+static void
|
|
+log_rxbuffer(const acx_device_t *adev)
|
|
+{
|
|
+ register const struct rxhostdesc *rxhostdesc;
|
|
+ int i;
|
|
+ /* no FN_ENTER here, we don't want that */
|
|
+
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+ if (unlikely(!rxhostdesc)) return;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ if ((rxhostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN))
|
|
+ && (rxhostdesc->Status & cpu_to_le32(DESC_STATUS_FULL)))
|
|
+ printk("rx: buf %d full\n", i);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void
|
|
+acxmem_l_process_rxdesc(acx_device_t *adev)
|
|
+{
|
|
+ register rxhostdesc_t *hostdesc;
|
|
+ register rxdesc_t *rxdesc;
|
|
+ unsigned count, tail;
|
|
+ u32 addr;
|
|
+ u8 Ctl_8;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(acx_debug & L_BUFR))
|
|
+ log_rxbuffer(adev);
|
|
+
|
|
+ /* First, have a loop to determine the first descriptor that's
|
|
+ * full, just in case there's a mismatch between our current
|
|
+ * rx_tail and the full descriptor we're supposed to handle. */
|
|
+ tail = adev->rx_tail;
|
|
+ count = RX_CNT;
|
|
+ while (1) {
|
|
+ hostdesc = &adev->rxhostdesc_start[tail];
|
|
+ rxdesc = &adev->rxdesc_start[tail];
|
|
+ /* advance tail regardless of outcome of the below test */
|
|
+ tail = (tail + 1) % RX_CNT;
|
|
+
|
|
+ /*
|
|
+ * Unlike the PCI interface, where the ACX can write directly to
|
|
+ * the host descriptors, on the slave memory interface we have to
|
|
+ * pull these. All we really need to do is check the Ctl_8 field
|
|
+ * in the rx descriptor on the ACX, which should be 0x11000000 if
|
|
+ * we should process it.
|
|
+ */
|
|
+ Ctl_8 = hostdesc->Ctl_16 = read_slavemem8 (adev, (u32) &(rxdesc->Ctl_8));
|
|
+ if ((Ctl_8 & DESC_CTL_HOSTOWN) &&
|
|
+ (Ctl_8 & DESC_CTL_ACXDONE))
|
|
+ break; /* found it! */
|
|
+
|
|
+ if (unlikely(!--count)) /* hmm, no luck: all descs empty, bail out */
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* now process descriptors, starting with the first we figured out */
|
|
+ while (1) {
|
|
+ log(L_BUFR, "rx: tail=%u Ctl_8=%02X\n", tail, Ctl_8);
|
|
+ /*
|
|
+ * If the ACX has CTL_RECLAIM set on this descriptor there
|
|
+ * is no buffer associated; it just wants us to tell it to
|
|
+ * reclaim the memory.
|
|
+ */
|
|
+ if (!(Ctl_8 & DESC_CTL_RECLAIM)) {
|
|
+
|
|
+ /*
|
|
+ * slave interface - pull data now
|
|
+ */
|
|
+ hostdesc->length = read_slavemem16 (adev, (u32) &(rxdesc->total_length));
|
|
+
|
|
+ /*
|
|
+ * hostdesc->data is an rxbuffer_t, which includes header information,
|
|
+ * but the length in the data packet doesn't. The header information
|
|
+ * takes up an additional 12 bytes, so add that to the length we copy.
|
|
+ */
|
|
+ addr = read_slavemem32 (adev, (u32) &(rxdesc->ACXMemPtr));
|
|
+ if (addr) {
|
|
+ /*
|
|
+ * How can &(rxdesc->ACXMemPtr) above ever be zero? Looks like we
|
|
+ * get that now and then - try to trap it for debug.
|
|
+ */
|
|
+ if (addr & 0xffff0000) {
|
|
+ printk("rxdesc 0x%08x\n", (u32) rxdesc);
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ panic ("Bad access!");
|
|
+ }
|
|
+ chaincopy_from_slavemem (adev, (u8 *) hostdesc->data, addr,
|
|
+ hostdesc->length +
|
|
+ (u32) &((rxbuffer_t *)0)->hdr_a3);
|
|
+ acx_l_process_rxbuf(adev, hostdesc->data);
|
|
+ }
|
|
+ }
|
|
+ else {
|
|
+ printk ("rx reclaim only!\n");
|
|
+ }
|
|
+
|
|
+ hostdesc->Status = 0;
|
|
+
|
|
+ /*
|
|
+ * Let the ACX know we're done.
|
|
+ */
|
|
+ CLEAR_BIT (Ctl_8, DESC_CTL_HOSTOWN);
|
|
+ SET_BIT (Ctl_8, DESC_CTL_HOSTDONE);
|
|
+ SET_BIT (Ctl_8, DESC_CTL_RECLAIM);
|
|
+ write_slavemem8 (adev, (u32) &rxdesc->Ctl_8, Ctl_8);
|
|
+
|
|
+ /*
|
|
+ * Now tell the ACX we've finished with the receive buffer so
|
|
+ * it can finish the reclaim.
|
|
+ */
|
|
+ write_reg16 (adev, IO_ACX_INT_TRIG, INT_TRIG_RXPRC);
|
|
+
|
|
+ /* ok, descriptor is handled, now check the next descriptor */
|
|
+ hostdesc = &adev->rxhostdesc_start[tail];
|
|
+ rxdesc = &adev->rxdesc_start[tail];
|
|
+
|
|
+ Ctl_8 = hostdesc->Ctl_16 = read_slavemem8 (adev, (u32) &(rxdesc->Ctl_8));
|
|
+
|
|
+ /* if next descriptor is empty, then bail out */
|
|
+ if (!(Ctl_8 & DESC_CTL_HOSTOWN) || !(Ctl_8 & DESC_CTL_ACXDONE))
|
|
+ break;
|
|
+
|
|
+ tail = (tail + 1) % RX_CNT;
|
|
+ }
|
|
+end:
|
|
+ adev->rx_tail = tail;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_i_interrupt
|
|
+**
|
|
+** IRQ handler (atomic context, must not sleep, blah, blah)
|
|
+*/
|
|
+
|
|
+/* scan is complete. all frames now on the receive queue are valid */
|
|
+#define INFO_SCAN_COMPLETE 0x0001
|
|
+#define INFO_WEP_KEY_NOT_FOUND 0x0002
|
|
+/* hw has been reset as the result of a watchdog timer timeout */
|
|
+#define INFO_WATCH_DOG_RESET 0x0003
|
|
+/* failed to send out NULL frame from PS mode notification to AP */
|
|
+/* recommended action: try entering 802.11 PS mode again */
|
|
+#define INFO_PS_FAIL 0x0004
|
|
+/* encryption/decryption process on a packet failed */
|
|
+#define INFO_IV_ICV_FAILURE 0x0005
|
|
+
|
|
+/* Info mailbox format:
|
|
+2 bytes: type
|
|
+2 bytes: status
|
|
+more bytes may follow
|
|
+ rumors say about status:
|
|
+ 0x0000 info available (set by hw)
|
|
+ 0x0001 information received (must be set by host)
|
|
+ 0x1000 info available, mailbox overflowed (messages lost) (set by hw)
|
|
+ but in practice we've seen:
|
|
+ 0x9000 when we did not set status to 0x0001 on prev message
|
|
+ 0x1001 when we did set it
|
|
+ 0x0000 was never seen
|
|
+ conclusion: this is really a bitfield:
|
|
+ 0x1000 is 'info available' bit
|
|
+ 'mailbox overflowed' bit is 0x8000, not 0x1000
|
|
+ value of 0x0000 probably means that there are no messages at all
|
|
+ P.S. I dunno how in hell hw is supposed to notice that messages are lost -
|
|
+ it does NOT clear bit 0x0001, and this bit will probably stay forever set
|
|
+ after we set it once. Let's hope this will be fixed in firmware someday
|
|
+*/
|
|
+
|
|
+static void
|
|
+handle_info_irq(acx_device_t *adev)
|
|
+{
|
|
+#if ACX_DEBUG
|
|
+ static const char * const info_type_msg[] = {
|
|
+ "(unknown)",
|
|
+ "scan complete",
|
|
+ "WEP key not found",
|
|
+ "internal watchdog reset was done",
|
|
+ "failed to send powersave (NULL frame) notification to AP",
|
|
+ "encrypt/decrypt on a packet has failed",
|
|
+ "TKIP tx keys disabled",
|
|
+ "TKIP rx keys disabled",
|
|
+ "TKIP rx: key ID not found",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "TKIP IV value exceeds thresh"
|
|
+ };
|
|
+#endif
|
|
+ u32 info_type, info_status;
|
|
+
|
|
+ info_type = read_slavemem32 (adev, (u32) adev->info_area);
|
|
+
|
|
+ info_status = (info_type >> 16);
|
|
+ info_type = (u16)info_type;
|
|
+
|
|
+ /* inform fw that we have read this info message */
|
|
+ write_slavemem32(adev, (u32) adev->info_area, info_type | 0x00010000);
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_INFOACK);
|
|
+ write_flush(adev);
|
|
+
|
|
+ log(L_CTL, "info_type:%04X info_status:%04X\n",
|
|
+ info_type, info_status);
|
|
+
|
|
+ log(L_IRQ, "got Info IRQ: status %04X type %04X: %s\n",
|
|
+ info_status, info_type,
|
|
+ info_type_msg[(info_type >= VEC_SIZE(info_type_msg)) ?
|
|
+ 0 : info_type]
|
|
+ );
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+log_unusual_irq(u16 irqtype) {
|
|
+ /*
|
|
+ if (!printk_ratelimit())
|
|
+ return;
|
|
+ */
|
|
+
|
|
+ printk("acx: got");
|
|
+ if (irqtype & HOST_INT_TX_XFER) {
|
|
+ printk(" Tx_Xfer");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_RX_COMPLETE) {
|
|
+ printk(" Rx_Complete");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_DTIM) {
|
|
+ printk(" DTIM");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_BEACON) {
|
|
+ printk(" Beacon");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_TIMER) {
|
|
+ log(L_IRQ, " Timer");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_KEY_NOT_FOUND) {
|
|
+ printk(" Key_Not_Found");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_IV_ICV_FAILURE) {
|
|
+ printk(" IV_ICV_Failure (crypto)");
|
|
+ }
|
|
+ /* HOST_INT_CMD_COMPLETE */
|
|
+ /* HOST_INT_INFO */
|
|
+ if (irqtype & HOST_INT_OVERFLOW) {
|
|
+ printk(" Overflow");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_PROCESS_ERROR) {
|
|
+ printk(" Process_Error");
|
|
+ }
|
|
+ /* HOST_INT_SCAN_COMPLETE */
|
|
+ if (irqtype & HOST_INT_FCS_THRESHOLD) {
|
|
+ printk(" FCS_Threshold");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_UNKNOWN) {
|
|
+ printk(" Unknown");
|
|
+ }
|
|
+ printk(" IRQ(s)\n");
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+update_link_quality_led(acx_device_t *adev)
|
|
+{
|
|
+ int qual;
|
|
+
|
|
+ qual = acx_signal_determine_quality(adev->wstats.qual.level, adev->wstats.qual.noise);
|
|
+ if (qual > adev->brange_max_quality)
|
|
+ qual = adev->brange_max_quality;
|
|
+
|
|
+ if (time_after(jiffies, adev->brange_time_last_state_change +
|
|
+ (HZ/2 - HZ/2 * (unsigned long)qual / adev->brange_max_quality ) )) {
|
|
+ acxmem_l_power_led(adev, (adev->brange_last_state == 0));
|
|
+ adev->brange_last_state ^= 1; /* toggle */
|
|
+ adev->brange_time_last_state_change = jiffies;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+#define MAX_IRQLOOPS_PER_JIFFY (20000/HZ) /* a la orinoco.c */
|
|
+
|
|
+static irqreturn_t
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+acxmem_i_interrupt(int irq, void *dev_id)
|
|
+#else
|
|
+acxmwm_i_interrupt(int irq, void *dev_id, struct pt_regs *regs)
|
|
+#endif
|
|
+{
|
|
+ acx_device_t *adev;
|
|
+ unsigned long flags;
|
|
+ unsigned int irqcount = MAX_IRQLOOPS_PER_JIFFY;
|
|
+ register u16 irqtype;
|
|
+ u16 unmasked;
|
|
+
|
|
+ adev = ndev2adev((struct net_device*)dev_id);
|
|
+
|
|
+ /* LOCKING: can just spin_lock() since IRQs are disabled anyway.
|
|
+ * I am paranoid */
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR);
|
|
+ if (unlikely(0xffff == unmasked)) {
|
|
+ /* 0xffff value hints at missing hardware,
|
|
+ * so don't do anything.
|
|
+ * Not very clean, but other drivers do the same... */
|
|
+ log(L_IRQ, "IRQ type:FFFF - device removed? IRQ_NONE\n");
|
|
+ goto none;
|
|
+ }
|
|
+
|
|
+ /* We will check only "interesting" IRQ types */
|
|
+ irqtype = unmasked & ~adev->irq_mask;
|
|
+ if (!irqtype) {
|
|
+ /* We are on a shared IRQ line and it wasn't our IRQ */
|
|
+ log(L_IRQ, "IRQ type:%04X, mask:%04X - all are masked, IRQ_NONE\n",
|
|
+ unmasked, adev->irq_mask);
|
|
+ goto none;
|
|
+ }
|
|
+
|
|
+ /* Done here because IRQ_NONEs taking three lines of log
|
|
+ ** drive me crazy */
|
|
+ FN_ENTER;
|
|
+
|
|
+#define IRQ_ITERATE 1
|
|
+#if IRQ_ITERATE
|
|
+if (jiffies != adev->irq_last_jiffies) {
|
|
+ adev->irq_loops_this_jiffy = 0;
|
|
+ adev->irq_last_jiffies = jiffies;
|
|
+}
|
|
+
|
|
+/* safety condition; we'll normally abort loop below
|
|
+ * in case no IRQ type occurred */
|
|
+while (likely(--irqcount)) {
|
|
+#endif
|
|
+ /* ACK all IRQs ASAP */
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK, 0xffff);
|
|
+
|
|
+ log(L_IRQ, "IRQ type:%04X, mask:%04X, type & ~mask:%04X\n",
|
|
+ unmasked, adev->irq_mask, irqtype);
|
|
+
|
|
+ /* Handle most important IRQ types first */
|
|
+ if (irqtype & HOST_INT_RX_DATA) {
|
|
+ log(L_IRQ, "got Rx_Data IRQ\n");
|
|
+ acxmem_l_process_rxdesc(adev);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_TX_COMPLETE) {
|
|
+ log(L_IRQ, "got Tx_Complete IRQ\n");
|
|
+ /* don't clean up on each Tx complete, wait a bit
|
|
+ * unless we're going towards full, in which case
|
|
+ * we do it immediately, too (otherwise we might lockup
|
|
+ * with a full Tx buffer if we go into
|
|
+ * acxmem_l_clean_txdesc() at a time when we won't wakeup
|
|
+ * the net queue in there for some reason...) */
|
|
+ if (adev->tx_free <= TX_START_CLEAN) {
|
|
+#if TX_CLEANUP_IN_SOFTIRQ
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_TX_CLEANUP);
|
|
+#else
|
|
+ acxmem_l_clean_txdesc(adev);
|
|
+#endif
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Less frequent ones */
|
|
+ if (irqtype & (0
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ )) {
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE) {
|
|
+ log(L_IRQ, "got Command_Complete IRQ\n");
|
|
+ /* save the state for the running issue_cmd() */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_CMD_COMPLETE);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_INFO) {
|
|
+ handle_info_irq(adev);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_SCAN_COMPLETE) {
|
|
+ log(L_IRQ, "got Scan_Complete IRQ\n");
|
|
+ /* need to do that in process context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_COMPLETE_SCAN);
|
|
+ /* remember that fw is not scanning anymore */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* These we just log, but either they happen rarely
|
|
+ * or we keep them masked out */
|
|
+ if (irqtype & (0
|
|
+ /* | HOST_INT_RX_DATA */
|
|
+ /* | HOST_INT_TX_COMPLETE */
|
|
+ | HOST_INT_TX_XFER
|
|
+ | HOST_INT_RX_COMPLETE
|
|
+ | HOST_INT_DTIM
|
|
+ | HOST_INT_BEACON
|
|
+ | HOST_INT_TIMER
|
|
+ | HOST_INT_KEY_NOT_FOUND
|
|
+ | HOST_INT_IV_ICV_FAILURE
|
|
+ /* | HOST_INT_CMD_COMPLETE */
|
|
+ /* | HOST_INT_INFO */
|
|
+ | HOST_INT_OVERFLOW
|
|
+ | HOST_INT_PROCESS_ERROR
|
|
+ /* | HOST_INT_SCAN_COMPLETE */
|
|
+ | HOST_INT_FCS_THRESHOLD
|
|
+ | HOST_INT_UNKNOWN
|
|
+ )) {
|
|
+ log_unusual_irq(irqtype);
|
|
+ }
|
|
+
|
|
+#if IRQ_ITERATE
|
|
+ unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR);
|
|
+ irqtype = unmasked & ~adev->irq_mask;
|
|
+ /* Bail out if no new IRQ bits or if all are masked out */
|
|
+ if (!irqtype)
|
|
+ break;
|
|
+
|
|
+ if (unlikely(++adev->irq_loops_this_jiffy > MAX_IRQLOOPS_PER_JIFFY)) {
|
|
+ printk(KERN_ERR "acx: too many interrupts per jiffy!\n");
|
|
+ /* Looks like card floods us with IRQs! Try to stop that */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ /* This will short-circuit all future attempts to handle IRQ.
|
|
+ * We cant do much more... */
|
|
+ adev->irq_mask = 0;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+ /* Routine to perform blink with range */
|
|
+ if (unlikely(adev->led_power == 2))
|
|
+ update_link_quality_led(adev);
|
|
+
|
|
+/* handled: */
|
|
+ /* write_flush(adev); - not needed, last op was read anyway */
|
|
+ acx_unlock(adev, flags);
|
|
+ FN_EXIT0;
|
|
+ return IRQ_HANDLED;
|
|
+
|
|
+none:
|
|
+ acx_unlock(adev, flags);
|
|
+ return IRQ_NONE;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_power_led
|
|
+*/
|
|
+void
|
|
+acxmem_l_power_led(acx_device_t *adev, int enable)
|
|
+{
|
|
+ u16 gpio_pled = IS_ACX111(adev) ? 0x0040 : 0x0800;
|
|
+
|
|
+ /* A hack. Not moving message rate limiting to adev->xxx
|
|
+ * (it's only a debug message after all) */
|
|
+ static int rate_limit = 0;
|
|
+
|
|
+ if (rate_limit++ < 3)
|
|
+ log(L_IOCTL, "Please report in case toggling the power "
|
|
+ "LED doesn't work for your card!\n");
|
|
+ if (enable)
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT,
|
|
+ read_reg16(adev, IO_ACX_GPIO_OUT) & ~gpio_pled);
|
|
+ else
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT,
|
|
+ read_reg16(adev, IO_ACX_GPIO_OUT) | gpio_pled);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Ioctls
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx111pci_ioctl_info(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+#if ACX_DEBUG > 1
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ rxdesc_t *rxdesc;
|
|
+ txdesc_t *txdesc;
|
|
+ rxhostdesc_t *rxhostdesc;
|
|
+ txhostdesc_t *txhostdesc;
|
|
+ struct acx111_ie_memoryconfig memconf;
|
|
+ struct acx111_ie_queueconfig queueconf;
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+ char memmap[0x34];
|
|
+ char rxconfig[0x8];
|
|
+ char fcserror[0x8];
|
|
+ char ratefallback[0x5];
|
|
+
|
|
+ if ( !(acx_debug & (L_IOCTL|L_DEBUG)) )
|
|
+ return OK;
|
|
+ /* using printk() since we checked debug flag already */
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (!IS_ACX111(adev)) {
|
|
+ printk("acx111-specific function called "
|
|
+ "with non-acx111 chip, aborting\n");
|
|
+ goto end_ok;
|
|
+ }
|
|
+
|
|
+ /* get Acx111 Memory Configuration */
|
|
+ memset(&memconf, 0, sizeof(memconf));
|
|
+ /* BTW, fails with 12 (Write only) error code.
|
|
+ ** Retained for easy testing of issue_cmd error handling :) */
|
|
+ printk ("Interrogating queue config\n");
|
|
+ acx_s_interrogate(adev, &memconf, ACX1xx_IE_QUEUE_CONFIG);
|
|
+ printk ("done with queue config\n");
|
|
+
|
|
+ /* get Acx111 Queue Configuration */
|
|
+ memset(&queueconf, 0, sizeof(queueconf));
|
|
+ printk ("Interrogating mem config options\n");
|
|
+ acx_s_interrogate(adev, &queueconf, ACX1xx_IE_MEMORY_CONFIG_OPTIONS);
|
|
+ printk ("done with mem config options\n");
|
|
+
|
|
+ /* get Acx111 Memory Map */
|
|
+ memset(memmap, 0, sizeof(memmap));
|
|
+ printk ("Interrogating mem map\n");
|
|
+ acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP);
|
|
+ printk ("done with mem map\n");
|
|
+
|
|
+ /* get Acx111 Rx Config */
|
|
+ memset(rxconfig, 0, sizeof(rxconfig));
|
|
+ printk ("Interrogating rxconfig\n");
|
|
+ acx_s_interrogate(adev, &rxconfig, ACX1xx_IE_RXCONFIG);
|
|
+ printk ("done with queue rxconfig\n");
|
|
+
|
|
+ /* get Acx111 fcs error count */
|
|
+ memset(fcserror, 0, sizeof(fcserror));
|
|
+ printk ("Interrogating fcs err count\n");
|
|
+ acx_s_interrogate(adev, &fcserror, ACX1xx_IE_FCS_ERROR_COUNT);
|
|
+ printk ("done with err count\n");
|
|
+
|
|
+ /* get Acx111 rate fallback */
|
|
+ memset(ratefallback, 0, sizeof(ratefallback));
|
|
+ printk ("Interrogating rate fallback\n");
|
|
+ acx_s_interrogate(adev, &ratefallback, ACX1xx_IE_RATE_FALLBACK);
|
|
+ printk ("done with rate fallback\n");
|
|
+
|
|
+ /* force occurrence of a beacon interrupt */
|
|
+ /* TODO: comment why is this necessary */
|
|
+ write_reg16(adev, IO_ACX_HINT_TRIG, HOST_INT_BEACON);
|
|
+
|
|
+ /* dump Acx111 Mem Configuration */
|
|
+ printk("dump mem config:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "Number of stations: %1X\n"
|
|
+ "Memory block size: %1X\n"
|
|
+ "tx/rx memory block allocation: %1X\n"
|
|
+ "count rx: %X / tx: %X queues\n"
|
|
+ "options %1X\n"
|
|
+ "fragmentation %1X\n"
|
|
+ "Rx Queue 1 Count Descriptors: %X\n"
|
|
+ "Rx Queue 1 Host Memory Start: %X\n"
|
|
+ "Tx Queue 1 Count Descriptors: %X\n"
|
|
+ "Tx Queue 1 Attributes: %X\n",
|
|
+ memconf.len, (int) sizeof(memconf),
|
|
+ memconf.no_of_stations,
|
|
+ memconf.memory_block_size,
|
|
+ memconf.tx_rx_memory_block_allocation,
|
|
+ memconf.count_rx_queues, memconf.count_tx_queues,
|
|
+ memconf.options,
|
|
+ memconf.fragmentation,
|
|
+ memconf.rx_queue1_count_descs,
|
|
+ acx2cpu(memconf.rx_queue1_host_rx_start),
|
|
+ memconf.tx_queue1_count_descs,
|
|
+ memconf.tx_queue1_attributes);
|
|
+
|
|
+ /* dump Acx111 Queue Configuration */
|
|
+ printk("dump queue head:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "tx_memory_block_address (from card): %X\n"
|
|
+ "rx_memory_block_address (from card): %X\n"
|
|
+ "rx1_queue address (from card): %X\n"
|
|
+ "tx1_queue address (from card): %X\n"
|
|
+ "tx1_queue attributes (from card): %X\n",
|
|
+ queueconf.len, (int) sizeof(queueconf),
|
|
+ queueconf.tx_memory_block_address,
|
|
+ queueconf.rx_memory_block_address,
|
|
+ queueconf.rx1_queue_address,
|
|
+ queueconf.tx1_queue_address,
|
|
+ queueconf.tx1_attributes);
|
|
+
|
|
+ /* dump Acx111 Mem Map */
|
|
+ printk("dump mem map:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "Code start: %X\n"
|
|
+ "Code end: %X\n"
|
|
+ "WEP default key start: %X\n"
|
|
+ "WEP default key end: %X\n"
|
|
+ "STA table start: %X\n"
|
|
+ "STA table end: %X\n"
|
|
+ "Packet template start: %X\n"
|
|
+ "Packet template end: %X\n"
|
|
+ "Queue memory start: %X\n"
|
|
+ "Queue memory end: %X\n"
|
|
+ "Packet memory pool start: %X\n"
|
|
+ "Packet memory pool end: %X\n"
|
|
+ "iobase: %p\n"
|
|
+ "iobase2: %p\n",
|
|
+ *((u16 *)&memmap[0x02]), (int) sizeof(memmap),
|
|
+ *((u32 *)&memmap[0x04]),
|
|
+ *((u32 *)&memmap[0x08]),
|
|
+ *((u32 *)&memmap[0x0C]),
|
|
+ *((u32 *)&memmap[0x10]),
|
|
+ *((u32 *)&memmap[0x14]),
|
|
+ *((u32 *)&memmap[0x18]),
|
|
+ *((u32 *)&memmap[0x1C]),
|
|
+ *((u32 *)&memmap[0x20]),
|
|
+ *((u32 *)&memmap[0x24]),
|
|
+ *((u32 *)&memmap[0x28]),
|
|
+ *((u32 *)&memmap[0x2C]),
|
|
+ *((u32 *)&memmap[0x30]),
|
|
+ adev->iobase,
|
|
+ adev->iobase2);
|
|
+
|
|
+ /* dump Acx111 Rx Config */
|
|
+ printk("dump rx config:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "rx config: %X\n"
|
|
+ "rx filter config: %X\n",
|
|
+ *((u16 *)&rxconfig[0x02]), (int) sizeof(rxconfig),
|
|
+ *((u16 *)&rxconfig[0x04]),
|
|
+ *((u16 *)&rxconfig[0x06]));
|
|
+
|
|
+ /* dump Acx111 fcs error */
|
|
+ printk("dump fcserror:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "fcserrors: %X\n",
|
|
+ *((u16 *)&fcserror[0x02]), (int) sizeof(fcserror),
|
|
+ *((u32 *)&fcserror[0x04]));
|
|
+
|
|
+ /* dump Acx111 rate fallback */
|
|
+ printk("dump rate fallback:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "ratefallback: %X\n",
|
|
+ *((u16 *)&ratefallback[0x02]), (int) sizeof(ratefallback),
|
|
+ *((u8 *)&ratefallback[0x04]));
|
|
+
|
|
+ /* protect against IRQ */
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* dump acx111 internal rx descriptor ring buffer */
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+
|
|
+ /* loop over complete receive pool */
|
|
+ if (rxdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ printk("\ndump internal rxdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "next 0x%X\n"
|
|
+ "acx mem pointer (dynamic) 0x%X\n"
|
|
+ "CTL (dynamic) 0x%X\n"
|
|
+ "Rate (dynamic) 0x%X\n"
|
|
+ "RxStatus (dynamic) 0x%X\n"
|
|
+ "Mod/Pre (dynamic) 0x%X\n",
|
|
+ i,
|
|
+ rxdesc,
|
|
+ acx2cpu(rxdesc->pNextDesc),
|
|
+ acx2cpu(rxdesc->ACXMemPtr),
|
|
+ rxdesc->Ctl_8,
|
|
+ rxdesc->rate,
|
|
+ rxdesc->error,
|
|
+ rxdesc->SNR);
|
|
+ rxdesc++;
|
|
+ }
|
|
+
|
|
+ /* dump host rx descriptor ring buffer */
|
|
+
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+
|
|
+ /* loop over complete receive pool */
|
|
+ if (rxhostdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ printk("\ndump host rxdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "buffer mem pos 0x%X\n"
|
|
+ "buffer mem offset 0x%X\n"
|
|
+ "CTL 0x%X\n"
|
|
+ "Length 0x%X\n"
|
|
+ "next 0x%X\n"
|
|
+ "Status 0x%X\n",
|
|
+ i,
|
|
+ rxhostdesc,
|
|
+ acx2cpu(rxhostdesc->data_phy),
|
|
+ rxhostdesc->data_offset,
|
|
+ le16_to_cpu(rxhostdesc->Ctl_16),
|
|
+ le16_to_cpu(rxhostdesc->length),
|
|
+ acx2cpu(rxhostdesc->desc_phy_next),
|
|
+ rxhostdesc->Status);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+
|
|
+ /* dump acx111 internal tx descriptor ring buffer */
|
|
+ txdesc = adev->txdesc_start;
|
|
+
|
|
+ /* loop over complete transmit pool */
|
|
+ if (txdesc) for (i = 0; i < TX_CNT; i++) {
|
|
+ printk("\ndump internal txdesc %d:\n"
|
|
+ "size 0x%X\n"
|
|
+ "mem pos %p\n"
|
|
+ "next 0x%X\n"
|
|
+ "acx mem pointer (dynamic) 0x%X\n"
|
|
+ "host mem pointer (dynamic) 0x%X\n"
|
|
+ "length (dynamic) 0x%X\n"
|
|
+ "CTL (dynamic) 0x%X\n"
|
|
+ "CTL2 (dynamic) 0x%X\n"
|
|
+ "Status (dynamic) 0x%X\n"
|
|
+ "Rate (dynamic) 0x%X\n",
|
|
+ i,
|
|
+ (int) sizeof(struct txdesc),
|
|
+ txdesc,
|
|
+ acx2cpu(txdesc->pNextDesc),
|
|
+ acx2cpu(txdesc->AcxMemPtr),
|
|
+ acx2cpu(txdesc->HostMemPtr),
|
|
+ le16_to_cpu(txdesc->total_length),
|
|
+ txdesc->Ctl_8,
|
|
+ txdesc->Ctl2_8, txdesc->error,
|
|
+ txdesc->u.r1.rate);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+
|
|
+ /* dump host tx descriptor ring buffer */
|
|
+
|
|
+ txhostdesc = adev->txhostdesc_start;
|
|
+
|
|
+ /* loop over complete host send pool */
|
|
+ if (txhostdesc) for (i = 0; i < TX_CNT * 2; i++) {
|
|
+ printk("\ndump host txdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "buffer mem pos 0x%X\n"
|
|
+ "buffer mem offset 0x%X\n"
|
|
+ "CTL 0x%X\n"
|
|
+ "Length 0x%X\n"
|
|
+ "next 0x%X\n"
|
|
+ "Status 0x%X\n",
|
|
+ i,
|
|
+ txhostdesc,
|
|
+ acx2cpu(txhostdesc->data_phy),
|
|
+ txhostdesc->data_offset,
|
|
+ le16_to_cpu(txhostdesc->Ctl_16),
|
|
+ le16_to_cpu(txhostdesc->length),
|
|
+ acx2cpu(txhostdesc->desc_phy_next),
|
|
+ le32_to_cpu(txhostdesc->Status));
|
|
+ txhostdesc++;
|
|
+ }
|
|
+
|
|
+ /* write_reg16(adev, 0xb4, 0x4); */
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+end_ok:
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+#endif /* ACX_DEBUG */
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx100mem_ioctl_set_phy_amp_bias(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ u16 gpio_old;
|
|
+
|
|
+ if (!IS_ACX100(adev)) {
|
|
+ /* WARNING!!!
|
|
+ * Removing this check *might* damage
|
|
+ * hardware, since we're tweaking GPIOs here after all!!!
|
|
+ * You've been warned...
|
|
+ * WARNING!!! */
|
|
+ printk("acx: sorry, setting bias level for non-acx100 "
|
|
+ "is not supported yet\n");
|
|
+ return OK;
|
|
+ }
|
|
+
|
|
+ if (*extra > 7) {
|
|
+ printk("acx: invalid bias parameter, range is 0-7\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* Need to lock accesses to [IO_ACX_GPIO_OUT]:
|
|
+ * IRQ handler uses it to update LED */
|
|
+ acx_lock(adev, flags);
|
|
+ gpio_old = read_reg16(adev, IO_ACX_GPIO_OUT);
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT, (gpio_old & 0xf8ff) | ((u16)*extra << 8));
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ log(L_DEBUG, "gpio_old: 0x%04X\n", gpio_old);
|
|
+ printk("%s: PHY power amplifier bias: old:%d, new:%d\n",
|
|
+ ndev->name,
|
|
+ (gpio_old & 0x0700) >> 8, (unsigned char)*extra);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_l_alloc_tx
|
|
+** Actually returns a txdesc_t* ptr
|
|
+**
|
|
+** FIXME: in case of fragments, should allocate multiple descrs
|
|
+** after figuring out how many we need and whether we still have
|
|
+** sufficiently many.
|
|
+*/
|
|
+tx_t*
|
|
+acxmem_l_alloc_tx(acx_device_t *adev)
|
|
+{
|
|
+ struct txdesc *txdesc;
|
|
+ unsigned head;
|
|
+ u8 ctl8;
|
|
+ static int txattempts = 0;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(!adev->tx_free)) {
|
|
+ printk("acx: BUG: no free txdesc left\n");
|
|
+ /*
|
|
+ * Probably the ACX ignored a transmit attempt and now there's a packet
|
|
+ * sitting in the queue we think should be transmitting but the ACX doesn't
|
|
+ * know about.
|
|
+ * On the first pass, send the ACX a TxProc interrupt to try moving
|
|
+ * things along, and if that doesn't work (ie, we get called again) completely
|
|
+ * flush the transmit queue.
|
|
+ */
|
|
+ if (txattempts < 10) {
|
|
+ txattempts++;
|
|
+ printk ("acx: trying to wake up ACX\n");
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_TXPRC);
|
|
+ write_flush(adev); }
|
|
+ else {
|
|
+ txattempts = 0;
|
|
+ printk ("acx: flushing transmit queue.\n");
|
|
+ acxmem_l_clean_txdesc_emergency (adev);
|
|
+ }
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Make a quick check to see if there is transmit buffer space on
|
|
+ * the ACX. This can't guarantee there is enough space for the packet
|
|
+ * since we don't yet know how big it is, but it will prevent at least some
|
|
+ * annoyances.
|
|
+ */
|
|
+ if (!adev->acx_txbuf_blocks_free) {
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ head = adev->tx_head;
|
|
+ /*
|
|
+ * txdesc points to ACX memory
|
|
+ */
|
|
+ txdesc = get_txdesc(adev, head);
|
|
+ ctl8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+
|
|
+ /*
|
|
+ * If we don't own the buffer (HOSTOWN) it is certainly not free; however,
|
|
+ * we may have previously thought we had enough memory to send
|
|
+ * a packet, allocated the buffer then gave up when we found not enough
|
|
+ * transmit buffer space on the ACX. In that case, HOSTOWN and
|
|
+ * ACXDONE will both be set.
|
|
+ */
|
|
+ if (unlikely(DESC_CTL_HOSTOWN != (ctl8 & DESC_CTL_HOSTOWN))) {
|
|
+ /* whoops, descr at current index is not free, so probably
|
|
+ * ring buffer already full */
|
|
+ printk("acx: BUG: tx_head:%d Ctl8:0x%02X - failed to find "
|
|
+ "free txdesc\n", head, ctl8);
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* Needed in case txdesc won't be eventually submitted for tx */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), DESC_CTL_ACXDONE_HOSTOWN);
|
|
+
|
|
+ adev->tx_free--;
|
|
+ log(L_BUFT, "tx: got desc %u, %u remain\n",
|
|
+ head, adev->tx_free);
|
|
+ /* Keep a few free descs between head and tail of tx ring.
|
|
+ ** It is not absolutely needed, just feels safer */
|
|
+ if (adev->tx_free < TX_STOP_QUEUE) {
|
|
+ log(L_BUF, "stop queue (%u tx desc left)\n",
|
|
+ adev->tx_free);
|
|
+ acx_stop_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+ /* returning current descriptor, so advance to next free one */
|
|
+ adev->tx_head = (head + 1) % TX_CNT;
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+
|
|
+ return (tx_t*)txdesc;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_l_dealloc_tx
|
|
+** Clears out a previously allocatedvoid acxmem_l_dealloc_tx(tx_t *tx_opaque);
|
|
+ transmit descriptor. The ACX
|
|
+** can get confused if we skip transmit descriptors in the queue,
|
|
+** so when we don't need a descriptor return it to its original
|
|
+** state and move the queue head pointer back.
|
|
+**
|
|
+*/
|
|
+void
|
|
+acxmem_l_dealloc_tx(acx_device_t *adev, tx_t *tx_opaque)
|
|
+{
|
|
+ /*
|
|
+ * txdesc is the address of the descriptor on the ACX.
|
|
+ */
|
|
+ txdesc_t *txdesc = (txdesc_t*)tx_opaque;
|
|
+ txdesc_t tmptxdesc;
|
|
+ int index;
|
|
+
|
|
+ memset (&tmptxdesc, 0, sizeof(tmptxdesc));
|
|
+ tmptxdesc.Ctl_8 = DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG;
|
|
+ tmptxdesc.u.r1.rate = 0x0a;
|
|
+
|
|
+ /*
|
|
+ * Clear out all of the transmit descriptor except for the next pointer
|
|
+ */
|
|
+ copy_to_slavemem (adev, (u32) &(txdesc->HostMemPtr),
|
|
+ (u8 *) &(tmptxdesc.HostMemPtr),
|
|
+ sizeof (tmptxdesc) - sizeof(tmptxdesc.pNextDesc));
|
|
+
|
|
+ /*
|
|
+ * This is only called immediately after we've allocated, so we should
|
|
+ * be able to set the head back to this descriptor.
|
|
+ */
|
|
+ index = ((u8*) txdesc - (u8*)adev->txdesc_start) / adev->txdesc_size;
|
|
+ printk ("acx_dealloc: moving head from %d to %d\n", adev->tx_head, index);
|
|
+ adev->tx_head = index;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void*
|
|
+acxmem_l_get_txbuf(acx_device_t *adev, tx_t* tx_opaque)
|
|
+{
|
|
+ return get_txhostdesc(adev, (txdesc_t*)tx_opaque)->data;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_tx_data
|
|
+**
|
|
+** Can be called from IRQ (rx -> (AP bridging or mgmt response) -> tx).
|
|
+** Can be called from acx_i_start_xmit (data frames from net core).
|
|
+**
|
|
+** FIXME: in case of fragments, should loop over the number of
|
|
+** pre-allocated tx descrs, properly setting up transfer data and
|
|
+** CTL_xxx flags according to fragment number.
|
|
+*/
|
|
+void
|
|
+acxmem_update_queue_indicator (acx_device_t *adev, int txqueue)
|
|
+{
|
|
+#ifdef USING_MORE_THAN_ONE_TRANSMIT_QUEUE
|
|
+ u32 indicator;
|
|
+ unsigned long flags;
|
|
+ int count;
|
|
+
|
|
+ /*
|
|
+ * Can't handle an interrupt while we're fiddling with the ACX's lock,
|
|
+ * according to TI. The ACX is supposed to hold fw_lock for at most
|
|
+ * 500ns.
|
|
+ */
|
|
+ local_irq_save (flags);
|
|
+
|
|
+ /*
|
|
+ * Wait for ACX to release the lock (at most 500ns).
|
|
+ */
|
|
+ count = 0;
|
|
+ while (read_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->fw_lock))
|
|
+ && (count++ < 50)) {
|
|
+ ndelay (10);
|
|
+ }
|
|
+ if (count < 50) {
|
|
+
|
|
+ /*
|
|
+ * Take out the host lock - anything non-zero will work, so don't worry about
|
|
+ * be/le
|
|
+ */
|
|
+ write_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->host_lock), 1);
|
|
+
|
|
+ /*
|
|
+ * Avoid a race condition
|
|
+ */
|
|
+ count = 0;
|
|
+ while (read_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->fw_lock))
|
|
+ && (count++ < 50)) {
|
|
+ ndelay (10);
|
|
+ }
|
|
+
|
|
+ if (count < 50) {
|
|
+ /*
|
|
+ * Mark the queue active
|
|
+ */
|
|
+ indicator = read_slavemem32 (adev, (u32) &(adev->acx_queue_indicator->indicator));
|
|
+ indicator |= cpu_to_le32 (1 << txqueue);
|
|
+ write_slavemem32 (adev, (u32) &(adev->acx_queue_indicator->indicator), indicator);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Release the host lock
|
|
+ */
|
|
+ write_slavemem16 (adev, (u32) &(adev->acx_queue_indicator->host_lock), 0);
|
|
+
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Restore interrupts
|
|
+ */
|
|
+ local_irq_restore (flags);
|
|
+#endif
|
|
+}
|
|
+
|
|
+void
|
|
+acxmem_l_tx_data(acx_device_t *adev, tx_t* tx_opaque, int len)
|
|
+{
|
|
+ /*
|
|
+ * txdesc is the address on the ACX
|
|
+ */
|
|
+ txdesc_t *txdesc = (txdesc_t*)tx_opaque;
|
|
+ txhostdesc_t *hostdesc1, *hostdesc2;
|
|
+ client_t *clt;
|
|
+ u16 rate_cur;
|
|
+ u8 Ctl_8, Ctl2_8;
|
|
+ u32 addr;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* fw doesn't tx such packets anyhow */
|
|
+ if (unlikely(len < WLAN_HDR_A3_LEN))
|
|
+ goto end;
|
|
+
|
|
+ hostdesc1 = get_txhostdesc(adev, txdesc);
|
|
+ /* modify flag status in separate variable to be able to write it back
|
|
+ * in one big swoop later (also in order to have less device memory
|
|
+ * accesses) */
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ Ctl2_8 = 0; /* really need to init it to 0, not txdesc->Ctl2_8, it seems */
|
|
+
|
|
+ hostdesc2 = hostdesc1 + 1;
|
|
+
|
|
+ /* DON'T simply set Ctl field to 0 here globally,
|
|
+ * it needs to maintain a consistent flag status (those are state flags!!),
|
|
+ * otherwise it may lead to severe disruption. Only set or reset particular
|
|
+ * flags at the exact moment this is needed... */
|
|
+
|
|
+ /* let chip do RTS/CTS handshaking before sending
|
|
+ * in case packet size exceeds threshold */
|
|
+ if (len > adev->rts_threshold)
|
|
+ SET_BIT(Ctl2_8, DESC_CTL2_RTS);
|
|
+ else
|
|
+ CLEAR_BIT(Ctl2_8, DESC_CTL2_RTS);
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_3_AP:
|
|
+ clt = acx_l_sta_list_get(adev, ((wlan_hdr_t*)hostdesc1->data)->a1);
|
|
+ break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ clt = adev->ap_client;
|
|
+ break;
|
|
+#if 0
|
|
+/* testing was done on acx111: */
|
|
+ case ACX_MODE_MONITOR:
|
|
+ SET_BIT(Ctl2_8, 0
|
|
+/* sends CTS to self before packet */
|
|
+ + DESC_CTL2_SEQ /* don't increase sequence field */
|
|
+/* not working (looks like good fcs is still added) */
|
|
+ + DESC_CTL2_FCS /* don't add the FCS */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_MORE_FRAG
|
|
+/* not tested */
|
|
+ + DESC_CTL2_RETRY /* don't increase retry field */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_POWER /* don't increase power mgmt. field */
|
|
+/* no effect */
|
|
+ + DESC_CTL2_WEP /* encrypt this frame */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_DUR /* don't increase duration field */
|
|
+ );
|
|
+ /* fallthrough */
|
|
+#endif
|
|
+ default: /* ACX_MODE_OFF, ACX_MODE_MONITOR */
|
|
+ clt = NULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ rate_cur = clt ? clt->rate_cur : adev->rate_bcast;
|
|
+ if (unlikely(!rate_cur)) {
|
|
+ printk("acx: driver bug! bad ratemask\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* used in tx cleanup routine for auto rate and accounting: */
|
|
+ put_txcr(adev, txdesc, clt, rate_cur);
|
|
+
|
|
+ write_slavemem16 (adev, (u32) &(txdesc->total_length), cpu_to_le16(len));
|
|
+ hostdesc2->length = cpu_to_le16(len - WLAN_HDR_A3_LEN);
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* note that if !txdesc->do_auto, txrate->cur
|
|
+ ** has only one nonzero bit */
|
|
+ txdesc->u.r2.rate111 = cpu_to_le16(
|
|
+ rate_cur
|
|
+ /* WARNING: I was never able to make it work with prism54 AP.
|
|
+ ** It was falling down to 1Mbit where shortpre is not applicable,
|
|
+ ** and not working at all at "5,11 basic rates only" setting.
|
|
+ ** I even didn't see tx packets in radio packet capture.
|
|
+ ** Disabled for now --vda */
|
|
+ /*| ((clt->shortpre && clt->cur!=RATE111_1) ? RATE111_SHORTPRE : 0) */
|
|
+ );
|
|
+#ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS
|
|
+ /* should add this to rate111 above as necessary */
|
|
+ | (clt->pbcc511 ? RATE111_PBCC511 : 0)
|
|
+#endif
|
|
+ hostdesc1->length = cpu_to_le16(len);
|
|
+ } else { /* ACX100 */
|
|
+ u8 rate_100 = clt ? clt->rate_100 : adev->rate_bcast100;
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->u.r1.rate), rate_100);
|
|
+#ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS
|
|
+ if (clt->pbcc511) {
|
|
+ if (n == RATE100_5 || n == RATE100_11)
|
|
+ n |= RATE100_PBCC511;
|
|
+ }
|
|
+
|
|
+ if (clt->shortpre && (clt->cur != RATE111_1))
|
|
+ SET_BIT(Ctl_8, DESC_CTL_SHORT_PREAMBLE); /* set Short Preamble */
|
|
+#endif
|
|
+ /* set autodma and reclaim and 1st mpdu */
|
|
+ SET_BIT(Ctl_8, DESC_CTL_FIRSTFRAG);
|
|
+
|
|
+#if ACX_FRAGMENTATION
|
|
+ /* SET_BIT(Ctl2_8, DESC_CTL2_MORE_FRAG); cannot set it unconditionally, needs to be set for all non-last fragments */
|
|
+#endif
|
|
+ hostdesc1->length = cpu_to_le16(WLAN_HDR_A3_LEN);
|
|
+
|
|
+ /*
|
|
+ * Since we're not using autodma copy the packet data to the acx now.
|
|
+ * Even host descriptors point to the packet header, and the odd indexed
|
|
+ * descriptor following points to the packet data.
|
|
+ *
|
|
+ * The first step is to find free memory in the ACX transmit buffers.
|
|
+ * They don't necessarily map one to one with the transmit queue entries,
|
|
+ * so search through them starting just after the last one used.
|
|
+ */
|
|
+ addr = allocate_acx_txbuf_space (adev, len);
|
|
+ if (addr) {
|
|
+ chaincopy_to_slavemem (adev, addr, hostdesc1->data, len);
|
|
+ }
|
|
+ else {
|
|
+ /*
|
|
+ * Bummer. We thought we might have enough room in the transmit
|
|
+ * buffers to send this packet, but it turns out we don't. alloc_tx
|
|
+ * has already marked this transmit descriptor as HOSTOWN and ACXDONE,
|
|
+ * which means the ACX will hang when it gets to this descriptor unless
|
|
+ * we do something about it. Having a bubble in the transmit queue just
|
|
+ * doesn't seem to work, so we have to reset this transmit queue entry's
|
|
+ * state to its original value and back up our head pointer to point
|
|
+ * back to this entry.
|
|
+ */
|
|
+ hostdesc1->length = 0;
|
|
+ hostdesc2->length = 0;
|
|
+ write_slavemem16 (adev, (u32) &(txdesc->total_length), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG);
|
|
+ adev->tx_head = ((u8*) txdesc - (u8*) adev->txdesc_start) / adev->txdesc_size;
|
|
+ goto end;
|
|
+ }
|
|
+ /*
|
|
+ * Tell the ACX where the packet is.
|
|
+ */
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr), addr);
|
|
+
|
|
+ }
|
|
+ /* don't need to clean ack/rts statistics here, already
|
|
+ * done on descr cleanup */
|
|
+
|
|
+ /* clears HOSTOWN and ACXDONE bits, thus telling that the descriptors
|
|
+ * are now owned by the acx100; do this as LAST operation */
|
|
+ CLEAR_BIT(Ctl_8, DESC_CTL_ACXDONE_HOSTOWN);
|
|
+ /* flush writes before we release hostdesc to the adapter here */
|
|
+ //wmb();
|
|
+
|
|
+ /* write back modified flags */
|
|
+ /*
|
|
+ * At this point Ctl_8 should just be FIRSTFRAG
|
|
+ */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl2_8),Ctl2_8);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), Ctl_8);
|
|
+ /* unused: txdesc->tx_time = cpu_to_le32(jiffies); */
|
|
+
|
|
+ /*
|
|
+ * Update the queue indicator to say there's data on the first queue.
|
|
+ */
|
|
+ acxmem_update_queue_indicator (adev, 0);
|
|
+
|
|
+ /* flush writes before we tell the adapter that it's its turn now */
|
|
+ mmiowb();
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_TXPRC);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* log the packet content AFTER sending it,
|
|
+ * in order to not delay sending any further than absolutely needed
|
|
+ * Do separate logs for acx100/111 to have human-readable rates */
|
|
+ if (unlikely(acx_debug & (L_XFER|L_DATA))) {
|
|
+ u16 fc = ((wlan_hdr_t*)hostdesc1->data)->fc;
|
|
+ if (IS_ACX111(adev))
|
|
+ printk("tx: pkt (%s): len %d "
|
|
+ "rate %04X%s status %u\n",
|
|
+ acx_get_packet_type_string(le16_to_cpu(fc)), len,
|
|
+ le16_to_cpu(txdesc->u.r2.rate111),
|
|
+ (le16_to_cpu(txdesc->u.r2.rate111) & RATE111_SHORTPRE) ? "(SPr)" : "",
|
|
+ adev->status);
|
|
+ else
|
|
+ printk("tx: pkt (%s): len %d rate %03u%s status %u\n",
|
|
+ acx_get_packet_type_string(fc), len,
|
|
+ read_slavemem8 (adev, (u32) &(txdesc->u.r1.rate)),
|
|
+ (Ctl_8 & DESC_CTL_SHORT_PREAMBLE) ? "(SPr)" : "",
|
|
+ adev->status);
|
|
+
|
|
+ if (acx_debug & L_DATA) {
|
|
+ printk("tx: 802.11 [%d]: ", len);
|
|
+ acx_dump_bytes(hostdesc1->data, len);
|
|
+ }
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_l_clean_txdesc
|
|
+**
|
|
+** This function resets the txdescs' status when the ACX100
|
|
+** signals the TX done IRQ (txdescs have been processed), starting with
|
|
+** the pool index of the descriptor which we would use next,
|
|
+** in order to make sure that we can be as fast as possible
|
|
+** in filling new txdescs.
|
|
+** Everytime we get called we know where the next packet to be cleaned is.
|
|
+*/
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline void log_txbuffer(const acx_device_t *adev) {}
|
|
+#else
|
|
+static void
|
|
+log_txbuffer(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+ u8 Ctl_8;
|
|
+
|
|
+ /* no FN_ENTER here, we don't want that */
|
|
+ /* no locks here, since it's entirely non-critical code */
|
|
+ txdesc = adev->txdesc_start;
|
|
+ if (unlikely(!txdesc)) return;
|
|
+ printk("tx: desc->Ctl8's:");
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ printk(" %02X", Ctl_8);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ printk("\n");
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+static void
|
|
+handle_tx_error(acx_device_t *adev, u8 error, unsigned int finger)
|
|
+{
|
|
+ const char *err = "unknown error";
|
|
+
|
|
+ /* hmm, should we handle this as a mask
|
|
+ * of *several* bits?
|
|
+ * For now I think only caring about
|
|
+ * individual bits is ok... */
|
|
+ switch (error) {
|
|
+ case 0x01:
|
|
+ err = "no Tx due to error in other fragment";
|
|
+ adev->wstats.discard.fragment++;
|
|
+ break;
|
|
+ case 0x02:
|
|
+ err = "Tx aborted";
|
|
+ adev->stats.tx_aborted_errors++;
|
|
+ break;
|
|
+ case 0x04:
|
|
+ err = "Tx desc wrong parameters";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x08:
|
|
+ err = "WEP key not found";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x10:
|
|
+ err = "MSDU lifetime timeout? - try changing "
|
|
+ "'iwconfig retry lifetime XXX'";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x20:
|
|
+ err = "excessive Tx retries due to either distance "
|
|
+ "too high or unable to Tx or Tx frame error - "
|
|
+ "try changing 'iwconfig txpower XXX' or "
|
|
+ "'sens'itivity or 'retry'";
|
|
+ adev->wstats.discard.retries++;
|
|
+ /* Tx error 0x20 also seems to occur on
|
|
+ * overheating, so I'm not sure whether we
|
|
+ * actually want to do aggressive radio recalibration,
|
|
+ * since people maybe won't notice then that their hardware
|
|
+ * is slowly getting cooked...
|
|
+ * Or is it still a safe long distance from utter
|
|
+ * radio non-functionality despite many radio recalibs
|
|
+ * to final destructive overheating of the hardware?
|
|
+ * In this case we really should do recalib here...
|
|
+ * I guess the only way to find out is to do a
|
|
+ * potentially fatal self-experiment :-\
|
|
+ * Or maybe only recalib in case we're using Tx
|
|
+ * rate auto (on errors switching to lower speed
|
|
+ * --> less heat?) or 802.11 power save mode?
|
|
+ *
|
|
+ * ok, just do it. */
|
|
+ if (++adev->retry_errors_msg_ratelimit % 4 == 0) {
|
|
+ if (adev->retry_errors_msg_ratelimit <= 20) {
|
|
+ printk("%s: several excessive Tx "
|
|
+ "retry errors occurred, attempting "
|
|
+ "to recalibrate radio. Radio "
|
|
+ "drift might be caused by increasing "
|
|
+ "card temperature, please check the card "
|
|
+ "before it's too late!\n",
|
|
+ adev->ndev->name);
|
|
+ if (adev->retry_errors_msg_ratelimit == 20)
|
|
+ printk("disabling above message\n");
|
|
+ }
|
|
+
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+ }
|
|
+ break;
|
|
+ case 0x40:
|
|
+ err = "Tx buffer overflow";
|
|
+ adev->stats.tx_fifo_errors++;
|
|
+ break;
|
|
+ case 0x80:
|
|
+ err = "DMA error";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ }
|
|
+ adev->stats.tx_errors++;
|
|
+ if (adev->stats.tx_errors <= 20)
|
|
+ printk("%s: tx error 0x%02X, buf %02u! (%s)\n",
|
|
+ adev->ndev->name, error, finger, err);
|
|
+ else
|
|
+ printk("%s: tx error 0x%02X, buf %02u!\n",
|
|
+ adev->ndev->name, error, finger);
|
|
+}
|
|
+
|
|
+
|
|
+unsigned int
|
|
+acxmem_l_clean_txdesc(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ unsigned finger;
|
|
+ int num_cleaned;
|
|
+ u16 r111;
|
|
+ u8 error, ack_failures, rts_failures, rts_ok, r100, Ctl_8;
|
|
+ u32 acxmem;
|
|
+ txdesc_t tmptxdesc;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /*
|
|
+ * Set up a template descriptor for re-initialization. The only
|
|
+ * things that get set are Ctl_8 and the rate, and the rate defaults
|
|
+ * to 1Mbps.
|
|
+ */
|
|
+ memset (&tmptxdesc, 0, sizeof (tmptxdesc));
|
|
+ tmptxdesc.Ctl_8 = DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG;
|
|
+ tmptxdesc.u.r1.rate = 0x0a;
|
|
+
|
|
+ if (unlikely(acx_debug & L_DEBUG))
|
|
+ log_txbuffer(adev);
|
|
+
|
|
+ log(L_BUFT, "tx: cleaning up bufs from %u\n", adev->tx_tail);
|
|
+
|
|
+ /* We know first descr which is not free yet. We advance it as far
|
|
+ ** as we see correct bits set in following descs (if next desc
|
|
+ ** is NOT free, we shouldn't advance at all). We know that in
|
|
+ ** front of tx_tail may be "holes" with isolated free descs.
|
|
+ ** We will catch up when all intermediate descs will be freed also */
|
|
+
|
|
+ finger = adev->tx_tail;
|
|
+ num_cleaned = 0;
|
|
+ while (likely(finger != adev->tx_head)) {
|
|
+ txdesc = get_txdesc(adev, finger);
|
|
+
|
|
+ /* If we allocated txdesc on tx path but then decided
|
|
+ ** to NOT use it, then it will be left as a free "bubble"
|
|
+ ** in the "allocated for tx" part of the ring.
|
|
+ ** We may meet it on the next ring pass here. */
|
|
+
|
|
+ /* stop if not marked as "tx finished" and "host owned" */
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ if ((Ctl_8 & DESC_CTL_ACXDONE_HOSTOWN)
|
|
+ != DESC_CTL_ACXDONE_HOSTOWN) {
|
|
+ if (unlikely(!num_cleaned)) { /* maybe remove completely */
|
|
+ log(L_BUFT, "clean_txdesc: tail isn't free. "
|
|
+ "tail:%d head:%d\n",
|
|
+ adev->tx_tail, adev->tx_head);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* remember desc values... */
|
|
+ error = read_slavemem8 (adev, (u32) &(txdesc->error));
|
|
+ ack_failures = read_slavemem8 (adev, (u32) &(txdesc->ack_failures));
|
|
+ rts_failures = read_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_failures));
|
|
+ rts_ok = read_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_ok));
|
|
+ r100 = read_slavemem8 (adev, (u32) &(txdesc->u.r1.rate));
|
|
+ r111 = le16_to_cpu(read_slavemem16 (adev, (u32) &(txdesc->u.r2.rate111)));
|
|
+
|
|
+ /* need to check for certain error conditions before we
|
|
+ * clean the descriptor: we still need valid descr data here */
|
|
+ if (unlikely(0x30 & error)) {
|
|
+ /* only send IWEVTXDROP in case of retry or lifetime exceeded;
|
|
+ * all other errors mean we screwed up locally */
|
|
+ union iwreq_data wrqu;
|
|
+ wlan_hdr_t *hdr;
|
|
+ txhostdesc_t *hostdesc;
|
|
+
|
|
+ hostdesc = get_txhostdesc(adev, txdesc);
|
|
+ hdr = (wlan_hdr_t *)hostdesc->data;
|
|
+ MAC_COPY(wrqu.addr.sa_data, hdr->a1);
|
|
+ wireless_send_event(adev->ndev, IWEVTXDROP, &wrqu, NULL);
|
|
+ }
|
|
+
|
|
+ /*
|
|
+ * Free up the transmit data buffers
|
|
+ */
|
|
+ acxmem = read_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr));
|
|
+ if (acxmem) {
|
|
+ reclaim_acx_txbuf_space (adev, acxmem);
|
|
+ }
|
|
+
|
|
+ /* ...and free the desc by clearing all the fields
|
|
+ except the next pointer */
|
|
+ copy_to_slavemem (adev,
|
|
+ (u32) &(txdesc->HostMemPtr),
|
|
+ (u8 *) &(tmptxdesc.HostMemPtr),
|
|
+ sizeof (tmptxdesc) - sizeof(tmptxdesc.pNextDesc)
|
|
+ );
|
|
+
|
|
+ adev->tx_free++;
|
|
+ num_cleaned++;
|
|
+
|
|
+ if ((adev->tx_free >= TX_START_QUEUE)
|
|
+ && (adev->status == ACX_STATUS_4_ASSOCIATED)
|
|
+ && (acx_queue_stopped(adev->ndev))
|
|
+ ) {
|
|
+ log(L_BUF, "tx: wake queue (avail. Tx desc %u)\n",
|
|
+ adev->tx_free);
|
|
+ acx_wake_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+ /* do error checking, rate handling and logging
|
|
+ * AFTER having done the work, it's faster */
|
|
+
|
|
+ /* do rate handling */
|
|
+ if (adev->rate_auto) {
|
|
+ struct client *clt = get_txc(adev, txdesc);
|
|
+ if (clt) {
|
|
+ u16 cur = get_txr(adev, txdesc);
|
|
+ if (clt->rate_cur == cur) {
|
|
+ acx_l_handle_txrate_auto(adev, clt,
|
|
+ cur, /* intended rate */
|
|
+ r100, r111, /* actually used rate */
|
|
+ (error & 0x30), /* was there an error? */
|
|
+ TX_CNT + TX_CLEAN_BACKLOG - adev->tx_free);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (unlikely(error))
|
|
+ handle_tx_error(adev, error, finger);
|
|
+
|
|
+ if (IS_ACX111(adev))
|
|
+ log(L_BUFT, "tx: cleaned %u: !ACK=%u !RTS=%u RTS=%u r111=%04X\n",
|
|
+ finger, ack_failures, rts_failures, rts_ok, r111);
|
|
+ else
|
|
+ log(L_BUFT, "tx: cleaned %u: !ACK=%u !RTS=%u RTS=%u rate=%u\n",
|
|
+ finger, ack_failures, rts_failures, rts_ok, r100);
|
|
+
|
|
+ /* update pointer for descr to be cleaned next */
|
|
+ finger = (finger + 1) % TX_CNT;
|
|
+ }
|
|
+
|
|
+ /* remember last position */
|
|
+ adev->tx_tail = finger;
|
|
+/* end: */
|
|
+ FN_EXIT1(num_cleaned);
|
|
+ return num_cleaned;
|
|
+}
|
|
+
|
|
+/* clean *all* Tx descriptors, and regardless of their previous state.
|
|
+ * Used for brute-force reset handling. */
|
|
+void
|
|
+acxmem_l_clean_txdesc_emergency(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+ u32 acxmem;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ txdesc = get_txdesc(adev, i);
|
|
+
|
|
+ /* free it */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->ack_failures), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_failures), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->u.rts.rts_ok), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->error), 0);
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8), DESC_CTL_HOSTOWN);
|
|
+
|
|
+ /*
|
|
+ * Clean up the memory allocated on the ACX for this transmit descriptor.
|
|
+ */
|
|
+ acxmem = read_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr));
|
|
+ if (acxmem) {
|
|
+ reclaim_acx_txbuf_space (adev, acxmem);
|
|
+ }
|
|
+
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr), 0);
|
|
+ }
|
|
+
|
|
+ adev->tx_free = TX_CNT;
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_s_create_tx_host_desc_queue
|
|
+*/
|
|
+
|
|
+static void*
|
|
+allocate(acx_device_t *adev, size_t size, dma_addr_t *phy, const char *msg)
|
|
+{
|
|
+ void *ptr;
|
|
+ ptr = kmalloc (size, GFP_KERNEL);
|
|
+ /*
|
|
+ * The ACX can't use the physical address, so we'll have to fake it
|
|
+ * later and it might be handy to have the virtual address.
|
|
+ */
|
|
+ *phy = (dma_addr_t) NULL;
|
|
+
|
|
+ if (ptr) {
|
|
+ log(L_DEBUG, "%s sz=%d adr=0x%p phy=0x%08llx\n",
|
|
+ msg, (int)size, ptr, (unsigned long long)*phy);
|
|
+ memset(ptr, 0, size);
|
|
+ return ptr;
|
|
+ }
|
|
+ printk(KERN_ERR "acx: %s allocation FAILED (%d bytes)\n",
|
|
+ msg, (int)size);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * In the generic slave memory access mode, most of the stuff in
|
|
+ * the txhostdesc_t is unused. It's only here because the rest of
|
|
+ * the ACX driver expects it to be since the PCI version uses indirect
|
|
+ * host memory organization with DMA. Since we're not using DMA the
|
|
+ * only use we have for the host descriptors is to store the packets
|
|
+ * on the way out.
|
|
+ */
|
|
+static int
|
|
+acxmem_s_create_tx_host_desc_queue(acx_device_t *adev)
|
|
+{
|
|
+ txhostdesc_t *hostdesc;
|
|
+ u8 *txbuf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* allocate TX buffer */
|
|
+ adev->txbuf_area_size = TX_CNT * WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+
|
|
+ adev->txbuf_start = allocate(adev, adev->txbuf_area_size,
|
|
+ &adev->txbuf_startphy, "txbuf_start");
|
|
+ if (!adev->txbuf_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* allocate the TX host descriptor queue pool */
|
|
+ adev->txhostdesc_area_size = TX_CNT * 2*sizeof(*hostdesc);
|
|
+
|
|
+ adev->txhostdesc_start = allocate(adev, adev->txhostdesc_area_size,
|
|
+ &adev->txhostdesc_startphy, "txhostdesc_start");
|
|
+ if (!adev->txhostdesc_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* check for proper alignment of TX host descriptor pool */
|
|
+ if ((long) adev->txhostdesc_start & 3) {
|
|
+ printk("acx: driver bug: dma alloc returns unaligned address\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ hostdesc = adev->txhostdesc_start;
|
|
+ txbuf = adev->txbuf_start;
|
|
+
|
|
+#if 0
|
|
+/* Each tx buffer is accessed by hardware via
|
|
+** txdesc -> txhostdesc(s) -> txbuffer(s).
|
|
+** We use only one txhostdesc per txdesc, but it looks like
|
|
+** acx111 is buggy: it accesses second txhostdesc
|
|
+** (via hostdesc.desc_phy_next field) even if
|
|
+** txdesc->length == hostdesc->length and thus
|
|
+** entire packet was placed into first txhostdesc.
|
|
+** Due to this bug acx111 hangs unless second txhostdesc
|
|
+** has le16_to_cpu(hostdesc.length) = 3 (or larger)
|
|
+** Storing NULL into hostdesc.desc_phy_next
|
|
+** doesn't seem to help.
|
|
+**
|
|
+** Update: although it worked on Xterasys XN-2522g
|
|
+** with len=3 trick, WG311v2 is even more bogus, doesn't work.
|
|
+** Keeping this code (#ifdef'ed out) for documentational purposes.
|
|
+*/
|
|
+ for (i = 0; i < TX_CNT*2; i++) {
|
|
+ hostdesc_phy += sizeof(*hostdesc);
|
|
+ if (!(i & 1)) {
|
|
+ hostdesc->data_phy = cpu2acx(txbuf_phy);
|
|
+ /* hostdesc->data_offset = ... */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ hostdesc->Ctl_16 = cpu_to_le16(DESC_CTL_HOSTOWN);
|
|
+ /* hostdesc->length = ... */
|
|
+ hostdesc->desc_phy_next = cpu2acx(hostdesc_phy);
|
|
+ hostdesc->pNext = ptr2acx(NULL);
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* below: non-hardware fields */
|
|
+ hostdesc->data = txbuf;
|
|
+
|
|
+ txbuf += WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ txbuf_phy += WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ } else {
|
|
+ /* hostdesc->data_phy = ... */
|
|
+ /* hostdesc->data_offset = ... */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ /* hostdesc->Ctl_16 = ... */
|
|
+ hostdesc->length = cpu_to_le16(3); /* bug workaround */
|
|
+ /* hostdesc->desc_phy_next = ... */
|
|
+ /* hostdesc->pNext = ... */
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* below: non-hardware fields */
|
|
+ /* hostdesc->data = ... */
|
|
+ }
|
|
+ hostdesc++;
|
|
+ }
|
|
+#endif
|
|
+/* We initialize two hostdescs so that they point to adjacent
|
|
+** memory areas. Thus txbuf is really just a contiguous memory area */
|
|
+ for (i = 0; i < TX_CNT*2; i++) {
|
|
+ /* ->data is a non-hardware field: */
|
|
+ hostdesc->data = txbuf;
|
|
+
|
|
+ if (!(i & 1)) {
|
|
+ txbuf += WLAN_HDR_A3_LEN;
|
|
+ } else {
|
|
+ txbuf += WLAN_A4FR_MAXLEN_WEP_FCS - WLAN_HDR_A3_LEN;
|
|
+ }
|
|
+ hostdesc++;
|
|
+ }
|
|
+ hostdesc--;
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+ printk("acx: create_tx_host_desc_queue FAILED\n");
|
|
+ /* dealloc will be done by free function on error case */
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_s_create_rx_host_desc_queue
|
|
+*/
|
|
+/* the whole size of a data buffer (header plus data body)
|
|
+ * plus 32 bytes safety offset at the end */
|
|
+#define RX_BUFFER_SIZE (sizeof(rxbuffer_t) + 32)
|
|
+
|
|
+static int
|
|
+acxmem_s_create_rx_host_desc_queue(acx_device_t *adev)
|
|
+{
|
|
+ rxhostdesc_t *hostdesc;
|
|
+ rxbuffer_t *rxbuf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* allocate the RX host descriptor queue pool */
|
|
+ adev->rxhostdesc_area_size = RX_CNT * sizeof(*hostdesc);
|
|
+
|
|
+ adev->rxhostdesc_start = allocate(adev, adev->rxhostdesc_area_size,
|
|
+ &adev->rxhostdesc_startphy, "rxhostdesc_start");
|
|
+ if (!adev->rxhostdesc_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* check for proper alignment of RX host descriptor pool */
|
|
+ if ((long) adev->rxhostdesc_start & 3) {
|
|
+ printk("acx: driver bug: dma alloc returns unaligned address\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* allocate Rx buffer pool which will be used by the acx
|
|
+ * to store the whole content of the received frames in it */
|
|
+ adev->rxbuf_area_size = RX_CNT * RX_BUFFER_SIZE;
|
|
+
|
|
+ adev->rxbuf_start = allocate(adev, adev->rxbuf_area_size,
|
|
+ &adev->rxbuf_startphy, "rxbuf_start");
|
|
+ if (!adev->rxbuf_start)
|
|
+ goto fail;
|
|
+
|
|
+ rxbuf = adev->rxbuf_start;
|
|
+ hostdesc = adev->rxhostdesc_start;
|
|
+
|
|
+ /* don't make any popular C programming pointer arithmetic mistakes
|
|
+ * here, otherwise I'll kill you...
|
|
+ * (and don't dare asking me why I'm warning you about that...) */
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ hostdesc->data = rxbuf;
|
|
+ hostdesc->length = cpu_to_le16(RX_BUFFER_SIZE);
|
|
+ rxbuf++;
|
|
+ hostdesc++;
|
|
+ }
|
|
+ hostdesc--;
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+ printk("acx: create_rx_host_desc_queue FAILED\n");
|
|
+ /* dealloc will be done by free function on error case */
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_s_create_hostdesc_queues
|
|
+*/
|
|
+int
|
|
+acxmem_s_create_hostdesc_queues(acx_device_t *adev)
|
|
+{
|
|
+ int result;
|
|
+ result = acxmem_s_create_tx_host_desc_queue(adev);
|
|
+ if (OK != result) return result;
|
|
+ result = acxmem_s_create_rx_host_desc_queue(adev);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_create_tx_desc_queue
|
|
+*/
|
|
+static void
|
|
+acxmem_create_tx_desc_queue(acx_device_t *adev, u32 tx_queue_start)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ u32 clr;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (IS_ACX100(adev))
|
|
+ adev->txdesc_size = sizeof(*txdesc);
|
|
+ else
|
|
+ /* the acx111 txdesc is 4 bytes larger */
|
|
+ adev->txdesc_size = sizeof(*txdesc) + 4;
|
|
+
|
|
+ /*
|
|
+ * This refers to an ACX address, not one of ours
|
|
+ */
|
|
+ adev->txdesc_start = (txdesc_t *) tx_queue_start;
|
|
+
|
|
+ log(L_DEBUG, "adev->txdesc_start=%p\n",
|
|
+ adev->txdesc_start);
|
|
+
|
|
+ adev->tx_free = TX_CNT;
|
|
+ /* done by memset: adev->tx_head = 0; */
|
|
+ /* done by memset: adev->tx_tail = 0; */
|
|
+ txdesc = adev->txdesc_start;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111 has a preinitialized Tx buffer! */
|
|
+ /* loop over whole send pool */
|
|
+ /* FIXME: do we have to do the hostmemptr stuff here?? */
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ txdesc->Ctl_8 = DESC_CTL_HOSTOWN;
|
|
+ /* reserve two (hdr desc and payload desc) */
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ } else {
|
|
+ /* ACX100 Tx buffer needs to be initialized by us */
|
|
+ /* clear whole send pool. sizeof is safe here (we are acx100) */
|
|
+
|
|
+ /*
|
|
+ * adev->txdesc_start refers to device memory, so we can't write
|
|
+ * directly to it.
|
|
+ */
|
|
+ clr = (u32) adev->txdesc_start;
|
|
+ while (clr < (u32) adev->txdesc_start + (TX_CNT * sizeof(*txdesc))) {
|
|
+ write_slavemem32 (adev, clr, 0);
|
|
+ clr += 4;
|
|
+ }
|
|
+
|
|
+ /* loop over whole send pool */
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ log(L_DEBUG, "configure card tx descriptor: 0x%p, "
|
|
+ "size: 0x%X\n", txdesc, adev->txdesc_size);
|
|
+
|
|
+ /* initialise ctl */
|
|
+ /*
|
|
+ * No auto DMA here
|
|
+ */
|
|
+ write_slavemem8 (adev, (u32) &(txdesc->Ctl_8),
|
|
+ (u8) (DESC_CTL_HOSTOWN | DESC_CTL_FIRSTFRAG));
|
|
+ /* done by memset(0): txdesc->Ctl2_8 = 0; */
|
|
+
|
|
+ /* point to next txdesc */
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 ((u8 *) txdesc + adev->txdesc_size));
|
|
+
|
|
+ /* go to the next one */
|
|
+ /* ++ is safe here (we are acx100) */
|
|
+ txdesc++;
|
|
+ }
|
|
+ /* go back to the last one */
|
|
+ txdesc--;
|
|
+ /* and point to the first making it a ring buffer */
|
|
+ write_slavemem32 (adev, (u32) &(txdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 (tx_queue_start));
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_create_rx_desc_queue
|
|
+*/
|
|
+static void
|
|
+acxmem_create_rx_desc_queue(acx_device_t *adev, u32 rx_queue_start)
|
|
+{
|
|
+ rxdesc_t *rxdesc;
|
|
+ u32 mem_offs;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* done by memset: adev->rx_tail = 0; */
|
|
+
|
|
+ /* ACX111 doesn't need any further config: preconfigures itself.
|
|
+ * Simply print ring buffer for debugging */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* rxdesc_start already set here */
|
|
+
|
|
+ adev->rxdesc_start = (rxdesc_t *) rx_queue_start;
|
|
+
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ log(L_DEBUG, "rx descriptor %d @ 0x%p\n", i, rxdesc);
|
|
+ rxdesc = adev->rxdesc_start = (rxdesc_t *)
|
|
+ acx2cpu(rxdesc->pNextDesc);
|
|
+ }
|
|
+ } else {
|
|
+ /* we didn't pre-calculate rxdesc_start in case of ACX100 */
|
|
+ /* rxdesc_start should be right AFTER Tx pool */
|
|
+ adev->rxdesc_start = (rxdesc_t *)
|
|
+ ((u8 *) adev->txdesc_start + (TX_CNT * sizeof(txdesc_t)));
|
|
+ /* NB: sizeof(txdesc_t) above is valid because we know
|
|
+ ** we are in if (acx100) block. Beware of cut-n-pasting elsewhere!
|
|
+ ** acx111's txdesc is larger! */
|
|
+
|
|
+ mem_offs = (u32) adev->rxdesc_start;
|
|
+ while (mem_offs < (u32) adev->rxdesc_start + (RX_CNT * sizeof (*rxdesc))) {
|
|
+ write_slavemem32 (adev, mem_offs, 0);
|
|
+ mem_offs += 4;
|
|
+ }
|
|
+
|
|
+ /* loop over whole receive pool */
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ log(L_DEBUG, "rx descriptor @ 0x%p\n", rxdesc);
|
|
+ /* point to next rxdesc */
|
|
+ write_slavemem32 (adev, (u32) &(rxdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 ((u8 *) rxdesc + sizeof(*rxdesc)));
|
|
+ /* go to the next one */
|
|
+ rxdesc++;
|
|
+ }
|
|
+ /* go to the last one */
|
|
+ rxdesc--;
|
|
+
|
|
+ /* and point to the first making it a ring buffer */
|
|
+ write_slavemem32 (adev, (u32) &(rxdesc->pNextDesc),
|
|
+ (u32) cpu_to_le32 (rx_queue_start));
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_create_desc_queues
|
|
+*/
|
|
+void
|
|
+acxmem_create_desc_queues(acx_device_t *adev, u32 tx_queue_start, u32 rx_queue_start)
|
|
+{
|
|
+ u32 *p;
|
|
+ int i;
|
|
+
|
|
+ acxmem_create_tx_desc_queue(adev, tx_queue_start);
|
|
+ acxmem_create_rx_desc_queue(adev, rx_queue_start);
|
|
+ p = (u32 *) adev->acx_queue_indicator;
|
|
+ for (i = 0; i < 4; i++) {
|
|
+ write_slavemem32 (adev, (u32) p, 0);
|
|
+ p++;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxmem_s_proc_diag_output
|
|
+*/
|
|
+char*
|
|
+acxmem_s_proc_diag_output(char *p, acx_device_t *adev)
|
|
+{
|
|
+ const char *rtl, *thd, *ttl;
|
|
+ txdesc_t *txdesc;
|
|
+ u8 Ctl_8;
|
|
+ rxdesc_t *rxdesc;
|
|
+ int i;
|
|
+ u32 tmp;
|
|
+ txdesc_t txd;
|
|
+ u8 buf[0x200];
|
|
+ int j, k;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+#if DUMP_MEM_DURING_DIAG > 0
|
|
+ dump_acxmem (adev, 0, 0x10000);
|
|
+ panic ("dump finished");
|
|
+#endif
|
|
+
|
|
+ p += sprintf(p, "** Rx buf **\n");
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ if (rxdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ rtl = (i == adev->rx_tail) ? " [tail]" : "";
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(rxdesc->Ctl_8));
|
|
+ if (Ctl_8 & DESC_CTL_HOSTOWN)
|
|
+ p += sprintf(p, "%02u (%02x) FULL%s\n", i, Ctl_8, rtl);
|
|
+ else
|
|
+ p += sprintf(p, "%02u (%02x) empty%s\n", i, Ctl_8, rtl);
|
|
+ rxdesc++;
|
|
+ }
|
|
+ p += sprintf(p, "** Tx buf (free %d, Linux netqueue %s) **\n", adev->tx_free,
|
|
+ acx_queue_stopped(adev->ndev) ? "STOPPED" : "running");
|
|
+
|
|
+ p += sprintf(p, "** Tx buf %d blocks total, %d available, free list head %04x\n",
|
|
+ adev->acx_txbuf_numblocks, adev->acx_txbuf_blocks_free, adev->acx_txbuf_free);
|
|
+ txdesc = adev->txdesc_start;
|
|
+ if (txdesc) {
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ thd = (i == adev->tx_head) ? " [head]" : "";
|
|
+ ttl = (i == adev->tx_tail) ? " [tail]" : "";
|
|
+ copy_from_slavemem (adev, (u8 *) &txd, (u32) txdesc, sizeof (txd));
|
|
+ Ctl_8 = read_slavemem8 (adev, (u32) &(txdesc->Ctl_8));
|
|
+ if (Ctl_8 & DESC_CTL_ACXDONE)
|
|
+ p += sprintf(p, "%02u ready to free (%02X)%s%s", i, Ctl_8, thd, ttl);
|
|
+ else if (Ctl_8 & DESC_CTL_HOSTOWN)
|
|
+ p += sprintf(p, "%02u available (%02X)%s%s", i, Ctl_8, thd, ttl);
|
|
+ else
|
|
+ p += sprintf(p, "%02u busy (%02X)%s%s", i, Ctl_8, thd, ttl);
|
|
+ tmp = read_slavemem32 (adev, (u32) &(txdesc->AcxMemPtr));
|
|
+ if (tmp) {
|
|
+ p += sprintf (p, " %04x", tmp);
|
|
+ while ((tmp = read_slavemem32 (adev, (u32) tmp)) != 0x02000000) {
|
|
+ tmp <<= 5;
|
|
+ p += sprintf (p, " %04x", tmp);
|
|
+ }
|
|
+ }
|
|
+ p += sprintf (p, "\n");
|
|
+ p += sprintf (p, " %04x: %04x %04x %04x %04x %04x %04x %04x %04x %04x %04x %02x %02x %02x %02x\n"
|
|
+ "%02x %02x %02x %02x %04x\n",
|
|
+ (u32) txdesc,
|
|
+ txd.pNextDesc.v, txd.HostMemPtr.v, txd.AcxMemPtr.v, txd.tx_time,
|
|
+ txd.total_length, txd.Reserved,
|
|
+ txd.dummy[0], txd.dummy[1], txd.dummy[2], txd.dummy[3],
|
|
+ txd.Ctl_8, txd.Ctl2_8, txd.error, txd.ack_failures,
|
|
+ txd.u.rts.rts_failures, txd.u.rts.rts_ok, txd.u.r1.rate, txd.u.r1.queue_ctrl,
|
|
+ txd.queue_info
|
|
+ );
|
|
+ if (txd.AcxMemPtr.v) {
|
|
+ copy_from_slavemem (adev, buf, txd.AcxMemPtr.v, sizeof (buf));
|
|
+ for (j = 0; (j < txd.total_length) && (j<(sizeof(buf)-4)); j+=16) {
|
|
+ p += sprintf (p, " ");
|
|
+ for (k = 0; (k < 16) && (j+k < txd.total_length); k++) {
|
|
+ p += sprintf (p, " %02x", buf[j+k+4]);
|
|
+ }
|
|
+ p += sprintf (p, "\n");
|
|
+ }
|
|
+ }
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ p += sprintf(p,
|
|
+ "\n"
|
|
+ "** Generic slave data **\n"
|
|
+ "irq_mask 0x%04x irq_status 0x%04x irq on acx 0x%04x\n"
|
|
+ "txbuf_start 0x%p, txbuf_area_size %u\n"
|
|
+ "txdesc_size %u, txdesc_start 0x%p\n"
|
|
+ "txhostdesc_start 0x%p, txhostdesc_area_size %u\n"
|
|
+ "txbuf start 0x%04x, txbuf size %d\n"
|
|
+ "rxdesc_start 0x%p\n"
|
|
+ "rxhostdesc_start 0x%p, rxhostdesc_area_size %u\n"
|
|
+ "rxbuf_start 0x%p, rxbuf_area_size %u\n",
|
|
+ adev->irq_mask, adev->irq_status, read_reg32(adev, IO_ACX_IRQ_STATUS_NON_DES),
|
|
+ adev->txbuf_start, adev->txbuf_area_size,
|
|
+ adev->txdesc_size, adev->txdesc_start,
|
|
+ adev->txhostdesc_start, adev->txhostdesc_area_size,
|
|
+ adev->acx_txbuf_start, adev->acx_txbuf_numblocks * adev->memblocksize,
|
|
+ adev->rxdesc_start,
|
|
+ adev->rxhostdesc_start, adev->rxhostdesc_area_size,
|
|
+ adev->rxbuf_start, adev->rxbuf_area_size);
|
|
+ FN_EXIT0;
|
|
+ return p;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxmem_proc_eeprom_output(char *buf, acx_device_t *adev)
|
|
+{
|
|
+ char *p = buf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ for (i = 0; i < 0x400; i++) {
|
|
+ acxmem_read_eeprom_byte(adev, i, p++);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(p - buf);
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void
|
|
+acxmem_set_interrupt_mask(acx_device_t *adev)
|
|
+{
|
|
+ if (IS_ACX111(adev)) {
|
|
+ adev->irq_mask = (u16) ~(0
|
|
+ | HOST_INT_RX_DATA
|
|
+ | HOST_INT_TX_COMPLETE
|
|
+ /* | HOST_INT_TX_XFER */
|
|
+ /* | HOST_INT_RX_COMPLETE */
|
|
+ /* | HOST_INT_DTIM */
|
|
+ /* | HOST_INT_BEACON */
|
|
+ /* | HOST_INT_TIMER */
|
|
+ /* | HOST_INT_KEY_NOT_FOUND */
|
|
+ | HOST_INT_IV_ICV_FAILURE
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ | HOST_INT_OVERFLOW
|
|
+ /* | HOST_INT_PROCESS_ERROR */
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ | HOST_INT_FCS_THRESHOLD
|
|
+ | HOST_INT_UNKNOWN
|
|
+ );
|
|
+ /* Or else acx100 won't signal cmd completion, right? */
|
|
+ adev->irq_mask_off = (u16)~( HOST_INT_CMD_COMPLETE ); /* 0xfdff */
|
|
+ } else {
|
|
+ adev->irq_mask = (u16) ~(0
|
|
+ | HOST_INT_RX_DATA
|
|
+ | HOST_INT_TX_COMPLETE
|
|
+ /* | HOST_INT_TX_XFER */
|
|
+ /* | HOST_INT_RX_COMPLETE */
|
|
+ /* | HOST_INT_DTIM */
|
|
+ /* | HOST_INT_BEACON */
|
|
+ /* | HOST_INT_TIMER */
|
|
+ /* | HOST_INT_KEY_NOT_FOUND */
|
|
+ /* | HOST_INT_IV_ICV_FAILURE */
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ /* | HOST_INT_OVERFLOW */
|
|
+ /* | HOST_INT_PROCESS_ERROR */
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ /* | HOST_INT_FCS_THRESHOLD */
|
|
+ /* | HOST_INT_BEACON_MISSED */
|
|
+ );
|
|
+ adev->irq_mask_off = (u16)~( HOST_INT_UNKNOWN ); /* 0x7fff */
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx100mem_s_set_tx_level(acx_device_t *adev, u8 level_dbm)
|
|
+{
|
|
+ struct acx111_ie_tx_level tx_level;
|
|
+
|
|
+ /* since it can be assumed that at least the Maxim radio has a
|
|
+ * maximum power output of 20dBm and since it also can be
|
|
+ * assumed that these values drive the DAC responsible for
|
|
+ * setting the linear Tx level, I'd guess that these values
|
|
+ * should be the corresponding linear values for a dBm value,
|
|
+ * in other words: calculate the values from that formula:
|
|
+ * Y [dBm] = 10 * log (X [mW])
|
|
+ * then scale the 0..63 value range onto the 1..100mW range (0..20 dBm)
|
|
+ * and you're done...
|
|
+ * Hopefully that's ok, but you never know if we're actually
|
|
+ * right... (especially since Windows XP doesn't seem to show
|
|
+ * actual Tx dBm values :-P) */
|
|
+
|
|
+ /* NOTE: on Maxim, value 30 IS 30mW, and value 10 IS 10mW - so the
|
|
+ * values are EXACTLY mW!!! Not sure about RFMD and others,
|
|
+ * though... */
|
|
+ static const u8 dbm2val_maxim[21] = {
|
|
+ 63, 63, 63, 62,
|
|
+ 61, 61, 60, 60,
|
|
+ 59, 58, 57, 55,
|
|
+ 53, 50, 47, 43,
|
|
+ 38, 31, 23, 13,
|
|
+ 0
|
|
+ };
|
|
+ static const u8 dbm2val_rfmd[21] = {
|
|
+ 0, 0, 0, 1,
|
|
+ 2, 2, 3, 3,
|
|
+ 4, 5, 6, 8,
|
|
+ 10, 13, 16, 20,
|
|
+ 25, 32, 41, 50,
|
|
+ 63
|
|
+ };
|
|
+ const u8 *table;
|
|
+
|
|
+ switch (adev->radio_type) {
|
|
+ case RADIO_MAXIM_0D:
|
|
+ table = &dbm2val_maxim[0];
|
|
+ break;
|
|
+ case RADIO_RFMD_11:
|
|
+ case RADIO_RALINK_15:
|
|
+ table = &dbm2val_rfmd[0];
|
|
+ break;
|
|
+ default:
|
|
+ printk("%s: unknown/unsupported radio type, "
|
|
+ "cannot modify tx power level yet!\n",
|
|
+ adev->ndev->name);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+ /*
|
|
+ * The hx4700 EEPROM, at least, only supports 1 power setting. The configure
|
|
+ * routine matches the PA bias with the gain, so just use its default value.
|
|
+ * The values are: 0x2b for the gain and 0x03 for the PA bias. The firmware
|
|
+ * writes the gain level to the Tx gain control DAC and the PA bias to the Maxim
|
|
+ * radio's PA bias register. The firmware limits itself to 0 - 64 when writing to the
|
|
+ * gain control DAC.
|
|
+ *
|
|
+ * Physically between the ACX and the radio, higher Tx gain control DAC values result
|
|
+ * in less power output; 0 volts to the Maxim radio results in the highest output power
|
|
+ * level, which I'm assuming matches up with 0 in the Tx Gain DAC register.
|
|
+ *
|
|
+ * Although there is only the 1 power setting, one of the radio firmware functions adjusts
|
|
+ * the transmit power level up and down. That function is called by the ACX FIQ handler
|
|
+ * under certain conditions.
|
|
+ */
|
|
+ tx_level.level = 1;
|
|
+ //return acx_s_configure(adev, &tx_level, ACX1xx_IE_DOT11_TX_POWER_LEVEL);
|
|
+
|
|
+ printk("%s: changing radio power level to %u dBm (%u)\n",
|
|
+ adev->ndev->name, level_dbm, table[level_dbm]);
|
|
+ acxmem_s_write_phy_reg(adev, 0x11, table[level_dbm]);
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+static struct platform_driver
|
|
+acxmem_drv_id = {
|
|
+ .driver = {
|
|
+ .name = "acx-mem",
|
|
+ },
|
|
+ .probe = acxmem_e_probe,
|
|
+ .remove = __devexit_p(acxmem_e_remove),
|
|
+#ifdef CONFIG_PM
|
|
+ .suspend = acxmem_e_suspend,
|
|
+ .resume = acxmem_e_resume
|
|
+#endif /* CONFIG_PM */
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_init_module
|
|
+**
|
|
+** Module initialization routine, called once at module load time
|
|
+*/
|
|
+int __init
|
|
+acxmem_e_init_module(void)
|
|
+{
|
|
+ int res;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+#if (ACX_IO_WIDTH==32)
|
|
+ printk("acx: compiled to use 32bit I/O access. "
|
|
+ "I/O timing issues might occur, such as "
|
|
+ "non-working firmware upload. Report them\n");
|
|
+#else
|
|
+ printk("acx: compiled to use 16bit I/O access only "
|
|
+ "(compatibility mode)\n");
|
|
+#endif
|
|
+
|
|
+#ifdef __LITTLE_ENDIAN
|
|
+#define ENDIANNESS_STRING "running on a little-endian CPU\n"
|
|
+#else
|
|
+#define ENDIANNESS_STRING "running on a BIG-ENDIAN CPU\n"
|
|
+#endif
|
|
+ log(L_INIT,
|
|
+ ENDIANNESS_STRING
|
|
+ "PCI module " ACX_RELEASE " initialized, "
|
|
+ "waiting for cards to probe...\n"
|
|
+ );
|
|
+
|
|
+ res = platform_driver_register (&acxmem_drv_id);
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxmem_e_cleanup_module
|
|
+**
|
|
+** Called at module unload time. This is our last chance to
|
|
+** clean up after ourselves.
|
|
+*/
|
|
+void __exit
|
|
+acxmem_e_cleanup_module(void)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ printk ("cleanup_module\n");
|
|
+ platform_driver_unregister( &acxmem_drv_id );
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+void acxmem_e_release(struct device *dev) {
|
|
+}
|
|
+
|
|
+MODULE_AUTHOR( "Todd Blumer <todd@sdgsystems.com>" );
|
|
+MODULE_DESCRIPTION( "ACX Slave Memory Driver" );
|
|
+MODULE_LICENSE( "GPL" );
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/pci.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/pci.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,4234 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+#define ACX_PCI 1
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+
|
|
+/* Linux 2.6.18+ uses <linux/utsrelease.h> */
|
|
+#ifndef UTS_RELEASE
|
|
+#include <linux/utsrelease.h>
|
|
+#endif
|
|
+
|
|
+#include <linux/compiler.h> /* required for Lx 2.6.8 ?? */
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/moduleparam.h>
|
|
+#include <linux/sched.h>
|
|
+#include <linux/types.h>
|
|
+#include <linux/skbuff.h>
|
|
+#include <linux/slab.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/ioport.h>
|
|
+#include <linux/pci.h>
|
|
+#include <linux/pm.h>
|
|
+#include <linux/vmalloc.h>
|
|
+#include <linux/dma-mapping.h>
|
|
+
|
|
+#include "acx.h"
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#define PCI_TYPE (PCI_USES_MEM | PCI_ADDR0 | PCI_NO_ACPI_WAKE)
|
|
+#define PCI_ACX100_REGION1 0x01
|
|
+#define PCI_ACX100_REGION1_SIZE 0x1000 /* Memory size - 4K bytes */
|
|
+#define PCI_ACX100_REGION2 0x02
|
|
+#define PCI_ACX100_REGION2_SIZE 0x10000 /* Memory size - 64K bytes */
|
|
+
|
|
+#define PCI_ACX111_REGION1 0x00
|
|
+#define PCI_ACX111_REGION1_SIZE 0x2000 /* Memory size - 8K bytes */
|
|
+#define PCI_ACX111_REGION2 0x01
|
|
+#define PCI_ACX111_REGION2_SIZE 0x20000 /* Memory size - 128K bytes */
|
|
+
|
|
+/* Texas Instruments Vendor ID */
|
|
+#define PCI_VENDOR_ID_TI 0x104c
|
|
+
|
|
+/* ACX100 22Mb/s WLAN controller */
|
|
+#define PCI_DEVICE_ID_TI_TNETW1100A 0x8400
|
|
+#define PCI_DEVICE_ID_TI_TNETW1100B 0x8401
|
|
+
|
|
+/* ACX111 54Mb/s WLAN controller */
|
|
+#define PCI_DEVICE_ID_TI_TNETW1130 0x9066
|
|
+
|
|
+/* PCI Class & Sub-Class code, Network-'Other controller' */
|
|
+#define PCI_CLASS_NETWORK_OTHERS 0x0280
|
|
+
|
|
+#define CARD_EEPROM_ID_SIZE 6
|
|
+
|
|
+#ifndef PCI_D0
|
|
+/* From include/linux/pci.h */
|
|
+#define PCI_D0 0
|
|
+#define PCI_D1 1
|
|
+#define PCI_D2 2
|
|
+#define PCI_D3hot 3
|
|
+#define PCI_D3cold 4
|
|
+#define PCI_UNKNOWN 5
|
|
+#define PCI_POWER_ERROR -1
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static void acxpci_i_tx_timeout(struct net_device *ndev);
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+static irqreturn_t acxpci_i_interrupt(int irq, void *dev_id);
|
|
+#else
|
|
+static irqreturn_t acxpci_i_interrupt(int irq, void *dev_id, struct pt_regs *regs);
|
|
+#endif
|
|
+static void acxpci_i_set_multicast_list(struct net_device *ndev);
|
|
+
|
|
+static int acxpci_e_open(struct net_device *ndev);
|
|
+static int acxpci_e_close(struct net_device *ndev);
|
|
+static void acxpci_s_up(struct net_device *ndev);
|
|
+static void acxpci_s_down(struct net_device *ndev);
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Register access
|
|
+*/
|
|
+
|
|
+/* Pick one */
|
|
+/* #define INLINE_IO static */
|
|
+#define INLINE_IO static inline
|
|
+
|
|
+INLINE_IO u32
|
|
+read_reg32(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+#if ACX_IO_WIDTH == 32
|
|
+ return readl((u8 *)adev->iobase + adev->io[offset]);
|
|
+#else
|
|
+ return readw((u8 *)adev->iobase + adev->io[offset])
|
|
+ + (readw((u8 *)adev->iobase + adev->io[offset] + 2) << 16);
|
|
+#endif
|
|
+}
|
|
+
|
|
+INLINE_IO u16
|
|
+read_reg16(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ return readw((u8 *)adev->iobase + adev->io[offset]);
|
|
+}
|
|
+
|
|
+INLINE_IO u8
|
|
+read_reg8(acx_device_t *adev, unsigned int offset)
|
|
+{
|
|
+ return readb((u8 *)adev->iobase + adev->io[offset]);
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg32(acx_device_t *adev, unsigned int offset, u32 val)
|
|
+{
|
|
+#if ACX_IO_WIDTH == 32
|
|
+ writel(val, (u8 *)adev->iobase + adev->io[offset]);
|
|
+#else
|
|
+ writew(val & 0xffff, (u8 *)adev->iobase + adev->io[offset]);
|
|
+ writew(val >> 16, (u8 *)adev->iobase + adev->io[offset] + 2);
|
|
+#endif
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg16(acx_device_t *adev, unsigned int offset, u16 val)
|
|
+{
|
|
+ writew(val, (u8 *)adev->iobase + adev->io[offset]);
|
|
+}
|
|
+
|
|
+INLINE_IO void
|
|
+write_reg8(acx_device_t *adev, unsigned int offset, u8 val)
|
|
+{
|
|
+ writeb(val, (u8 *)adev->iobase + adev->io[offset]);
|
|
+}
|
|
+
|
|
+/* Handle PCI posting properly:
|
|
+ * Make sure that writes reach the adapter in case they require to be executed
|
|
+ * *before* the next write, by reading a random (and safely accessible) register.
|
|
+ * This call has to be made if there is no read following (which would flush the data
|
|
+ * to the adapter), yet the written data has to reach the adapter immediately. */
|
|
+INLINE_IO void
|
|
+write_flush(acx_device_t *adev)
|
|
+{
|
|
+ /* readb(adev->iobase + adev->io[IO_ACX_INFO_MAILBOX_OFFS]); */
|
|
+ /* faster version (accesses the first register, IO_ACX_SOFT_RESET,
|
|
+ * which should also be safe): */
|
|
+ readb(adev->iobase);
|
|
+}
|
|
+
|
|
+INLINE_IO int
|
|
+adev_present(acx_device_t *adev)
|
|
+{
|
|
+ /* fast version (accesses the first register, IO_ACX_SOFT_RESET,
|
|
+ * which should be safe): */
|
|
+ return readl(adev->iobase) != 0xffffffff;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static inline txdesc_t*
|
|
+get_txdesc(acx_device_t *adev, int index)
|
|
+{
|
|
+ return (txdesc_t*) (((u8*)adev->txdesc_start) + index * adev->txdesc_size);
|
|
+}
|
|
+
|
|
+static inline txdesc_t*
|
|
+advance_txdesc(acx_device_t *adev, txdesc_t* txdesc, int inc)
|
|
+{
|
|
+ return (txdesc_t*) (((u8*)txdesc) + inc * adev->txdesc_size);
|
|
+}
|
|
+
|
|
+static txhostdesc_t*
|
|
+get_txhostdesc(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ return &adev->txhostdesc_start[index*2];
|
|
+}
|
|
+
|
|
+static inline client_t*
|
|
+get_txc(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return NULL;
|
|
+ }
|
|
+ return adev->txc[index];
|
|
+}
|
|
+
|
|
+static inline u16
|
|
+get_txr(acx_device_t *adev, txdesc_t* txdesc)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ index /= adev->txdesc_size;
|
|
+ return adev->txr[index];
|
|
+}
|
|
+
|
|
+static inline void
|
|
+put_txcr(acx_device_t *adev, txdesc_t* txdesc, client_t* c, u16 r111)
|
|
+{
|
|
+ int index = (u8*)txdesc - (u8*)adev->txdesc_start;
|
|
+ if (unlikely(ACX_DEBUG && (index % adev->txdesc_size))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return;
|
|
+ }
|
|
+ index /= adev->txdesc_size;
|
|
+ if (unlikely(ACX_DEBUG && (index >= TX_CNT))) {
|
|
+ printk("bad txdesc ptr %p\n", txdesc);
|
|
+ return;
|
|
+ }
|
|
+ adev->txc[index] = c;
|
|
+ adev->txr[index] = r111;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** EEPROM and PHY read/write helpers
|
|
+*/
|
|
+/***********************************************************************
|
|
+** acxpci_read_eeprom_byte
|
|
+**
|
|
+** Function called to read an octet in the EEPROM.
|
|
+**
|
|
+** This function is used by acxpci_e_probe to check if the
|
|
+** connected card is a legal one or not.
|
|
+**
|
|
+** Arguments:
|
|
+** adev ptr to acx_device structure
|
|
+** addr address to read in the EEPROM
|
|
+** charbuf ptr to a char. This is where the read octet
|
|
+** will be stored
|
|
+*/
|
|
+int
|
|
+acxpci_read_eeprom_byte(acx_device_t *adev, u32 addr, u8 *charbuf)
|
|
+{
|
|
+ int result;
|
|
+ int count;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for EEPROM read\n",
|
|
+ adev->ndev->name);
|
|
+ result = NOT_OK;
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ *charbuf = read_reg8(adev, IO_ACX_EEPROM_DATA);
|
|
+ log(L_DEBUG, "EEPROM at 0x%04X = 0x%02X\n", addr, *charbuf);
|
|
+ result = OK;
|
|
+
|
|
+fail:
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** We don't lock hw accesses here since we never r/w eeprom in IRQ
|
|
+** Note: this function sleeps only because of GFP_KERNEL alloc
|
|
+*/
|
|
+#ifdef UNUSED
|
|
+int
|
|
+acxpci_s_write_eeprom(acx_device_t *adev, u32 addr, u32 len, const u8 *charbuf)
|
|
+{
|
|
+ u8 *data_verify = NULL;
|
|
+ unsigned long flags;
|
|
+ int count, i;
|
|
+ int result = NOT_OK;
|
|
+ u16 gpio_orig;
|
|
+
|
|
+ printk("acx: WARNING! I would write to EEPROM now. "
|
|
+ "Since I really DON'T want to unless you know "
|
|
+ "what you're doing (THIS CODE WILL PROBABLY "
|
|
+ "NOT WORK YET!), I will abort that now. And "
|
|
+ "definitely make sure to make a "
|
|
+ "/proc/driver/acx_wlan0_eeprom backup copy first!!! "
|
|
+ "(the EEPROM content includes the PCI config header!! "
|
|
+ "If you kill important stuff, then you WILL "
|
|
+ "get in trouble and people DID get in trouble already)\n");
|
|
+ return OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ data_verify = kmalloc(len, GFP_KERNEL);
|
|
+ if (!data_verify) {
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* first we need to enable the OE (EEPROM Output Enable) GPIO line
|
|
+ * to be able to write to the EEPROM.
|
|
+ * NOTE: an EEPROM writing success has been reported,
|
|
+ * but you probably have to modify GPIO_OUT, too,
|
|
+ * and you probably need to activate a different GPIO
|
|
+ * line instead! */
|
|
+ gpio_orig = read_reg16(adev, IO_ACX_GPIO_OE);
|
|
+ write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig & ~1);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* ok, now start writing the data out */
|
|
+ for (i = 0; i < len; i++) {
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_DATA, *(charbuf + i));
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 1);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("WARNING, DANGER!!! "
|
|
+ "Timeout waiting for EEPROM write\n");
|
|
+ goto end;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* disable EEPROM writing */
|
|
+ write_reg16(adev, IO_ACX_GPIO_OE, gpio_orig);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* now start a verification run */
|
|
+ for (i = 0; i < len; i++) {
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CFG, 0);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_ADDR, addr + i);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_EEPROM_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg16(adev, IO_ACX_EEPROM_CTL)) {
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("timeout waiting for EEPROM read\n");
|
|
+ goto end;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ data_verify[i] = read_reg16(adev, IO_ACX_EEPROM_DATA);
|
|
+ }
|
|
+
|
|
+ if (0 == memcmp(charbuf, data_verify, len))
|
|
+ result = OK; /* read data matches, success */
|
|
+
|
|
+end:
|
|
+ kfree(data_verify);
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+#endif /* UNUSED */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_read_phy_reg
|
|
+**
|
|
+** Messing with rx/tx disabling and enabling here
|
|
+** (write_reg32(adev, IO_ACX_ENABLE, 0b000000xx)) kills traffic
|
|
+*/
|
|
+int
|
|
+acxpci_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+ int count;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_PHY_ADDR, reg);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_PHY_CTL, 2);
|
|
+
|
|
+ count = 0xffff;
|
|
+ while (read_reg32(adev, IO_ACX_PHY_CTL)) {
|
|
+ /* scheduling away instead of CPU burning loop
|
|
+ * doesn't seem to work here at all:
|
|
+ * awful delay, sometimes also failure.
|
|
+ * Doesn't matter anyway (only small delay). */
|
|
+ if (unlikely(!--count)) {
|
|
+ printk("%s: timeout waiting for phy read\n",
|
|
+ adev->ndev->name);
|
|
+ *charbuf = 0;
|
|
+ goto fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "count was %u\n", count);
|
|
+ *charbuf = read_reg8(adev, IO_ACX_PHY_DATA);
|
|
+
|
|
+ log(L_DEBUG, "radio PHY at 0x%04X = 0x%02X\n", *charbuf, reg);
|
|
+ result = OK;
|
|
+ goto fail; /* silence compiler warning */
|
|
+fail:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxpci_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* mprusko said that 32bit accesses result in distorted sensitivity
|
|
+ * on his card. Unconfirmed, looks like it's not true (most likely since we
|
|
+ * now properly flush writes). */
|
|
+ write_reg32(adev, IO_ACX_PHY_DATA, value);
|
|
+ write_reg32(adev, IO_ACX_PHY_ADDR, reg);
|
|
+ write_flush(adev);
|
|
+ write_reg32(adev, IO_ACX_PHY_CTL, 1);
|
|
+ write_flush(adev);
|
|
+ log(L_DEBUG, "radio PHY write 0x%02X at 0x%04X\n", value, reg);
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+#define NO_AUTO_INCREMENT 1
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_write_fw
|
|
+**
|
|
+** Write the firmware image into the card.
|
|
+**
|
|
+** Arguments:
|
|
+** adev wlan device structure
|
|
+** fw_image firmware image.
|
|
+**
|
|
+** Returns:
|
|
+** 1 firmware image corrupted
|
|
+** 0 success
|
|
+*/
|
|
+static int
|
|
+acxpci_s_write_fw(acx_device_t *adev, const firmware_image_t *fw_image, u32 offset)
|
|
+{
|
|
+ int len, size;
|
|
+ u32 sum, v32;
|
|
+ /* we skip the first four bytes which contain the control sum */
|
|
+ const u8 *p = (u8*)fw_image + 4;
|
|
+
|
|
+ /* start the image checksum by adding the image size value */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_SLV_END_CTL, 0);
|
|
+
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */
|
|
+#else
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+
|
|
+ len = 0;
|
|
+ size = le32_to_cpu(fw_image->size) & (~3);
|
|
+
|
|
+ while (likely(len < size)) {
|
|
+ v32 = be32_to_cpu(*(u32*)p);
|
|
+ sum += p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+ len += 4;
|
|
+
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4);
|
|
+ write_flush(adev);
|
|
+#endif
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_DATA, v32);
|
|
+ }
|
|
+
|
|
+ log(L_DEBUG, "firmware written, size:%d sum1:%x sum2:%x\n",
|
|
+ size, sum, le32_to_cpu(fw_image->chksum));
|
|
+
|
|
+ /* compare our checksum with the stored image checksum */
|
|
+ return (sum != le32_to_cpu(fw_image->chksum));
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_validate_fw
|
|
+**
|
|
+** Compare the firmware image given with
|
|
+** the firmware image written into the card.
|
|
+**
|
|
+** Arguments:
|
|
+** adev wlan device structure
|
|
+** fw_image firmware image.
|
|
+**
|
|
+** Returns:
|
|
+** NOT_OK firmware image corrupted or not correctly written
|
|
+** OK success
|
|
+*/
|
|
+static int
|
|
+acxpci_s_validate_fw(acx_device_t *adev, const firmware_image_t *fw_image,
|
|
+ u32 offset)
|
|
+{
|
|
+ u32 sum, v32, w32;
|
|
+ int len, size;
|
|
+ int result = OK;
|
|
+ /* we skip the first four bytes which contain the control sum */
|
|
+ const u8 *p = (u8*)fw_image + 4;
|
|
+
|
|
+ /* start the image checksum by adding the image size value */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+ write_reg32(adev, IO_ACX_SLV_END_CTL, 0);
|
|
+
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 0); /* use basic mode */
|
|
+#else
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_CTL, 1); /* use autoincrement mode */
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset); /* configure start address */
|
|
+#endif
|
|
+
|
|
+ len = 0;
|
|
+ size = le32_to_cpu(fw_image->size) & (~3);
|
|
+
|
|
+ while (likely(len < size)) {
|
|
+ v32 = be32_to_cpu(*(u32*)p);
|
|
+ p += 4;
|
|
+ len += 4;
|
|
+
|
|
+#if NO_AUTO_INCREMENT
|
|
+ write_reg32(adev, IO_ACX_SLV_MEM_ADDR, offset + len - 4);
|
|
+#endif
|
|
+ w32 = read_reg32(adev, IO_ACX_SLV_MEM_DATA);
|
|
+
|
|
+ if (unlikely(w32 != v32)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "data parts at offset %d don't match (0x%08X vs. 0x%08X)! "
|
|
+ "I/O timing issues or defective memory, with DWL-xx0+? "
|
|
+ "ACX_IO_WIDTH=16 may help. Please report\n",
|
|
+ len, v32, w32);
|
|
+ result = NOT_OK;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ sum += (u8)w32 + (u8)(w32>>8) + (u8)(w32>>16) + (u8)(w32>>24);
|
|
+ }
|
|
+
|
|
+ /* sum control verification */
|
|
+ if (result != NOT_OK) {
|
|
+ if (sum != le32_to_cpu(fw_image->chksum)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "checksums don't match!\n");
|
|
+ result = NOT_OK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_upload_fw
|
|
+**
|
|
+** Called from acx_reset_dev
|
|
+*/
|
|
+static int
|
|
+acxpci_s_upload_fw(acx_device_t *adev)
|
|
+{
|
|
+ firmware_image_t *fw_image = NULL;
|
|
+ int res = NOT_OK;
|
|
+ int try;
|
|
+ u32 file_size;
|
|
+ char filename[sizeof("tiacx1NNcNN")];
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* print exact chipset and radio ID to make sure people really get a clue on which files exactly they are supposed to provide,
|
|
+ * since firmware loading is the biggest enduser PITA with these chipsets.
|
|
+ * Not printing radio ID in 0xHEX in order to not confuse them into wrong file naming */
|
|
+ printk( "acx: need to load firmware for acx1%02d chipset with radio ID %02x, please provide via firmware hotplug:\n"
|
|
+ "acx: either one file only (<c>ombined firmware image file, radio-specific) or two files (radio-less base image file *plus* separate <r>adio-specific extension file)\n",
|
|
+ IS_ACX111(adev)*11, adev->radio_type);
|
|
+
|
|
+ /* Try combined, then main image */
|
|
+ adev->need_radio_fw = 0;
|
|
+ snprintf(filename, sizeof(filename), "tiacx1%02dc%02X",
|
|
+ IS_ACX111(adev)*11, adev->radio_type);
|
|
+
|
|
+ fw_image = acx_s_read_fw(&adev->pdev->dev, filename, &file_size);
|
|
+ if (!fw_image) {
|
|
+ adev->need_radio_fw = 1;
|
|
+ filename[sizeof("tiacx1NN")-1] = '\0';
|
|
+ fw_image = acx_s_read_fw(&adev->pdev->dev, filename, &file_size);
|
|
+ if (!fw_image) {
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (try = 1; try <= 5; try++) {
|
|
+ res = acxpci_s_write_fw(adev, fw_image, 0);
|
|
+ log(L_DEBUG|L_INIT, "acx_write_fw (main/combined): %d\n", res);
|
|
+ if (OK == res) {
|
|
+ res = acxpci_s_validate_fw(adev, fw_image, 0);
|
|
+ log(L_DEBUG|L_INIT, "acx_validate_fw "
|
|
+ "(main/combined): %d\n", res);
|
|
+ }
|
|
+
|
|
+ if (OK == res) {
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_FW_LOADED);
|
|
+ break;
|
|
+ }
|
|
+ printk("acx: firmware upload attempt #%d FAILED, "
|
|
+ "retrying...\n", try);
|
|
+ acx_s_msleep(1000); /* better wait for a while... */
|
|
+ }
|
|
+
|
|
+ vfree(fw_image);
|
|
+
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_upload_radio
|
|
+**
|
|
+** Uploads the appropriate radio module firmware into the card.
|
|
+*/
|
|
+int
|
|
+acxpci_s_upload_radio(acx_device_t *adev)
|
|
+{
|
|
+ acx_ie_memmap_t mm;
|
|
+ firmware_image_t *radio_image;
|
|
+ acx_cmd_radioinit_t radioinit;
|
|
+ int res = NOT_OK;
|
|
+ int try;
|
|
+ u32 offset;
|
|
+ u32 size;
|
|
+ char filename[sizeof("tiacx1NNrNN")];
|
|
+
|
|
+ if (!adev->need_radio_fw) return OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP);
|
|
+ offset = le32_to_cpu(mm.CodeEnd);
|
|
+
|
|
+ snprintf(filename, sizeof(filename), "tiacx1%02dr%02X",
|
|
+ IS_ACX111(adev)*11,
|
|
+ adev->radio_type);
|
|
+ radio_image = acx_s_read_fw(&adev->pdev->dev, filename, &size);
|
|
+ if (!radio_image) {
|
|
+ printk("acx: can't load radio module '%s'\n", filename);
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_SLEEP, NULL, 0);
|
|
+
|
|
+ for (try = 1; try <= 5; try++) {
|
|
+ res = acxpci_s_write_fw(adev, radio_image, offset);
|
|
+ log(L_DEBUG|L_INIT, "acx_write_fw (radio): %d\n", res);
|
|
+ if (OK == res) {
|
|
+ res = acxpci_s_validate_fw(adev, radio_image, offset);
|
|
+ log(L_DEBUG|L_INIT, "acx_validate_fw (radio): %d\n", res);
|
|
+ }
|
|
+
|
|
+ if (OK == res)
|
|
+ break;
|
|
+ printk("acx: radio firmware upload attempt #%d FAILED, "
|
|
+ "retrying...\n", try);
|
|
+ acx_s_msleep(1000); /* better wait for a while... */
|
|
+ }
|
|
+
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0);
|
|
+ radioinit.offset = cpu_to_le32(offset);
|
|
+ /* no endian conversion needed, remains in card CPU area: */
|
|
+ radioinit.len = radio_image->size;
|
|
+
|
|
+ vfree(radio_image);
|
|
+
|
|
+ if (OK != res)
|
|
+ goto fail;
|
|
+
|
|
+ /* will take a moment so let's have a big timeout */
|
|
+ acx_s_issue_cmd_timeo(adev, ACX1xx_CMD_RADIOINIT,
|
|
+ &radioinit, sizeof(radioinit), CMD_TIMEOUT_MS(1000));
|
|
+
|
|
+ res = acx_s_interrogate(adev, &mm, ACX1xx_IE_MEMORY_MAP);
|
|
+fail:
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_l_reset_mac
|
|
+**
|
|
+** MAC will be reset
|
|
+** Call context: reset_dev
|
|
+*/
|
|
+static void
|
|
+acxpci_l_reset_mac(acx_device_t *adev)
|
|
+{
|
|
+ u16 temp;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* halt eCPU */
|
|
+ temp = read_reg16(adev, IO_ACX_ECPU_CTRL) | 0x1;
|
|
+ write_reg16(adev, IO_ACX_ECPU_CTRL, temp);
|
|
+
|
|
+ /* now do soft reset of eCPU, set bit */
|
|
+ temp = read_reg16(adev, IO_ACX_SOFT_RESET) | 0x1;
|
|
+ log(L_DEBUG, "%s: enable soft reset...\n", __func__);
|
|
+ write_reg16(adev, IO_ACX_SOFT_RESET, temp);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* now clear bit again: deassert eCPU reset */
|
|
+ log(L_DEBUG, "%s: disable soft reset and go to init mode...\n", __func__);
|
|
+ write_reg16(adev, IO_ACX_SOFT_RESET, temp & ~0x1);
|
|
+
|
|
+ /* now start a burst read from initial EEPROM */
|
|
+ temp = read_reg16(adev, IO_ACX_EE_START) | 0x1;
|
|
+ write_reg16(adev, IO_ACX_EE_START, temp);
|
|
+ write_flush(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_verify_init
|
|
+*/
|
|
+static int
|
|
+acxpci_s_verify_init(acx_device_t *adev)
|
|
+{
|
|
+ int result = NOT_OK;
|
|
+ unsigned long timeout;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ timeout = jiffies + 2*HZ;
|
|
+ for (;;) {
|
|
+ u16 irqstat = read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES);
|
|
+ if (irqstat & HOST_INT_FCS_THRESHOLD) {
|
|
+ result = OK;
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK, HOST_INT_FCS_THRESHOLD);
|
|
+ break;
|
|
+ }
|
|
+ if (time_after(jiffies, timeout))
|
|
+ break;
|
|
+ /* Init may take up to ~0.5 sec total */
|
|
+ acx_s_msleep(50);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** A few low-level helpers
|
|
+**
|
|
+** Note: these functions are not protected by lock
|
|
+** and thus are never allowed to be called from IRQ.
|
|
+** Also they must not race with fw upload which uses same hw regs
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_write_cmd_type_status
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+acxpci_write_cmd_type_status(acx_device_t *adev, u16 type, u16 status)
|
|
+{
|
|
+ writel(type | (status << 16), adev->cmd_area);
|
|
+ write_flush(adev);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_read_cmd_type_status
|
|
+*/
|
|
+static u32
|
|
+acxpci_read_cmd_type_status(acx_device_t *adev)
|
|
+{
|
|
+ u32 cmd_type, cmd_status;
|
|
+
|
|
+ cmd_type = readl(adev->cmd_area);
|
|
+ cmd_status = (cmd_type >> 16);
|
|
+ cmd_type = (u16)cmd_type;
|
|
+
|
|
+ log(L_CTL, "cmd_type:%04X cmd_status:%04X [%s]\n",
|
|
+ cmd_type, cmd_status,
|
|
+ acx_cmd_status_str(cmd_status));
|
|
+
|
|
+ return cmd_status;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_reset_dev
|
|
+**
|
|
+** Arguments:
|
|
+** netdevice that contains the adev variable
|
|
+** Returns:
|
|
+** NOT_OK on fail
|
|
+** OK on success
|
|
+** Side effects:
|
|
+** device is hard reset
|
|
+** Call context:
|
|
+** acxpci_e_probe
|
|
+** Comment:
|
|
+** This resets the device using low level hardware calls
|
|
+** as well as uploads and verifies the firmware to the card
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+init_mboxes(acx_device_t *adev)
|
|
+{
|
|
+ u32 cmd_offs, info_offs;
|
|
+
|
|
+ cmd_offs = read_reg32(adev, IO_ACX_CMD_MAILBOX_OFFS);
|
|
+ info_offs = read_reg32(adev, IO_ACX_INFO_MAILBOX_OFFS);
|
|
+ adev->cmd_area = (u8 *)adev->iobase2 + cmd_offs;
|
|
+ adev->info_area = (u8 *)adev->iobase2 + info_offs;
|
|
+ log(L_DEBUG, "iobase2=%p\n"
|
|
+ "cmd_mbox_offset=%X cmd_area=%p\n"
|
|
+ "info_mbox_offset=%X info_area=%p\n",
|
|
+ adev->iobase2,
|
|
+ cmd_offs, adev->cmd_area,
|
|
+ info_offs, adev->info_area);
|
|
+}
|
|
+
|
|
+
|
|
+static inline void
|
|
+read_eeprom_area(acx_device_t *adev)
|
|
+{
|
|
+#if ACX_DEBUG > 1
|
|
+ int offs;
|
|
+ u8 tmp;
|
|
+
|
|
+ for (offs = 0x8c; offs < 0xb9; offs++)
|
|
+ acxpci_read_eeprom_byte(adev, offs, &tmp);
|
|
+#endif
|
|
+}
|
|
+
|
|
+
|
|
+static int
|
|
+acxpci_s_reset_dev(acx_device_t *adev)
|
|
+{
|
|
+ const char* msg = "";
|
|
+ unsigned long flags;
|
|
+ int result = NOT_OK;
|
|
+ u16 hardware_info;
|
|
+ u16 ecpu_ctrl;
|
|
+ int count;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* reset the device to make sure the eCPU is stopped
|
|
+ * to upload the firmware correctly */
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ acxpci_l_reset_mac(adev);
|
|
+
|
|
+ ecpu_ctrl = read_reg16(adev, IO_ACX_ECPU_CTRL) & 1;
|
|
+ if (!ecpu_ctrl) {
|
|
+ msg = "eCPU is already running. ";
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+#ifdef WE_DONT_NEED_THAT_DO_WE
|
|
+ if (read_reg16(adev, IO_ACX_SOR_CFG) & 2) {
|
|
+ /* eCPU most likely means "embedded CPU" */
|
|
+ msg = "eCPU did not start after boot from flash. ";
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ /* check sense on reset flags */
|
|
+ if (read_reg16(adev, IO_ACX_SOR_CFG) & 0x10) {
|
|
+ printk("%s: eCPU did not start after boot (SOR), "
|
|
+ "is this fatal?\n", adev->ndev->name);
|
|
+ }
|
|
+#endif
|
|
+ /* scan, if any, is stopped now, setting corresponding IRQ bit */
|
|
+ adev->irq_status |= HOST_INT_SCAN_COMPLETE;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* need to know radio type before fw load */
|
|
+ /* Need to wait for arrival of this information in a loop,
|
|
+ * most probably since eCPU runs some init code from EEPROM
|
|
+ * (started burst read in reset_mac()) which also
|
|
+ * sets the radio type ID */
|
|
+
|
|
+ count = 0xffff;
|
|
+ do {
|
|
+ hardware_info = read_reg16(adev, IO_ACX_EEPROM_INFORMATION);
|
|
+ if (!--count) {
|
|
+ msg = "eCPU didn't indicate radio type";
|
|
+ goto end_fail;
|
|
+ }
|
|
+ cpu_relax();
|
|
+ } while (!(hardware_info & 0xff00)); /* radio type still zero? */
|
|
+
|
|
+ /* printk("DEBUG: count %d\n", count); */
|
|
+ adev->form_factor = hardware_info & 0xff;
|
|
+ adev->radio_type = hardware_info >> 8;
|
|
+
|
|
+ /* load the firmware */
|
|
+ if (OK != acxpci_s_upload_fw(adev))
|
|
+ goto end_fail;
|
|
+
|
|
+ /* acx_s_msleep(10); this one really shouldn't be required */
|
|
+
|
|
+ /* now start eCPU by clearing bit */
|
|
+ write_reg16(adev, IO_ACX_ECPU_CTRL, ecpu_ctrl & ~0x1);
|
|
+ log(L_DEBUG, "booted eCPU up and waiting for completion...\n");
|
|
+
|
|
+ /* wait for eCPU bootup */
|
|
+ if (OK != acxpci_s_verify_init(adev)) {
|
|
+ msg = "timeout waiting for eCPU. ";
|
|
+ goto end_fail;
|
|
+ }
|
|
+ log(L_DEBUG, "eCPU has woken up, card is ready to be configured\n");
|
|
+
|
|
+ init_mboxes(adev);
|
|
+ acxpci_write_cmd_type_status(adev, 0, 0);
|
|
+
|
|
+ /* test that EEPROM is readable */
|
|
+ read_eeprom_area(adev);
|
|
+
|
|
+ result = OK;
|
|
+ goto end;
|
|
+
|
|
+/* Finish error message. Indicate which function failed */
|
|
+end_unlock:
|
|
+ acx_unlock(adev, flags);
|
|
+end_fail:
|
|
+ printk("acx: %sreset_dev() FAILED\n", msg);
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_issue_cmd_timeo
|
|
+**
|
|
+** Sends command to fw, extract result
|
|
+**
|
|
+** NB: we do _not_ take lock inside, so be sure to not touch anything
|
|
+** which may interfere with IRQ handler operation
|
|
+**
|
|
+** TODO: busy wait is a bit silly, so:
|
|
+** 1) stop doing many iters - go to sleep after first
|
|
+** 2) go to waitqueue based approach: wait, not poll!
|
|
+*/
|
|
+#undef FUNC
|
|
+#define FUNC "issue_cmd"
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+int
|
|
+acxpci_s_issue_cmd_timeo(
|
|
+ acx_device_t *adev,
|
|
+ unsigned int cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned cmd_timeout)
|
|
+{
|
|
+#else
|
|
+int
|
|
+acxpci_s_issue_cmd_timeo_debug(
|
|
+ acx_device_t *adev,
|
|
+ unsigned cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned cmd_timeout,
|
|
+ const char* cmdstr)
|
|
+{
|
|
+ unsigned long start = jiffies;
|
|
+#endif
|
|
+ const char *devname;
|
|
+ unsigned counter;
|
|
+ u16 irqtype;
|
|
+ u16 cmd_status;
|
|
+ unsigned long timeout;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ devname = adev->ndev->name;
|
|
+ if (!devname || !devname[0] || devname[4]=='%')
|
|
+ devname = "acx";
|
|
+
|
|
+ log(L_CTL, FUNC"(cmd:%s,buflen:%u,timeout:%ums,type:0x%04X)\n",
|
|
+ cmdstr, buflen, cmd_timeout,
|
|
+ buffer ? le16_to_cpu(((acx_ie_generic_t *)buffer)->type) : -1);
|
|
+
|
|
+ if (!(adev->dev_state_mask & ACX_STATE_FW_LOADED)) {
|
|
+ printk("%s: "FUNC"(): firmware is not loaded yet, "
|
|
+ "cannot execute commands!\n", devname);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ if ((acx_debug & L_DEBUG) && (cmd != ACX1xx_CMD_INTERROGATE)) {
|
|
+ printk("input buffer (len=%u):\n", buflen);
|
|
+ acx_dump_bytes(buffer, buflen);
|
|
+ }
|
|
+
|
|
+ /* wait for firmware to become idle for our command submission */
|
|
+ timeout = HZ/5;
|
|
+ counter = (timeout * 1000 / HZ) - 1; /* in ms */
|
|
+ timeout += jiffies;
|
|
+ do {
|
|
+ cmd_status = acxpci_read_cmd_type_status(adev);
|
|
+ /* Test for IDLE state */
|
|
+ if (!cmd_status)
|
|
+ break;
|
|
+ if (counter % 8 == 0) {
|
|
+ if (time_after(jiffies, timeout)) {
|
|
+ counter = 0;
|
|
+ break;
|
|
+ }
|
|
+ /* we waited 8 iterations, no luck. Sleep 8 ms */
|
|
+ acx_s_msleep(8);
|
|
+ }
|
|
+ } while (likely(--counter));
|
|
+
|
|
+ if (!counter) {
|
|
+ /* the card doesn't get idle, we're in trouble */
|
|
+ printk("%s: "FUNC"(): cmd_status is not IDLE: 0x%04X!=0\n",
|
|
+ devname, cmd_status);
|
|
+ goto bad;
|
|
+ } else if (counter < 190) { /* if waited >10ms... */
|
|
+ log(L_CTL|L_DEBUG, FUNC"(): waited for IDLE %dms. "
|
|
+ "Please report\n", 199 - counter);
|
|
+ }
|
|
+
|
|
+ /* now write the parameters of the command if needed */
|
|
+ if (buffer && buflen) {
|
|
+ /* if it's an INTERROGATE command, just pass the length
|
|
+ * of parameters to read, as data */
|
|
+#if CMD_DISCOVERY
|
|
+ if (cmd == ACX1xx_CMD_INTERROGATE)
|
|
+ memset_io(adev->cmd_area + 4, 0xAA, buflen);
|
|
+#endif
|
|
+ /* adev->cmd_area points to PCI device's memory, not to RAM! */
|
|
+ memcpy_toio(adev->cmd_area + 4, buffer,
|
|
+ (cmd == ACX1xx_CMD_INTERROGATE) ? 4 : buflen);
|
|
+ }
|
|
+ /* now write the actual command type */
|
|
+ acxpci_write_cmd_type_status(adev, cmd, 0);
|
|
+ /* execute command */
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_CMD);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* wait for firmware to process command */
|
|
+
|
|
+ /* Ensure nonzero and not too large timeout.
|
|
+ ** Also converts e.g. 100->99, 200->199
|
|
+ ** which is nice but not essential */
|
|
+ cmd_timeout = (cmd_timeout-1) | 1;
|
|
+ if (unlikely(cmd_timeout > 1199))
|
|
+ cmd_timeout = 1199;
|
|
+ /* clear CMD_COMPLETE bit. can be set only by IRQ handler: */
|
|
+ adev->irq_status &= ~HOST_INT_CMD_COMPLETE;
|
|
+
|
|
+ /* we schedule away sometimes (timeout can be large) */
|
|
+ counter = cmd_timeout;
|
|
+ timeout = jiffies + cmd_timeout * HZ / 1000;
|
|
+ do {
|
|
+ if (!adev->irqs_active) { /* IRQ disabled: poll */
|
|
+ irqtype = read_reg16(adev, IO_ACX_IRQ_STATUS_NON_DES);
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE) {
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK,
|
|
+ HOST_INT_CMD_COMPLETE);
|
|
+ break;
|
|
+ }
|
|
+ } else { /* Wait when IRQ will set the bit */
|
|
+ irqtype = adev->irq_status;
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE)
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (counter % 8 == 0) {
|
|
+ if (time_after(jiffies, timeout)) {
|
|
+ counter = 0;
|
|
+ break;
|
|
+ }
|
|
+ /* we waited 8 iterations, no luck. Sleep 8 ms */
|
|
+ acx_s_msleep(8);
|
|
+ }
|
|
+ } while (likely(--counter));
|
|
+
|
|
+ /* save state for debugging */
|
|
+ cmd_status = acxpci_read_cmd_type_status(adev);
|
|
+
|
|
+ /* put the card in IDLE state */
|
|
+ acxpci_write_cmd_type_status(adev, 0, 0);
|
|
+
|
|
+ if (!counter) { /* timed out! */
|
|
+ printk("%s: "FUNC"(): timed out %s for CMD_COMPLETE. "
|
|
+ "irq bits:0x%04X irq_status:0x%04X timeout:%dms "
|
|
+ "cmd_status:%d (%s)\n",
|
|
+ devname, (adev->irqs_active) ? "waiting" : "polling",
|
|
+ irqtype, adev->irq_status, cmd_timeout,
|
|
+ cmd_status, acx_cmd_status_str(cmd_status));
|
|
+ goto bad;
|
|
+ } else if (cmd_timeout - counter > 30) { /* if waited >30ms... */
|
|
+ log(L_CTL|L_DEBUG, FUNC"(): %s for CMD_COMPLETE %dms. "
|
|
+ "count:%d. Please report\n",
|
|
+ (adev->irqs_active) ? "waited" : "polled",
|
|
+ cmd_timeout - counter, counter);
|
|
+ }
|
|
+
|
|
+ if (1 != cmd_status) { /* it is not a 'Success' */
|
|
+ printk("%s: "FUNC"(): cmd_status is not SUCCESS: %d (%s). "
|
|
+ "Took %dms of %d\n",
|
|
+ devname, cmd_status, acx_cmd_status_str(cmd_status),
|
|
+ cmd_timeout - counter, cmd_timeout);
|
|
+ /* zero out result buffer
|
|
+ * WARNING: this will trash stack in case of illegally large input
|
|
+ * length! */
|
|
+ if (buffer && buflen)
|
|
+ memset(buffer, 0, buflen);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* read in result parameters if needed */
|
|
+ if (buffer && buflen && (cmd == ACX1xx_CMD_INTERROGATE)) {
|
|
+ /* adev->cmd_area points to PCI device's memory, not to RAM! */
|
|
+ memcpy_fromio(buffer, adev->cmd_area + 4, buflen);
|
|
+ if (acx_debug & L_DEBUG) {
|
|
+ printk("output buffer (len=%u): ", buflen);
|
|
+ acx_dump_bytes(buffer, buflen);
|
|
+ }
|
|
+ }
|
|
+/* ok: */
|
|
+ log(L_CTL, FUNC"(%s): took %ld jiffies to complete\n",
|
|
+ cmdstr, jiffies - start);
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+
|
|
+bad:
|
|
+ /* Give enough info so that callers can avoid
|
|
+ ** printing their own diagnostic messages */
|
|
+#if ACX_DEBUG
|
|
+ printk("%s: "FUNC"(cmd:%s) FAILED\n", devname, cmdstr);
|
|
+#else
|
|
+ printk("%s: "FUNC"(cmd:0x%04X) FAILED\n", devname, cmd);
|
|
+#endif
|
|
+ dump_stack();
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#ifdef NONESSENTIAL_FEATURES
|
|
+typedef struct device_id {
|
|
+ unsigned char id[6];
|
|
+ char *descr;
|
|
+ char *type;
|
|
+} device_id_t;
|
|
+
|
|
+static const device_id_t
|
|
+device_ids[] =
|
|
+{
|
|
+ {
|
|
+ {'G', 'l', 'o', 'b', 'a', 'l'},
|
|
+ NULL,
|
|
+ NULL,
|
|
+ },
|
|
+ {
|
|
+ {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
|
|
+ "uninitialized",
|
|
+ "SpeedStream SS1021 or Gigafast WF721-AEX"
|
|
+ },
|
|
+ {
|
|
+ {0x80, 0x81, 0x82, 0x83, 0x84, 0x85},
|
|
+ "non-standard",
|
|
+ "DrayTek Vigor 520"
|
|
+ },
|
|
+ {
|
|
+ {'?', '?', '?', '?', '?', '?'},
|
|
+ "non-standard",
|
|
+ "Level One WPC-0200"
|
|
+ },
|
|
+ {
|
|
+ {0x00, 0x00, 0x00, 0x00, 0x00, 0x00},
|
|
+ "empty",
|
|
+ "DWL-650+ variant"
|
|
+ }
|
|
+};
|
|
+
|
|
+static void
|
|
+acx_show_card_eeprom_id(acx_device_t *adev)
|
|
+{
|
|
+ unsigned char buffer[CARD_EEPROM_ID_SIZE];
|
|
+ int i;
|
|
+
|
|
+ memset(&buffer, 0, CARD_EEPROM_ID_SIZE);
|
|
+ /* use direct EEPROM access */
|
|
+ for (i = 0; i < CARD_EEPROM_ID_SIZE; i++) {
|
|
+ if (OK != acxpci_read_eeprom_byte(adev,
|
|
+ ACX100_EEPROM_ID_OFFSET + i,
|
|
+ &buffer[i])) {
|
|
+ printk("acx: reading EEPROM FAILED\n");
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ for (i = 0; i < VEC_SIZE(device_ids); i++) {
|
|
+ if (!memcmp(&buffer, device_ids[i].id, CARD_EEPROM_ID_SIZE)) {
|
|
+ if (device_ids[i].descr) {
|
|
+ printk("acx: EEPROM card ID string check "
|
|
+ "found %s card ID: is this %s?\n",
|
|
+ device_ids[i].descr, device_ids[i].type);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+ }
|
|
+ if (i == VEC_SIZE(device_ids)) {
|
|
+ printk("acx: EEPROM card ID string check found "
|
|
+ "unknown card: expected 'Global', got '%.*s\'. "
|
|
+ "Please report\n", CARD_EEPROM_ID_SIZE, buffer);
|
|
+ }
|
|
+}
|
|
+#endif /* NONESSENTIAL_FEATURES */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_free_desc_queues
|
|
+**
|
|
+** Releases the queues that have been allocated, the
|
|
+** others have been initialised to NULL so this
|
|
+** function can be used if only part of the queues were allocated.
|
|
+*/
|
|
+
|
|
+static inline void
|
|
+free_coherent(struct pci_dev *hwdev, size_t size,
|
|
+ void *vaddr, dma_addr_t dma_handle)
|
|
+{
|
|
+ dma_free_coherent(hwdev == NULL ? NULL : &hwdev->dev,
|
|
+ size, vaddr, dma_handle);
|
|
+}
|
|
+
|
|
+void
|
|
+acxpci_free_desc_queues(acx_device_t *adev)
|
|
+{
|
|
+#define ACX_FREE_QUEUE(size, ptr, phyaddr) \
|
|
+ if (ptr) { \
|
|
+ free_coherent(0, size, ptr, phyaddr); \
|
|
+ ptr = NULL; \
|
|
+ size = 0; \
|
|
+ }
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ACX_FREE_QUEUE(adev->txhostdesc_area_size, adev->txhostdesc_start, adev->txhostdesc_startphy);
|
|
+ ACX_FREE_QUEUE(adev->txbuf_area_size, adev->txbuf_start, adev->txbuf_startphy);
|
|
+
|
|
+ adev->txdesc_start = NULL;
|
|
+
|
|
+ ACX_FREE_QUEUE(adev->rxhostdesc_area_size, adev->rxhostdesc_start, adev->rxhostdesc_startphy);
|
|
+ ACX_FREE_QUEUE(adev->rxbuf_area_size, adev->rxbuf_start, adev->rxbuf_startphy);
|
|
+
|
|
+ adev->rxdesc_start = NULL;
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_delete_dma_regions
|
|
+*/
|
|
+static void
|
|
+acxpci_s_delete_dma_regions(acx_device_t *adev)
|
|
+{
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+ /* disable radio Tx/Rx. Shouldn't we use the firmware commands
|
|
+ * here instead? Or are we that much down the road that it's no
|
|
+ * longer possible here? */
|
|
+ write_reg16(adev, IO_ACX_ENABLE, 0);
|
|
+
|
|
+ acx_s_msleep(100);
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ acxpci_free_desc_queues(adev);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_e_probe
|
|
+**
|
|
+** Probe routine called when a PCI device w/ matching ID is found.
|
|
+** Here's the sequence:
|
|
+** - Allocate the PCI resources.
|
|
+** - Read the PCMCIA attribute memory to make sure we have a WLAN card
|
|
+** - Reset the MAC
|
|
+** - Initialize the dev and wlan data
|
|
+** - Initialize the MAC
|
|
+**
|
|
+** pdev - ptr to pci device structure containing info about pci configuration
|
|
+** id - ptr to the device id entry that matched this device
|
|
+*/
|
|
+static const u16
|
|
+IO_ACX100[] =
|
|
+{
|
|
+ 0x0000, /* IO_ACX_SOFT_RESET */
|
|
+
|
|
+ 0x0014, /* IO_ACX_SLV_MEM_ADDR */
|
|
+ 0x0018, /* IO_ACX_SLV_MEM_DATA */
|
|
+ 0x001c, /* IO_ACX_SLV_MEM_CTL */
|
|
+ 0x0020, /* IO_ACX_SLV_END_CTL */
|
|
+
|
|
+ 0x0034, /* IO_ACX_FEMR */
|
|
+
|
|
+ 0x007c, /* IO_ACX_INT_TRIG */
|
|
+ 0x0098, /* IO_ACX_IRQ_MASK */
|
|
+ 0x00a4, /* IO_ACX_IRQ_STATUS_NON_DES */
|
|
+ 0x00a8, /* IO_ACX_IRQ_STATUS_CLEAR */
|
|
+ 0x00ac, /* IO_ACX_IRQ_ACK */
|
|
+ 0x00b0, /* IO_ACX_HINT_TRIG */
|
|
+
|
|
+ 0x0104, /* IO_ACX_ENABLE */
|
|
+
|
|
+ 0x0250, /* IO_ACX_EEPROM_CTL */
|
|
+ 0x0254, /* IO_ACX_EEPROM_ADDR */
|
|
+ 0x0258, /* IO_ACX_EEPROM_DATA */
|
|
+ 0x025c, /* IO_ACX_EEPROM_CFG */
|
|
+
|
|
+ 0x0268, /* IO_ACX_PHY_ADDR */
|
|
+ 0x026c, /* IO_ACX_PHY_DATA */
|
|
+ 0x0270, /* IO_ACX_PHY_CTL */
|
|
+
|
|
+ 0x0290, /* IO_ACX_GPIO_OE */
|
|
+
|
|
+ 0x0298, /* IO_ACX_GPIO_OUT */
|
|
+
|
|
+ 0x02a4, /* IO_ACX_CMD_MAILBOX_OFFS */
|
|
+ 0x02a8, /* IO_ACX_INFO_MAILBOX_OFFS */
|
|
+ 0x02ac, /* IO_ACX_EEPROM_INFORMATION */
|
|
+
|
|
+ 0x02d0, /* IO_ACX_EE_START */
|
|
+ 0x02d4, /* IO_ACX_SOR_CFG */
|
|
+ 0x02d8 /* IO_ACX_ECPU_CTRL */
|
|
+};
|
|
+
|
|
+static const u16
|
|
+IO_ACX111[] =
|
|
+{
|
|
+ 0x0000, /* IO_ACX_SOFT_RESET */
|
|
+
|
|
+ 0x0014, /* IO_ACX_SLV_MEM_ADDR */
|
|
+ 0x0018, /* IO_ACX_SLV_MEM_DATA */
|
|
+ 0x001c, /* IO_ACX_SLV_MEM_CTL */
|
|
+ 0x0020, /* IO_ACX_SLV_END_CTL */
|
|
+
|
|
+ 0x0034, /* IO_ACX_FEMR */
|
|
+
|
|
+ 0x00b4, /* IO_ACX_INT_TRIG */
|
|
+ 0x00d4, /* IO_ACX_IRQ_MASK */
|
|
+ /* we do mean NON_DES (0xf0), not NON_DES_MASK which is at 0xe0: */
|
|
+ 0x00f0, /* IO_ACX_IRQ_STATUS_NON_DES */
|
|
+ 0x00e4, /* IO_ACX_IRQ_STATUS_CLEAR */
|
|
+ 0x00e8, /* IO_ACX_IRQ_ACK */
|
|
+ 0x00ec, /* IO_ACX_HINT_TRIG */
|
|
+
|
|
+ 0x01d0, /* IO_ACX_ENABLE */
|
|
+
|
|
+ 0x0338, /* IO_ACX_EEPROM_CTL */
|
|
+ 0x033c, /* IO_ACX_EEPROM_ADDR */
|
|
+ 0x0340, /* IO_ACX_EEPROM_DATA */
|
|
+ 0x0344, /* IO_ACX_EEPROM_CFG */
|
|
+
|
|
+ 0x0350, /* IO_ACX_PHY_ADDR */
|
|
+ 0x0354, /* IO_ACX_PHY_DATA */
|
|
+ 0x0358, /* IO_ACX_PHY_CTL */
|
|
+
|
|
+ 0x0374, /* IO_ACX_GPIO_OE */
|
|
+
|
|
+ 0x037c, /* IO_ACX_GPIO_OUT */
|
|
+
|
|
+ 0x0388, /* IO_ACX_CMD_MAILBOX_OFFS */
|
|
+ 0x038c, /* IO_ACX_INFO_MAILBOX_OFFS */
|
|
+ 0x0390, /* IO_ACX_EEPROM_INFORMATION */
|
|
+
|
|
+ 0x0100, /* IO_ACX_EE_START */
|
|
+ 0x0104, /* IO_ACX_SOR_CFG */
|
|
+ 0x0108, /* IO_ACX_ECPU_CTRL */
|
|
+};
|
|
+
|
|
+static void
|
|
+dummy_netdev_init(struct net_device *ndev) {}
|
|
+
|
|
+static int __devinit
|
|
+acxpci_e_probe(struct pci_dev *pdev, const struct pci_device_id *id)
|
|
+{
|
|
+ acx111_ie_configoption_t co;
|
|
+ unsigned long mem_region1 = 0;
|
|
+ unsigned long mem_region2 = 0;
|
|
+ unsigned long mem_region1_size;
|
|
+ unsigned long mem_region2_size;
|
|
+ unsigned long phymem1;
|
|
+ unsigned long phymem2;
|
|
+ void *mem1 = NULL;
|
|
+ void *mem2 = NULL;
|
|
+ acx_device_t *adev = NULL;
|
|
+ struct net_device *ndev = NULL;
|
|
+ const char *chip_name;
|
|
+ int result = -EIO;
|
|
+ int err;
|
|
+ u8 chip_type;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* Enable the PCI device */
|
|
+ if (pci_enable_device(pdev)) {
|
|
+ printk("acx: pci_enable_device() FAILED\n");
|
|
+ result = -ENODEV;
|
|
+ goto fail_pci_enable_device;
|
|
+ }
|
|
+
|
|
+ /* enable busmastering (required for CardBus) */
|
|
+ pci_set_master(pdev);
|
|
+
|
|
+ /* FIXME: prism54 calls pci_set_mwi() here,
|
|
+ * should we do/support the same? */
|
|
+
|
|
+ /* chiptype is u8 but id->driver_data is ulong
|
|
+ ** Works for now (possible values are 1 and 2) */
|
|
+ chip_type = (u8)id->driver_data;
|
|
+ /* acx100 and acx111 have different PCI memory regions */
|
|
+ if (chip_type == CHIPTYPE_ACX100) {
|
|
+ chip_name = "ACX100";
|
|
+ mem_region1 = PCI_ACX100_REGION1;
|
|
+ mem_region1_size = PCI_ACX100_REGION1_SIZE;
|
|
+
|
|
+ mem_region2 = PCI_ACX100_REGION2;
|
|
+ mem_region2_size = PCI_ACX100_REGION2_SIZE;
|
|
+ } else if (chip_type == CHIPTYPE_ACX111) {
|
|
+ chip_name = "ACX111";
|
|
+ mem_region1 = PCI_ACX111_REGION1;
|
|
+ mem_region1_size = PCI_ACX111_REGION1_SIZE;
|
|
+
|
|
+ mem_region2 = PCI_ACX111_REGION2;
|
|
+ mem_region2_size = PCI_ACX111_REGION2_SIZE;
|
|
+ } else {
|
|
+ printk("acx: unknown chip type 0x%04X\n", chip_type);
|
|
+ goto fail_unknown_chiptype;
|
|
+ }
|
|
+
|
|
+ /* Figure out our resources */
|
|
+ phymem1 = pci_resource_start(pdev, mem_region1);
|
|
+ phymem2 = pci_resource_start(pdev, mem_region2);
|
|
+ if (!request_mem_region(phymem1, pci_resource_len(pdev, mem_region1), "acx_1")) {
|
|
+ printk("acx: cannot reserve PCI memory region 1 (are you sure "
|
|
+ "you have CardBus support in kernel?)\n");
|
|
+ goto fail_request_mem_region1;
|
|
+ }
|
|
+ if (!request_mem_region(phymem2, pci_resource_len(pdev, mem_region2), "acx_2")) {
|
|
+ printk("acx: cannot reserve PCI memory region 2\n");
|
|
+ goto fail_request_mem_region2;
|
|
+ }
|
|
+
|
|
+ /* this used to be ioremap(), but ioremap_nocache()
|
|
+ * is much less risky, right? (and slower?)
|
|
+ * FIXME: we may want to go back to cached variant if it's
|
|
+ * certain that our code really properly handles
|
|
+ * cached operation (memory barriers, volatile?, ...)
|
|
+ * (but always keep this comment here regardless!)
|
|
+ * Possibly make this a driver config setting? */
|
|
+
|
|
+ mem1 = ioremap_nocache(phymem1, mem_region1_size);
|
|
+ if (!mem1) {
|
|
+ printk("acx: ioremap() FAILED\n");
|
|
+ goto fail_ioremap1;
|
|
+ }
|
|
+ mem2 = ioremap_nocache(phymem2, mem_region2_size);
|
|
+ if (!mem2) {
|
|
+ printk("acx: ioremap() #2 FAILED\n");
|
|
+ goto fail_ioremap2;
|
|
+ }
|
|
+
|
|
+ printk("acx: found %s-based wireless network card at %s, irq:%d, "
|
|
+ "phymem1:0x%lX, phymem2:0x%lX, mem1:0x%p, mem1_size:%ld, "
|
|
+ "mem2:0x%p, mem2_size:%ld\n",
|
|
+ chip_name, pci_name(pdev), pdev->irq, phymem1, phymem2,
|
|
+ mem1, mem_region1_size,
|
|
+ mem2, mem_region2_size);
|
|
+ log(L_ANY, "initial debug setting is 0x%04X\n", acx_debug);
|
|
+
|
|
+ if (0 == pdev->irq) {
|
|
+ printk("acx: can't use IRQ 0\n");
|
|
+ goto fail_irq;
|
|
+ }
|
|
+
|
|
+ ndev = alloc_netdev(sizeof(*adev), "wlan%d", dummy_netdev_init);
|
|
+ /* (NB: memsets to 0 entire area) */
|
|
+ if (!ndev) {
|
|
+ printk("acx: no memory for netdevice struct\n");
|
|
+ goto fail_alloc_netdev;
|
|
+ }
|
|
+
|
|
+ ether_setup(ndev);
|
|
+ ndev->open = &acxpci_e_open;
|
|
+ ndev->stop = &acxpci_e_close;
|
|
+ ndev->hard_start_xmit = &acx_i_start_xmit;
|
|
+ ndev->get_stats = &acx_e_get_stats;
|
|
+#if IW_HANDLER_VERSION <= 5
|
|
+ ndev->get_wireless_stats = &acx_e_get_wireless_stats;
|
|
+#endif
|
|
+ ndev->wireless_handlers = (struct iw_handler_def *)&acx_ioctl_handler_def;
|
|
+ ndev->set_multicast_list = &acxpci_i_set_multicast_list;
|
|
+ ndev->tx_timeout = &acxpci_i_tx_timeout;
|
|
+ ndev->change_mtu = &acx_e_change_mtu;
|
|
+ ndev->watchdog_timeo = 4 * HZ;
|
|
+ ndev->irq = pdev->irq;
|
|
+ ndev->base_addr = pci_resource_start(pdev, 0);
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ spin_lock_init(&adev->lock); /* initial state: unlocked */
|
|
+ /* We do not start with downed sem: we want PARANOID_LOCKING to work */
|
|
+ sema_init(&adev->sem, 1); /* initial state: 1 (upped) */
|
|
+ /* since nobody can see new netdev yet, we can as well
|
|
+ ** just _presume_ that we're under sem (instead of actually taking it): */
|
|
+ /* acx_sem_lock(adev); */
|
|
+ adev->pdev = pdev;
|
|
+ adev->ndev = ndev;
|
|
+ adev->dev_type = DEVTYPE_PCI;
|
|
+ adev->chip_type = chip_type;
|
|
+ adev->chip_name = chip_name;
|
|
+ adev->io = (CHIPTYPE_ACX100 == chip_type) ? IO_ACX100 : IO_ACX111;
|
|
+ adev->membase = phymem1;
|
|
+ adev->iobase = mem1;
|
|
+ adev->membase2 = phymem2;
|
|
+ adev->iobase2 = mem2;
|
|
+ /* to find crashes due to weird driver access
|
|
+ * to unconfigured interface (ifup) */
|
|
+ adev->mgmt_timer.function = (void (*)(unsigned long))0x0000dead;
|
|
+
|
|
+#ifdef NONESSENTIAL_FEATURES
|
|
+ acx_show_card_eeprom_id(adev);
|
|
+#endif /* NONESSENTIAL_FEATURES */
|
|
+
|
|
+#ifdef SET_MODULE_OWNER
|
|
+ SET_MODULE_OWNER(ndev);
|
|
+#endif
|
|
+ SET_NETDEV_DEV(ndev, &pdev->dev);
|
|
+
|
|
+ log(L_IRQ|L_INIT, "using IRQ %d\n", pdev->irq);
|
|
+
|
|
+ /* need to be able to restore PCI state after a suspend */
|
|
+ pci_save_state(pdev);
|
|
+ pci_set_drvdata(pdev, ndev);
|
|
+
|
|
+ /* ok, pci setup is finished, now start initializing the card */
|
|
+
|
|
+ /* NB: read_reg() reads may return bogus data before reset_dev(),
|
|
+ * since the firmware which directly controls large parts of the I/O
|
|
+ * registers isn't initialized yet.
|
|
+ * acx100 seems to be more affected than acx111 */
|
|
+ if (OK != acxpci_s_reset_dev(adev))
|
|
+ goto fail_reset;
|
|
+
|
|
+ if (IS_ACX100(adev)) {
|
|
+ /* ACX100: configopt struct in cmd mailbox - directly after reset */
|
|
+ memcpy_fromio(&co, adev->cmd_area, sizeof(co));
|
|
+ }
|
|
+
|
|
+ if (OK != acx_s_init_mac(adev))
|
|
+ goto fail_init_mac;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111: configopt struct needs to be queried after full init */
|
|
+ acx_s_interrogate(adev, &co, ACX111_IE_CONFIG_OPTIONS);
|
|
+ }
|
|
+
|
|
+/* TODO: merge them into one function, they are called just once and are the same for pci & usb */
|
|
+ if (OK != acxpci_read_eeprom_byte(adev, 0x05, &adev->eeprom_version))
|
|
+ goto fail_read_eeprom_version;
|
|
+
|
|
+ acx_s_parse_configoption(adev, &co);
|
|
+ acx_s_set_defaults(adev);
|
|
+ acx_s_get_firmware_version(adev); /* needs to be after acx_s_init_mac() */
|
|
+ acx_display_hardware_details(adev);
|
|
+
|
|
+ /* Register the card, AFTER everything else has been set up,
|
|
+ * since otherwise an ioctl could step on our feet due to
|
|
+ * firmware operations happening in parallel or uninitialized data */
|
|
+ err = register_netdev(ndev);
|
|
+ if (OK != err) {
|
|
+ printk("acx: register_netdev() FAILED: %d\n", err);
|
|
+ goto fail_register_netdev;
|
|
+ }
|
|
+
|
|
+ acx_proc_register_entries(ndev);
|
|
+
|
|
+ /* Now we have our device, so make sure the kernel doesn't try
|
|
+ * to send packets even though we're not associated to a network yet */
|
|
+ acx_stop_queue(ndev, "on probe");
|
|
+ acx_carrier_off(ndev, "on probe");
|
|
+
|
|
+ /* after register_netdev() userspace may start working with dev
|
|
+ * (in particular, on other CPUs), we only need to up the sem */
|
|
+ /* acx_sem_unlock(adev); */
|
|
+
|
|
+ printk("acx "ACX_RELEASE": net device %s, driver compiled "
|
|
+ "against wireless extensions %d and Linux %s\n",
|
|
+ ndev->name, WIRELESS_EXT, UTS_RELEASE);
|
|
+
|
|
+#if CMD_DISCOVERY
|
|
+ great_inquisitor(adev);
|
|
+#endif
|
|
+
|
|
+ result = OK;
|
|
+ goto done;
|
|
+
|
|
+ /* error paths: undo everything in reverse order... */
|
|
+
|
|
+fail_register_netdev:
|
|
+
|
|
+ acxpci_s_delete_dma_regions(adev);
|
|
+ pci_set_drvdata(pdev, NULL);
|
|
+
|
|
+fail_init_mac:
|
|
+fail_read_eeprom_version:
|
|
+fail_reset:
|
|
+
|
|
+ free_netdev(ndev);
|
|
+fail_alloc_netdev:
|
|
+fail_irq:
|
|
+
|
|
+ iounmap(mem2);
|
|
+fail_ioremap2:
|
|
+
|
|
+ iounmap(mem1);
|
|
+fail_ioremap1:
|
|
+
|
|
+ release_mem_region(pci_resource_start(pdev, mem_region2),
|
|
+ pci_resource_len(pdev, mem_region2));
|
|
+fail_request_mem_region2:
|
|
+
|
|
+ release_mem_region(pci_resource_start(pdev, mem_region1),
|
|
+ pci_resource_len(pdev, mem_region1));
|
|
+fail_request_mem_region1:
|
|
+fail_unknown_chiptype:
|
|
+
|
|
+ pci_disable_device(pdev);
|
|
+fail_pci_enable_device:
|
|
+
|
|
+ pci_set_power_state(pdev, PCI_D3hot);
|
|
+
|
|
+done:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_e_remove
|
|
+**
|
|
+** Shut device down (if not hot unplugged)
|
|
+** and deallocate PCI resources for the acx chip.
|
|
+**
|
|
+** pdev - ptr to PCI device structure containing info about pci configuration
|
|
+*/
|
|
+static void __devexit
|
|
+acxpci_e_remove(struct pci_dev *pdev)
|
|
+{
|
|
+ struct net_device *ndev;
|
|
+ acx_device_t *adev;
|
|
+ unsigned long mem_region1, mem_region2;
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ ndev = (struct net_device*) pci_get_drvdata(pdev);
|
|
+ if (!ndev) {
|
|
+ log(L_DEBUG, "%s: card is unused. Skipping any release code\n",
|
|
+ __func__);
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+
|
|
+ /* If device wasn't hot unplugged... */
|
|
+ if (adev_present(adev)) {
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* disable both Tx and Rx to shut radio down properly */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0);
|
|
+
|
|
+#ifdef REDUNDANT
|
|
+ /* put the eCPU to sleep to save power
|
|
+ * Halting is not possible currently,
|
|
+ * since not supported by all firmware versions */
|
|
+ acx_s_issue_cmd(adev, ACX100_CMD_SLEEP, NULL, 0);
|
|
+#endif
|
|
+ acx_lock(adev, flags);
|
|
+ /* disable power LED to save power :-) */
|
|
+ log(L_INIT, "switching off power LED to save power\n");
|
|
+ acxpci_l_power_led(adev, 0);
|
|
+ /* stop our eCPU */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* FIXME: does this actually keep halting the eCPU?
|
|
+ * I don't think so...
|
|
+ */
|
|
+ acxpci_l_reset_mac(adev);
|
|
+ } else {
|
|
+ u16 temp;
|
|
+ /* halt eCPU */
|
|
+ temp = read_reg16(adev, IO_ACX_ECPU_CTRL) | 0x1;
|
|
+ write_reg16(adev, IO_ACX_ECPU_CTRL, temp);
|
|
+ write_flush(adev);
|
|
+ }
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+ }
|
|
+
|
|
+ /* unregister the device to not let the kernel
|
|
+ * (e.g. ioctls) access a half-deconfigured device
|
|
+ * NB: this will cause acxpci_e_close() to be called,
|
|
+ * thus we shouldn't call it under sem! */
|
|
+ log(L_INIT, "removing device %s\n", ndev->name);
|
|
+ unregister_netdev(ndev);
|
|
+
|
|
+ /* unregister_netdev ensures that no references to us left.
|
|
+ * For paranoid reasons we continue to follow the rules */
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (adev->dev_state_mask & ACX_STATE_IFACE_UP) {
|
|
+ acxpci_s_down(ndev);
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ }
|
|
+
|
|
+ acx_proc_unregister_entries(ndev);
|
|
+
|
|
+ if (IS_ACX100(adev)) {
|
|
+ mem_region1 = PCI_ACX100_REGION1;
|
|
+ mem_region2 = PCI_ACX100_REGION2;
|
|
+ } else {
|
|
+ mem_region1 = PCI_ACX111_REGION1;
|
|
+ mem_region2 = PCI_ACX111_REGION2;
|
|
+ }
|
|
+
|
|
+ /* finally, clean up PCI bus state */
|
|
+ acxpci_s_delete_dma_regions(adev);
|
|
+ if (adev->iobase) iounmap(adev->iobase);
|
|
+ if (adev->iobase2) iounmap(adev->iobase2);
|
|
+ release_mem_region(pci_resource_start(pdev, mem_region1),
|
|
+ pci_resource_len(pdev, mem_region1));
|
|
+ release_mem_region(pci_resource_start(pdev, mem_region2),
|
|
+ pci_resource_len(pdev, mem_region2));
|
|
+ pci_disable_device(pdev);
|
|
+
|
|
+ /* remove dev registration */
|
|
+ pci_set_drvdata(pdev, NULL);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ /* Free netdev (quite late,
|
|
+ * since otherwise we might get caught off-guard
|
|
+ * by a netdev timeout handler execution
|
|
+ * expecting to see a working dev...) */
|
|
+ free_netdev(ndev);
|
|
+
|
|
+ /* put device into ACPI D3 mode (shutdown) */
|
|
+ pci_set_power_state(pdev, PCI_D3hot);
|
|
+
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** TODO: PM code needs to be fixed / debugged / tested.
|
|
+*/
|
|
+#ifdef CONFIG_PM
|
|
+static int
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+acxpci_e_suspend(struct pci_dev *pdev, pm_message_t state)
|
|
+#else
|
|
+acxpci_e_suspend(struct pci_dev *pdev, u32 state)
|
|
+#endif
|
|
+{
|
|
+ struct net_device *ndev = pci_get_drvdata(pdev);
|
|
+ acx_device_t *adev;
|
|
+
|
|
+ FN_ENTER;
|
|
+ printk("acx: suspend handler is experimental!\n");
|
|
+ printk("sus: dev %p\n", ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ goto end;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ printk("sus: adev %p\n", adev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ netif_device_detach(ndev); /* this one cannot sleep */
|
|
+ acxpci_s_down(ndev);
|
|
+ /* down() does not set it to 0xffff, but here we really want that */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ acxpci_s_delete_dma_regions(adev);
|
|
+ pci_save_state(pdev);
|
|
+ pci_set_power_state(pdev, PCI_D3hot);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+static int
|
|
+acxpci_e_resume(struct pci_dev *pdev)
|
|
+{
|
|
+ struct net_device *ndev = pci_get_drvdata(pdev);
|
|
+ acx_device_t *adev;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ printk("acx: resume handler is experimental!\n");
|
|
+ printk("rsm: got dev %p\n", ndev);
|
|
+
|
|
+ if (!netif_running(ndev))
|
|
+ goto end;
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ printk("rsm: got adev %p\n", adev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ pci_set_power_state(pdev, PCI_D0);
|
|
+ printk("rsm: power state PCI_D0 set\n");
|
|
+ pci_restore_state(pdev);
|
|
+ printk("rsm: PCI state restored\n");
|
|
+
|
|
+ if (OK != acxpci_s_reset_dev(adev))
|
|
+ goto end_unlock;
|
|
+ printk("rsm: device reset done\n");
|
|
+ if (OK != acx_s_init_mac(adev))
|
|
+ goto end_unlock;
|
|
+ printk("rsm: init MAC done\n");
|
|
+
|
|
+ acxpci_s_up(ndev);
|
|
+ printk("rsm: acx up done\n");
|
|
+
|
|
+ /* now even reload all card parameters as they were before suspend,
|
|
+ * and possibly be back in the network again already :-) */
|
|
+ if (ACX_STATE_IFACE_UP & adev->dev_state_mask) {
|
|
+ adev->set_mask = GETSET_ALL;
|
|
+ acx_s_update_card_settings(adev);
|
|
+ printk("rsm: settings updated\n");
|
|
+ }
|
|
+ netif_device_attach(ndev);
|
|
+ printk("rsm: device attached\n");
|
|
+
|
|
+end_unlock:
|
|
+ acx_sem_unlock(adev);
|
|
+end:
|
|
+ /* we need to return OK here anyway, right? */
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+#endif /* CONFIG_PM */
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_up
|
|
+**
|
|
+** This function is called by acxpci_e_open (when ifconfig sets the device as up)
|
|
+**
|
|
+** Side effects:
|
|
+** - Enables on-card interrupt requests
|
|
+** - calls acx_s_start
|
|
+*/
|
|
+
|
|
+static void
|
|
+enable_acx_irq(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x8000);
|
|
+ adev->irqs_active = 1;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+static void
|
|
+acxpci_s_up(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ enable_acx_irq(adev);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* acx fw < 1.9.3.e has a hardware timer, and older drivers
|
|
+ ** used to use it. But we don't do that anymore, our OS
|
|
+ ** has reliable software timers */
|
|
+ init_timer(&adev->mgmt_timer);
|
|
+ adev->mgmt_timer.function = acx_i_timer;
|
|
+ adev->mgmt_timer.data = (unsigned long)adev;
|
|
+
|
|
+ /* Need to set ACX_STATE_IFACE_UP first, or else
|
|
+ ** timer won't be started by acx_set_status() */
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_2_STA:
|
|
+ /* actual scan cmd will happen in start() */
|
|
+ acx_set_status(adev, ACX_STATUS_1_SCANNING); break;
|
|
+ case ACX_MODE_3_AP:
|
|
+ case ACX_MODE_MONITOR:
|
|
+ acx_set_status(adev, ACX_STATUS_4_ASSOCIATED); break;
|
|
+ }
|
|
+
|
|
+ acx_s_start(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_down
|
|
+**
|
|
+** NB: device may be already hot unplugged if called from acxpci_e_remove()
|
|
+**
|
|
+** Disables on-card interrupt request, stops softirq and timer, stops queue,
|
|
+** sets status == STOPPED
|
|
+*/
|
|
+
|
|
+static void
|
|
+disable_acx_irq(acx_device_t *adev)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* I guess mask is not 0xffff because acx100 won't signal
|
|
+ ** cmd completion then (needed for ifup).
|
|
+ ** Someone with acx100 please confirm */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, adev->irq_mask_off);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ adev->irqs_active = 0;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+static void
|
|
+acxpci_s_down(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* Disable IRQs first, so that IRQs cannot race with us */
|
|
+ /* then wait until interrupts have finished executing on other CPUs */
|
|
+ acx_lock(adev, flags);
|
|
+ disable_acx_irq(adev);
|
|
+ synchronize_irq(adev->pdev->irq);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* we really don't want to have an asynchronous tasklet disturb us
|
|
+ ** after something vital for its job has been shut down, so
|
|
+ ** end all remaining work now.
|
|
+ **
|
|
+ ** NB: carrier_off (done by set_status below) would lead to
|
|
+ ** not yet fully understood deadlock in FLUSH_SCHEDULED_WORK().
|
|
+ ** That's why we do FLUSH first.
|
|
+ **
|
|
+ ** NB2: we have a bad locking bug here: FLUSH_SCHEDULED_WORK()
|
|
+ ** waits for acx_e_after_interrupt_task to complete if it is running
|
|
+ ** on another CPU, but acx_e_after_interrupt_task
|
|
+ ** will sleep on sem forever, because it is taken by us!
|
|
+ ** Work around that by temporary sem unlock.
|
|
+ ** This will fail miserably if we'll be hit by concurrent
|
|
+ ** iwconfig or something in between. TODO! */
|
|
+ acx_sem_unlock(adev);
|
|
+ FLUSH_SCHEDULED_WORK();
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* This is possible:
|
|
+ ** FLUSH_SCHEDULED_WORK -> acx_e_after_interrupt_task ->
|
|
+ ** -> set_status(ASSOCIATED) -> wake_queue()
|
|
+ ** That's why we stop queue _after_ FLUSH_SCHEDULED_WORK
|
|
+ ** lock/unlock is just paranoia, maybe not needed */
|
|
+ acx_lock(adev, flags);
|
|
+ acx_stop_queue(ndev, "on ifdown");
|
|
+ acx_set_status(adev, ACX_STATUS_0_STOPPED);
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* kernel/timer.c says it's illegal to del_timer_sync()
|
|
+ ** a timer which restarts itself. We guarantee this cannot
|
|
+ ** ever happen because acx_i_timer() never does this if
|
|
+ ** status is ACX_STATUS_0_STOPPED */
|
|
+ del_timer_sync(&adev->mgmt_timer);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_e_open
|
|
+**
|
|
+** Called as a result of SIOCSIFFLAGS ioctl changing the flags bit IFF_UP
|
|
+** from clear to set. In other words: ifconfig up.
|
|
+**
|
|
+** Returns:
|
|
+** 0 success
|
|
+** >0 f/w reported error
|
|
+** <0 driver reported error
|
|
+*/
|
|
+static int
|
|
+acxpci_e_open(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ int result = OK;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ acx_init_task_scheduler(adev);
|
|
+
|
|
+/* TODO: pci_set_power_state(pdev, PCI_D0); ? */
|
|
+
|
|
+ /* request shared IRQ handler */
|
|
+ if (request_irq(ndev->irq, acxpci_i_interrupt, SA_SHIRQ, ndev->name, ndev)) {
|
|
+ printk("%s: request_irq FAILED\n", ndev->name);
|
|
+ result = -EAGAIN;
|
|
+ goto done;
|
|
+ }
|
|
+ log(L_DEBUG|L_IRQ, "request_irq %d successful\n", ndev->irq);
|
|
+
|
|
+ /* ifup device */
|
|
+ acxpci_s_up(ndev);
|
|
+
|
|
+ /* We don't currently have to do anything else.
|
|
+ * The setup of the MAC should be subsequently completed via
|
|
+ * the mlme commands.
|
|
+ * Higher layers know we're ready from dev->start==1 and
|
|
+ * dev->tbusy==0. Our rx path knows to pass up received/
|
|
+ * frames because of dev->flags&IFF_UP is true.
|
|
+ */
|
|
+done:
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_e_close
|
|
+**
|
|
+** Called as a result of SIOCSIIFFLAGS ioctl changing the flags bit IFF_UP
|
|
+** from set to clear. I.e. called by "ifconfig DEV down"
|
|
+**
|
|
+** Returns:
|
|
+** 0 success
|
|
+** >0 f/w reported error
|
|
+** <0 driver reported error
|
|
+*/
|
|
+static int
|
|
+acxpci_e_close(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* ifdown device */
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ if (netif_device_present(ndev)) {
|
|
+ acxpci_s_down(ndev);
|
|
+ }
|
|
+
|
|
+ /* disable all IRQs, release shared IRQ handler */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ write_reg16(adev, IO_ACX_FEMR, 0x0);
|
|
+ free_irq(ndev->irq, ndev);
|
|
+
|
|
+/* TODO: pci_set_power_state(pdev, PCI_D3hot); ? */
|
|
+
|
|
+ /* We currently don't have to do anything else.
|
|
+ * Higher layers know we're not ready from dev->start==0 and
|
|
+ * dev->tbusy==1. Our rx path knows to not pass up received
|
|
+ * frames because of dev->flags&IFF_UP is false.
|
|
+ */
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ log(L_INIT, "closed device\n");
|
|
+ FN_EXIT0;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_i_tx_timeout
|
|
+**
|
|
+** Called from network core. Must not sleep!
|
|
+*/
|
|
+static void
|
|
+acxpci_i_tx_timeout(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ unsigned int tx_num_cleaned;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* clean processed tx descs, they may have been completely full */
|
|
+ tx_num_cleaned = acxpci_l_clean_txdesc(adev);
|
|
+
|
|
+ /* nothing cleaned, yet (almost) no free buffers available?
|
|
+ * --> clean all tx descs, no matter which status!!
|
|
+ * Note that I strongly suspect that doing emergency cleaning
|
|
+ * may confuse the firmware. This is a last ditch effort to get
|
|
+ * ANYTHING to work again...
|
|
+ *
|
|
+ * TODO: it's best to simply reset & reinit hw from scratch...
|
|
+ */
|
|
+ if ((adev->tx_free <= TX_EMERG_CLEAN) && (tx_num_cleaned == 0)) {
|
|
+ printk("%s: FAILED to free any of the many full tx buffers. "
|
|
+ "Switching to emergency freeing. "
|
|
+ "Please report!\n", ndev->name);
|
|
+ acxpci_l_clean_txdesc_emergency(adev);
|
|
+ }
|
|
+
|
|
+ if (acx_queue_stopped(ndev) && (ACX_STATUS_4_ASSOCIATED == adev->status))
|
|
+ acx_wake_queue(ndev, "after tx timeout");
|
|
+
|
|
+ /* stall may have happened due to radio drift, so recalib radio */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+
|
|
+ /* do unimportant work last */
|
|
+ printk("%s: tx timeout!\n", ndev->name);
|
|
+ adev->stats.tx_errors++;
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_i_set_multicast_list
|
|
+** FIXME: most likely needs refinement
|
|
+*/
|
|
+static void
|
|
+acxpci_i_set_multicast_list(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* firmwares don't have allmulti capability,
|
|
+ * so just use promiscuous mode instead in this case. */
|
|
+ if (ndev->flags & (IFF_PROMISC|IFF_ALLMULTI)) {
|
|
+ SET_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS);
|
|
+ CLEAR_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ /* let kernel know in case *we* needed to set promiscuous */
|
|
+ ndev->flags |= (IFF_PROMISC|IFF_ALLMULTI);
|
|
+ } else {
|
|
+ CLEAR_BIT(adev->rx_config_1, RX_CFG1_RCV_PROMISCUOUS);
|
|
+ SET_BIT(adev->rx_config_1, RX_CFG1_FILTER_ALL_MULTI);
|
|
+ SET_BIT(adev->set_mask, SET_RXCONFIG);
|
|
+ ndev->flags &= ~(IFF_PROMISC|IFF_ALLMULTI);
|
|
+ }
|
|
+
|
|
+ /* cannot update card settings directly here, atomic context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_UPDATE_CARD_CFG);
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_l_process_rxdesc
|
|
+**
|
|
+** Called directly and only from the IRQ handler
|
|
+*/
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline void log_rxbuffer(const acx_device_t *adev) {}
|
|
+#else
|
|
+static void
|
|
+log_rxbuffer(const acx_device_t *adev)
|
|
+{
|
|
+ register const struct rxhostdesc *rxhostdesc;
|
|
+ int i;
|
|
+ /* no FN_ENTER here, we don't want that */
|
|
+
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+ if (unlikely(!rxhostdesc)) return;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ if ((rxhostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN))
|
|
+ && (rxhostdesc->Status & cpu_to_le32(DESC_STATUS_FULL)))
|
|
+ printk("rx: buf %d full\n", i);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+
|
|
+static void
|
|
+acxpci_l_process_rxdesc(acx_device_t *adev)
|
|
+{
|
|
+ register rxhostdesc_t *hostdesc;
|
|
+ unsigned count, tail;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(acx_debug & L_BUFR))
|
|
+ log_rxbuffer(adev);
|
|
+
|
|
+ /* First, have a loop to determine the first descriptor that's
|
|
+ * full, just in case there's a mismatch between our current
|
|
+ * rx_tail and the full descriptor we're supposed to handle. */
|
|
+ tail = adev->rx_tail;
|
|
+ count = RX_CNT;
|
|
+ while (1) {
|
|
+ hostdesc = &adev->rxhostdesc_start[tail];
|
|
+ /* advance tail regardless of outcome of the below test */
|
|
+ tail = (tail + 1) % RX_CNT;
|
|
+
|
|
+ if ((hostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN))
|
|
+ && (hostdesc->Status & cpu_to_le32(DESC_STATUS_FULL)))
|
|
+ break; /* found it! */
|
|
+
|
|
+ if (unlikely(!--count)) /* hmm, no luck: all descs empty, bail out */
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* now process descriptors, starting with the first we figured out */
|
|
+ while (1) {
|
|
+ log(L_BUFR, "rx: tail=%u Ctl_16=%04X Status=%08X\n",
|
|
+ tail, hostdesc->Ctl_16, hostdesc->Status);
|
|
+
|
|
+ acx_l_process_rxbuf(adev, hostdesc->data);
|
|
+
|
|
+ hostdesc->Status = 0;
|
|
+ /* flush all writes before adapter sees CTL_HOSTOWN change */
|
|
+ wmb();
|
|
+ /* Host no longer owns this, needs to be LAST */
|
|
+ CLEAR_BIT(hostdesc->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN));
|
|
+
|
|
+ /* ok, descriptor is handled, now check the next descriptor */
|
|
+ hostdesc = &adev->rxhostdesc_start[tail];
|
|
+
|
|
+ /* if next descriptor is empty, then bail out */
|
|
+ if (!(hostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN))
|
|
+ || !(hostdesc->Status & cpu_to_le32(DESC_STATUS_FULL)))
|
|
+ break;
|
|
+
|
|
+ tail = (tail + 1) % RX_CNT;
|
|
+ }
|
|
+end:
|
|
+ adev->rx_tail = tail;
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_i_interrupt
|
|
+**
|
|
+** IRQ handler (atomic context, must not sleep, blah, blah)
|
|
+*/
|
|
+
|
|
+/* scan is complete. all frames now on the receive queue are valid */
|
|
+#define INFO_SCAN_COMPLETE 0x0001
|
|
+#define INFO_WEP_KEY_NOT_FOUND 0x0002
|
|
+/* hw has been reset as the result of a watchdog timer timeout */
|
|
+#define INFO_WATCH_DOG_RESET 0x0003
|
|
+/* failed to send out NULL frame from PS mode notification to AP */
|
|
+/* recommended action: try entering 802.11 PS mode again */
|
|
+#define INFO_PS_FAIL 0x0004
|
|
+/* encryption/decryption process on a packet failed */
|
|
+#define INFO_IV_ICV_FAILURE 0x0005
|
|
+
|
|
+/* Info mailbox format:
|
|
+2 bytes: type
|
|
+2 bytes: status
|
|
+more bytes may follow
|
|
+ rumors say about status:
|
|
+ 0x0000 info available (set by hw)
|
|
+ 0x0001 information received (must be set by host)
|
|
+ 0x1000 info available, mailbox overflowed (messages lost) (set by hw)
|
|
+ but in practice we've seen:
|
|
+ 0x9000 when we did not set status to 0x0001 on prev message
|
|
+ 0x1001 when we did set it
|
|
+ 0x0000 was never seen
|
|
+ conclusion: this is really a bitfield:
|
|
+ 0x1000 is 'info available' bit
|
|
+ 'mailbox overflowed' bit is 0x8000, not 0x1000
|
|
+ value of 0x0000 probably means that there are no messages at all
|
|
+ P.S. I dunno how in hell hw is supposed to notice that messages are lost -
|
|
+ it does NOT clear bit 0x0001, and this bit will probably stay forever set
|
|
+ after we set it once. Let's hope this will be fixed in firmware someday
|
|
+*/
|
|
+
|
|
+static void
|
|
+handle_info_irq(acx_device_t *adev)
|
|
+{
|
|
+#if ACX_DEBUG
|
|
+ static const char * const info_type_msg[] = {
|
|
+ "(unknown)",
|
|
+ "scan complete",
|
|
+ "WEP key not found",
|
|
+ "internal watchdog reset was done",
|
|
+ "failed to send powersave (NULL frame) notification to AP",
|
|
+ "encrypt/decrypt on a packet has failed",
|
|
+ "TKIP tx keys disabled",
|
|
+ "TKIP rx keys disabled",
|
|
+ "TKIP rx: key ID not found",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "???",
|
|
+ "TKIP IV value exceeds thresh"
|
|
+ };
|
|
+#endif
|
|
+ u32 info_type, info_status;
|
|
+
|
|
+ info_type = readl(adev->info_area);
|
|
+ info_status = (info_type >> 16);
|
|
+ info_type = (u16)info_type;
|
|
+
|
|
+ /* inform fw that we have read this info message */
|
|
+ writel(info_type | 0x00010000, adev->info_area);
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_INFOACK);
|
|
+ write_flush(adev);
|
|
+
|
|
+ log(L_CTL, "info_type:%04X info_status:%04X\n",
|
|
+ info_type, info_status);
|
|
+
|
|
+ log(L_IRQ, "got Info IRQ: status %04X type %04X: %s\n",
|
|
+ info_status, info_type,
|
|
+ info_type_msg[(info_type >= VEC_SIZE(info_type_msg)) ?
|
|
+ 0 : info_type]
|
|
+ );
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+log_unusual_irq(u16 irqtype) {
|
|
+ /*
|
|
+ if (!printk_ratelimit())
|
|
+ return;
|
|
+ */
|
|
+
|
|
+ printk("acx: got");
|
|
+ if (irqtype & HOST_INT_RX_DATA) {
|
|
+ printk(" Rx_Data");
|
|
+ }
|
|
+ /* HOST_INT_TX_COMPLETE */
|
|
+ if (irqtype & HOST_INT_TX_XFER) {
|
|
+ printk(" Tx_Xfer");
|
|
+ }
|
|
+ /* HOST_INT_RX_COMPLETE */
|
|
+ if (irqtype & HOST_INT_DTIM) {
|
|
+ printk(" DTIM");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_BEACON) {
|
|
+ printk(" Beacon");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_TIMER) {
|
|
+ log(L_IRQ, " Timer");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_KEY_NOT_FOUND) {
|
|
+ printk(" Key_Not_Found");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_IV_ICV_FAILURE) {
|
|
+ printk(" IV_ICV_Failure (crypto)");
|
|
+ }
|
|
+ /* HOST_INT_CMD_COMPLETE */
|
|
+ /* HOST_INT_INFO */
|
|
+ if (irqtype & HOST_INT_OVERFLOW) {
|
|
+ printk(" Overflow");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_PROCESS_ERROR) {
|
|
+ printk(" Process_Error");
|
|
+ }
|
|
+ /* HOST_INT_SCAN_COMPLETE */
|
|
+ if (irqtype & HOST_INT_FCS_THRESHOLD) {
|
|
+ printk(" FCS_Threshold");
|
|
+ }
|
|
+ if (irqtype & HOST_INT_UNKNOWN) {
|
|
+ printk(" Unknown");
|
|
+ }
|
|
+ printk(" IRQ(s)\n");
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+update_link_quality_led(acx_device_t *adev)
|
|
+{
|
|
+ int qual;
|
|
+
|
|
+ qual = acx_signal_determine_quality(adev->wstats.qual.level, adev->wstats.qual.noise);
|
|
+ if (qual > adev->brange_max_quality)
|
|
+ qual = adev->brange_max_quality;
|
|
+
|
|
+ if (time_after(jiffies, adev->brange_time_last_state_change +
|
|
+ (HZ/2 - HZ/2 * (unsigned long)qual / adev->brange_max_quality ) )) {
|
|
+ acxpci_l_power_led(adev, (adev->brange_last_state == 0));
|
|
+ adev->brange_last_state ^= 1; /* toggle */
|
|
+ adev->brange_time_last_state_change = jiffies;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+#define MAX_IRQLOOPS_PER_JIFFY (20000/HZ) /* a la orinoco.c */
|
|
+
|
|
+static irqreturn_t
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+acxpci_i_interrupt(int irq, void *dev_id)
|
|
+#else
|
|
+acxpci_i_interrupt(int irq, void *dev_id, struct pt_regs *regs)
|
|
+#endif
|
|
+{
|
|
+ acx_device_t *adev;
|
|
+ unsigned long flags;
|
|
+ unsigned int irqcount = MAX_IRQLOOPS_PER_JIFFY;
|
|
+ register u16 irqtype;
|
|
+ u16 unmasked;
|
|
+
|
|
+ adev = ndev2adev((struct net_device*)dev_id);
|
|
+
|
|
+ /* LOCKING: can just spin_lock() since IRQs are disabled anyway.
|
|
+ * I am paranoid */
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR);
|
|
+ if (unlikely(0xffff == unmasked)) {
|
|
+ /* 0xffff value hints at missing hardware,
|
|
+ * so don't do anything.
|
|
+ * Not very clean, but other drivers do the same... */
|
|
+ log(L_IRQ, "IRQ type:FFFF - device removed? IRQ_NONE\n");
|
|
+ goto none;
|
|
+ }
|
|
+
|
|
+ /* We will check only "interesting" IRQ types */
|
|
+ irqtype = unmasked & ~adev->irq_mask;
|
|
+ if (!irqtype) {
|
|
+ /* We are on a shared IRQ line and it wasn't our IRQ */
|
|
+ log(L_IRQ, "IRQ type:%04X, mask:%04X - all are masked, IRQ_NONE\n",
|
|
+ unmasked, adev->irq_mask);
|
|
+ goto none;
|
|
+ }
|
|
+
|
|
+ /* Done here because IRQ_NONEs taking three lines of log
|
|
+ ** drive me crazy */
|
|
+ FN_ENTER;
|
|
+
|
|
+#define IRQ_ITERATE 1
|
|
+#if IRQ_ITERATE
|
|
+if (jiffies != adev->irq_last_jiffies) {
|
|
+ adev->irq_loops_this_jiffy = 0;
|
|
+ adev->irq_last_jiffies = jiffies;
|
|
+}
|
|
+
|
|
+/* safety condition; we'll normally abort loop below
|
|
+ * in case no IRQ type occurred */
|
|
+while (likely(--irqcount)) {
|
|
+#endif
|
|
+ /* ACK all IRQs ASAP */
|
|
+ write_reg16(adev, IO_ACX_IRQ_ACK, 0xffff);
|
|
+
|
|
+ log(L_IRQ, "IRQ type:%04X, mask:%04X, type & ~mask:%04X\n",
|
|
+ unmasked, adev->irq_mask, irqtype);
|
|
+
|
|
+ /* Handle most important IRQ types first */
|
|
+ if (irqtype & HOST_INT_RX_COMPLETE) {
|
|
+ log(L_IRQ, "got Rx_Complete IRQ\n");
|
|
+ acxpci_l_process_rxdesc(adev);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_TX_COMPLETE) {
|
|
+ log(L_IRQ, "got Tx_Complete IRQ\n");
|
|
+ /* don't clean up on each Tx complete, wait a bit
|
|
+ * unless we're going towards full, in which case
|
|
+ * we do it immediately, too (otherwise we might lockup
|
|
+ * with a full Tx buffer if we go into
|
|
+ * acxpci_l_clean_txdesc() at a time when we won't wakeup
|
|
+ * the net queue in there for some reason...) */
|
|
+ if (adev->tx_free <= TX_START_CLEAN) {
|
|
+#if TX_CLEANUP_IN_SOFTIRQ
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_TX_CLEANUP);
|
|
+#else
|
|
+ acxpci_l_clean_txdesc(adev);
|
|
+#endif
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* Less frequent ones */
|
|
+ if (irqtype & (0
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ )) {
|
|
+ if (irqtype & HOST_INT_CMD_COMPLETE) {
|
|
+ log(L_IRQ, "got Command_Complete IRQ\n");
|
|
+ /* save the state for the running issue_cmd() */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_CMD_COMPLETE);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_INFO) {
|
|
+ handle_info_irq(adev);
|
|
+ }
|
|
+ if (irqtype & HOST_INT_SCAN_COMPLETE) {
|
|
+ log(L_IRQ, "got Scan_Complete IRQ\n");
|
|
+ /* need to do that in process context */
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_COMPLETE_SCAN);
|
|
+ /* remember that fw is not scanning anymore */
|
|
+ SET_BIT(adev->irq_status, HOST_INT_SCAN_COMPLETE);
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* These we just log, but either they happen rarely
|
|
+ * or we keep them masked out */
|
|
+ if (irqtype & (0
|
|
+ | HOST_INT_RX_DATA
|
|
+ /* | HOST_INT_TX_COMPLETE */
|
|
+ | HOST_INT_TX_XFER
|
|
+ /* | HOST_INT_RX_COMPLETE */
|
|
+ | HOST_INT_DTIM
|
|
+ | HOST_INT_BEACON
|
|
+ | HOST_INT_TIMER
|
|
+ | HOST_INT_KEY_NOT_FOUND
|
|
+ | HOST_INT_IV_ICV_FAILURE
|
|
+ /* | HOST_INT_CMD_COMPLETE */
|
|
+ /* | HOST_INT_INFO */
|
|
+ | HOST_INT_OVERFLOW
|
|
+ | HOST_INT_PROCESS_ERROR
|
|
+ /* | HOST_INT_SCAN_COMPLETE */
|
|
+ | HOST_INT_FCS_THRESHOLD
|
|
+ | HOST_INT_UNKNOWN
|
|
+ )) {
|
|
+ log_unusual_irq(irqtype);
|
|
+ }
|
|
+
|
|
+#if IRQ_ITERATE
|
|
+ unmasked = read_reg16(adev, IO_ACX_IRQ_STATUS_CLEAR);
|
|
+ irqtype = unmasked & ~adev->irq_mask;
|
|
+ /* Bail out if no new IRQ bits or if all are masked out */
|
|
+ if (!irqtype)
|
|
+ break;
|
|
+
|
|
+ if (unlikely(++adev->irq_loops_this_jiffy > MAX_IRQLOOPS_PER_JIFFY)) {
|
|
+ printk(KERN_ERR "acx: too many interrupts per jiffy!\n");
|
|
+ /* Looks like card floods us with IRQs! Try to stop that */
|
|
+ write_reg16(adev, IO_ACX_IRQ_MASK, 0xffff);
|
|
+ /* This will short-circuit all future attempts to handle IRQ.
|
|
+ * We cant do much more... */
|
|
+ adev->irq_mask = 0;
|
|
+ break;
|
|
+ }
|
|
+}
|
|
+#endif
|
|
+ /* Routine to perform blink with range */
|
|
+ if (unlikely(adev->led_power == 2))
|
|
+ update_link_quality_led(adev);
|
|
+
|
|
+/* handled: */
|
|
+ /* write_flush(adev); - not needed, last op was read anyway */
|
|
+ acx_unlock(adev, flags);
|
|
+ FN_EXIT0;
|
|
+ return IRQ_HANDLED;
|
|
+
|
|
+none:
|
|
+ acx_unlock(adev, flags);
|
|
+ return IRQ_NONE;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_l_power_led
|
|
+*/
|
|
+void
|
|
+acxpci_l_power_led(acx_device_t *adev, int enable)
|
|
+{
|
|
+ u16 gpio_pled = IS_ACX111(adev) ? 0x0040 : 0x0800;
|
|
+
|
|
+ /* A hack. Not moving message rate limiting to adev->xxx
|
|
+ * (it's only a debug message after all) */
|
|
+ static int rate_limit = 0;
|
|
+
|
|
+ if (rate_limit++ < 3)
|
|
+ log(L_IOCTL, "Please report in case toggling the power "
|
|
+ "LED doesn't work for your card!\n");
|
|
+ if (enable)
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT,
|
|
+ read_reg16(adev, IO_ACX_GPIO_OUT) & ~gpio_pled);
|
|
+ else
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT,
|
|
+ read_reg16(adev, IO_ACX_GPIO_OUT) | gpio_pled);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Ioctls
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx111pci_ioctl_info(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+#if ACX_DEBUG > 1
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ rxdesc_t *rxdesc;
|
|
+ txdesc_t *txdesc;
|
|
+ rxhostdesc_t *rxhostdesc;
|
|
+ txhostdesc_t *txhostdesc;
|
|
+ struct acx111_ie_memoryconfig memconf;
|
|
+ struct acx111_ie_queueconfig queueconf;
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+ char memmap[0x34];
|
|
+ char rxconfig[0x8];
|
|
+ char fcserror[0x8];
|
|
+ char ratefallback[0x5];
|
|
+
|
|
+ if ( !(acx_debug & (L_IOCTL|L_DEBUG)) )
|
|
+ return OK;
|
|
+ /* using printk() since we checked debug flag already */
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ if (!IS_ACX111(adev)) {
|
|
+ printk("acx111-specific function called "
|
|
+ "with non-acx111 chip, aborting\n");
|
|
+ goto end_ok;
|
|
+ }
|
|
+
|
|
+ /* get Acx111 Memory Configuration */
|
|
+ memset(&memconf, 0, sizeof(memconf));
|
|
+ /* BTW, fails with 12 (Write only) error code.
|
|
+ ** Retained for easy testing of issue_cmd error handling :) */
|
|
+ acx_s_interrogate(adev, &memconf, ACX1xx_IE_QUEUE_CONFIG);
|
|
+
|
|
+ /* get Acx111 Queue Configuration */
|
|
+ memset(&queueconf, 0, sizeof(queueconf));
|
|
+ acx_s_interrogate(adev, &queueconf, ACX1xx_IE_MEMORY_CONFIG_OPTIONS);
|
|
+
|
|
+ /* get Acx111 Memory Map */
|
|
+ memset(memmap, 0, sizeof(memmap));
|
|
+ acx_s_interrogate(adev, &memmap, ACX1xx_IE_MEMORY_MAP);
|
|
+
|
|
+ /* get Acx111 Rx Config */
|
|
+ memset(rxconfig, 0, sizeof(rxconfig));
|
|
+ acx_s_interrogate(adev, &rxconfig, ACX1xx_IE_RXCONFIG);
|
|
+
|
|
+ /* get Acx111 fcs error count */
|
|
+ memset(fcserror, 0, sizeof(fcserror));
|
|
+ acx_s_interrogate(adev, &fcserror, ACX1xx_IE_FCS_ERROR_COUNT);
|
|
+
|
|
+ /* get Acx111 rate fallback */
|
|
+ memset(ratefallback, 0, sizeof(ratefallback));
|
|
+ acx_s_interrogate(adev, &ratefallback, ACX1xx_IE_RATE_FALLBACK);
|
|
+
|
|
+ /* force occurrence of a beacon interrupt */
|
|
+ /* TODO: comment why is this necessary */
|
|
+ write_reg16(adev, IO_ACX_HINT_TRIG, HOST_INT_BEACON);
|
|
+
|
|
+ /* dump Acx111 Mem Configuration */
|
|
+ printk("dump mem config:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "Number of stations: %1X\n"
|
|
+ "Memory block size: %1X\n"
|
|
+ "tx/rx memory block allocation: %1X\n"
|
|
+ "count rx: %X / tx: %X queues\n"
|
|
+ "options %1X\n"
|
|
+ "fragmentation %1X\n"
|
|
+ "Rx Queue 1 Count Descriptors: %X\n"
|
|
+ "Rx Queue 1 Host Memory Start: %X\n"
|
|
+ "Tx Queue 1 Count Descriptors: %X\n"
|
|
+ "Tx Queue 1 Attributes: %X\n",
|
|
+ memconf.len, (int) sizeof(memconf),
|
|
+ memconf.no_of_stations,
|
|
+ memconf.memory_block_size,
|
|
+ memconf.tx_rx_memory_block_allocation,
|
|
+ memconf.count_rx_queues, memconf.count_tx_queues,
|
|
+ memconf.options,
|
|
+ memconf.fragmentation,
|
|
+ memconf.rx_queue1_count_descs,
|
|
+ acx2cpu(memconf.rx_queue1_host_rx_start),
|
|
+ memconf.tx_queue1_count_descs,
|
|
+ memconf.tx_queue1_attributes);
|
|
+
|
|
+ /* dump Acx111 Queue Configuration */
|
|
+ printk("dump queue head:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "tx_memory_block_address (from card): %X\n"
|
|
+ "rx_memory_block_address (from card): %X\n"
|
|
+ "rx1_queue address (from card): %X\n"
|
|
+ "tx1_queue address (from card): %X\n"
|
|
+ "tx1_queue attributes (from card): %X\n",
|
|
+ queueconf.len, (int) sizeof(queueconf),
|
|
+ queueconf.tx_memory_block_address,
|
|
+ queueconf.rx_memory_block_address,
|
|
+ queueconf.rx1_queue_address,
|
|
+ queueconf.tx1_queue_address,
|
|
+ queueconf.tx1_attributes);
|
|
+
|
|
+ /* dump Acx111 Mem Map */
|
|
+ printk("dump mem map:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "Code start: %X\n"
|
|
+ "Code end: %X\n"
|
|
+ "WEP default key start: %X\n"
|
|
+ "WEP default key end: %X\n"
|
|
+ "STA table start: %X\n"
|
|
+ "STA table end: %X\n"
|
|
+ "Packet template start: %X\n"
|
|
+ "Packet template end: %X\n"
|
|
+ "Queue memory start: %X\n"
|
|
+ "Queue memory end: %X\n"
|
|
+ "Packet memory pool start: %X\n"
|
|
+ "Packet memory pool end: %X\n"
|
|
+ "iobase: %p\n"
|
|
+ "iobase2: %p\n",
|
|
+ *((u16 *)&memmap[0x02]), (int) sizeof(memmap),
|
|
+ *((u32 *)&memmap[0x04]),
|
|
+ *((u32 *)&memmap[0x08]),
|
|
+ *((u32 *)&memmap[0x0C]),
|
|
+ *((u32 *)&memmap[0x10]),
|
|
+ *((u32 *)&memmap[0x14]),
|
|
+ *((u32 *)&memmap[0x18]),
|
|
+ *((u32 *)&memmap[0x1C]),
|
|
+ *((u32 *)&memmap[0x20]),
|
|
+ *((u32 *)&memmap[0x24]),
|
|
+ *((u32 *)&memmap[0x28]),
|
|
+ *((u32 *)&memmap[0x2C]),
|
|
+ *((u32 *)&memmap[0x30]),
|
|
+ adev->iobase,
|
|
+ adev->iobase2);
|
|
+
|
|
+ /* dump Acx111 Rx Config */
|
|
+ printk("dump rx config:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "rx config: %X\n"
|
|
+ "rx filter config: %X\n",
|
|
+ *((u16 *)&rxconfig[0x02]), (int) sizeof(rxconfig),
|
|
+ *((u16 *)&rxconfig[0x04]),
|
|
+ *((u16 *)&rxconfig[0x06]));
|
|
+
|
|
+ /* dump Acx111 fcs error */
|
|
+ printk("dump fcserror:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "fcserrors: %X\n",
|
|
+ *((u16 *)&fcserror[0x02]), (int) sizeof(fcserror),
|
|
+ *((u32 *)&fcserror[0x04]));
|
|
+
|
|
+ /* dump Acx111 rate fallback */
|
|
+ printk("dump rate fallback:\n"
|
|
+ "data read: %d, struct size: %d\n"
|
|
+ "ratefallback: %X\n",
|
|
+ *((u16 *)&ratefallback[0x02]), (int) sizeof(ratefallback),
|
|
+ *((u8 *)&ratefallback[0x04]));
|
|
+
|
|
+ /* protect against IRQ */
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /* dump acx111 internal rx descriptor ring buffer */
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+
|
|
+ /* loop over complete receive pool */
|
|
+ if (rxdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ printk("\ndump internal rxdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "next 0x%X\n"
|
|
+ "acx mem pointer (dynamic) 0x%X\n"
|
|
+ "CTL (dynamic) 0x%X\n"
|
|
+ "Rate (dynamic) 0x%X\n"
|
|
+ "RxStatus (dynamic) 0x%X\n"
|
|
+ "Mod/Pre (dynamic) 0x%X\n",
|
|
+ i,
|
|
+ rxdesc,
|
|
+ acx2cpu(rxdesc->pNextDesc),
|
|
+ acx2cpu(rxdesc->ACXMemPtr),
|
|
+ rxdesc->Ctl_8,
|
|
+ rxdesc->rate,
|
|
+ rxdesc->error,
|
|
+ rxdesc->SNR);
|
|
+ rxdesc++;
|
|
+ }
|
|
+
|
|
+ /* dump host rx descriptor ring buffer */
|
|
+
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+
|
|
+ /* loop over complete receive pool */
|
|
+ if (rxhostdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ printk("\ndump host rxdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "buffer mem pos 0x%X\n"
|
|
+ "buffer mem offset 0x%X\n"
|
|
+ "CTL 0x%X\n"
|
|
+ "Length 0x%X\n"
|
|
+ "next 0x%X\n"
|
|
+ "Status 0x%X\n",
|
|
+ i,
|
|
+ rxhostdesc,
|
|
+ acx2cpu(rxhostdesc->data_phy),
|
|
+ rxhostdesc->data_offset,
|
|
+ le16_to_cpu(rxhostdesc->Ctl_16),
|
|
+ le16_to_cpu(rxhostdesc->length),
|
|
+ acx2cpu(rxhostdesc->desc_phy_next),
|
|
+ rxhostdesc->Status);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+
|
|
+ /* dump acx111 internal tx descriptor ring buffer */
|
|
+ txdesc = adev->txdesc_start;
|
|
+
|
|
+ /* loop over complete transmit pool */
|
|
+ if (txdesc) for (i = 0; i < TX_CNT; i++) {
|
|
+ printk("\ndump internal txdesc %d:\n"
|
|
+ "size 0x%X\n"
|
|
+ "mem pos %p\n"
|
|
+ "next 0x%X\n"
|
|
+ "acx mem pointer (dynamic) 0x%X\n"
|
|
+ "host mem pointer (dynamic) 0x%X\n"
|
|
+ "length (dynamic) 0x%X\n"
|
|
+ "CTL (dynamic) 0x%X\n"
|
|
+ "CTL2 (dynamic) 0x%X\n"
|
|
+ "Status (dynamic) 0x%X\n"
|
|
+ "Rate (dynamic) 0x%X\n",
|
|
+ i,
|
|
+ (int) sizeof(struct txdesc),
|
|
+ txdesc,
|
|
+ acx2cpu(txdesc->pNextDesc),
|
|
+ acx2cpu(txdesc->AcxMemPtr),
|
|
+ acx2cpu(txdesc->HostMemPtr),
|
|
+ le16_to_cpu(txdesc->total_length),
|
|
+ txdesc->Ctl_8,
|
|
+ txdesc->Ctl2_8, txdesc->error,
|
|
+ txdesc->u.r1.rate);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+
|
|
+ /* dump host tx descriptor ring buffer */
|
|
+
|
|
+ txhostdesc = adev->txhostdesc_start;
|
|
+
|
|
+ /* loop over complete host send pool */
|
|
+ if (txhostdesc) for (i = 0; i < TX_CNT * 2; i++) {
|
|
+ printk("\ndump host txdesc %d:\n"
|
|
+ "mem pos %p\n"
|
|
+ "buffer mem pos 0x%X\n"
|
|
+ "buffer mem offset 0x%X\n"
|
|
+ "CTL 0x%X\n"
|
|
+ "Length 0x%X\n"
|
|
+ "next 0x%X\n"
|
|
+ "Status 0x%X\n",
|
|
+ i,
|
|
+ txhostdesc,
|
|
+ acx2cpu(txhostdesc->data_phy),
|
|
+ txhostdesc->data_offset,
|
|
+ le16_to_cpu(txhostdesc->Ctl_16),
|
|
+ le16_to_cpu(txhostdesc->length),
|
|
+ acx2cpu(txhostdesc->desc_phy_next),
|
|
+ le32_to_cpu(txhostdesc->Status));
|
|
+ txhostdesc++;
|
|
+ }
|
|
+
|
|
+ /* write_reg16(adev, 0xb4, 0x4); */
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+end_ok:
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+#endif /* ACX_DEBUG */
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx100pci_ioctl_set_phy_amp_bias(
|
|
+ struct net_device *ndev,
|
|
+ struct iw_request_info *info,
|
|
+ struct iw_param *vwrq,
|
|
+ char *extra)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ u16 gpio_old;
|
|
+
|
|
+ if (!IS_ACX100(adev)) {
|
|
+ /* WARNING!!!
|
|
+ * Removing this check *might* damage
|
|
+ * hardware, since we're tweaking GPIOs here after all!!!
|
|
+ * You've been warned...
|
|
+ * WARNING!!! */
|
|
+ printk("acx: sorry, setting bias level for non-acx100 "
|
|
+ "is not supported yet\n");
|
|
+ return OK;
|
|
+ }
|
|
+
|
|
+ if (*extra > 7) {
|
|
+ printk("acx: invalid bias parameter, range is 0-7\n");
|
|
+ return -EINVAL;
|
|
+ }
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* Need to lock accesses to [IO_ACX_GPIO_OUT]:
|
|
+ * IRQ handler uses it to update LED */
|
|
+ acx_lock(adev, flags);
|
|
+ gpio_old = read_reg16(adev, IO_ACX_GPIO_OUT);
|
|
+ write_reg16(adev, IO_ACX_GPIO_OUT, (gpio_old & 0xf8ff) | ((u16)*extra << 8));
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ log(L_DEBUG, "gpio_old: 0x%04X\n", gpio_old);
|
|
+ printk("%s: PHY power amplifier bias: old:%d, new:%d\n",
|
|
+ ndev->name,
|
|
+ (gpio_old & 0x0700) >> 8, (unsigned char)*extra);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_l_alloc_tx
|
|
+** Actually returns a txdesc_t* ptr
|
|
+**
|
|
+** FIXME: in case of fragments, should allocate multiple descrs
|
|
+** after figuring out how many we need and whether we still have
|
|
+** sufficiently many.
|
|
+*/
|
|
+tx_t*
|
|
+acxpci_l_alloc_tx(acx_device_t *adev)
|
|
+{
|
|
+ struct txdesc *txdesc;
|
|
+ unsigned head;
|
|
+ u8 ctl8;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(!adev->tx_free)) {
|
|
+ printk("acx: BUG: no free txdesc left\n");
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ head = adev->tx_head;
|
|
+ txdesc = get_txdesc(adev, head);
|
|
+ ctl8 = txdesc->Ctl_8;
|
|
+
|
|
+ /* 2005-10-11: there were several bug reports on this happening
|
|
+ ** but now cause seems to be understood & fixed */
|
|
+ if (unlikely(DESC_CTL_HOSTOWN != (ctl8 & DESC_CTL_ACXDONE_HOSTOWN))) {
|
|
+ /* whoops, descr at current index is not free, so probably
|
|
+ * ring buffer already full */
|
|
+ printk("acx: BUG: tx_head:%d Ctl8:0x%02X - failed to find "
|
|
+ "free txdesc\n", head, ctl8);
|
|
+ txdesc = NULL;
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* Needed in case txdesc won't be eventually submitted for tx */
|
|
+ txdesc->Ctl_8 = DESC_CTL_ACXDONE_HOSTOWN;
|
|
+
|
|
+ adev->tx_free--;
|
|
+ log(L_BUFT, "tx: got desc %u, %u remain\n",
|
|
+ head, adev->tx_free);
|
|
+ /* Keep a few free descs between head and tail of tx ring.
|
|
+ ** It is not absolutely needed, just feels safer */
|
|
+ if (adev->tx_free < TX_STOP_QUEUE) {
|
|
+ log(L_BUF, "stop queue (%u tx desc left)\n",
|
|
+ adev->tx_free);
|
|
+ acx_stop_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+ /* returning current descriptor, so advance to next free one */
|
|
+ adev->tx_head = (head + 1) % TX_CNT;
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+
|
|
+ return (tx_t*)txdesc;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void*
|
|
+acxpci_l_get_txbuf(acx_device_t *adev, tx_t* tx_opaque)
|
|
+{
|
|
+ return get_txhostdesc(adev, (txdesc_t*)tx_opaque)->data;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_l_tx_data
|
|
+**
|
|
+** Can be called from IRQ (rx -> (AP bridging or mgmt response) -> tx).
|
|
+** Can be called from acx_i_start_xmit (data frames from net core).
|
|
+**
|
|
+** FIXME: in case of fragments, should loop over the number of
|
|
+** pre-allocated tx descrs, properly setting up transfer data and
|
|
+** CTL_xxx flags according to fragment number.
|
|
+*/
|
|
+void
|
|
+acxpci_l_tx_data(acx_device_t *adev, tx_t* tx_opaque, int len)
|
|
+{
|
|
+ txdesc_t *txdesc = (txdesc_t*)tx_opaque;
|
|
+ txhostdesc_t *hostdesc1, *hostdesc2;
|
|
+ client_t *clt;
|
|
+ u16 rate_cur;
|
|
+ u8 Ctl_8, Ctl2_8;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* fw doesn't tx such packets anyhow */
|
|
+ if (unlikely(len < WLAN_HDR_A3_LEN))
|
|
+ goto end;
|
|
+
|
|
+ hostdesc1 = get_txhostdesc(adev, txdesc);
|
|
+ /* modify flag status in separate variable to be able to write it back
|
|
+ * in one big swoop later (also in order to have less device memory
|
|
+ * accesses) */
|
|
+ Ctl_8 = txdesc->Ctl_8;
|
|
+ Ctl2_8 = 0; /* really need to init it to 0, not txdesc->Ctl2_8, it seems */
|
|
+
|
|
+ hostdesc2 = hostdesc1 + 1;
|
|
+
|
|
+ /* DON'T simply set Ctl field to 0 here globally,
|
|
+ * it needs to maintain a consistent flag status (those are state flags!!),
|
|
+ * otherwise it may lead to severe disruption. Only set or reset particular
|
|
+ * flags at the exact moment this is needed... */
|
|
+
|
|
+ /* let chip do RTS/CTS handshaking before sending
|
|
+ * in case packet size exceeds threshold */
|
|
+ if (len > adev->rts_threshold)
|
|
+ SET_BIT(Ctl2_8, DESC_CTL2_RTS);
|
|
+ else
|
|
+ CLEAR_BIT(Ctl2_8, DESC_CTL2_RTS);
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_3_AP:
|
|
+ clt = acx_l_sta_list_get(adev, ((wlan_hdr_t*)hostdesc1->data)->a1);
|
|
+ break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ clt = adev->ap_client;
|
|
+ break;
|
|
+#if 0
|
|
+/* testing was done on acx111: */
|
|
+ case ACX_MODE_MONITOR:
|
|
+ SET_BIT(Ctl2_8, 0
|
|
+/* sends CTS to self before packet */
|
|
+ + DESC_CTL2_SEQ /* don't increase sequence field */
|
|
+/* not working (looks like good fcs is still added) */
|
|
+ + DESC_CTL2_FCS /* don't add the FCS */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_MORE_FRAG
|
|
+/* not tested */
|
|
+ + DESC_CTL2_RETRY /* don't increase retry field */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_POWER /* don't increase power mgmt. field */
|
|
+/* no effect */
|
|
+ + DESC_CTL2_WEP /* encrypt this frame */
|
|
+/* not tested */
|
|
+ + DESC_CTL2_DUR /* don't increase duration field */
|
|
+ );
|
|
+ /* fallthrough */
|
|
+#endif
|
|
+ default: /* ACX_MODE_OFF, ACX_MODE_MONITOR */
|
|
+ clt = NULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ rate_cur = clt ? clt->rate_cur : adev->rate_bcast;
|
|
+ if (unlikely(!rate_cur)) {
|
|
+ printk("acx: driver bug! bad ratemask\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* used in tx cleanup routine for auto rate and accounting: */
|
|
+ put_txcr(adev, txdesc, clt, rate_cur);
|
|
+
|
|
+ txdesc->total_length = cpu_to_le16(len);
|
|
+ hostdesc2->length = cpu_to_le16(len - WLAN_HDR_A3_LEN);
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* note that if !txdesc->do_auto, txrate->cur
|
|
+ ** has only one nonzero bit */
|
|
+ txdesc->u.r2.rate111 = cpu_to_le16(
|
|
+ rate_cur
|
|
+ /* WARNING: I was never able to make it work with prism54 AP.
|
|
+ ** It was falling down to 1Mbit where shortpre is not applicable,
|
|
+ ** and not working at all at "5,11 basic rates only" setting.
|
|
+ ** I even didn't see tx packets in radio packet capture.
|
|
+ ** Disabled for now --vda */
|
|
+ /*| ((clt->shortpre && clt->cur!=RATE111_1) ? RATE111_SHORTPRE : 0) */
|
|
+ );
|
|
+#ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS
|
|
+ /* should add this to rate111 above as necessary */
|
|
+ | (clt->pbcc511 ? RATE111_PBCC511 : 0)
|
|
+#endif
|
|
+ hostdesc1->length = cpu_to_le16(len);
|
|
+ } else { /* ACX100 */
|
|
+ u8 rate_100 = clt ? clt->rate_100 : adev->rate_bcast100;
|
|
+ txdesc->u.r1.rate = rate_100;
|
|
+#ifdef TODO_FIGURE_OUT_WHEN_TO_SET_THIS
|
|
+ if (clt->pbcc511) {
|
|
+ if (n == RATE100_5 || n == RATE100_11)
|
|
+ n |= RATE100_PBCC511;
|
|
+ }
|
|
+
|
|
+ if (clt->shortpre && (clt->cur != RATE111_1))
|
|
+ SET_BIT(Ctl_8, DESC_CTL_SHORT_PREAMBLE); /* set Short Preamble */
|
|
+#endif
|
|
+ /* set autodma and reclaim and 1st mpdu */
|
|
+ SET_BIT(Ctl_8, DESC_CTL_AUTODMA | DESC_CTL_RECLAIM | DESC_CTL_FIRSTFRAG);
|
|
+#if ACX_FRAGMENTATION
|
|
+ /* SET_BIT(Ctl2_8, DESC_CTL2_MORE_FRAG); cannot set it unconditionally, needs to be set for all non-last fragments */
|
|
+#endif
|
|
+ hostdesc1->length = cpu_to_le16(WLAN_HDR_A3_LEN);
|
|
+ }
|
|
+ /* don't need to clean ack/rts statistics here, already
|
|
+ * done on descr cleanup */
|
|
+
|
|
+ /* clears HOSTOWN and ACXDONE bits, thus telling that the descriptors
|
|
+ * are now owned by the acx100; do this as LAST operation */
|
|
+ CLEAR_BIT(Ctl_8, DESC_CTL_ACXDONE_HOSTOWN);
|
|
+ /* flush writes before we release hostdesc to the adapter here */
|
|
+ wmb();
|
|
+ CLEAR_BIT(hostdesc1->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN));
|
|
+ CLEAR_BIT(hostdesc2->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN));
|
|
+
|
|
+ /* write back modified flags */
|
|
+ txdesc->Ctl2_8 = Ctl2_8;
|
|
+ txdesc->Ctl_8 = Ctl_8;
|
|
+ /* unused: txdesc->tx_time = cpu_to_le32(jiffies); */
|
|
+
|
|
+ /* flush writes before we tell the adapter that it's its turn now */
|
|
+ mmiowb();
|
|
+ write_reg16(adev, IO_ACX_INT_TRIG, INT_TRIG_TXPRC);
|
|
+ write_flush(adev);
|
|
+
|
|
+ /* log the packet content AFTER sending it,
|
|
+ * in order to not delay sending any further than absolutely needed
|
|
+ * Do separate logs for acx100/111 to have human-readable rates */
|
|
+ if (unlikely(acx_debug & (L_XFER|L_DATA))) {
|
|
+ u16 fc = ((wlan_hdr_t*)hostdesc1->data)->fc;
|
|
+ if (IS_ACX111(adev))
|
|
+ printk("tx: pkt (%s): len %d "
|
|
+ "rate %04X%s status %u\n",
|
|
+ acx_get_packet_type_string(le16_to_cpu(fc)), len,
|
|
+ le16_to_cpu(txdesc->u.r2.rate111),
|
|
+ (le16_to_cpu(txdesc->u.r2.rate111) & RATE111_SHORTPRE) ? "(SPr)" : "",
|
|
+ adev->status);
|
|
+ else
|
|
+ printk("tx: pkt (%s): len %d rate %03u%s status %u\n",
|
|
+ acx_get_packet_type_string(fc), len,
|
|
+ txdesc->u.r1.rate,
|
|
+ (Ctl_8 & DESC_CTL_SHORT_PREAMBLE) ? "(SPr)" : "",
|
|
+ adev->status);
|
|
+
|
|
+ if (acx_debug & L_DATA) {
|
|
+ printk("tx: 802.11 [%d]: ", len);
|
|
+ acx_dump_bytes(hostdesc1->data, len);
|
|
+ }
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_l_clean_txdesc
|
|
+**
|
|
+** This function resets the txdescs' status when the ACX100
|
|
+** signals the TX done IRQ (txdescs have been processed), starting with
|
|
+** the pool index of the descriptor which we would use next,
|
|
+** in order to make sure that we can be as fast as possible
|
|
+** in filling new txdescs.
|
|
+** Everytime we get called we know where the next packet to be cleaned is.
|
|
+*/
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+static inline void log_txbuffer(const acx_device_t *adev) {}
|
|
+#else
|
|
+static void
|
|
+log_txbuffer(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+
|
|
+ /* no FN_ENTER here, we don't want that */
|
|
+ /* no locks here, since it's entirely non-critical code */
|
|
+ txdesc = adev->txdesc_start;
|
|
+ if (unlikely(!txdesc)) return;
|
|
+ printk("tx: desc->Ctl8's:");
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ printk(" %02X", txdesc->Ctl_8);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ printk("\n");
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+static void
|
|
+handle_tx_error(acx_device_t *adev, u8 error, unsigned int finger)
|
|
+{
|
|
+ const char *err = "unknown error";
|
|
+
|
|
+ /* hmm, should we handle this as a mask
|
|
+ * of *several* bits?
|
|
+ * For now I think only caring about
|
|
+ * individual bits is ok... */
|
|
+ switch (error) {
|
|
+ case 0x01:
|
|
+ err = "no Tx due to error in other fragment";
|
|
+ adev->wstats.discard.fragment++;
|
|
+ break;
|
|
+ case 0x02:
|
|
+ err = "Tx aborted";
|
|
+ adev->stats.tx_aborted_errors++;
|
|
+ break;
|
|
+ case 0x04:
|
|
+ err = "Tx desc wrong parameters";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x08:
|
|
+ err = "WEP key not found";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x10:
|
|
+ err = "MSDU lifetime timeout? - try changing "
|
|
+ "'iwconfig retry lifetime XXX'";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ case 0x20:
|
|
+ err = "excessive Tx retries due to either distance "
|
|
+ "too high or unable to Tx or Tx frame error - "
|
|
+ "try changing 'iwconfig txpower XXX' or "
|
|
+ "'sens'itivity or 'retry'";
|
|
+ adev->wstats.discard.retries++;
|
|
+ /* Tx error 0x20 also seems to occur on
|
|
+ * overheating, so I'm not sure whether we
|
|
+ * actually want to do aggressive radio recalibration,
|
|
+ * since people maybe won't notice then that their hardware
|
|
+ * is slowly getting cooked...
|
|
+ * Or is it still a safe long distance from utter
|
|
+ * radio non-functionality despite many radio recalibs
|
|
+ * to final destructive overheating of the hardware?
|
|
+ * In this case we really should do recalib here...
|
|
+ * I guess the only way to find out is to do a
|
|
+ * potentially fatal self-experiment :-\
|
|
+ * Or maybe only recalib in case we're using Tx
|
|
+ * rate auto (on errors switching to lower speed
|
|
+ * --> less heat?) or 802.11 power save mode?
|
|
+ *
|
|
+ * ok, just do it. */
|
|
+ if (++adev->retry_errors_msg_ratelimit % 4 == 0) {
|
|
+ if (adev->retry_errors_msg_ratelimit <= 20) {
|
|
+ printk("%s: several excessive Tx "
|
|
+ "retry errors occurred, attempting "
|
|
+ "to recalibrate radio. Radio "
|
|
+ "drift might be caused by increasing "
|
|
+ "card temperature, please check the card "
|
|
+ "before it's too late!\n",
|
|
+ adev->ndev->name);
|
|
+ if (adev->retry_errors_msg_ratelimit == 20)
|
|
+ printk("disabling above message\n");
|
|
+ }
|
|
+
|
|
+ acx_schedule_task(adev, ACX_AFTER_IRQ_CMD_RADIO_RECALIB);
|
|
+ }
|
|
+ break;
|
|
+ case 0x40:
|
|
+ err = "Tx buffer overflow";
|
|
+ adev->stats.tx_fifo_errors++;
|
|
+ break;
|
|
+ case 0x80:
|
|
+ /* possibly ACPI C-state powersaving related!!!
|
|
+ * (DMA timeout due to excessively high wakeup
|
|
+ * latency after C-state activation!?)
|
|
+ * Disable C-State powersaving and try again,
|
|
+ * then PLEASE REPORT, I'm VERY interested in
|
|
+ * whether my theory is correct that this is
|
|
+ * actually the problem here.
|
|
+ * In that case, use new Linux idle wakeup latency
|
|
+ * requirements kernel API to prevent this issue. */
|
|
+ err = "DMA error";
|
|
+ adev->wstats.discard.misc++;
|
|
+ break;
|
|
+ }
|
|
+ adev->stats.tx_errors++;
|
|
+ if (adev->stats.tx_errors <= 20)
|
|
+ printk("%s: tx error 0x%02X, buf %02u! (%s)\n",
|
|
+ adev->ndev->name, error, finger, err);
|
|
+ else
|
|
+ printk("%s: tx error 0x%02X, buf %02u!\n",
|
|
+ adev->ndev->name, error, finger);
|
|
+}
|
|
+
|
|
+
|
|
+unsigned int
|
|
+acxpci_l_clean_txdesc(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ unsigned finger;
|
|
+ int num_cleaned;
|
|
+ u16 r111;
|
|
+ u8 error, ack_failures, rts_failures, rts_ok, r100;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (unlikely(acx_debug & L_DEBUG))
|
|
+ log_txbuffer(adev);
|
|
+
|
|
+ log(L_BUFT, "tx: cleaning up bufs from %u\n", adev->tx_tail);
|
|
+
|
|
+ /* We know first descr which is not free yet. We advance it as far
|
|
+ ** as we see correct bits set in following descs (if next desc
|
|
+ ** is NOT free, we shouldn't advance at all). We know that in
|
|
+ ** front of tx_tail may be "holes" with isolated free descs.
|
|
+ ** We will catch up when all intermediate descs will be freed also */
|
|
+
|
|
+ finger = adev->tx_tail;
|
|
+ num_cleaned = 0;
|
|
+ while (likely(finger != adev->tx_head)) {
|
|
+ txdesc = get_txdesc(adev, finger);
|
|
+
|
|
+ /* If we allocated txdesc on tx path but then decided
|
|
+ ** to NOT use it, then it will be left as a free "bubble"
|
|
+ ** in the "allocated for tx" part of the ring.
|
|
+ ** We may meet it on the next ring pass here. */
|
|
+
|
|
+ /* stop if not marked as "tx finished" and "host owned" */
|
|
+ if ((txdesc->Ctl_8 & DESC_CTL_ACXDONE_HOSTOWN)
|
|
+ != DESC_CTL_ACXDONE_HOSTOWN) {
|
|
+ if (unlikely(!num_cleaned)) { /* maybe remove completely */
|
|
+ log(L_BUFT, "clean_txdesc: tail isn't free. "
|
|
+ "tail:%d head:%d\n",
|
|
+ adev->tx_tail, adev->tx_head);
|
|
+ }
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* remember desc values... */
|
|
+ error = txdesc->error;
|
|
+ ack_failures = txdesc->ack_failures;
|
|
+ rts_failures = txdesc->rts_failures;
|
|
+ rts_ok = txdesc->rts_ok;
|
|
+ r100 = txdesc->u.r1.rate;
|
|
+ r111 = le16_to_cpu(txdesc->u.r2.rate111);
|
|
+
|
|
+ /* need to check for certain error conditions before we
|
|
+ * clean the descriptor: we still need valid descr data here */
|
|
+ if (unlikely(0x30 & error)) {
|
|
+ /* only send IWEVTXDROP in case of retry or lifetime exceeded;
|
|
+ * all other errors mean we screwed up locally */
|
|
+ union iwreq_data wrqu;
|
|
+ wlan_hdr_t *hdr;
|
|
+ txhostdesc_t *hostdesc;
|
|
+
|
|
+ hostdesc = get_txhostdesc(adev, txdesc);
|
|
+ hdr = (wlan_hdr_t *)hostdesc->data;
|
|
+ MAC_COPY(wrqu.addr.sa_data, hdr->a1);
|
|
+ wireless_send_event(adev->ndev, IWEVTXDROP, &wrqu, NULL);
|
|
+ }
|
|
+
|
|
+ /* ...and free the desc */
|
|
+ txdesc->error = 0;
|
|
+ txdesc->ack_failures = 0;
|
|
+ txdesc->rts_failures = 0;
|
|
+ txdesc->rts_ok = 0;
|
|
+ /* signal host owning it LAST, since ACX already knows that this
|
|
+ ** descriptor is finished since it set Ctl_8 accordingly. */
|
|
+ txdesc->Ctl_8 = DESC_CTL_HOSTOWN;
|
|
+
|
|
+ adev->tx_free++;
|
|
+ num_cleaned++;
|
|
+
|
|
+ if ((adev->tx_free >= TX_START_QUEUE)
|
|
+ && (adev->status == ACX_STATUS_4_ASSOCIATED)
|
|
+ && (acx_queue_stopped(adev->ndev))
|
|
+ ) {
|
|
+ log(L_BUF, "tx: wake queue (avail. Tx desc %u)\n",
|
|
+ adev->tx_free);
|
|
+ acx_wake_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+ /* do error checking, rate handling and logging
|
|
+ * AFTER having done the work, it's faster */
|
|
+
|
|
+ /* do rate handling */
|
|
+ if (adev->rate_auto) {
|
|
+ struct client *clt = get_txc(adev, txdesc);
|
|
+ if (clt) {
|
|
+ u16 cur = get_txr(adev, txdesc);
|
|
+ if (clt->rate_cur == cur) {
|
|
+ acx_l_handle_txrate_auto(adev, clt,
|
|
+ cur, /* intended rate */
|
|
+ r100, r111, /* actually used rate */
|
|
+ (error & 0x30), /* was there an error? */
|
|
+ TX_CNT + TX_CLEAN_BACKLOG - adev->tx_free);
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+
|
|
+ if (unlikely(error))
|
|
+ handle_tx_error(adev, error, finger);
|
|
+
|
|
+ if (IS_ACX111(adev))
|
|
+ log(L_BUFT, "tx: cleaned %u: !ACK=%u !RTS=%u RTS=%u r111=%04X\n",
|
|
+ finger, ack_failures, rts_failures, rts_ok, r111);
|
|
+ else
|
|
+ log(L_BUFT, "tx: cleaned %u: !ACK=%u !RTS=%u RTS=%u rate=%u\n",
|
|
+ finger, ack_failures, rts_failures, rts_ok, r100);
|
|
+
|
|
+ /* update pointer for descr to be cleaned next */
|
|
+ finger = (finger + 1) % TX_CNT;
|
|
+ }
|
|
+
|
|
+ /* remember last position */
|
|
+ adev->tx_tail = finger;
|
|
+/* end: */
|
|
+ FN_EXIT1(num_cleaned);
|
|
+ return num_cleaned;
|
|
+}
|
|
+
|
|
+/* clean *all* Tx descriptors, and regardless of their previous state.
|
|
+ * Used for brute-force reset handling. */
|
|
+void
|
|
+acxpci_l_clean_txdesc_emergency(acx_device_t *adev)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ txdesc = get_txdesc(adev, i);
|
|
+
|
|
+ /* free it */
|
|
+ txdesc->ack_failures = 0;
|
|
+ txdesc->rts_failures = 0;
|
|
+ txdesc->rts_ok = 0;
|
|
+ txdesc->error = 0;
|
|
+ txdesc->Ctl_8 = DESC_CTL_HOSTOWN;
|
|
+ }
|
|
+
|
|
+ adev->tx_free = TX_CNT;
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_s_create_tx_host_desc_queue
|
|
+*/
|
|
+
|
|
+static void*
|
|
+allocate(acx_device_t *adev, size_t size, dma_addr_t *phy, const char *msg)
|
|
+{
|
|
+ void *ptr;
|
|
+
|
|
+ ptr = dma_alloc_coherent(adev->pdev ? &adev->pdev->dev : NULL,
|
|
+ size, phy, GFP_KERNEL);
|
|
+
|
|
+ if (ptr) {
|
|
+ log(L_DEBUG, "%s sz=%d adr=0x%p phy=0x%08llx\n",
|
|
+ msg, (int)size, ptr, (unsigned long long)*phy);
|
|
+ memset(ptr, 0, size);
|
|
+ return ptr;
|
|
+ }
|
|
+ printk(KERN_ERR "acx: %s allocation FAILED (%d bytes)\n",
|
|
+ msg, (int)size);
|
|
+ return NULL;
|
|
+}
|
|
+
|
|
+
|
|
+static int
|
|
+acxpci_s_create_tx_host_desc_queue(acx_device_t *adev)
|
|
+{
|
|
+ txhostdesc_t *hostdesc;
|
|
+ u8 *txbuf;
|
|
+ dma_addr_t hostdesc_phy;
|
|
+ dma_addr_t txbuf_phy;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* allocate TX buffer */
|
|
+ adev->txbuf_area_size = TX_CNT * WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ adev->txbuf_start = allocate(adev, adev->txbuf_area_size,
|
|
+ &adev->txbuf_startphy, "txbuf_start");
|
|
+ if (!adev->txbuf_start)
|
|
+ goto fail;
|
|
+
|
|
+ /* allocate the TX host descriptor queue pool */
|
|
+ adev->txhostdesc_area_size = TX_CNT * 2*sizeof(*hostdesc);
|
|
+ adev->txhostdesc_start = allocate(adev, adev->txhostdesc_area_size,
|
|
+ &adev->txhostdesc_startphy, "txhostdesc_start");
|
|
+ if (!adev->txhostdesc_start)
|
|
+ goto fail;
|
|
+ /* check for proper alignment of TX host descriptor pool */
|
|
+ if ((long) adev->txhostdesc_start & 3) {
|
|
+ printk("acx: driver bug: dma alloc returns unaligned address\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ hostdesc = adev->txhostdesc_start;
|
|
+ hostdesc_phy = adev->txhostdesc_startphy;
|
|
+ txbuf = adev->txbuf_start;
|
|
+ txbuf_phy = adev->txbuf_startphy;
|
|
+
|
|
+#if 0
|
|
+/* Each tx buffer is accessed by hardware via
|
|
+** txdesc -> txhostdesc(s) -> txbuffer(s).
|
|
+** We use only one txhostdesc per txdesc, but it looks like
|
|
+** acx111 is buggy: it accesses second txhostdesc
|
|
+** (via hostdesc.desc_phy_next field) even if
|
|
+** txdesc->length == hostdesc->length and thus
|
|
+** entire packet was placed into first txhostdesc.
|
|
+** Due to this bug acx111 hangs unless second txhostdesc
|
|
+** has le16_to_cpu(hostdesc.length) = 3 (or larger)
|
|
+** Storing NULL into hostdesc.desc_phy_next
|
|
+** doesn't seem to help.
|
|
+**
|
|
+** Update: although it worked on Xterasys XN-2522g
|
|
+** with len=3 trick, WG311v2 is even more bogus, doesn't work.
|
|
+** Keeping this code (#ifdef'ed out) for documentational purposes.
|
|
+*/
|
|
+ for (i = 0; i < TX_CNT*2; i++) {
|
|
+ hostdesc_phy += sizeof(*hostdesc);
|
|
+ if (!(i & 1)) {
|
|
+ hostdesc->data_phy = cpu2acx(txbuf_phy);
|
|
+ /* hostdesc->data_offset = ... */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ hostdesc->Ctl_16 = cpu_to_le16(DESC_CTL_HOSTOWN);
|
|
+ /* hostdesc->length = ... */
|
|
+ hostdesc->desc_phy_next = cpu2acx(hostdesc_phy);
|
|
+ hostdesc->pNext = ptr2acx(NULL);
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* below: non-hardware fields */
|
|
+ hostdesc->data = txbuf;
|
|
+
|
|
+ txbuf += WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ txbuf_phy += WLAN_A4FR_MAXLEN_WEP_FCS;
|
|
+ } else {
|
|
+ /* hostdesc->data_phy = ... */
|
|
+ /* hostdesc->data_offset = ... */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ /* hostdesc->Ctl_16 = ... */
|
|
+ hostdesc->length = cpu_to_le16(3); /* bug workaround */
|
|
+ /* hostdesc->desc_phy_next = ... */
|
|
+ /* hostdesc->pNext = ... */
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* below: non-hardware fields */
|
|
+ /* hostdesc->data = ... */
|
|
+ }
|
|
+ hostdesc++;
|
|
+ }
|
|
+#endif
|
|
+/* We initialize two hostdescs so that they point to adjacent
|
|
+** memory areas. Thus txbuf is really just a contiguous memory area */
|
|
+ for (i = 0; i < TX_CNT*2; i++) {
|
|
+ hostdesc_phy += sizeof(*hostdesc);
|
|
+
|
|
+ hostdesc->data_phy = cpu2acx(txbuf_phy);
|
|
+ /* done by memset(0): hostdesc->data_offset = 0; */
|
|
+ /* hostdesc->reserved = ... */
|
|
+ hostdesc->Ctl_16 = cpu_to_le16(DESC_CTL_HOSTOWN);
|
|
+ /* hostdesc->length = ... */
|
|
+ hostdesc->desc_phy_next = cpu2acx(hostdesc_phy);
|
|
+ /* done by memset(0): hostdesc->pNext = ptr2acx(NULL); */
|
|
+ /* hostdesc->Status = ... */
|
|
+ /* ->data is a non-hardware field: */
|
|
+ hostdesc->data = txbuf;
|
|
+
|
|
+ if (!(i & 1)) {
|
|
+ txbuf += WLAN_HDR_A3_LEN;
|
|
+ txbuf_phy += WLAN_HDR_A3_LEN;
|
|
+ } else {
|
|
+ txbuf += WLAN_A4FR_MAXLEN_WEP_FCS - WLAN_HDR_A3_LEN;
|
|
+ txbuf_phy += WLAN_A4FR_MAXLEN_WEP_FCS - WLAN_HDR_A3_LEN;
|
|
+ }
|
|
+ hostdesc++;
|
|
+ }
|
|
+ hostdesc--;
|
|
+ hostdesc->desc_phy_next = cpu2acx(adev->txhostdesc_startphy);
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+ printk("acx: create_tx_host_desc_queue FAILED\n");
|
|
+ /* dealloc will be done by free function on error case */
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_s_create_rx_host_desc_queue
|
|
+*/
|
|
+/* the whole size of a data buffer (header plus data body)
|
|
+ * plus 32 bytes safety offset at the end */
|
|
+#define RX_BUFFER_SIZE (sizeof(rxbuffer_t) + 32)
|
|
+
|
|
+static int
|
|
+acxpci_s_create_rx_host_desc_queue(acx_device_t *adev)
|
|
+{
|
|
+ rxhostdesc_t *hostdesc;
|
|
+ rxbuffer_t *rxbuf;
|
|
+ dma_addr_t hostdesc_phy;
|
|
+ dma_addr_t rxbuf_phy;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* allocate the RX host descriptor queue pool */
|
|
+ adev->rxhostdesc_area_size = RX_CNT * sizeof(*hostdesc);
|
|
+ adev->rxhostdesc_start = allocate(adev, adev->rxhostdesc_area_size,
|
|
+ &adev->rxhostdesc_startphy, "rxhostdesc_start");
|
|
+ if (!adev->rxhostdesc_start)
|
|
+ goto fail;
|
|
+ /* check for proper alignment of RX host descriptor pool */
|
|
+ if ((long) adev->rxhostdesc_start & 3) {
|
|
+ printk("acx: driver bug: dma alloc returns unaligned address\n");
|
|
+ goto fail;
|
|
+ }
|
|
+
|
|
+ /* allocate Rx buffer pool which will be used by the acx
|
|
+ * to store the whole content of the received frames in it */
|
|
+ adev->rxbuf_area_size = RX_CNT * RX_BUFFER_SIZE;
|
|
+ adev->rxbuf_start = allocate(adev, adev->rxbuf_area_size,
|
|
+ &adev->rxbuf_startphy, "rxbuf_start");
|
|
+ if (!adev->rxbuf_start)
|
|
+ goto fail;
|
|
+
|
|
+ rxbuf = adev->rxbuf_start;
|
|
+ rxbuf_phy = adev->rxbuf_startphy;
|
|
+ hostdesc = adev->rxhostdesc_start;
|
|
+ hostdesc_phy = adev->rxhostdesc_startphy;
|
|
+
|
|
+ /* don't make any popular C programming pointer arithmetic mistakes
|
|
+ * here, otherwise I'll kill you...
|
|
+ * (and don't dare asking me why I'm warning you about that...) */
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ hostdesc->data = rxbuf;
|
|
+ hostdesc->data_phy = cpu2acx(rxbuf_phy);
|
|
+ hostdesc->length = cpu_to_le16(RX_BUFFER_SIZE);
|
|
+ CLEAR_BIT(hostdesc->Ctl_16, cpu_to_le16(DESC_CTL_HOSTOWN));
|
|
+ rxbuf++;
|
|
+ rxbuf_phy += sizeof(*rxbuf);
|
|
+ hostdesc_phy += sizeof(*hostdesc);
|
|
+ hostdesc->desc_phy_next = cpu2acx(hostdesc_phy);
|
|
+ hostdesc++;
|
|
+ }
|
|
+ hostdesc--;
|
|
+ hostdesc->desc_phy_next = cpu2acx(adev->rxhostdesc_startphy);
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+fail:
|
|
+ printk("acx: create_rx_host_desc_queue FAILED\n");
|
|
+ /* dealloc will be done by free function on error case */
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_s_create_hostdesc_queues
|
|
+*/
|
|
+int
|
|
+acxpci_s_create_hostdesc_queues(acx_device_t *adev)
|
|
+{
|
|
+ int result;
|
|
+ result = acxpci_s_create_tx_host_desc_queue(adev);
|
|
+ if (OK != result) return result;
|
|
+ result = acxpci_s_create_rx_host_desc_queue(adev);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_create_tx_desc_queue
|
|
+*/
|
|
+static void
|
|
+acxpci_create_tx_desc_queue(acx_device_t *adev, u32 tx_queue_start)
|
|
+{
|
|
+ txdesc_t *txdesc;
|
|
+ txhostdesc_t *hostdesc;
|
|
+ dma_addr_t hostmemptr;
|
|
+ u32 mem_offs;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (IS_ACX100(adev))
|
|
+ adev->txdesc_size = sizeof(*txdesc);
|
|
+ else
|
|
+ /* the acx111 txdesc is 4 bytes larger */
|
|
+ adev->txdesc_size = sizeof(*txdesc) + 4;
|
|
+
|
|
+ adev->txdesc_start = (txdesc_t *) (adev->iobase2 + tx_queue_start);
|
|
+
|
|
+ log(L_DEBUG, "adev->iobase2=%p\n"
|
|
+ "tx_queue_start=%08X\n"
|
|
+ "adev->txdesc_start=%p\n",
|
|
+ adev->iobase2,
|
|
+ tx_queue_start,
|
|
+ adev->txdesc_start);
|
|
+
|
|
+ adev->tx_free = TX_CNT;
|
|
+ /* done by memset: adev->tx_head = 0; */
|
|
+ /* done by memset: adev->tx_tail = 0; */
|
|
+ txdesc = adev->txdesc_start;
|
|
+ mem_offs = tx_queue_start;
|
|
+ hostmemptr = adev->txhostdesc_startphy;
|
|
+ hostdesc = adev->txhostdesc_start;
|
|
+
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* ACX111 has a preinitialized Tx buffer! */
|
|
+ /* loop over whole send pool */
|
|
+ /* FIXME: do we have to do the hostmemptr stuff here?? */
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ txdesc->HostMemPtr = ptr2acx(hostmemptr);
|
|
+ txdesc->Ctl_8 = DESC_CTL_HOSTOWN;
|
|
+ /* reserve two (hdr desc and payload desc) */
|
|
+ hostdesc += 2;
|
|
+ hostmemptr += 2 * sizeof(*hostdesc);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ } else {
|
|
+ /* ACX100 Tx buffer needs to be initialized by us */
|
|
+ /* clear whole send pool. sizeof is safe here (we are acx100) */
|
|
+ memset(adev->txdesc_start, 0, TX_CNT * sizeof(*txdesc));
|
|
+
|
|
+ /* loop over whole send pool */
|
|
+ for (i = 0; i < TX_CNT; i++) {
|
|
+ log(L_DEBUG, "configure card tx descriptor: 0x%p, "
|
|
+ "size: 0x%X\n", txdesc, adev->txdesc_size);
|
|
+
|
|
+ /* pointer to hostdesc memory */
|
|
+ txdesc->HostMemPtr = ptr2acx(hostmemptr);
|
|
+ /* initialise ctl */
|
|
+ txdesc->Ctl_8 = ( DESC_CTL_HOSTOWN | DESC_CTL_RECLAIM
|
|
+ | DESC_CTL_AUTODMA | DESC_CTL_FIRSTFRAG);
|
|
+ /* done by memset(0): txdesc->Ctl2_8 = 0; */
|
|
+ /* point to next txdesc */
|
|
+ txdesc->pNextDesc = cpu2acx(mem_offs + adev->txdesc_size);
|
|
+ /* reserve two (hdr desc and payload desc) */
|
|
+ hostdesc += 2;
|
|
+ hostmemptr += 2 * sizeof(*hostdesc);
|
|
+ /* go to the next one */
|
|
+ mem_offs += adev->txdesc_size;
|
|
+ /* ++ is safe here (we are acx100) */
|
|
+ txdesc++;
|
|
+ }
|
|
+ /* go back to the last one */
|
|
+ txdesc--;
|
|
+ /* and point to the first making it a ring buffer */
|
|
+ txdesc->pNextDesc = cpu2acx(tx_queue_start);
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_create_rx_desc_queue
|
|
+*/
|
|
+static void
|
|
+acxpci_create_rx_desc_queue(acx_device_t *adev, u32 rx_queue_start)
|
|
+{
|
|
+ rxdesc_t *rxdesc;
|
|
+ u32 mem_offs;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* done by memset: adev->rx_tail = 0; */
|
|
+
|
|
+ /* ACX111 doesn't need any further config: preconfigures itself.
|
|
+ * Simply print ring buffer for debugging */
|
|
+ if (IS_ACX111(adev)) {
|
|
+ /* rxdesc_start already set here */
|
|
+
|
|
+ adev->rxdesc_start = (rxdesc_t *) ((u8 *)adev->iobase2 + rx_queue_start);
|
|
+
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ log(L_DEBUG, "rx descriptor %d @ 0x%p\n", i, rxdesc);
|
|
+ rxdesc = adev->rxdesc_start = (rxdesc_t *)
|
|
+ (adev->iobase2 + acx2cpu(rxdesc->pNextDesc));
|
|
+ }
|
|
+ } else {
|
|
+ /* we didn't pre-calculate rxdesc_start in case of ACX100 */
|
|
+ /* rxdesc_start should be right AFTER Tx pool */
|
|
+ adev->rxdesc_start = (rxdesc_t *)
|
|
+ ((u8 *) adev->txdesc_start + (TX_CNT * sizeof(txdesc_t)));
|
|
+ /* NB: sizeof(txdesc_t) above is valid because we know
|
|
+ ** we are in if (acx100) block. Beware of cut-n-pasting elsewhere!
|
|
+ ** acx111's txdesc is larger! */
|
|
+
|
|
+ memset(adev->rxdesc_start, 0, RX_CNT * sizeof(*rxdesc));
|
|
+
|
|
+ /* loop over whole receive pool */
|
|
+ rxdesc = adev->rxdesc_start;
|
|
+ mem_offs = rx_queue_start;
|
|
+ for (i = 0; i < RX_CNT; i++) {
|
|
+ log(L_DEBUG, "rx descriptor @ 0x%p\n", rxdesc);
|
|
+ rxdesc->Ctl_8 = DESC_CTL_RECLAIM | DESC_CTL_AUTODMA;
|
|
+ /* point to next rxdesc */
|
|
+ rxdesc->pNextDesc = cpu2acx(mem_offs + sizeof(*rxdesc));
|
|
+ /* go to the next one */
|
|
+ mem_offs += sizeof(*rxdesc);
|
|
+ rxdesc++;
|
|
+ }
|
|
+ /* go to the last one */
|
|
+ rxdesc--;
|
|
+
|
|
+ /* and point to the first making it a ring buffer */
|
|
+ rxdesc->pNextDesc = cpu2acx(rx_queue_start);
|
|
+ }
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_create_desc_queues
|
|
+*/
|
|
+void
|
|
+acxpci_create_desc_queues(acx_device_t *adev, u32 tx_queue_start, u32 rx_queue_start)
|
|
+{
|
|
+ acxpci_create_tx_desc_queue(adev, tx_queue_start);
|
|
+ acxpci_create_rx_desc_queue(adev, rx_queue_start);
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxpci_s_proc_diag_output
|
|
+*/
|
|
+char*
|
|
+acxpci_s_proc_diag_output(char *p, acx_device_t *adev)
|
|
+{
|
|
+ const char *rtl, *thd, *ttl;
|
|
+ rxhostdesc_t *rxhostdesc;
|
|
+ txdesc_t *txdesc;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ p += sprintf(p, "** Rx buf **\n");
|
|
+ rxhostdesc = adev->rxhostdesc_start;
|
|
+ if (rxhostdesc) for (i = 0; i < RX_CNT; i++) {
|
|
+ rtl = (i == adev->rx_tail) ? " [tail]" : "";
|
|
+ if ((rxhostdesc->Ctl_16 & cpu_to_le16(DESC_CTL_HOSTOWN))
|
|
+ && (rxhostdesc->Status & cpu_to_le32(DESC_STATUS_FULL)) )
|
|
+ p += sprintf(p, "%02u FULL%s\n", i, rtl);
|
|
+ else
|
|
+ p += sprintf(p, "%02u empty%s\n", i, rtl);
|
|
+ rxhostdesc++;
|
|
+ }
|
|
+ p += sprintf(p, "** Tx buf (free %d, Linux netqueue %s) **\n", adev->tx_free,
|
|
+ acx_queue_stopped(adev->ndev) ? "STOPPED" : "running");
|
|
+ txdesc = adev->txdesc_start;
|
|
+ if (txdesc) for (i = 0; i < TX_CNT; i++) {
|
|
+ thd = (i == adev->tx_head) ? " [head]" : "";
|
|
+ ttl = (i == adev->tx_tail) ? " [tail]" : "";
|
|
+ if (txdesc->Ctl_8 & DESC_CTL_ACXDONE)
|
|
+ p += sprintf(p, "%02u free (%02X)%s%s\n", i, txdesc->Ctl_8, thd, ttl);
|
|
+ else
|
|
+ p += sprintf(p, "%02u tx (%02X)%s%s\n", i, txdesc->Ctl_8, thd, ttl);
|
|
+ txdesc = advance_txdesc(adev, txdesc, 1);
|
|
+ }
|
|
+ p += sprintf(p,
|
|
+ "\n"
|
|
+ "** PCI data **\n"
|
|
+ "txbuf_start %p, txbuf_area_size %u, txbuf_startphy %08llx\n"
|
|
+ "txdesc_size %u, txdesc_start %p\n"
|
|
+ "txhostdesc_start %p, txhostdesc_area_size %u, txhostdesc_startphy %08llx\n"
|
|
+ "rxdesc_start %p\n"
|
|
+ "rxhostdesc_start %p, rxhostdesc_area_size %u, rxhostdesc_startphy %08llx\n"
|
|
+ "rxbuf_start %p, rxbuf_area_size %u, rxbuf_startphy %08llx\n",
|
|
+ adev->txbuf_start, adev->txbuf_area_size,
|
|
+ (unsigned long long)adev->txbuf_startphy,
|
|
+ adev->txdesc_size, adev->txdesc_start,
|
|
+ adev->txhostdesc_start, adev->txhostdesc_area_size,
|
|
+ (unsigned long long)adev->txhostdesc_startphy,
|
|
+ adev->rxdesc_start,
|
|
+ adev->rxhostdesc_start, adev->rxhostdesc_area_size,
|
|
+ (unsigned long long)adev->rxhostdesc_startphy,
|
|
+ adev->rxbuf_start, adev->rxbuf_area_size,
|
|
+ (unsigned long long)adev->rxbuf_startphy);
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return p;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxpci_proc_eeprom_output(char *buf, acx_device_t *adev)
|
|
+{
|
|
+ char *p = buf;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ for (i = 0; i < 0x400; i++) {
|
|
+ acxpci_read_eeprom_byte(adev, i, p++);
|
|
+ }
|
|
+
|
|
+ FN_EXIT1(p - buf);
|
|
+ return p - buf;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+void
|
|
+acxpci_set_interrupt_mask(acx_device_t *adev)
|
|
+{
|
|
+ if (IS_ACX111(adev)) {
|
|
+ adev->irq_mask = (u16) ~(0
|
|
+ /* | HOST_INT_RX_DATA */
|
|
+ | HOST_INT_TX_COMPLETE
|
|
+ /* | HOST_INT_TX_XFER */
|
|
+ | HOST_INT_RX_COMPLETE
|
|
+ /* | HOST_INT_DTIM */
|
|
+ /* | HOST_INT_BEACON */
|
|
+ /* | HOST_INT_TIMER */
|
|
+ /* | HOST_INT_KEY_NOT_FOUND */
|
|
+ | HOST_INT_IV_ICV_FAILURE
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ /* | HOST_INT_OVERFLOW */
|
|
+ /* | HOST_INT_PROCESS_ERROR */
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ | HOST_INT_FCS_THRESHOLD
|
|
+ /* | HOST_INT_UNKNOWN */
|
|
+ );
|
|
+ /* Or else acx100 won't signal cmd completion, right? */
|
|
+ adev->irq_mask_off = (u16)~( HOST_INT_CMD_COMPLETE ); /* 0xfdff */
|
|
+ } else {
|
|
+ adev->irq_mask = (u16) ~(0
|
|
+ /* | HOST_INT_RX_DATA */
|
|
+ | HOST_INT_TX_COMPLETE
|
|
+ /* | HOST_INT_TX_XFER */
|
|
+ | HOST_INT_RX_COMPLETE
|
|
+ /* | HOST_INT_DTIM */
|
|
+ /* | HOST_INT_BEACON */
|
|
+ /* | HOST_INT_TIMER */
|
|
+ /* | HOST_INT_KEY_NOT_FOUND */
|
|
+ /* | HOST_INT_IV_ICV_FAILURE */
|
|
+ | HOST_INT_CMD_COMPLETE
|
|
+ | HOST_INT_INFO
|
|
+ /* | HOST_INT_OVERFLOW */
|
|
+ /* | HOST_INT_PROCESS_ERROR */
|
|
+ | HOST_INT_SCAN_COMPLETE
|
|
+ /* | HOST_INT_FCS_THRESHOLD */
|
|
+ /* | HOST_INT_UNKNOWN */
|
|
+ );
|
|
+ adev->irq_mask_off = (u16)~( HOST_INT_UNKNOWN ); /* 0x7fff */
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acx100pci_s_set_tx_level(acx_device_t *adev, u8 level_dbm)
|
|
+{
|
|
+ /* since it can be assumed that at least the Maxim radio has a
|
|
+ * maximum power output of 20dBm and since it also can be
|
|
+ * assumed that these values drive the DAC responsible for
|
|
+ * setting the linear Tx level, I'd guess that these values
|
|
+ * should be the corresponding linear values for a dBm value,
|
|
+ * in other words: calculate the values from that formula:
|
|
+ * Y [dBm] = 10 * log (X [mW])
|
|
+ * then scale the 0..63 value range onto the 1..100mW range (0..20 dBm)
|
|
+ * and you're done...
|
|
+ * Hopefully that's ok, but you never know if we're actually
|
|
+ * right... (especially since Windows XP doesn't seem to show
|
|
+ * actual Tx dBm values :-P) */
|
|
+
|
|
+ /* NOTE: on Maxim, value 30 IS 30mW, and value 10 IS 10mW - so the
|
|
+ * values are EXACTLY mW!!! Not sure about RFMD and others,
|
|
+ * though... */
|
|
+ static const u8 dbm2val_maxim[21] = {
|
|
+ 63, 63, 63, 62,
|
|
+ 61, 61, 60, 60,
|
|
+ 59, 58, 57, 55,
|
|
+ 53, 50, 47, 43,
|
|
+ 38, 31, 23, 13,
|
|
+ 0
|
|
+ };
|
|
+ static const u8 dbm2val_rfmd[21] = {
|
|
+ 0, 0, 0, 1,
|
|
+ 2, 2, 3, 3,
|
|
+ 4, 5, 6, 8,
|
|
+ 10, 13, 16, 20,
|
|
+ 25, 32, 41, 50,
|
|
+ 63
|
|
+ };
|
|
+ const u8 *table;
|
|
+
|
|
+ switch (adev->radio_type) {
|
|
+ case RADIO_MAXIM_0D:
|
|
+ table = &dbm2val_maxim[0];
|
|
+ break;
|
|
+ case RADIO_RFMD_11:
|
|
+ case RADIO_RALINK_15:
|
|
+ table = &dbm2val_rfmd[0];
|
|
+ break;
|
|
+ default:
|
|
+ printk("%s: unknown/unsupported radio type, "
|
|
+ "cannot modify tx power level yet!\n",
|
|
+ adev->ndev->name);
|
|
+ return NOT_OK;
|
|
+ }
|
|
+ printk("%s: changing radio power level to %u dBm (%u)\n",
|
|
+ adev->ndev->name, level_dbm, table[level_dbm]);
|
|
+ acxpci_s_write_phy_reg(adev, 0x11, table[level_dbm]);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Data for init_module/cleanup_module
|
|
+*/
|
|
+static const struct pci_device_id
|
|
+acxpci_id_tbl[] __devinitdata = {
|
|
+ {
|
|
+ .vendor = PCI_VENDOR_ID_TI,
|
|
+ .device = PCI_DEVICE_ID_TI_TNETW1100A,
|
|
+ .subvendor = PCI_ANY_ID,
|
|
+ .subdevice = PCI_ANY_ID,
|
|
+ .driver_data = CHIPTYPE_ACX100,
|
|
+ },
|
|
+ {
|
|
+ .vendor = PCI_VENDOR_ID_TI,
|
|
+ .device = PCI_DEVICE_ID_TI_TNETW1100B,
|
|
+ .subvendor = PCI_ANY_ID,
|
|
+ .subdevice = PCI_ANY_ID,
|
|
+ .driver_data = CHIPTYPE_ACX100,
|
|
+ },
|
|
+ {
|
|
+ .vendor = PCI_VENDOR_ID_TI,
|
|
+ .device = PCI_DEVICE_ID_TI_TNETW1130,
|
|
+ .subvendor = PCI_ANY_ID,
|
|
+ .subdevice = PCI_ANY_ID,
|
|
+ .driver_data = CHIPTYPE_ACX111,
|
|
+ },
|
|
+ {
|
|
+ .vendor = 0,
|
|
+ .device = 0,
|
|
+ .subvendor = 0,
|
|
+ .subdevice = 0,
|
|
+ .driver_data = 0,
|
|
+ }
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(pci, acxpci_id_tbl);
|
|
+
|
|
+/* FIXME: checks should be removed once driver is included in the kernel */
|
|
+#if LINUX_VERSION_CODE < KERNEL_VERSION(2, 6, 11)
|
|
+/* pci_name() got introduced at start of 2.6.x,
|
|
+ * got mandatory (slot_name member removed) in 2.6.11-bk1 */
|
|
+#define pci_name(x) x->slot_name
|
|
+#endif
|
|
+
|
|
+static struct pci_driver
|
|
+acxpci_drv_id = {
|
|
+ .name = "acx_pci",
|
|
+ .id_table = acxpci_id_tbl,
|
|
+ .probe = acxpci_e_probe,
|
|
+ .remove = __devexit_p(acxpci_e_remove),
|
|
+#ifdef CONFIG_PM
|
|
+ .suspend = acxpci_e_suspend,
|
|
+ .resume = acxpci_e_resume
|
|
+#endif /* CONFIG_PM */
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_e_init_module
|
|
+**
|
|
+** Module initialization routine, called once at module load time
|
|
+*/
|
|
+int __init
|
|
+acxpci_e_init_module(void)
|
|
+{
|
|
+ int res;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+#if (ACX_IO_WIDTH==32)
|
|
+ printk("acx: compiled to use 32bit I/O access. "
|
|
+ "I/O timing issues might occur, such as "
|
|
+ "non-working firmware upload. Report them\n");
|
|
+#else
|
|
+ printk("acx: compiled to use 16bit I/O access only "
|
|
+ "(compatibility mode)\n");
|
|
+#endif
|
|
+
|
|
+#ifdef __LITTLE_ENDIAN
|
|
+#define ENDIANNESS_STRING "running on a little-endian CPU\n"
|
|
+#else
|
|
+#define ENDIANNESS_STRING "running on a BIG-ENDIAN CPU\n"
|
|
+#endif
|
|
+ log(L_INIT,
|
|
+ ENDIANNESS_STRING
|
|
+ "PCI module " ACX_RELEASE " initialized, "
|
|
+ "waiting for cards to probe...\n"
|
|
+ );
|
|
+
|
|
+ res = pci_register_driver(&acxpci_drv_id);
|
|
+ FN_EXIT1(res);
|
|
+ return res;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxpci_e_cleanup_module
|
|
+**
|
|
+** Called at module unload time. This is our last chance to
|
|
+** clean up after ourselves.
|
|
+*/
|
|
+void __exit
|
|
+acxpci_e_cleanup_module(void)
|
|
+{
|
|
+ FN_ENTER;
|
|
+
|
|
+ pci_unregister_driver(&acxpci_drv_id);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/rx3000_acx.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/rx3000_acx.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,110 @@
|
|
+/*
|
|
+ * WLAN (TI TNETW1100B) support in the HP iPAQ RX3000
|
|
+ *
|
|
+ * Copyright (c) 2006 SDG Systems, LLC
|
|
+ * Copyright (c) 2006 Roman Moravcik
|
|
+ *
|
|
+ * This file is subject to the terms and conditions of the GNU General Public
|
|
+ * License. See the file COPYING in the main directory of this archive for
|
|
+ * more details.
|
|
+ *
|
|
+ * Based on hx4700_acx.c
|
|
+ */
|
|
+
|
|
+
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/platform_device.h>
|
|
+#include <linux/delay.h>
|
|
+#include <linux/dpm.h>
|
|
+#include <linux/leds.h>
|
|
+
|
|
+#include <asm/hardware.h>
|
|
+
|
|
+#include <asm/arch/regs-gpio.h>
|
|
+#include <linux/mfd/asic3_base.h>
|
|
+#include <asm/arch/rx3000.h>
|
|
+#include <asm/arch/rx3000-asic3.h>
|
|
+#include <asm/io.h>
|
|
+
|
|
+#include "acx_hw.h"
|
|
+
|
|
+extern struct platform_device s3c_device_asic3;
|
|
+
|
|
+static int rx3000_wlan_start(void)
|
|
+{
|
|
+ DPM_DEBUG("rx3000_acx: Turning on\n");
|
|
+ asic3_set_gpio_out_b(&s3c_device_asic3.dev, ASIC3_GPB3, ASIC3_GPB3);
|
|
+ mdelay(20);
|
|
+ asic3_set_gpio_out_c(&s3c_device_asic3.dev, ASIC3_GPC13, ASIC3_GPC13);
|
|
+ mdelay(20);
|
|
+ asic3_set_gpio_out_c(&s3c_device_asic3.dev, ASIC3_GPC11, ASIC3_GPC11);
|
|
+ mdelay(100);
|
|
+ asic3_set_gpio_out_b(&s3c_device_asic3.dev, ASIC3_GPB3, ASIC3_GPB3);
|
|
+ mdelay(20);
|
|
+ s3c2410_gpio_cfgpin(S3C2410_GPA15, S3C2410_GPA15_nGCS4);
|
|
+ mdelay(100);
|
|
+ s3c2410_gpio_setpin(S3C2410_GPA11, 0);
|
|
+ mdelay(50);
|
|
+ s3c2410_gpio_setpin(S3C2410_GPA11, 1);
|
|
+ led_trigger_event_shared(rx3000_radio_trig, LED_FULL);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static int rx3000_wlan_stop(void)
|
|
+{
|
|
+ DPM_DEBUG("rx3000_acx: Turning off\n");
|
|
+ s3c2410_gpio_setpin(S3C2410_GPA15, 1);
|
|
+ s3c2410_gpio_cfgpin(S3C2410_GPA15, S3C2410_GPA15_OUT);
|
|
+ asic3_set_gpio_out_b(&s3c_device_asic3.dev, ASIC3_GPB3, 0);
|
|
+ asic3_set_gpio_out_c(&s3c_device_asic3.dev, ASIC3_GPC13, 0);
|
|
+ asic3_set_gpio_out_c(&s3c_device_asic3.dev, ASIC3_GPC11, 0);
|
|
+ led_trigger_event_shared(rx3000_radio_trig, LED_OFF);
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static struct resource acx_resources[] = {
|
|
+ [0] = {
|
|
+ .start = RX3000_PA_WLAN,
|
|
+ .end = RX3000_PA_WLAN + 0x20,
|
|
+ .flags = IORESOURCE_MEM,
|
|
+ },
|
|
+ [1] = {
|
|
+ .start = IRQ_EINT16,
|
|
+ .end = IRQ_EINT16,
|
|
+ .flags = IORESOURCE_IRQ,
|
|
+ },
|
|
+};
|
|
+
|
|
+static struct acx_hardware_data acx_data = {
|
|
+ .start_hw = rx3000_wlan_start,
|
|
+ .stop_hw = rx3000_wlan_stop,
|
|
+};
|
|
+
|
|
+static struct platform_device acx_device = {
|
|
+ .name = "acx-mem",
|
|
+ .dev = {
|
|
+ .platform_data = &acx_data,
|
|
+ },
|
|
+ .num_resources = ARRAY_SIZE(acx_resources),
|
|
+ .resource = acx_resources,
|
|
+};
|
|
+
|
|
+static int __init rx3000_wlan_init(void)
|
|
+{
|
|
+ printk("rx3000_wlan_init: acx-mem platform_device_register\n");
|
|
+ return platform_device_register(&acx_device);
|
|
+}
|
|
+
|
|
+
|
|
+static void __exit rx3000_wlan_exit(void)
|
|
+{
|
|
+ platform_device_unregister(&acx_device);
|
|
+}
|
|
+
|
|
+module_init(rx3000_wlan_init);
|
|
+module_exit(rx3000_wlan_exit);
|
|
+
|
|
+MODULE_AUTHOR("Todd Blumer <todd@sdgsystems.com>, Roman Moravcik <roman.moravcik@gmail.com>");
|
|
+MODULE_DESCRIPTION("WLAN driver for HP iPAQ RX3000");
|
|
+MODULE_LICENSE("GPL");
|
|
+
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/setrate.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/setrate.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,213 @@
|
|
+/* TODO: stop #including, move into wireless.c
|
|
+ * until then, keep in sync copies in prism54/ and acx/ dirs
|
|
+ * code+data size: less than 1k */
|
|
+
|
|
+enum {
|
|
+ DOT11_RATE_1,
|
|
+ DOT11_RATE_2,
|
|
+ DOT11_RATE_5,
|
|
+ DOT11_RATE_11,
|
|
+ DOT11_RATE_22,
|
|
+ DOT11_RATE_33,
|
|
+ DOT11_RATE_6,
|
|
+ DOT11_RATE_9,
|
|
+ DOT11_RATE_12,
|
|
+ DOT11_RATE_18,
|
|
+ DOT11_RATE_24,
|
|
+ DOT11_RATE_36,
|
|
+ DOT11_RATE_48,
|
|
+ DOT11_RATE_54
|
|
+};
|
|
+enum {
|
|
+ DOT11_MOD_DBPSK,
|
|
+ DOT11_MOD_DQPSK,
|
|
+ DOT11_MOD_CCK,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_CCKOFDM,
|
|
+ DOT11_MOD_PBCC
|
|
+};
|
|
+static const u8 ratelist[] = { 1,2,5,11,22,33,6,9,12,18,24,36,48,54 };
|
|
+static const u8 dot11ratebyte[] = { 1*2,2*2,11,11*2,22*2,33*2,6*2,9*2,12*2,18*2,24*2,36*2,48*2,54*2 };
|
|
+static const u8 default_modulation[] = {
|
|
+ DOT11_MOD_DBPSK,
|
|
+ DOT11_MOD_DQPSK,
|
|
+ DOT11_MOD_CCK,
|
|
+ DOT11_MOD_CCK,
|
|
+ DOT11_MOD_PBCC,
|
|
+ DOT11_MOD_PBCC,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM,
|
|
+ DOT11_MOD_OFDM
|
|
+};
|
|
+
|
|
+static /* TODO: remove 'static' when moved to wireless.c */
|
|
+int
|
|
+rate_mbit2enum(int n) {
|
|
+ int i=0;
|
|
+ while(i<sizeof(ratelist)) {
|
|
+ if(n==ratelist[i]) return i;
|
|
+ i++;
|
|
+ }
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+static int
|
|
+get_modulation(int r_enum, char suffix) {
|
|
+ if(suffix==',' || suffix==' ' || suffix=='\0') {
|
|
+ /* could shorten default_mod by 8 bytes:
|
|
+ if(r_enum>=DOT11_RATE_6) return DOT11_MOD_OFDM; */
|
|
+ return default_modulation[r_enum];
|
|
+ }
|
|
+ if(suffix=='c') {
|
|
+ if(r_enum<DOT11_RATE_5 || r_enum>DOT11_RATE_11) return -EINVAL;
|
|
+ return DOT11_MOD_CCK;
|
|
+ }
|
|
+ if(suffix=='p') {
|
|
+ if(r_enum<DOT11_RATE_5 || r_enum>DOT11_RATE_33) return -EINVAL;
|
|
+ return DOT11_MOD_PBCC;
|
|
+ }
|
|
+ if(suffix=='o') {
|
|
+ if(r_enum<DOT11_RATE_6) return -EINVAL;
|
|
+ return DOT11_MOD_OFDM;
|
|
+ }
|
|
+ if(suffix=='d') {
|
|
+ if(r_enum<DOT11_RATE_6) return -EINVAL;
|
|
+ return DOT11_MOD_CCKOFDM;
|
|
+ }
|
|
+ return -EINVAL;
|
|
+}
|
|
+
|
|
+#ifdef UNUSED
|
|
+static int
|
|
+fill_ratevector(const char **pstr, u8 *vector, int size,
|
|
+ int (*supported)(int mbit, int mod, void *opaque), void *opaque, int or_mask)
|
|
+{
|
|
+ unsigned long rate_mbit;
|
|
+ int rate_enum,mod;
|
|
+ const char *str = *pstr;
|
|
+ char c;
|
|
+
|
|
+ do {
|
|
+ rate_mbit = simple_strtoul(str, (char**)&str, 10);
|
|
+ if(rate_mbit>INT_MAX) return -EINVAL;
|
|
+
|
|
+ rate_enum = rate_mbit2enum(rate_mbit);
|
|
+ if(rate_enum<0) return rate_enum;
|
|
+
|
|
+ c = *str;
|
|
+ mod = get_modulation(rate_enum, c);
|
|
+ if(mod<0) return mod;
|
|
+
|
|
+ if(c>='a' && c<='z') c = *++str;
|
|
+ if(c!=',' && c!=' ' && c!='\0') return -EINVAL;
|
|
+
|
|
+ if(supported) {
|
|
+ int r = supported(rate_mbit, mod, opaque);
|
|
+ if(r) return r;
|
|
+ }
|
|
+
|
|
+ *vector++ = dot11ratebyte[rate_enum] | or_mask;
|
|
+
|
|
+ size--;
|
|
+ str++;
|
|
+ } while(size>0 && c==',');
|
|
+
|
|
+ if(size<1) return -E2BIG;
|
|
+ *vector=0; /* TODO: sort, remove dups? */
|
|
+
|
|
+ *pstr = str-1;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static /* TODO: remove 'static' when moved to wireless.c */
|
|
+int
|
|
+fill_ratevectors(const char *str, u8 *brate, u8 *orate, int size,
|
|
+ int (*supported)(int mbit, int mod, void *opaque), void *opaque)
|
|
+{
|
|
+ int r;
|
|
+
|
|
+ r = fill_ratevector(&str, brate, size, supported, opaque, 0x80);
|
|
+ if(r) return r;
|
|
+
|
|
+ orate[0] = 0;
|
|
+ if(*str==' ') {
|
|
+ str++;
|
|
+ r = fill_ratevector(&str, orate, size, supported, opaque, 0);
|
|
+ if(r) return r;
|
|
+ /* TODO: sanitize, e.g. remove/error on rates already in basic rate set? */
|
|
+ }
|
|
+ if(*str)
|
|
+ return -EINVAL;
|
|
+
|
|
+ return 0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+/* TODO: use u64 masks? */
|
|
+
|
|
+static int
|
|
+fill_ratemask(const char **pstr, u32* mask,
|
|
+ int (*supported)(int mbit, int mod,void *opaque),
|
|
+ u32 (*gen_mask)(int mbit, int mod,void *opaque),
|
|
+ void *opaque)
|
|
+{
|
|
+ unsigned long rate_mbit;
|
|
+ int rate_enum,mod;
|
|
+ u32 m = 0;
|
|
+ const char *str = *pstr;
|
|
+ char c;
|
|
+
|
|
+ do {
|
|
+ rate_mbit = simple_strtoul(str, (char**)&str, 10);
|
|
+ if(rate_mbit>INT_MAX) return -EINVAL;
|
|
+
|
|
+ rate_enum = rate_mbit2enum(rate_mbit);
|
|
+ if(rate_enum<0) return rate_enum;
|
|
+
|
|
+ c = *str;
|
|
+ mod = get_modulation(rate_enum, c);
|
|
+ if(mod<0) return mod;
|
|
+
|
|
+ if(c>='a' && c<='z') c = *++str;
|
|
+ if(c!=',' && c!=' ' && c!='\0') return -EINVAL;
|
|
+
|
|
+ if(supported) {
|
|
+ int r = supported(rate_mbit, mod, opaque);
|
|
+ if(r) return r;
|
|
+ }
|
|
+
|
|
+ m |= gen_mask(rate_mbit, mod, opaque);
|
|
+ str++;
|
|
+ } while(c==',');
|
|
+
|
|
+ *pstr = str-1;
|
|
+ *mask |= m;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+static /* TODO: remove 'static' when moved to wireless.c */
|
|
+int
|
|
+fill_ratemasks(const char *str, u32 *bmask, u32 *omask,
|
|
+ int (*supported)(int mbit, int mod,void *opaque),
|
|
+ u32 (*gen_mask)(int mbit, int mod,void *opaque),
|
|
+ void *opaque)
|
|
+{
|
|
+ int r;
|
|
+
|
|
+ r = fill_ratemask(&str, bmask, supported, gen_mask, opaque);
|
|
+ if(r) return r;
|
|
+
|
|
+ if(*str==' ') {
|
|
+ str++;
|
|
+ r = fill_ratemask(&str, omask, supported, gen_mask, opaque);
|
|
+ if(r) return r;
|
|
+ }
|
|
+ if(*str)
|
|
+ return -EINVAL;
|
|
+ return 0;
|
|
+}
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/usb.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/usb.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,1922 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** USB support for TI ACX100 based devices. Many parts are taken from
|
|
+** the PCI driver.
|
|
+**
|
|
+** Authors:
|
|
+** Martin Wawro <martin.wawro AT uni-dortmund.de>
|
|
+** Andreas Mohr <andi AT lisas.de>
|
|
+**
|
|
+** LOCKING
|
|
+** callback functions called by USB core are running in interrupt context
|
|
+** and thus have names with _i_.
|
|
+*/
|
|
+#define ACX_USB 1
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+#include <linux/types.h>
|
|
+#include <linux/module.h>
|
|
+#include <linux/moduleparam.h>
|
|
+#include <linux/kernel.h>
|
|
+#include <linux/usb.h>
|
|
+#include <linux/netdevice.h>
|
|
+#include <linux/rtnetlink.h>
|
|
+#include <linux/etherdevice.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+#include <linux/vmalloc.h>
|
|
+
|
|
+#include "acx.h"
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+/* number of endpoints of an interface */
|
|
+#define NUM_EP(intf) (intf)->altsetting[0].desc.bNumEndpoints
|
|
+#define EP(intf, nr) (intf)->altsetting[0].endpoint[(nr)].desc
|
|
+#define GET_DEV(udev) usb_get_dev((udev))
|
|
+#define PUT_DEV(udev) usb_put_dev((udev))
|
|
+#define SET_NETDEV_OWNER(ndev, owner) /* not needed anymore ??? */
|
|
+
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,14)
|
|
+/* removed in 2.6.14. We will use fake value for now */
|
|
+#define URB_ASYNC_UNLINK 0
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+/* ACX100 (TNETW1100) USB device: D-Link DWL-120+ */
|
|
+#define ACX100_VENDOR_ID 0x2001
|
|
+#define ACX100_PRODUCT_ID_UNBOOTED 0x3B01
|
|
+#define ACX100_PRODUCT_ID_BOOTED 0x3B00
|
|
+
|
|
+/* TNETW1450 USB devices */
|
|
+#define VENDOR_ID_DLINK 0x07b8 /* D-Link Corp. */
|
|
+#define PRODUCT_ID_WUG2400 0xb21a /* AboCom WUG2400 or SafeCom SWLUT-54125 */
|
|
+#define VENDOR_ID_AVM_GMBH 0x057c
|
|
+#define PRODUCT_ID_AVM_WLAN_USB 0x5601
|
|
+#define PRODUCT_ID_AVM_WLAN_USB_si 0x6201 /* "self install" named Version: driver kills kernel on inbound scans from fritz box ??? */
|
|
+#define VENDOR_ID_ZCOM 0x0cde
|
|
+#define PRODUCT_ID_ZCOM_XG750 0x0017 /* not tested yet */
|
|
+#define VENDOR_ID_TI 0x0451
|
|
+#define PRODUCT_ID_TI_UNKNOWN 0x60c5 /* not tested yet */
|
|
+
|
|
+#define ACX_USB_CTRL_TIMEOUT 5500 /* steps in ms */
|
|
+
|
|
+/* Buffer size for fw upload, same for both ACX100 USB and TNETW1450 */
|
|
+#define USB_RWMEM_MAXLEN 2048
|
|
+
|
|
+/* The number of bulk URBs to use */
|
|
+#define ACX_TX_URB_CNT 8
|
|
+#define ACX_RX_URB_CNT 2
|
|
+
|
|
+/* Should be sent to the bulkout endpoint */
|
|
+#define ACX_USB_REQ_UPLOAD_FW 0x10
|
|
+#define ACX_USB_REQ_ACK_CS 0x11
|
|
+#define ACX_USB_REQ_CMD 0x12
|
|
+
|
|
+/***********************************************************************
|
|
+** Prototypes
|
|
+*/
|
|
+static int acxusb_e_probe(struct usb_interface *, const struct usb_device_id *);
|
|
+static void acxusb_e_disconnect(struct usb_interface *);
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+static void acxusb_i_complete_tx(struct urb *);
|
|
+static void acxusb_i_complete_rx(struct urb *);
|
|
+#else
|
|
+static void acxusb_i_complete_tx(struct urb *, struct pt_regs *);
|
|
+static void acxusb_i_complete_rx(struct urb *, struct pt_regs *);
|
|
+#endif
|
|
+static int acxusb_e_open(struct net_device *);
|
|
+static int acxusb_e_close(struct net_device *);
|
|
+static void acxusb_i_set_rx_mode(struct net_device *);
|
|
+static int acxusb_boot(struct usb_device *, int is_tnetw1450, int *radio_type);
|
|
+
|
|
+static void acxusb_l_poll_rx(acx_device_t *adev, usb_rx_t* rx);
|
|
+
|
|
+static void acxusb_i_tx_timeout(struct net_device *);
|
|
+
|
|
+/* static void dump_device(struct usb_device *); */
|
|
+/* static void dump_device_descriptor(struct usb_device_descriptor *); */
|
|
+/* static void dump_config_descriptor(struct usb_config_descriptor *); */
|
|
+
|
|
+/***********************************************************************
|
|
+** Module Data
|
|
+*/
|
|
+#define TXBUFSIZE sizeof(usb_txbuffer_t)
|
|
+/*
|
|
+ * Now, this is just plain lying, but the device insists in giving us
|
|
+ * huge packets. We supply extra space after rxbuffer. Need to understand
|
|
+ * it better...
|
|
+ */
|
|
+#define RXBUFSIZE (sizeof(rxbuffer_t) + \
|
|
+ (sizeof(usb_rx_t) - sizeof(struct usb_rx_plain)))
|
|
+
|
|
+static const struct usb_device_id
|
|
+acxusb_ids[] = {
|
|
+ { USB_DEVICE(ACX100_VENDOR_ID, ACX100_PRODUCT_ID_BOOTED) },
|
|
+ { USB_DEVICE(ACX100_VENDOR_ID, ACX100_PRODUCT_ID_UNBOOTED) },
|
|
+ { USB_DEVICE(VENDOR_ID_DLINK, PRODUCT_ID_WUG2400) },
|
|
+ { USB_DEVICE(VENDOR_ID_AVM_GMBH, PRODUCT_ID_AVM_WLAN_USB) },
|
|
+ { USB_DEVICE(VENDOR_ID_AVM_GMBH, PRODUCT_ID_AVM_WLAN_USB_si) },
|
|
+ { USB_DEVICE(VENDOR_ID_ZCOM, PRODUCT_ID_ZCOM_XG750) },
|
|
+ { USB_DEVICE(VENDOR_ID_TI, PRODUCT_ID_TI_UNKNOWN) },
|
|
+ {}
|
|
+};
|
|
+
|
|
+MODULE_DEVICE_TABLE(usb, acxusb_ids);
|
|
+
|
|
+/* USB driver data structure as required by the kernel's USB core */
|
|
+static struct usb_driver
|
|
+acxusb_driver = {
|
|
+ .name = "acx_usb",
|
|
+ .probe = acxusb_e_probe,
|
|
+ .disconnect = acxusb_e_disconnect,
|
|
+ .id_table = acxusb_ids
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** USB helper
|
|
+**
|
|
+** ldd3 ch13 says:
|
|
+** When the function is usb_kill_urb, the urb lifecycle is stopped. This
|
|
+** function is usually used when the device is disconnected from the system,
|
|
+** in the disconnect callback. For some drivers, the usb_unlink_urb function
|
|
+** should be used to tell the USB core to stop an urb. This function does not
|
|
+** wait for the urb to be fully stopped before returning to the caller.
|
|
+** This is useful for stoppingthe urb while in an interrupt handler or when
|
|
+** a spinlock is held, as waiting for a urb to fully stop requires the ability
|
|
+** for the USB core to put the calling process to sleep. This function requires
|
|
+** that the URB_ASYNC_UNLINK flag value be set in the urb that is being asked
|
|
+** to be stopped in order to work properly.
|
|
+**
|
|
+** (URB_ASYNC_UNLINK is obsolete, usb_unlink_urb will always be
|
|
+** asynchronous while usb_kill_urb is synchronous and should be called
|
|
+** directly (drivers/usb/core/urb.c))
|
|
+**
|
|
+** In light of this, timeout is just for paranoid reasons...
|
|
+*
|
|
+* Actually, it's useful for debugging. If we reach timeout, we're doing
|
|
+* something wrong with the urbs.
|
|
+*/
|
|
+static void
|
|
+acxusb_unlink_urb(struct urb* urb)
|
|
+{
|
|
+ if (!urb)
|
|
+ return;
|
|
+
|
|
+ if (urb->status == -EINPROGRESS) {
|
|
+ int timeout = 10;
|
|
+
|
|
+ usb_unlink_urb(urb);
|
|
+ while (--timeout && urb->status == -EINPROGRESS) {
|
|
+ mdelay(1);
|
|
+ }
|
|
+ if (!timeout) {
|
|
+ printk("acx_usb: urb unlink timeout!\n");
|
|
+ }
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** EEPROM and PHY read/write helpers
|
|
+*/
|
|
+/***********************************************************************
|
|
+** acxusb_s_read_phy_reg
|
|
+*/
|
|
+int
|
|
+acxusb_s_read_phy_reg(acx_device_t *adev, u32 reg, u8 *charbuf)
|
|
+{
|
|
+ /* mem_read_write_t mem; */
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ printk("%s doesn't seem to work yet, disabled.\n", __func__);
|
|
+
|
|
+ /*
|
|
+ mem.addr = cpu_to_le16(reg);
|
|
+ mem.type = cpu_to_le16(0x82);
|
|
+ mem.len = cpu_to_le32(4);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_MEM_READ, &mem, sizeof(mem));
|
|
+ *charbuf = mem.data;
|
|
+ log(L_DEBUG, "read radio PHY[0x%04X]=0x%02X\n", reg, *charbuf);
|
|
+ */
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+int
|
|
+acxusb_s_write_phy_reg(acx_device_t *adev, u32 reg, u8 value)
|
|
+{
|
|
+ mem_read_write_t mem;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ mem.addr = cpu_to_le16(reg);
|
|
+ mem.type = cpu_to_le16(0x82);
|
|
+ mem.len = cpu_to_le32(4);
|
|
+ mem.data = value;
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_MEM_WRITE, &mem, sizeof(mem));
|
|
+ log(L_DEBUG, "write radio PHY[0x%04X]=0x%02X\n", reg, value);
|
|
+
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_s_issue_cmd_timeo
|
|
+** Excecutes a command in the command mailbox
|
|
+**
|
|
+** buffer = a pointer to the data.
|
|
+** The data must not include 4 byte command header
|
|
+*/
|
|
+
|
|
+/* TODO: ideally we shall always know how much we need
|
|
+** and this shall be 0 */
|
|
+#define BOGUS_SAFETY_PADDING 0x40
|
|
+
|
|
+#undef FUNC
|
|
+#define FUNC "issue_cmd"
|
|
+
|
|
+#if !ACX_DEBUG
|
|
+int
|
|
+acxusb_s_issue_cmd_timeo(
|
|
+ acx_device_t *adev,
|
|
+ unsigned cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned timeout)
|
|
+{
|
|
+#else
|
|
+int
|
|
+acxusb_s_issue_cmd_timeo_debug(
|
|
+ acx_device_t *adev,
|
|
+ unsigned cmd,
|
|
+ void *buffer,
|
|
+ unsigned buflen,
|
|
+ unsigned timeout,
|
|
+ const char* cmdstr)
|
|
+{
|
|
+#endif
|
|
+ /* USB ignores timeout param */
|
|
+
|
|
+ struct usb_device *usbdev;
|
|
+ struct {
|
|
+ u16 cmd;
|
|
+ u16 status;
|
|
+ u8 data[1];
|
|
+ } ACX_PACKED *loc;
|
|
+ const char *devname;
|
|
+ int acklen, blocklen, inpipe, outpipe;
|
|
+ int cmd_status;
|
|
+ int result;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ devname = adev->ndev->name;
|
|
+ /* no "wlan%%d: ..." please */
|
|
+ if (!devname || !devname[0] || devname[4]=='%')
|
|
+ devname = "acx";
|
|
+
|
|
+ log(L_CTL, FUNC"(cmd:%s,buflen:%u,type:0x%04X)\n",
|
|
+ cmdstr, buflen,
|
|
+ buffer ? le16_to_cpu(((acx_ie_generic_t *)buffer)->type) : -1);
|
|
+
|
|
+ loc = kmalloc(buflen + 4 + BOGUS_SAFETY_PADDING, GFP_KERNEL);
|
|
+ if (!loc) {
|
|
+ printk("%s: "FUNC"(): no memory for data buffer\n", devname);
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* get context from acx_device */
|
|
+ usbdev = adev->usbdev;
|
|
+
|
|
+ /* check which kind of command was issued */
|
|
+ loc->cmd = cpu_to_le16(cmd);
|
|
+ loc->status = 0;
|
|
+
|
|
+/* NB: buflen == frmlen + 4
|
|
+**
|
|
+** Interrogate: write 8 bytes: (cmd,status,rid,frmlen), then
|
|
+** read (cmd,status,rid,frmlen,data[frmlen]) back
|
|
+**
|
|
+** Configure: write (cmd,status,rid,frmlen,data[frmlen])
|
|
+**
|
|
+** Possibly bogus special handling of ACX1xx_IE_SCAN_STATUS removed
|
|
+*/
|
|
+
|
|
+ /* now write the parameters of the command if needed */
|
|
+ acklen = buflen + 4 + BOGUS_SAFETY_PADDING;
|
|
+ blocklen = buflen;
|
|
+ if (buffer && buflen) {
|
|
+ /* if it's an INTERROGATE command, just pass the length
|
|
+ * of parameters to read, as data */
|
|
+ if (cmd == ACX1xx_CMD_INTERROGATE) {
|
|
+ blocklen = 4;
|
|
+ acklen = buflen + 4;
|
|
+ }
|
|
+ memcpy(loc->data, buffer, blocklen);
|
|
+ }
|
|
+ blocklen += 4; /* account for cmd,status */
|
|
+
|
|
+ /* obtain the I/O pipes */
|
|
+ outpipe = usb_sndctrlpipe(usbdev, 0);
|
|
+ inpipe = usb_rcvctrlpipe(usbdev, 0);
|
|
+ log(L_CTL, "ctrl inpipe=0x%X outpipe=0x%X\n", inpipe, outpipe);
|
|
+ log(L_CTL, "sending USB control msg (out) (blocklen=%d)\n", blocklen);
|
|
+ if (acx_debug & L_DATA)
|
|
+ acx_dump_bytes(loc, blocklen);
|
|
+
|
|
+ result = usb_control_msg(usbdev, outpipe,
|
|
+ ACX_USB_REQ_CMD, /* request */
|
|
+ USB_TYPE_VENDOR|USB_DIR_OUT, /* requesttype */
|
|
+ 0, /* value */
|
|
+ 0, /* index */
|
|
+ loc, /* dataptr */
|
|
+ blocklen, /* size */
|
|
+ ACX_USB_CTRL_TIMEOUT /* timeout in ms */
|
|
+ );
|
|
+
|
|
+ if (result == -ENODEV) {
|
|
+ log(L_CTL, "no device present (unplug?)\n");
|
|
+ goto good;
|
|
+ }
|
|
+
|
|
+ log(L_CTL, "wrote %d bytes\n", result);
|
|
+ if (result < 0) {
|
|
+ goto bad;
|
|
+ }
|
|
+
|
|
+ /* check for device acknowledge */
|
|
+ log(L_CTL, "sending USB control msg (in) (acklen=%d)\n", acklen);
|
|
+ loc->status = 0; /* delete old status flag -> set to IDLE */
|
|
+ /* shall we zero out the rest? */
|
|
+ result = usb_control_msg(usbdev, inpipe,
|
|
+ ACX_USB_REQ_CMD, /* request */
|
|
+ USB_TYPE_VENDOR|USB_DIR_IN, /* requesttype */
|
|
+ 0, /* value */
|
|
+ 0, /* index */
|
|
+ loc, /* dataptr */
|
|
+ acklen, /* size */
|
|
+ ACX_USB_CTRL_TIMEOUT /* timeout in ms */
|
|
+ );
|
|
+ if (result < 0) {
|
|
+ printk("%s: "FUNC"(): USB read error %d\n", devname, result);
|
|
+ goto bad;
|
|
+ }
|
|
+ if (acx_debug & L_CTL) {
|
|
+ printk("read %d bytes: ", result);
|
|
+ acx_dump_bytes(loc, result);
|
|
+ }
|
|
+
|
|
+/*
|
|
+ check for result==buflen+4? Was seen:
|
|
+
|
|
+interrogate(type:ACX100_IE_DOT11_ED_THRESHOLD,len:4)
|
|
+issue_cmd(cmd:ACX1xx_CMD_INTERROGATE,buflen:8,type:4111)
|
|
+ctrl inpipe=0x80000280 outpipe=0x80000200
|
|
+sending USB control msg (out) (blocklen=8)
|
|
+01 00 00 00 0F 10 04 00
|
|
+wrote 8 bytes
|
|
+sending USB control msg (in) (acklen=12) sizeof(loc->data
|
|
+read 4 bytes <==== MUST BE 12!!
|
|
+*/
|
|
+
|
|
+ cmd_status = le16_to_cpu(loc->status);
|
|
+ if (cmd_status != 1) {
|
|
+ printk("%s: "FUNC"(): cmd_status is not SUCCESS: %d (%s)\n",
|
|
+ devname, cmd_status, acx_cmd_status_str(cmd_status));
|
|
+ /* TODO: goto bad; ? */
|
|
+ }
|
|
+ if ((cmd == ACX1xx_CMD_INTERROGATE) && buffer && buflen) {
|
|
+ memcpy(buffer, loc->data, buflen);
|
|
+ log(L_CTL, "response frame: cmd=0x%04X status=%d\n",
|
|
+ le16_to_cpu(loc->cmd),
|
|
+ cmd_status);
|
|
+ }
|
|
+good:
|
|
+ kfree(loc);
|
|
+ FN_EXIT1(OK);
|
|
+ return OK;
|
|
+bad:
|
|
+ /* Give enough info so that callers can avoid
|
|
+ ** printing their own diagnostic messages */
|
|
+#if ACX_DEBUG
|
|
+ printk("%s: "FUNC"(cmd:%s) FAILED\n", devname, cmdstr);
|
|
+#else
|
|
+ printk("%s: "FUNC"(cmd:0x%04X) FAILED\n", devname, cmd);
|
|
+#endif
|
|
+ dump_stack();
|
|
+ kfree(loc);
|
|
+ FN_EXIT1(NOT_OK);
|
|
+ return NOT_OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_boot()
|
|
+** Inputs:
|
|
+** usbdev -> Pointer to kernel's usb_device structure
|
|
+**
|
|
+** Returns:
|
|
+** (int) Errorcode or 0 on success
|
|
+**
|
|
+** This function triggers the loading of the firmware image from harddisk
|
|
+** and then uploads the firmware to the USB device. After uploading the
|
|
+** firmware and transmitting the checksum, the device resets and appears
|
|
+** as a new device on the USB bus (the device we can finally deal with)
|
|
+*/
|
|
+static inline int
|
|
+acxusb_fw_needs_padding(firmware_image_t *fw_image, unsigned int usb_maxlen)
|
|
+{
|
|
+ unsigned int num_xfers = ((fw_image->size - 1) / usb_maxlen) + 1;
|
|
+
|
|
+ return ((num_xfers % 2) == 0);
|
|
+}
|
|
+
|
|
+static int
|
|
+acxusb_boot(struct usb_device *usbdev, int is_tnetw1450, int *radio_type)
|
|
+{
|
|
+ char filename[sizeof("tiacx1NNusbcRR")];
|
|
+
|
|
+ firmware_image_t *fw_image = NULL;
|
|
+ char *usbbuf;
|
|
+ unsigned int offset;
|
|
+ unsigned int blk_len, inpipe, outpipe;
|
|
+ u32 num_processed;
|
|
+ u32 img_checksum, sum;
|
|
+ u32 file_size;
|
|
+ int result = -EIO;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* dump_device(usbdev); */
|
|
+
|
|
+ usbbuf = kmalloc(USB_RWMEM_MAXLEN, GFP_KERNEL);
|
|
+ if (!usbbuf) {
|
|
+ printk(KERN_ERR "acx: no memory for USB transfer buffer (%d bytes)\n", USB_RWMEM_MAXLEN);
|
|
+ result = -ENOMEM;
|
|
+ goto end;
|
|
+ }
|
|
+ if (is_tnetw1450) {
|
|
+ /* Obtain the I/O pipes */
|
|
+ outpipe = usb_sndbulkpipe(usbdev, 1);
|
|
+ inpipe = usb_rcvbulkpipe(usbdev, 2);
|
|
+
|
|
+ printk(KERN_DEBUG "wait for device ready\n");
|
|
+ for (i = 0; i <= 2; i++) {
|
|
+ result = usb_bulk_msg(usbdev, inpipe,
|
|
+ usbbuf,
|
|
+ USB_RWMEM_MAXLEN,
|
|
+ &num_processed,
|
|
+ 2000
|
|
+ );
|
|
+
|
|
+ if ((*(u32 *)&usbbuf[4] == 0x40000001)
|
|
+ && (*(u16 *)&usbbuf[2] == 0x1)
|
|
+ && ((*(u16 *)usbbuf & 0x3fff) == 0)
|
|
+ && ((*(u16 *)usbbuf & 0xc000) == 0xc000))
|
|
+ break;
|
|
+ msleep(10);
|
|
+ }
|
|
+ if (i == 2)
|
|
+ goto fw_end;
|
|
+
|
|
+ *radio_type = usbbuf[8];
|
|
+ } else {
|
|
+ /* Obtain the I/O pipes */
|
|
+ outpipe = usb_sndctrlpipe(usbdev, 0);
|
|
+ inpipe = usb_rcvctrlpipe(usbdev, 0);
|
|
+
|
|
+ /* FIXME: shouldn't be hardcoded */
|
|
+ *radio_type = RADIO_MAXIM_0D;
|
|
+ }
|
|
+
|
|
+ snprintf(filename, sizeof(filename), "tiacx1%02dusbc%02X",
|
|
+ is_tnetw1450 * 11, *radio_type);
|
|
+
|
|
+ fw_image = acx_s_read_fw(&usbdev->dev, filename, &file_size);
|
|
+ if (!fw_image) {
|
|
+ result = -EIO;
|
|
+ goto end;
|
|
+ }
|
|
+ log(L_INIT, "firmware size: %d bytes\n", file_size);
|
|
+
|
|
+ img_checksum = le32_to_cpu(fw_image->chksum);
|
|
+
|
|
+ if (is_tnetw1450) {
|
|
+ u8 cmdbuf[20];
|
|
+ const u8 *p;
|
|
+ u8 need_padding;
|
|
+ u32 tmplen, val;
|
|
+
|
|
+ memset(cmdbuf, 0, 16);
|
|
+
|
|
+ need_padding = acxusb_fw_needs_padding(fw_image, USB_RWMEM_MAXLEN);
|
|
+ tmplen = need_padding ? file_size-4 : file_size-8;
|
|
+ *(u16 *)&cmdbuf[0] = 0xc000;
|
|
+ *(u16 *)&cmdbuf[2] = 0x000b;
|
|
+ *(u32 *)&cmdbuf[4] = tmplen;
|
|
+ *(u32 *)&cmdbuf[8] = file_size-8;
|
|
+ *(u32 *)&cmdbuf[12] = img_checksum;
|
|
+
|
|
+ result = usb_bulk_msg(usbdev, outpipe, cmdbuf, 16, &num_processed, HZ);
|
|
+ if (result < 0)
|
|
+ goto fw_end;
|
|
+
|
|
+ p = (const u8 *)&fw_image->size;
|
|
+
|
|
+ /* first calculate checksum for image size part */
|
|
+ sum = p[0]+p[1]+p[2]+p[3];
|
|
+ p += 4;
|
|
+
|
|
+ /* now continue checksum for firmware data part */
|
|
+ tmplen = le32_to_cpu(fw_image->size);
|
|
+ for (i = 0; i < tmplen /* image size */; i++) {
|
|
+ sum += *p++;
|
|
+ }
|
|
+
|
|
+ if (sum != le32_to_cpu(fw_image->chksum)) {
|
|
+ printk("acx: FATAL: firmware upload: "
|
|
+ "checksums don't match! "
|
|
+ "(0x%08x vs. 0x%08x)\n",
|
|
+ sum, fw_image->chksum);
|
|
+ goto fw_end;
|
|
+ }
|
|
+
|
|
+ offset = 8;
|
|
+ while (offset < file_size) {
|
|
+ blk_len = file_size - offset;
|
|
+ if (blk_len > USB_RWMEM_MAXLEN) {
|
|
+ blk_len = USB_RWMEM_MAXLEN;
|
|
+ }
|
|
+
|
|
+ log(L_INIT, "uploading firmware (%d bytes, offset=%d)\n",
|
|
+ blk_len, offset);
|
|
+ memcpy(usbbuf, ((u8 *)fw_image) + offset, blk_len);
|
|
+
|
|
+ p = usbbuf;
|
|
+ for (i = 0; i < blk_len; i += 4) {
|
|
+ *(u32 *)p = be32_to_cpu(*(u32 *)p);
|
|
+ p += 4;
|
|
+ }
|
|
+
|
|
+ result = usb_bulk_msg(usbdev, outpipe, usbbuf, blk_len, &num_processed, HZ);
|
|
+ if ((result < 0) || (num_processed != blk_len))
|
|
+ goto fw_end;
|
|
+ offset += blk_len;
|
|
+ }
|
|
+ if (need_padding) {
|
|
+ printk(KERN_DEBUG "send padding\n");
|
|
+ memset(usbbuf, 0, 4);
|
|
+ result = usb_bulk_msg(usbdev, outpipe, usbbuf, 4, &num_processed, HZ);
|
|
+ if ((result < 0) || (num_processed != 4))
|
|
+ goto fw_end;
|
|
+ }
|
|
+ printk(KERN_DEBUG "read firmware upload result\n");
|
|
+ memset(cmdbuf, 0, 20); /* additional memset */
|
|
+ result = usb_bulk_msg(usbdev, inpipe, cmdbuf, 20, &num_processed, 2000);
|
|
+ if (result < 0)
|
|
+ goto fw_end;
|
|
+ if (*(u32 *)&cmdbuf[4] == 0x40000003)
|
|
+ goto fw_end;
|
|
+ if (*(u32 *)&cmdbuf[4])
|
|
+ goto fw_end;
|
|
+ if (*(u16 *)&cmdbuf[16] != 1)
|
|
+ goto fw_end;
|
|
+
|
|
+ val = *(u32 *)&cmdbuf[0];
|
|
+ if ((val & 0x3fff)
|
|
+ || ((val & 0xc000) != 0xc000))
|
|
+ goto fw_end;
|
|
+
|
|
+ val = *(u32 *)&cmdbuf[8];
|
|
+ if (val & 2) {
|
|
+ result = usb_bulk_msg(usbdev, inpipe, cmdbuf, 20, &num_processed, 2000);
|
|
+ if (result < 0)
|
|
+ goto fw_end;
|
|
+ val = *(u32 *)&cmdbuf[8];
|
|
+ }
|
|
+ /* yup, no "else" here! */
|
|
+ if (val & 1) {
|
|
+ memset(usbbuf, 0, 4);
|
|
+ result = usb_bulk_msg(usbdev, outpipe, usbbuf, 4, &num_processed, HZ);
|
|
+ if ((result < 0) || (!num_processed))
|
|
+ goto fw_end;
|
|
+ }
|
|
+
|
|
+ printk("TNETW1450 firmware upload successful!\n");
|
|
+ result = 0;
|
|
+ goto end;
|
|
+fw_end:
|
|
+ result = -EIO;
|
|
+ goto end;
|
|
+ } else {
|
|
+ /* ACX100 USB */
|
|
+
|
|
+ /* now upload the firmware, slice the data into blocks */
|
|
+ offset = 8;
|
|
+ while (offset < file_size) {
|
|
+ blk_len = file_size - offset;
|
|
+ if (blk_len > USB_RWMEM_MAXLEN) {
|
|
+ blk_len = USB_RWMEM_MAXLEN;
|
|
+ }
|
|
+ log(L_INIT, "uploading firmware (%d bytes, offset=%d)\n",
|
|
+ blk_len, offset);
|
|
+ memcpy(usbbuf, ((u8 *)fw_image) + offset, blk_len);
|
|
+ result = usb_control_msg(usbdev, outpipe,
|
|
+ ACX_USB_REQ_UPLOAD_FW,
|
|
+ USB_TYPE_VENDOR|USB_DIR_OUT,
|
|
+ (file_size - 8) & 0xffff, /* value */
|
|
+ (file_size - 8) >> 16, /* index */
|
|
+ usbbuf, /* dataptr */
|
|
+ blk_len, /* size */
|
|
+ 3000 /* timeout in ms */
|
|
+ );
|
|
+ offset += blk_len;
|
|
+ if (result < 0) {
|
|
+ printk(KERN_ERR "acx: error %d during upload "
|
|
+ "of firmware, aborting\n", result);
|
|
+ goto end;
|
|
+ }
|
|
+ }
|
|
+
|
|
+ /* finally, send the checksum and reboot the device */
|
|
+ /* does this trigger the reboot? */
|
|
+ result = usb_control_msg(usbdev, outpipe,
|
|
+ ACX_USB_REQ_UPLOAD_FW,
|
|
+ USB_TYPE_VENDOR|USB_DIR_OUT,
|
|
+ img_checksum & 0xffff, /* value */
|
|
+ img_checksum >> 16, /* index */
|
|
+ NULL, /* dataptr */
|
|
+ 0, /* size */
|
|
+ 3000 /* timeout in ms */
|
|
+ );
|
|
+ if (result < 0) {
|
|
+ printk(KERN_ERR "acx: error %d during tx of checksum, "
|
|
+ "aborting\n", result);
|
|
+ goto end;
|
|
+ }
|
|
+ result = usb_control_msg(usbdev, inpipe,
|
|
+ ACX_USB_REQ_ACK_CS,
|
|
+ USB_TYPE_VENDOR|USB_DIR_IN,
|
|
+ img_checksum & 0xffff, /* value */
|
|
+ img_checksum >> 16, /* index */
|
|
+ usbbuf, /* dataptr */
|
|
+ 8, /* size */
|
|
+ 3000 /* timeout in ms */
|
|
+ );
|
|
+ if (result < 0) {
|
|
+ printk(KERN_ERR "acx: error %d during ACK of checksum, "
|
|
+ "aborting\n", result);
|
|
+ goto end;
|
|
+ }
|
|
+ if (*usbbuf != 0x10) {
|
|
+ printk(KERN_ERR "acx: invalid checksum?\n");
|
|
+ result = -EINVAL;
|
|
+ goto end;
|
|
+ }
|
|
+ result = 0;
|
|
+ }
|
|
+
|
|
+end:
|
|
+ vfree(fw_image);
|
|
+ kfree(usbbuf);
|
|
+
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/* FIXME: maybe merge it with usual eeprom reading, into common code? */
|
|
+static void
|
|
+acxusb_s_read_eeprom_version(acx_device_t *adev)
|
|
+{
|
|
+ u8 eeprom_ver[0x8];
|
|
+
|
|
+ memset(eeprom_ver, 0, sizeof(eeprom_ver));
|
|
+ acx_s_interrogate(adev, &eeprom_ver, ACX1FF_IE_EEPROM_VER);
|
|
+
|
|
+ /* FIXME: which one of those values to take? */
|
|
+ adev->eeprom_version = eeprom_ver[5];
|
|
+}
|
|
+
|
|
+
|
|
+/*
|
|
+ * temporary helper function to at least fill important cfgopt members with
|
|
+ * useful replacement values until we figure out how one manages to fetch
|
|
+ * the configoption struct in the USB device case...
|
|
+ */
|
|
+static int
|
|
+acxusb_s_fill_configoption(acx_device_t *adev)
|
|
+{
|
|
+ adev->cfgopt_probe_delay = 200;
|
|
+ adev->cfgopt_dot11CCAModes = 4;
|
|
+ adev->cfgopt_dot11Diversity = 1;
|
|
+ adev->cfgopt_dot11ShortPreambleOption = 1;
|
|
+ adev->cfgopt_dot11PBCCOption = 1;
|
|
+ adev->cfgopt_dot11ChannelAgility = 0;
|
|
+ adev->cfgopt_dot11PhyType = 5;
|
|
+ adev->cfgopt_dot11TempType = 1;
|
|
+ return OK;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_e_probe()
|
|
+**
|
|
+** This function is invoked by the kernel's USB core whenever a new device is
|
|
+** attached to the system or the module is loaded. It is presented a usb_device
|
|
+** structure from which information regarding the device is obtained and evaluated.
|
|
+** In case this driver is able to handle one of the offered devices, it returns
|
|
+** a non-null pointer to a driver context and thereby claims the device.
|
|
+*/
|
|
+
|
|
+static void
|
|
+dummy_netdev_init(struct net_device *ndev) {}
|
|
+
|
|
+static int
|
|
+acxusb_e_probe(struct usb_interface *intf, const struct usb_device_id *devID)
|
|
+{
|
|
+ struct usb_device *usbdev = interface_to_usbdev(intf);
|
|
+ acx_device_t *adev = NULL;
|
|
+ struct net_device *ndev = NULL;
|
|
+ struct usb_config_descriptor *config;
|
|
+ struct usb_endpoint_descriptor *epdesc;
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+ struct usb_host_endpoint *ep;
|
|
+#endif
|
|
+ struct usb_interface_descriptor *ifdesc;
|
|
+ const char* msg;
|
|
+ int numconfigs, numfaces, numep;
|
|
+ int result = OK;
|
|
+ int i;
|
|
+ int radio_type;
|
|
+ /* this one needs to be more precise in case there appears a TNETW1450 from the same vendor */
|
|
+ int is_tnetw1450 = (usbdev->descriptor.idVendor != ACX100_VENDOR_ID);
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ if (is_tnetw1450) {
|
|
+ /* Boot the device (i.e. upload the firmware) */
|
|
+ acxusb_boot(usbdev, is_tnetw1450, &radio_type);
|
|
+
|
|
+ /* TNETW1450-based cards will continue right away with
|
|
+ * the same USB ID after booting */
|
|
+ } else {
|
|
+ /* First check if this is the "unbooted" hardware */
|
|
+ if (usbdev->descriptor.idProduct == ACX100_PRODUCT_ID_UNBOOTED) {
|
|
+
|
|
+ /* Boot the device (i.e. upload the firmware) */
|
|
+ acxusb_boot(usbdev, is_tnetw1450, &radio_type);
|
|
+
|
|
+ /* DWL-120+ will first boot the firmware,
|
|
+ * then later have a *separate* probe() run
|
|
+ * since its USB ID will have changed after
|
|
+ * firmware boot!
|
|
+ * Since the first probe() run has no
|
|
+ * other purpose than booting the firmware,
|
|
+ * simply return immediately.
|
|
+ */
|
|
+ log(L_INIT, "finished booting, returning from probe()\n");
|
|
+ result = OK; /* success */
|
|
+ goto end;
|
|
+ }
|
|
+ else
|
|
+ /* device not unbooted, but invalid USB ID!? */
|
|
+ if (usbdev->descriptor.idProduct != ACX100_PRODUCT_ID_BOOTED)
|
|
+ goto end_nodev;
|
|
+ }
|
|
+
|
|
+/* Ok, so it's our device and it has already booted */
|
|
+
|
|
+ /* Allocate memory for a network device */
|
|
+
|
|
+ ndev = alloc_netdev(sizeof(*adev), "wlan%d", dummy_netdev_init);
|
|
+ /* (NB: memsets to 0 entire area) */
|
|
+ if (!ndev) {
|
|
+ msg = "acx: no memory for netdev\n";
|
|
+ goto end_nomem;
|
|
+ }
|
|
+
|
|
+ /* Register the callbacks for the network device functions */
|
|
+
|
|
+ ether_setup(ndev);
|
|
+ ndev->open = &acxusb_e_open;
|
|
+ ndev->stop = &acxusb_e_close;
|
|
+ ndev->hard_start_xmit = (void *)&acx_i_start_xmit;
|
|
+ ndev->get_stats = (void *)&acx_e_get_stats;
|
|
+#if IW_HANDLER_VERSION <= 5
|
|
+ ndev->get_wireless_stats = (void *)&acx_e_get_wireless_stats;
|
|
+#endif
|
|
+ ndev->wireless_handlers = (struct iw_handler_def *)&acx_ioctl_handler_def;
|
|
+ ndev->set_multicast_list = (void *)&acxusb_i_set_rx_mode;
|
|
+#ifdef HAVE_TX_TIMEOUT
|
|
+ ndev->tx_timeout = &acxusb_i_tx_timeout;
|
|
+ ndev->watchdog_timeo = 4 * HZ;
|
|
+#endif
|
|
+ ndev->change_mtu = &acx_e_change_mtu;
|
|
+ SET_MODULE_OWNER(ndev);
|
|
+
|
|
+ /* Setup private driver context */
|
|
+
|
|
+ adev = ndev2adev(ndev);
|
|
+ adev->ndev = ndev;
|
|
+
|
|
+ adev->dev_type = DEVTYPE_USB;
|
|
+ adev->radio_type = radio_type;
|
|
+ if (is_tnetw1450) {
|
|
+ /* well, actually it's a TNETW1450, but since it
|
|
+ * seems to be sufficiently similar to TNETW1130,
|
|
+ * I don't want to change large amounts of code now */
|
|
+ adev->chip_type = CHIPTYPE_ACX111;
|
|
+ } else {
|
|
+ adev->chip_type = CHIPTYPE_ACX100;
|
|
+ }
|
|
+
|
|
+ adev->usbdev = usbdev;
|
|
+ spin_lock_init(&adev->lock); /* initial state: unlocked */
|
|
+ sema_init(&adev->sem, 1); /* initial state: 1 (upped) */
|
|
+
|
|
+ /* Check that this is really the hardware we know about.
|
|
+ ** If not sure, at least notify the user that he
|
|
+ ** may be in trouble...
|
|
+ */
|
|
+ numconfigs = (int)usbdev->descriptor.bNumConfigurations;
|
|
+ if (numconfigs != 1)
|
|
+ printk("acx: number of configurations is %d, "
|
|
+ "this driver only knows how to handle 1, "
|
|
+ "be prepared for surprises\n", numconfigs);
|
|
+
|
|
+ config = &usbdev->config->desc;
|
|
+ numfaces = config->bNumInterfaces;
|
|
+ if (numfaces != 1)
|
|
+ printk("acx: number of interfaces is %d, "
|
|
+ "this driver only knows how to handle 1, "
|
|
+ "be prepared for surprises\n", numfaces);
|
|
+
|
|
+ ifdesc = &intf->altsetting->desc;
|
|
+ numep = ifdesc->bNumEndpoints;
|
|
+ log(L_DEBUG, "# of endpoints: %d\n", numep);
|
|
+
|
|
+ if (is_tnetw1450) {
|
|
+ adev->bulkoutep = 1;
|
|
+ adev->bulkinep = 2;
|
|
+ } else {
|
|
+ /* obtain information about the endpoint
|
|
+ ** addresses, begin with some default values
|
|
+ */
|
|
+ adev->bulkoutep = 1;
|
|
+ adev->bulkinep = 1;
|
|
+ for (i = 0; i < numep; i++) {
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+ ep = usbdev->ep_in[i];
|
|
+ if (!ep)
|
|
+ continue;
|
|
+ epdesc = &ep->desc;
|
|
+#else
|
|
+ epdesc = usb_epnum_to_ep_desc(usbdev, i);
|
|
+ if (!epdesc)
|
|
+ continue;
|
|
+#endif
|
|
+ if (epdesc->bmAttributes & USB_ENDPOINT_XFER_BULK) {
|
|
+ if (epdesc->bEndpointAddress & 0x80)
|
|
+ adev->bulkinep = epdesc->bEndpointAddress & 0xF;
|
|
+ else
|
|
+ adev->bulkoutep = epdesc->bEndpointAddress & 0xF;
|
|
+ }
|
|
+ }
|
|
+ }
|
|
+ log(L_DEBUG, "bulkout ep: 0x%X\n", adev->bulkoutep);
|
|
+ log(L_DEBUG, "bulkin ep: 0x%X\n", adev->bulkinep);
|
|
+
|
|
+ /* already done by memset: adev->rxtruncsize = 0; */
|
|
+ log(L_DEBUG, "TXBUFSIZE=%d RXBUFSIZE=%d\n",
|
|
+ (int) TXBUFSIZE, (int) RXBUFSIZE);
|
|
+
|
|
+ /* Allocate the RX/TX containers. */
|
|
+ adev->usb_tx = kmalloc(sizeof(usb_tx_t) * ACX_TX_URB_CNT, GFP_KERNEL);
|
|
+ if (!adev->usb_tx) {
|
|
+ msg = "acx: no memory for tx container";
|
|
+ goto end_nomem;
|
|
+ }
|
|
+ adev->usb_rx = kmalloc(sizeof(usb_rx_t) * ACX_RX_URB_CNT, GFP_KERNEL);
|
|
+ if (!adev->usb_rx) {
|
|
+ msg = "acx: no memory for rx container";
|
|
+ goto end_nomem;
|
|
+ }
|
|
+
|
|
+ /* Setup URBs for bulk-in/out messages */
|
|
+ for (i = 0; i < ACX_RX_URB_CNT; i++) {
|
|
+ adev->usb_rx[i].urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!adev->usb_rx[i].urb) {
|
|
+ msg = "acx: no memory for input URB\n";
|
|
+ goto end_nomem;
|
|
+ }
|
|
+ adev->usb_rx[i].urb->status = 0;
|
|
+ adev->usb_rx[i].adev = adev;
|
|
+ adev->usb_rx[i].busy = 0;
|
|
+ }
|
|
+
|
|
+ for (i = 0; i< ACX_TX_URB_CNT; i++) {
|
|
+ adev->usb_tx[i].urb = usb_alloc_urb(0, GFP_KERNEL);
|
|
+ if (!adev->usb_tx[i].urb) {
|
|
+ msg = "acx: no memory for output URB\n";
|
|
+ goto end_nomem;
|
|
+ }
|
|
+ adev->usb_tx[i].urb->status = 0;
|
|
+ adev->usb_tx[i].adev = adev;
|
|
+ adev->usb_tx[i].busy = 0;
|
|
+ }
|
|
+ adev->tx_free = ACX_TX_URB_CNT;
|
|
+
|
|
+ usb_set_intfdata(intf, adev);
|
|
+ SET_NETDEV_DEV(ndev, &intf->dev);
|
|
+
|
|
+ /* TODO: move all of fw cmds to open()? But then we won't know our MAC addr
|
|
+ until ifup (it's available via reading ACX1xx_IE_DOT11_STATION_ID)... */
|
|
+
|
|
+ /* put acx out of sleep mode and initialize it */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0);
|
|
+
|
|
+ result = acx_s_init_mac(adev);
|
|
+ if (result)
|
|
+ goto end;
|
|
+
|
|
+ /* TODO: see similar code in pci.c */
|
|
+ acxusb_s_read_eeprom_version(adev);
|
|
+ acxusb_s_fill_configoption(adev);
|
|
+ acx_s_set_defaults(adev);
|
|
+ acx_s_get_firmware_version(adev);
|
|
+ acx_display_hardware_details(adev);
|
|
+
|
|
+ /* Register the network device */
|
|
+ log(L_INIT, "registering network device\n");
|
|
+ result = register_netdev(ndev);
|
|
+ if (result) {
|
|
+ msg = "acx: failed to register USB network device "
|
|
+ "(error %d)\n";
|
|
+ goto end_nomem;
|
|
+ }
|
|
+
|
|
+ acx_proc_register_entries(ndev);
|
|
+
|
|
+ acx_stop_queue(ndev, "on probe");
|
|
+ acx_carrier_off(ndev, "on probe");
|
|
+
|
|
+ printk("acx: USB module " ACX_RELEASE " loaded successfully\n");
|
|
+
|
|
+#if CMD_DISCOVERY
|
|
+ great_inquisitor(adev);
|
|
+#endif
|
|
+
|
|
+ /* Everything went OK, we are happy now */
|
|
+ result = OK;
|
|
+ goto end;
|
|
+
|
|
+end_nomem:
|
|
+ printk(msg, result);
|
|
+
|
|
+ if (ndev) {
|
|
+ if (adev->usb_rx) {
|
|
+ for (i = 0; i < ACX_RX_URB_CNT; i++)
|
|
+ usb_free_urb(adev->usb_rx[i].urb);
|
|
+ kfree(adev->usb_rx);
|
|
+ }
|
|
+ if (adev->usb_tx) {
|
|
+ for (i = 0; i < ACX_TX_URB_CNT; i++)
|
|
+ usb_free_urb(adev->usb_tx[i].urb);
|
|
+ kfree(adev->usb_tx);
|
|
+ }
|
|
+ free_netdev(ndev);
|
|
+ }
|
|
+
|
|
+ result = -ENOMEM;
|
|
+ goto end;
|
|
+
|
|
+end_nodev:
|
|
+ /* no device we could handle, return error. */
|
|
+ result = -EIO;
|
|
+
|
|
+end:
|
|
+ FN_EXIT1(result);
|
|
+ return result;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_e_disconnect()
|
|
+**
|
|
+** This function is invoked whenever the user pulls the plug from the USB
|
|
+** device or the module is removed from the kernel. In these cases, the
|
|
+** network devices have to be taken down and all allocated memory has
|
|
+** to be freed.
|
|
+*/
|
|
+static void
|
|
+acxusb_e_disconnect(struct usb_interface *intf)
|
|
+{
|
|
+ acx_device_t *adev = usb_get_intfdata(intf);
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ /* No WLAN device... no sense */
|
|
+ if (!adev)
|
|
+ goto end;
|
|
+
|
|
+ /* Unregister network device
|
|
+ *
|
|
+ * If the interface is up, unregister_netdev() will take
|
|
+ * care of calling our close() function, which takes
|
|
+ * care of unlinking the urbs, sending the device to
|
|
+ * sleep, etc...
|
|
+ * This can't be called with sem or lock held because
|
|
+ * _close() will try to grab it as well if it's called,
|
|
+ * deadlocking the machine.
|
|
+ */
|
|
+ unregister_netdev(adev->ndev);
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+ acx_lock(adev, flags);
|
|
+ /* This device exists no more */
|
|
+ usb_set_intfdata(intf, NULL);
|
|
+ acx_proc_unregister_entries(adev->ndev);
|
|
+
|
|
+ /*
|
|
+ * Here we only free them. _close() took care of
|
|
+ * unlinking them.
|
|
+ */
|
|
+ for (i = 0; i < ACX_RX_URB_CNT; ++i) {
|
|
+ usb_free_urb(adev->usb_rx[i].urb);
|
|
+ }
|
|
+ for (i = 0; i< ACX_TX_URB_CNT; ++i) {
|
|
+ usb_free_urb(adev->usb_tx[i].urb);
|
|
+ }
|
|
+
|
|
+ /* Freeing containers */
|
|
+ kfree(adev->usb_rx);
|
|
+ kfree(adev->usb_tx);
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ free_netdev(adev->ndev);
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_e_open()
|
|
+** This function is called when the user sets up the network interface.
|
|
+** It initializes a management timer, sets up the USB card and starts
|
|
+** the network tx queue and USB receive.
|
|
+*/
|
|
+static int
|
|
+acxusb_e_open(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* put the ACX100 out of sleep mode */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_WAKE, NULL, 0);
|
|
+
|
|
+ acx_init_task_scheduler(adev);
|
|
+
|
|
+ init_timer(&adev->mgmt_timer);
|
|
+ adev->mgmt_timer.function = acx_i_timer;
|
|
+ adev->mgmt_timer.data = (unsigned long)adev;
|
|
+
|
|
+ /* acx_s_start needs it */
|
|
+ SET_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+ acx_s_start(adev);
|
|
+
|
|
+ /* don't acx_start_queue() here, we need to associate first */
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ for (i = 0; i < ACX_RX_URB_CNT; i++) {
|
|
+ adev->usb_rx[i].urb->status = 0;
|
|
+ }
|
|
+
|
|
+ acxusb_l_poll_rx(adev, &adev->usb_rx[0]);
|
|
+
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_e_close()
|
|
+**
|
|
+** This function stops the network functionality of the interface (invoked
|
|
+** when the user calls ifconfig <wlan> down). The tx queue is halted and
|
|
+** the device is marked as down. In case there were any pending USB bulk
|
|
+** transfers, these are unlinked (asynchronously). The module in-use count
|
|
+** is also decreased in this function.
|
|
+*/
|
|
+static int
|
|
+acxusb_e_close(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+#ifdef WE_STILL_DONT_CARE_ABOUT_IT
|
|
+ /* Transmit a disassociate frame */
|
|
+ lock
|
|
+ acx_l_transmit_disassoc(adev, &client);
|
|
+ unlock
|
|
+#endif
|
|
+
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ CLEAR_BIT(adev->dev_state_mask, ACX_STATE_IFACE_UP);
|
|
+
|
|
+/* Code below is remarkably similar to acxpci_s_down(). Maybe we can merge them? */
|
|
+
|
|
+ /* Make sure we don't get any more rx requests */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_RX, NULL, 0);
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_DISABLE_TX, NULL, 0);
|
|
+
|
|
+ /*
|
|
+ * We must do FLUSH *without* holding sem to avoid a deadlock.
|
|
+ * See pci.c:acxpci_s_down() for deails.
|
|
+ */
|
|
+ acx_sem_unlock(adev);
|
|
+ FLUSH_SCHEDULED_WORK();
|
|
+ acx_sem_lock(adev);
|
|
+
|
|
+ /* Power down the device */
|
|
+ acx_s_issue_cmd(adev, ACX1xx_CMD_SLEEP, NULL, 0);
|
|
+
|
|
+ /* Stop the transmit queue, mark the device as DOWN */
|
|
+ acx_lock(adev, flags);
|
|
+ acx_stop_queue(ndev, "on ifdown");
|
|
+ acx_set_status(adev, ACX_STATUS_0_STOPPED);
|
|
+ /* stop pending rx/tx urb transfers */
|
|
+ for (i = 0; i < ACX_TX_URB_CNT; i++) {
|
|
+ acxusb_unlink_urb(adev->usb_tx[i].urb);
|
|
+ adev->usb_tx[i].busy = 0;
|
|
+ }
|
|
+ for (i = 0; i < ACX_RX_URB_CNT; i++) {
|
|
+ acxusb_unlink_urb(adev->usb_rx[i].urb);
|
|
+ adev->usb_rx[i].busy = 0;
|
|
+ }
|
|
+ adev->tx_free = ACX_TX_URB_CNT;
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ /* Must do this outside of lock */
|
|
+ del_timer_sync(&adev->mgmt_timer);
|
|
+
|
|
+ acx_sem_unlock(adev);
|
|
+
|
|
+ FN_EXIT0;
|
|
+ return 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_l_poll_rx
|
|
+** This function (re)initiates a bulk-in USB transfer on a given urb
|
|
+*/
|
|
+static void
|
|
+acxusb_l_poll_rx(acx_device_t *adev, usb_rx_t* rx)
|
|
+{
|
|
+ struct usb_device *usbdev;
|
|
+ struct urb *rxurb;
|
|
+ int errcode, rxnum;
|
|
+ unsigned int inpipe;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ rxurb = rx->urb;
|
|
+ usbdev = adev->usbdev;
|
|
+
|
|
+ rxnum = rx - adev->usb_rx;
|
|
+
|
|
+ inpipe = usb_rcvbulkpipe(usbdev, adev->bulkinep);
|
|
+ if (unlikely(rxurb->status == -EINPROGRESS)) {
|
|
+ printk(KERN_ERR "acx: error, rx triggered while rx urb in progress\n");
|
|
+ /* FIXME: this is nasty, receive is being cancelled by this code
|
|
+ * on the other hand, this should not happen anyway...
|
|
+ */
|
|
+ usb_unlink_urb(rxurb);
|
|
+ } else
|
|
+ if (unlikely(rxurb->status == -ECONNRESET)) {
|
|
+ log(L_USBRXTX, "acx_usb: _poll_rx: connection reset\n");
|
|
+ goto end;
|
|
+ }
|
|
+ rxurb->actual_length = 0;
|
|
+ usb_fill_bulk_urb(rxurb, usbdev, inpipe,
|
|
+ &rx->bulkin, /* dataptr */
|
|
+ RXBUFSIZE, /* size */
|
|
+ acxusb_i_complete_rx, /* handler */
|
|
+ rx /* handler param */
|
|
+ );
|
|
+ rxurb->transfer_flags = URB_ASYNC_UNLINK;
|
|
+
|
|
+ /* ATOMIC: we may be called from complete_rx() usb callback */
|
|
+ errcode = usb_submit_urb(rxurb, GFP_ATOMIC);
|
|
+ /* FIXME: evaluate the error code! */
|
|
+ log(L_USBRXTX, "SUBMIT RX (%d) inpipe=0x%X size=%d errcode=%d\n",
|
|
+ rxnum, inpipe, (int) RXBUFSIZE, errcode);
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_i_complete_rx()
|
|
+** Inputs:
|
|
+** urb -> pointer to USB request block
|
|
+** regs -> pointer to register-buffer for syscalls (see asm/ptrace.h)
|
|
+**
|
|
+** This function is invoked by USB subsystem whenever a bulk receive
|
|
+** request returns.
|
|
+** The received data is then committed to the network stack and the next
|
|
+** USB receive is triggered.
|
|
+*/
|
|
+static void
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+acxusb_i_complete_rx(struct urb *urb)
|
|
+#else
|
|
+acxusb_i_complete_rx(struct urb *urb, struct pt_regs *regs)
|
|
+#endif
|
|
+{
|
|
+ acx_device_t *adev;
|
|
+ rxbuffer_t *ptr;
|
|
+ rxbuffer_t *inbuf;
|
|
+ usb_rx_t *rx;
|
|
+ unsigned long flags;
|
|
+ int size, remsize, packetsize, rxnum;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ BUG_ON(!urb->context);
|
|
+
|
|
+ rx = (usb_rx_t *)urb->context;
|
|
+ adev = rx->adev;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /*
|
|
+ * Happens on disconnect or close. Don't play with the urb.
|
|
+ * Don't resubmit it. It will get unlinked by close()
|
|
+ */
|
|
+ if (unlikely(!(adev->dev_state_mask & ACX_STATE_IFACE_UP))) {
|
|
+ log(L_USBRXTX, "rx: device is down, not doing anything\n");
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ inbuf = &rx->bulkin;
|
|
+ size = urb->actual_length;
|
|
+ remsize = size;
|
|
+ rxnum = rx - adev->usb_rx;
|
|
+
|
|
+ log(L_USBRXTX, "RETURN RX (%d) status=%d size=%d\n",
|
|
+ rxnum, urb->status, size);
|
|
+
|
|
+ /* Send the URB that's waiting. */
|
|
+ log(L_USBRXTX, "rxnum=%d, sending=%d\n", rxnum, rxnum^1);
|
|
+ acxusb_l_poll_rx(adev, &adev->usb_rx[rxnum^1]);
|
|
+
|
|
+ if (unlikely(size > sizeof(rxbuffer_t)))
|
|
+ printk("acx_usb: rx too large: %d, please report\n", size);
|
|
+
|
|
+ /* check if the transfer was aborted */
|
|
+ switch (urb->status) {
|
|
+ case 0: /* No error */
|
|
+ break;
|
|
+ case -EOVERFLOW:
|
|
+ printk(KERN_ERR "acx: rx data overrun\n");
|
|
+ adev->rxtruncsize = 0; /* Not valid anymore. */
|
|
+ goto end_unlock;
|
|
+ case -ECONNRESET:
|
|
+ adev->rxtruncsize = 0;
|
|
+ goto end_unlock;
|
|
+ case -ESHUTDOWN: /* rmmod */
|
|
+ adev->rxtruncsize = 0;
|
|
+ goto end_unlock;
|
|
+ default:
|
|
+ adev->rxtruncsize = 0;
|
|
+ adev->stats.rx_errors++;
|
|
+ printk("acx: rx error (urb status=%d)\n", urb->status);
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ if (unlikely(!size))
|
|
+ printk("acx: warning, encountered zerolength rx packet\n");
|
|
+
|
|
+ if (urb->transfer_buffer != inbuf)
|
|
+ goto end_unlock;
|
|
+
|
|
+ /* check if previous frame was truncated
|
|
+ ** FIXME: this code can only handle truncation
|
|
+ ** of consecutive packets!
|
|
+ */
|
|
+ ptr = inbuf;
|
|
+ if (adev->rxtruncsize) {
|
|
+ int tail_size;
|
|
+
|
|
+ ptr = &adev->rxtruncbuf;
|
|
+ packetsize = RXBUF_BYTES_USED(ptr);
|
|
+ if (acx_debug & L_USBRXTX) {
|
|
+ printk("handling truncated frame (truncsize=%d size=%d "
|
|
+ "packetsize(from trunc)=%d)\n",
|
|
+ adev->rxtruncsize, size, packetsize);
|
|
+ acx_dump_bytes(ptr, RXBUF_HDRSIZE);
|
|
+ acx_dump_bytes(inbuf, RXBUF_HDRSIZE);
|
|
+ }
|
|
+
|
|
+ /* bytes needed for rxtruncbuf completion: */
|
|
+ tail_size = packetsize - adev->rxtruncsize;
|
|
+
|
|
+ if (size < tail_size) {
|
|
+ /* there is not enough data to complete this packet,
|
|
+ ** simply append the stuff to the truncation buffer
|
|
+ */
|
|
+ memcpy(((char *)ptr) + adev->rxtruncsize, inbuf, size);
|
|
+ adev->rxtruncsize += size;
|
|
+ remsize = 0;
|
|
+ } else {
|
|
+ /* ok, this data completes the previously
|
|
+ ** truncated packet. copy it into a descriptor
|
|
+ ** and give it to the rest of the stack */
|
|
+
|
|
+ /* append tail to previously truncated part
|
|
+ ** NB: adev->rxtruncbuf (pointed to by ptr) can't
|
|
+ ** overflow because this is already checked before
|
|
+ ** truncation buffer was filled. See below,
|
|
+ ** "if (packetsize > sizeof(rxbuffer_t))..." code */
|
|
+ memcpy(((char *)ptr) + adev->rxtruncsize, inbuf, tail_size);
|
|
+
|
|
+ if (acx_debug & L_USBRXTX) {
|
|
+ printk("full trailing packet + 12 bytes:\n");
|
|
+ acx_dump_bytes(inbuf, tail_size + RXBUF_HDRSIZE);
|
|
+ }
|
|
+ acx_l_process_rxbuf(adev, ptr);
|
|
+ adev->rxtruncsize = 0;
|
|
+ ptr = (rxbuffer_t *) (((char *)inbuf) + tail_size);
|
|
+ remsize -= tail_size;
|
|
+ }
|
|
+ log(L_USBRXTX, "post-merge size=%d remsize=%d\n",
|
|
+ size, remsize);
|
|
+ }
|
|
+
|
|
+ /* size = USB data block size
|
|
+ ** remsize = unprocessed USB bytes left
|
|
+ ** ptr = current pos in USB data block
|
|
+ */
|
|
+ while (remsize) {
|
|
+ if (remsize < RXBUF_HDRSIZE) {
|
|
+ printk("acx: truncated rx header (%d bytes)!\n",
|
|
+ remsize);
|
|
+ if (ACX_DEBUG)
|
|
+ acx_dump_bytes(ptr, remsize);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ packetsize = RXBUF_BYTES_USED(ptr);
|
|
+ log(L_USBRXTX, "packet with packetsize=%d\n", packetsize);
|
|
+
|
|
+ if (RXBUF_IS_TXSTAT(ptr)) {
|
|
+ /* do rate handling */
|
|
+ usb_txstatus_t *stat = (void*)ptr;
|
|
+ u16 client_no = (u16)stat->hostdata;
|
|
+
|
|
+ log(L_USBRXTX, "tx: stat: mac_cnt_rcvd:%04X "
|
|
+ "queue_index:%02X mac_status:%02X hostdata:%08X "
|
|
+ "rate:%u ack_failures:%02X rts_failures:%02X "
|
|
+ "rts_ok:%02X\n",
|
|
+ stat->mac_cnt_rcvd,
|
|
+ stat->queue_index, stat->mac_status, stat->hostdata,
|
|
+ stat->rate, stat->ack_failures, stat->rts_failures,
|
|
+ stat->rts_ok);
|
|
+
|
|
+ if (adev->rate_auto && client_no < VEC_SIZE(adev->sta_list)) {
|
|
+ client_t *clt = &adev->sta_list[client_no];
|
|
+ u16 cur = stat->hostdata >> 16;
|
|
+
|
|
+ if (clt && clt->rate_cur == cur) {
|
|
+ acx_l_handle_txrate_auto(adev, clt,
|
|
+ cur, /* intended rate */
|
|
+ stat->rate, 0, /* actually used rate */
|
|
+ stat->mac_status, /* error? */
|
|
+ ACX_TX_URB_CNT - adev->tx_free);
|
|
+ }
|
|
+ }
|
|
+ goto next;
|
|
+ }
|
|
+
|
|
+ if (packetsize > sizeof(rxbuffer_t)) {
|
|
+ printk("acx: packet exceeds max wlan "
|
|
+ "frame size (%d > %d). size=%d\n",
|
|
+ packetsize, (int) sizeof(rxbuffer_t), size);
|
|
+ if (ACX_DEBUG)
|
|
+ acx_dump_bytes(ptr, 16);
|
|
+ /* FIXME: put some real error-handling in here! */
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (packetsize > remsize) {
|
|
+ /* frame truncation handling */
|
|
+ if (acx_debug & L_USBRXTX) {
|
|
+ printk("need to truncate packet, "
|
|
+ "packetsize=%d remsize=%d "
|
|
+ "size=%d bytes:",
|
|
+ packetsize, remsize, size);
|
|
+ acx_dump_bytes(ptr, RXBUF_HDRSIZE);
|
|
+ }
|
|
+ memcpy(&adev->rxtruncbuf, ptr, remsize);
|
|
+ adev->rxtruncsize = remsize;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ /* packetsize <= remsize */
|
|
+ /* now handle the received data */
|
|
+ acx_l_process_rxbuf(adev, ptr);
|
|
+next:
|
|
+ ptr = (rxbuffer_t *)(((char *)ptr) + packetsize);
|
|
+ remsize -= packetsize;
|
|
+ if ((acx_debug & L_USBRXTX) && remsize) {
|
|
+ printk("more than one packet in buffer, "
|
|
+ "second packet hdr:");
|
|
+ acx_dump_bytes(ptr, RXBUF_HDRSIZE);
|
|
+ }
|
|
+ }
|
|
+
|
|
+end_unlock:
|
|
+ acx_unlock(adev, flags);
|
|
+/* end: */
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** acxusb_i_complete_tx()
|
|
+** Inputs:
|
|
+** urb -> pointer to USB request block
|
|
+** regs -> pointer to register-buffer for syscalls (see asm/ptrace.h)
|
|
+**
|
|
+** This function is invoked upon termination of a USB transfer.
|
|
+*/
|
|
+static void
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 19)
|
|
+acxusb_i_complete_tx(struct urb *urb)
|
|
+#else
|
|
+acxusb_i_complete_tx(struct urb *urb, struct pt_regs *regs)
|
|
+#endif
|
|
+{
|
|
+ acx_device_t *adev;
|
|
+ usb_tx_t *tx;
|
|
+ unsigned long flags;
|
|
+ int txnum;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ BUG_ON(!urb->context);
|
|
+
|
|
+ tx = (usb_tx_t *)urb->context;
|
|
+ adev = tx->adev;
|
|
+
|
|
+ txnum = tx - adev->usb_tx;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+
|
|
+ /*
|
|
+ * If the iface isn't up, we don't have any right
|
|
+ * to play with them. The urb may get unlinked.
|
|
+ */
|
|
+ if (unlikely(!(adev->dev_state_mask & ACX_STATE_IFACE_UP))) {
|
|
+ log(L_USBRXTX, "tx: device is down, not doing anything\n");
|
|
+ goto end_unlock;
|
|
+ }
|
|
+
|
|
+ log(L_USBRXTX, "RETURN TX (%d): status=%d size=%d\n",
|
|
+ txnum, urb->status, urb->actual_length);
|
|
+
|
|
+ /* handle USB transfer errors */
|
|
+ switch (urb->status) {
|
|
+ case 0: /* No error */
|
|
+ break;
|
|
+ case -ESHUTDOWN:
|
|
+ goto end_unlock;
|
|
+ break;
|
|
+ case -ECONNRESET:
|
|
+ goto end_unlock;
|
|
+ break;
|
|
+ /* FIXME: real error-handling code here please */
|
|
+ default:
|
|
+ printk(KERN_ERR "acx: tx error, urb status=%d\n", urb->status);
|
|
+ /* FIXME: real error-handling code here please */
|
|
+ }
|
|
+
|
|
+ /* free the URB and check for more data */
|
|
+ tx->busy = 0;
|
|
+ adev->tx_free++;
|
|
+ if ((adev->tx_free >= TX_START_QUEUE)
|
|
+ && (adev->status == ACX_STATUS_4_ASSOCIATED)
|
|
+ && (acx_queue_stopped(adev->ndev))
|
|
+ ) {
|
|
+ log(L_BUF, "tx: wake queue (%u free txbufs)\n",
|
|
+ adev->tx_free);
|
|
+ acx_wake_queue(adev->ndev, NULL);
|
|
+ }
|
|
+
|
|
+end_unlock:
|
|
+ acx_unlock(adev, flags);
|
|
+/* end: */
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxusb_l_alloc_tx
|
|
+** Actually returns a usb_tx_t* ptr
|
|
+*/
|
|
+tx_t*
|
|
+acxusb_l_alloc_tx(acx_device_t *adev)
|
|
+{
|
|
+ usb_tx_t *tx;
|
|
+ unsigned head;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ head = adev->tx_head;
|
|
+ do {
|
|
+ head = (head + 1) % ACX_TX_URB_CNT;
|
|
+ if (!adev->usb_tx[head].busy) {
|
|
+ log(L_USBRXTX, "allocated tx %d\n", head);
|
|
+ tx = &adev->usb_tx[head];
|
|
+ tx->busy = 1;
|
|
+ adev->tx_free--;
|
|
+ /* Keep a few free descs between head and tail of tx ring.
|
|
+ ** It is not absolutely needed, just feels safer */
|
|
+ if (adev->tx_free < TX_STOP_QUEUE) {
|
|
+ log(L_BUF, "tx: stop queue "
|
|
+ "(%u free txbufs)\n", adev->tx_free);
|
|
+ acx_stop_queue(adev->ndev, NULL);
|
|
+ }
|
|
+ goto end;
|
|
+ }
|
|
+ } while (likely(head!=adev->tx_head));
|
|
+ tx = NULL;
|
|
+ printk_ratelimited("acx: tx buffers full\n");
|
|
+end:
|
|
+ adev->tx_head = head;
|
|
+ FN_EXIT0;
|
|
+ return (tx_t*)tx;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** Used if alloc_tx()'ed buffer needs to be cancelled without doing tx
|
|
+*/
|
|
+void
|
|
+acxusb_l_dealloc_tx(tx_t *tx_opaque)
|
|
+{
|
|
+ usb_tx_t* tx = (usb_tx_t*)tx_opaque;
|
|
+ tx->busy = 0;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+*/
|
|
+void*
|
|
+acxusb_l_get_txbuf(acx_device_t *adev, tx_t* tx_opaque)
|
|
+{
|
|
+ usb_tx_t* tx = (usb_tx_t*)tx_opaque;
|
|
+ return &tx->bulkout.data;
|
|
+}
|
|
+
|
|
+
|
|
+/***************************************************************
|
|
+** acxusb_l_tx_data
|
|
+**
|
|
+** Can be called from IRQ (rx -> (AP bridging or mgmt response) -> tx).
|
|
+** Can be called from acx_i_start_xmit (data frames from net core).
|
|
+*/
|
|
+void
|
|
+acxusb_l_tx_data(acx_device_t *adev, tx_t* tx_opaque, int wlanpkt_len)
|
|
+{
|
|
+ struct usb_device *usbdev;
|
|
+ struct urb* txurb;
|
|
+ usb_tx_t* tx;
|
|
+ usb_txbuffer_t* txbuf;
|
|
+ client_t *clt;
|
|
+ wlan_hdr_t* whdr;
|
|
+ unsigned int outpipe;
|
|
+ int ucode, txnum;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ tx = ((usb_tx_t *)tx_opaque);
|
|
+ txurb = tx->urb;
|
|
+ txbuf = &tx->bulkout;
|
|
+ whdr = (wlan_hdr_t *)txbuf->data;
|
|
+ txnum = tx - adev->usb_tx;
|
|
+
|
|
+ log(L_DEBUG, "using buf#%d free=%d len=%d\n",
|
|
+ txnum, adev->tx_free, wlanpkt_len);
|
|
+
|
|
+ switch (adev->mode) {
|
|
+ case ACX_MODE_0_ADHOC:
|
|
+ case ACX_MODE_3_AP:
|
|
+ clt = acx_l_sta_list_get(adev, whdr->a1);
|
|
+ break;
|
|
+ case ACX_MODE_2_STA:
|
|
+ clt = adev->ap_client;
|
|
+ break;
|
|
+ default: /* ACX_MODE_OFF, ACX_MODE_MONITOR */
|
|
+ clt = NULL;
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ if (unlikely(clt && !clt->rate_cur)) {
|
|
+ printk("acx: driver bug! bad ratemask\n");
|
|
+ goto end;
|
|
+ }
|
|
+
|
|
+ /* fill the USB transfer header */
|
|
+ txbuf->desc = cpu_to_le16(USB_TXBUF_TXDESC);
|
|
+ txbuf->mpdu_len = cpu_to_le16(wlanpkt_len);
|
|
+ txbuf->queue_index = 1;
|
|
+ if (clt) {
|
|
+ txbuf->rate = clt->rate_100;
|
|
+ txbuf->hostdata = (clt - adev->sta_list) | (clt->rate_cur << 16);
|
|
+ } else {
|
|
+ txbuf->rate = adev->rate_bcast100;
|
|
+ txbuf->hostdata = ((u16)-1) | (adev->rate_bcast << 16);
|
|
+ }
|
|
+ txbuf->ctrl1 = DESC_CTL_FIRSTFRAG;
|
|
+ if (1 == adev->preamble_cur)
|
|
+ SET_BIT(txbuf->ctrl1, DESC_CTL_SHORT_PREAMBLE);
|
|
+ txbuf->ctrl2 = 0;
|
|
+ txbuf->data_len = cpu_to_le16(wlanpkt_len);
|
|
+
|
|
+ if (unlikely(acx_debug & L_DATA)) {
|
|
+ printk("dump of bulk out urb:\n");
|
|
+ acx_dump_bytes(txbuf, wlanpkt_len + USB_TXBUF_HDRSIZE);
|
|
+ }
|
|
+
|
|
+ if (unlikely(txurb->status == -EINPROGRESS)) {
|
|
+ printk("acx: trying to submit tx urb while already in progress\n");
|
|
+ }
|
|
+
|
|
+ /* now schedule the USB transfer */
|
|
+ usbdev = adev->usbdev;
|
|
+ outpipe = usb_sndbulkpipe(usbdev, adev->bulkoutep);
|
|
+
|
|
+ usb_fill_bulk_urb(txurb, usbdev, outpipe,
|
|
+ txbuf, /* dataptr */
|
|
+ wlanpkt_len + USB_TXBUF_HDRSIZE, /* size */
|
|
+ acxusb_i_complete_tx, /* handler */
|
|
+ tx /* handler param */
|
|
+ );
|
|
+
|
|
+ txurb->transfer_flags = URB_ASYNC_UNLINK|URB_ZERO_PACKET;
|
|
+ ucode = usb_submit_urb(txurb, GFP_ATOMIC);
|
|
+ log(L_USBRXTX, "SUBMIT TX (%d): outpipe=0x%X buf=%p txsize=%d "
|
|
+ "rate=%u errcode=%d\n", txnum, outpipe, txbuf,
|
|
+ wlanpkt_len + USB_TXBUF_HDRSIZE, txbuf->rate, ucode);
|
|
+
|
|
+ if (unlikely(ucode)) {
|
|
+ printk(KERN_ERR "acx: submit_urb() error=%d txsize=%d\n",
|
|
+ ucode, wlanpkt_len + USB_TXBUF_HDRSIZE);
|
|
+
|
|
+ /* on error, just mark the frame as done and update
|
|
+ ** the statistics
|
|
+ */
|
|
+ adev->stats.tx_errors++;
|
|
+ tx->busy = 0;
|
|
+ adev->tx_free++;
|
|
+ /* needed? if (adev->tx_free > TX_START_QUEUE) acx_wake_queue(...) */
|
|
+ }
|
|
+end:
|
|
+ FN_EXIT0;
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static void
|
|
+acxusb_i_set_rx_mode(struct net_device *ndev)
|
|
+{
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#ifdef HAVE_TX_TIMEOUT
|
|
+static void
|
|
+acxusb_i_tx_timeout(struct net_device *ndev)
|
|
+{
|
|
+ acx_device_t *adev = ndev2adev(ndev);
|
|
+ unsigned long flags;
|
|
+ int i;
|
|
+
|
|
+ FN_ENTER;
|
|
+
|
|
+ acx_lock(adev, flags);
|
|
+ /* unlink the URBs */
|
|
+ for (i = 0; i < ACX_TX_URB_CNT; i++) {
|
|
+ acxusb_unlink_urb(adev->usb_tx[i].urb);
|
|
+ adev->usb_tx[i].busy = 0;
|
|
+ }
|
|
+ adev->tx_free = ACX_TX_URB_CNT;
|
|
+ /* TODO: stats update */
|
|
+ acx_unlock(adev, flags);
|
|
+
|
|
+ FN_EXIT0;
|
|
+}
|
|
+#endif
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** init_module()
|
|
+**
|
|
+** This function is invoked upon loading of the kernel module.
|
|
+** It registers itself at the kernel's USB subsystem.
|
|
+**
|
|
+** Returns: Errorcode on failure, 0 on success
|
|
+*/
|
|
+int __init
|
|
+acxusb_e_init_module(void)
|
|
+{
|
|
+ log(L_INIT, "USB module " ACX_RELEASE " initialized, "
|
|
+ "probing for devices...\n");
|
|
+ return usb_register(&acxusb_driver);
|
|
+}
|
|
+
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** cleanup_module()
|
|
+**
|
|
+** This function is invoked as last step of the module unloading. It simply
|
|
+** deregisters this module at the kernel's USB subsystem.
|
|
+*/
|
|
+void __exit
|
|
+acxusb_e_cleanup_module()
|
|
+{
|
|
+ usb_deregister(&acxusb_driver);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** DEBUG STUFF
|
|
+*/
|
|
+#if ACX_DEBUG
|
|
+
|
|
+#ifdef UNUSED
|
|
+static void
|
|
+dump_device(struct usb_device *usbdev)
|
|
+{
|
|
+ int i;
|
|
+ struct usb_config_descriptor *cd;
|
|
+
|
|
+ printk("acx device dump:\n");
|
|
+ printk(" devnum: %d\n", usbdev->devnum);
|
|
+ printk(" speed: %d\n", usbdev->speed);
|
|
+ printk(" tt: 0x%X\n", (unsigned int)(usbdev->tt));
|
|
+ printk(" ttport: %d\n", (unsigned int)(usbdev->ttport));
|
|
+ printk(" toggle[0]: 0x%X toggle[1]: 0x%X\n", (unsigned int)(usbdev->toggle[0]), (unsigned int)(usbdev->toggle[1]));
|
|
+#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 11)
|
|
+ /* This saw a change after 2.6.10 */
|
|
+ printk(" ep_in wMaxPacketSize: ");
|
|
+ for (i = 0; i < 16; ++i)
|
|
+ if (usbdev->ep_in[i] != NULL)
|
|
+ printk("%d:%d ", i, usbdev->ep_in[i]->desc.wMaxPacketSize);
|
|
+ printk("\n");
|
|
+ printk(" ep_out wMaxPacketSize: ");
|
|
+ for (i = 0; i < VEC_SIZE(usbdev->ep_out); ++i)
|
|
+ if (usbdev->ep_out[i] != NULL)
|
|
+ printk("%d:%d ", i, usbdev->ep_out[i]->desc.wMaxPacketSize);
|
|
+ printk("\n");
|
|
+#else
|
|
+ printk(" epmaxpacketin: ");
|
|
+ for (i = 0; i < 16; i++)
|
|
+ printk("%d ", usbdev->epmaxpacketin[i]);
|
|
+ printk("\n");
|
|
+ printk(" epmaxpacketout: ");
|
|
+ for (i = 0; i < 16; i++)
|
|
+ printk("%d ", usbdev->epmaxpacketout[i]);
|
|
+ printk("\n");
|
|
+#endif
|
|
+ printk(" parent: 0x%X\n", (unsigned int)usbdev->parent);
|
|
+ printk(" bus: 0x%X\n", (unsigned int)usbdev->bus);
|
|
+#ifdef NO_DATATYPE
|
|
+ printk(" configs: ");
|
|
+ for (i = 0; i < usbdev->descriptor.bNumConfigurations; i++)
|
|
+ printk("0x%X ", usbdev->config[i]);
|
|
+ printk("\n");
|
|
+#endif
|
|
+ printk(" actconfig: %p\n", usbdev->actconfig);
|
|
+ dump_device_descriptor(&usbdev->descriptor);
|
|
+
|
|
+ cd = &usbdev->config->desc;
|
|
+ dump_config_descriptor(cd);
|
|
+}
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+static void
|
|
+dump_config_descriptor(struct usb_config_descriptor *cd)
|
|
+{
|
|
+ printk("Configuration Descriptor:\n");
|
|
+ if (!cd) {
|
|
+ printk("NULL\n");
|
|
+ return;
|
|
+ }
|
|
+ printk(" bLength: %d (0x%X)\n", cd->bLength, cd->bLength);
|
|
+ printk(" bDescriptorType: %d (0x%X)\n", cd->bDescriptorType, cd->bDescriptorType);
|
|
+ printk(" bNumInterfaces: %d (0x%X)\n", cd->bNumInterfaces, cd->bNumInterfaces);
|
|
+ printk(" bConfigurationValue: %d (0x%X)\n", cd->bConfigurationValue, cd->bConfigurationValue);
|
|
+ printk(" iConfiguration: %d (0x%X)\n", cd->iConfiguration, cd->iConfiguration);
|
|
+ printk(" bmAttributes: %d (0x%X)\n", cd->bmAttributes, cd->bmAttributes);
|
|
+ /* printk(" MaxPower: %d (0x%X)\n", cd->bMaxPower, cd->bMaxPower); */
|
|
+}
|
|
+
|
|
+
|
|
+static void
|
|
+dump_device_descriptor(struct usb_device_descriptor *dd)
|
|
+{
|
|
+ printk("Device Descriptor:\n");
|
|
+ if (!dd) {
|
|
+ printk("NULL\n");
|
|
+ return;
|
|
+ }
|
|
+ printk(" bLength: %d (0x%X)\n", dd->bLength, dd->bLength);
|
|
+ printk(" bDescriptortype: %d (0x%X)\n", dd->bDescriptorType, dd->bDescriptorType);
|
|
+ printk(" bcdUSB: %d (0x%X)\n", dd->bcdUSB, dd->bcdUSB);
|
|
+ printk(" bDeviceClass: %d (0x%X)\n", dd->bDeviceClass, dd->bDeviceClass);
|
|
+ printk(" bDeviceSubClass: %d (0x%X)\n", dd->bDeviceSubClass, dd->bDeviceSubClass);
|
|
+ printk(" bDeviceProtocol: %d (0x%X)\n", dd->bDeviceProtocol, dd->bDeviceProtocol);
|
|
+ printk(" bMaxPacketSize0: %d (0x%X)\n", dd->bMaxPacketSize0, dd->bMaxPacketSize0);
|
|
+ printk(" idVendor: %d (0x%X)\n", dd->idVendor, dd->idVendor);
|
|
+ printk(" idProduct: %d (0x%X)\n", dd->idProduct, dd->idProduct);
|
|
+ printk(" bcdDevice: %d (0x%X)\n", dd->bcdDevice, dd->bcdDevice);
|
|
+ printk(" iManufacturer: %d (0x%X)\n", dd->iManufacturer, dd->iManufacturer);
|
|
+ printk(" iProduct: %d (0x%X)\n", dd->iProduct, dd->iProduct);
|
|
+ printk(" iSerialNumber: %d (0x%X)\n", dd->iSerialNumber, dd->iSerialNumber);
|
|
+ printk(" bNumConfigurations: %d (0x%X)\n", dd->bNumConfigurations, dd->bNumConfigurations);
|
|
+}
|
|
+#endif /* UNUSED */
|
|
+
|
|
+#endif /* ACX_DEBUG */
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/wlan.c
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/wlan.c 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,424 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** This code is based on elements which are
|
|
+** Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved.
|
|
+** info@linux-wlan.com
|
|
+** http://www.linux-wlan.com
|
|
+*/
|
|
+
|
|
+#include <linux/version.h>
|
|
+#if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
|
|
+#include <linux/config.h>
|
|
+#endif
|
|
+#include <linux/types.h>
|
|
+#include <linux/if_arp.h>
|
|
+#include <linux/wireless.h>
|
|
+#include <net/iw_handler.h>
|
|
+
|
|
+#include "acx.h"
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+*/
|
|
+#define LOG_BAD_EID(hdr,len,ie_ptr) acx_log_bad_eid(hdr, len, ((wlan_ie_t*)ie_ptr))
|
|
+
|
|
+#define IE_EID(ie_ptr) (((wlan_ie_t*)(ie_ptr))->eid)
|
|
+#define IE_LEN(ie_ptr) (((wlan_ie_t*)(ie_ptr))->len)
|
|
+#define OFFSET(hdr,off) (WLAN_HDR_A3_DATAP(hdr) + (off))
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** wlan_mgmt_decode_XXX
|
|
+**
|
|
+** Given a complete frame in f->hdr, sets the pointers in f to
|
|
+** the areas that correspond to the parts of the frame.
|
|
+**
|
|
+** Assumptions:
|
|
+** 1) f->len and f->hdr are already set
|
|
+** 2) f->len is the length of the MAC header + data, the FCS
|
|
+** is NOT included
|
|
+** 3) all members except len and hdr are zero
|
|
+** Arguments:
|
|
+** f frame structure
|
|
+**
|
|
+** Returns:
|
|
+** nothing
|
|
+**
|
|
+** Side effects:
|
|
+** frame structure members are pointing at their
|
|
+** respective portions of the frame buffer.
|
|
+*/
|
|
+void
|
|
+wlan_mgmt_decode_beacon(wlan_fr_beacon_t * f)
|
|
+{
|
|
+ u8 *ie_ptr;
|
|
+ u8 *end = (u8*)f->hdr + f->len;
|
|
+
|
|
+ f->type = WLAN_FSTYPE_BEACON;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->ts = (u64 *) OFFSET(f->hdr, WLAN_BEACON_OFF_TS);
|
|
+ f->bcn_int = (u16 *) OFFSET(f->hdr, WLAN_BEACON_OFF_BCN_INT);
|
|
+ f->cap_info = (u16 *) OFFSET(f->hdr, WLAN_BEACON_OFF_CAPINFO);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ ie_ptr = OFFSET(f->hdr, WLAN_BEACON_OFF_SSID);
|
|
+ while (ie_ptr < end) {
|
|
+ switch (IE_EID(ie_ptr)) {
|
|
+ case WLAN_EID_SSID:
|
|
+ f->ssid = (wlan_ie_ssid_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_SUPP_RATES:
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_EXT_RATES:
|
|
+ f->ext_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_FH_PARMS:
|
|
+ f->fh_parms = (wlan_ie_fh_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_DS_PARMS:
|
|
+ f->ds_parms = (wlan_ie_ds_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_CF_PARMS:
|
|
+ f->cf_parms = (wlan_ie_cf_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_IBSS_PARMS:
|
|
+ f->ibss_parms = (wlan_ie_ibss_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_TIM:
|
|
+ f->tim = (wlan_ie_tim_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_ERP_INFO:
|
|
+ f->erp = (wlan_ie_erp_t *) ie_ptr;
|
|
+ break;
|
|
+
|
|
+ case WLAN_EID_COUNTRY:
|
|
+ /* was seen: 07 06 47 42 20 01 0D 14 */
|
|
+ case WLAN_EID_PWR_CONSTRAINT:
|
|
+ /* was seen by Ashwin Mansinghka <ashwin_man@yahoo.com> from
|
|
+ Atheros-based PCI card in AP mode using madwifi drivers: */
|
|
+ /* 20 01 00 */
|
|
+ case WLAN_EID_NONERP:
|
|
+ /* was seen from WRT54GS with OpenWrt: 2F 01 07 */
|
|
+ case WLAN_EID_UNKNOWN128:
|
|
+ /* was seen by Jacek Jablonski <conexion2000@gmail.com> from Orinoco AP */
|
|
+ /* 80 06 00 60 1D 2C 3B 00 */
|
|
+ case WLAN_EID_UNKNOWN133:
|
|
+ /* was seen by David Bronaugh <dbronaugh@linuxboxen.org> from ???? */
|
|
+ /* 85 1E 00 00 84 12 07 00 FF 00 11 00 61 70 63 31 */
|
|
+ /* 63 73 72 30 34 32 00 00 00 00 00 00 00 00 00 25 */
|
|
+ case WLAN_EID_UNKNOWN223:
|
|
+ /* was seen by Carlos Martin <carlosmn@gmail.com> from ???? */
|
|
+ /* DF 20 01 1E 04 00 00 00 06 63 09 02 FF 0F 30 30 */
|
|
+ /* 30 42 36 42 33 34 30 39 46 31 00 00 00 00 00 00 00 00 */
|
|
+ case WLAN_EID_GENERIC:
|
|
+ /* WPA: hostap code:
|
|
+ if (pos[1] >= 4 &&
|
|
+ pos[2] == 0x00 && pos[3] == 0x50 &&
|
|
+ pos[4] == 0xf2 && pos[5] == 1) {
|
|
+ wpa = pos;
|
|
+ wpa_len = pos[1] + 2;
|
|
+ }
|
|
+ TI x4 mode: seen DD 04 08 00 28 00
|
|
+ (08 00 28 is TI's OUI)
|
|
+ last byte is probably 0/1 - disabled/enabled
|
|
+ */
|
|
+ case WLAN_EID_RSN:
|
|
+ /* hostap does something with it:
|
|
+ rsn = pos;
|
|
+ rsn_len = pos[1] + 2;
|
|
+ */
|
|
+ break;
|
|
+
|
|
+ default:
|
|
+ LOG_BAD_EID(f->hdr, f->len, ie_ptr);
|
|
+ break;
|
|
+ }
|
|
+ ie_ptr = ie_ptr + 2 + IE_LEN(ie_ptr);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+#ifdef UNUSED
|
|
+void wlan_mgmt_decode_ibssatim(wlan_fr_ibssatim_t * f)
|
|
+{
|
|
+ f->type = WLAN_FSTYPE_ATIM;
|
|
+ /*-- Fixed Fields ----*/
|
|
+ /*-- Information elements */
|
|
+}
|
|
+#endif /* UNUSED */
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_disassoc(wlan_fr_disassoc_t * f)
|
|
+{
|
|
+ f->type = WLAN_FSTYPE_DISASSOC;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->reason = (u16 *) OFFSET(f->hdr, WLAN_DISASSOC_OFF_REASON);
|
|
+
|
|
+ /*-- Information elements */
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_assocreq(wlan_fr_assocreq_t * f)
|
|
+{
|
|
+ u8 *ie_ptr;
|
|
+ u8 *end = (u8*)f->hdr + f->len;
|
|
+
|
|
+
|
|
+ f->type = WLAN_FSTYPE_ASSOCREQ;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->cap_info = (u16 *) OFFSET(f->hdr, WLAN_ASSOCREQ_OFF_CAP_INFO);
|
|
+ f->listen_int = (u16 *) OFFSET(f->hdr, WLAN_ASSOCREQ_OFF_LISTEN_INT);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ ie_ptr = OFFSET(f->hdr, WLAN_ASSOCREQ_OFF_SSID);
|
|
+ while (ie_ptr < end) {
|
|
+ switch (IE_EID(ie_ptr)) {
|
|
+ case WLAN_EID_SSID:
|
|
+ f->ssid = (wlan_ie_ssid_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_SUPP_RATES:
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_EXT_RATES:
|
|
+ f->ext_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ default:
|
|
+ LOG_BAD_EID(f->hdr, f->len, ie_ptr);
|
|
+ break;
|
|
+ }
|
|
+ ie_ptr = ie_ptr + 2 + IE_LEN(ie_ptr);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_assocresp(wlan_fr_assocresp_t * f)
|
|
+{
|
|
+ f->type = WLAN_FSTYPE_ASSOCRESP;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->cap_info = (u16 *) OFFSET(f->hdr, WLAN_ASSOCRESP_OFF_CAP_INFO);
|
|
+ f->status = (u16 *) OFFSET(f->hdr, WLAN_ASSOCRESP_OFF_STATUS);
|
|
+ f->aid = (u16 *) OFFSET(f->hdr, WLAN_ASSOCRESP_OFF_AID);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *)
|
|
+ OFFSET(f->hdr, WLAN_ASSOCRESP_OFF_SUPP_RATES);
|
|
+}
|
|
+
|
|
+
|
|
+#ifdef UNUSED
|
|
+void
|
|
+wlan_mgmt_decode_reassocreq(wlan_fr_reassocreq_t * f)
|
|
+{
|
|
+ u8 *ie_ptr;
|
|
+ u8 *end = (u8*)f->hdr + f->len;
|
|
+
|
|
+ f->type = WLAN_FSTYPE_REASSOCREQ;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->cap_info = (u16 *) OFFSET(f->hdr, WLAN_REASSOCREQ_OFF_CAP_INFO);
|
|
+ f->listen_int = (u16 *) OFFSET(f->hdr, WLAN_REASSOCREQ_OFF_LISTEN_INT);
|
|
+ f->curr_ap = (u8 *) OFFSET(f->hdr, WLAN_REASSOCREQ_OFF_CURR_AP);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ ie_ptr = OFFSET(f->hdr, WLAN_REASSOCREQ_OFF_SSID);
|
|
+ while (ie_ptr < end) {
|
|
+ switch (IE_EID(ie_ptr)) {
|
|
+ case WLAN_EID_SSID:
|
|
+ f->ssid = (wlan_ie_ssid_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_SUPP_RATES:
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_EXT_RATES:
|
|
+ f->ext_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ default:
|
|
+ LOG_BAD_EID(f->hdr, f->len, ie_ptr);
|
|
+ break;
|
|
+ }
|
|
+ ie_ptr = ie_ptr + 2 + IE_LEN(ie_ptr);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_reassocresp(wlan_fr_reassocresp_t * f)
|
|
+{
|
|
+ f->type = WLAN_FSTYPE_REASSOCRESP;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->cap_info = (u16 *) OFFSET(f->hdr, WLAN_REASSOCRESP_OFF_CAP_INFO);
|
|
+ f->status = (u16 *) OFFSET(f->hdr, WLAN_REASSOCRESP_OFF_STATUS);
|
|
+ f->aid = (u16 *) OFFSET(f->hdr, WLAN_REASSOCRESP_OFF_AID);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *)
|
|
+ OFFSET(f->hdr, WLAN_REASSOCRESP_OFF_SUPP_RATES);
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_probereq(wlan_fr_probereq_t * f)
|
|
+{
|
|
+ u8 *ie_ptr;
|
|
+ u8 *end = (u8*)f->hdr + f->len;
|
|
+
|
|
+ f->type = WLAN_FSTYPE_PROBEREQ;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+
|
|
+ /*-- Information elements */
|
|
+ ie_ptr = OFFSET(f->hdr, WLAN_PROBEREQ_OFF_SSID);
|
|
+ while (ie_ptr < end) {
|
|
+ switch (IE_EID(ie_ptr)) {
|
|
+ case WLAN_EID_SSID:
|
|
+ f->ssid = (wlan_ie_ssid_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_SUPP_RATES:
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_EXT_RATES:
|
|
+ f->ext_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ default:
|
|
+ LOG_BAD_EID(f->hdr, f->len, ie_ptr);
|
|
+ break;
|
|
+ }
|
|
+ ie_ptr = ie_ptr + 2 + IE_LEN(ie_ptr);
|
|
+ }
|
|
+}
|
|
+#endif /* UNUSED */
|
|
+
|
|
+
|
|
+/* TODO: decoding of beacon and proberesp can be merged (similar structure) */
|
|
+void
|
|
+wlan_mgmt_decode_proberesp(wlan_fr_proberesp_t * f)
|
|
+{
|
|
+ u8 *ie_ptr;
|
|
+ u8 *end = (u8*)f->hdr + f->len;
|
|
+
|
|
+ f->type = WLAN_FSTYPE_PROBERESP;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->ts = (u64 *) OFFSET(f->hdr, WLAN_PROBERESP_OFF_TS);
|
|
+ f->bcn_int = (u16 *) OFFSET(f->hdr, WLAN_PROBERESP_OFF_BCN_INT);
|
|
+ f->cap_info = (u16 *) OFFSET(f->hdr, WLAN_PROBERESP_OFF_CAP_INFO);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ ie_ptr = OFFSET(f->hdr, WLAN_PROBERESP_OFF_SSID);
|
|
+ while (ie_ptr < end) {
|
|
+ switch (IE_EID(ie_ptr)) {
|
|
+ case WLAN_EID_SSID:
|
|
+ f->ssid = (wlan_ie_ssid_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_SUPP_RATES:
|
|
+ f->supp_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_EXT_RATES:
|
|
+ f->ext_rates = (wlan_ie_supp_rates_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_FH_PARMS:
|
|
+ f->fh_parms = (wlan_ie_fh_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_DS_PARMS:
|
|
+ f->ds_parms = (wlan_ie_ds_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_CF_PARMS:
|
|
+ f->cf_parms = (wlan_ie_cf_parms_t *) ie_ptr;
|
|
+ break;
|
|
+ case WLAN_EID_IBSS_PARMS:
|
|
+ f->ibss_parms = (wlan_ie_ibss_parms_t *) ie_ptr;
|
|
+ break;
|
|
+#ifdef DONT_DO_IT_ADD_REAL_HANDLING_INSTEAD
|
|
+ case WLAN_EID_COUNTRY:
|
|
+ break;
|
|
+ ...
|
|
+#endif
|
|
+#ifdef SENT_HERE_BY_OPENWRT
|
|
+ /* should those be trapped or handled?? */
|
|
+ case WLAN_EID_ERP_INFO:
|
|
+ break;
|
|
+ case WLAN_EID_NONERP:
|
|
+ break;
|
|
+ case WLAN_EID_GENERIC:
|
|
+ break;
|
|
+#endif
|
|
+ default:
|
|
+ LOG_BAD_EID(f->hdr, f->len, ie_ptr);
|
|
+ break;
|
|
+ }
|
|
+
|
|
+ ie_ptr = ie_ptr + 2 + IE_LEN(ie_ptr);
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_authen(wlan_fr_authen_t * f)
|
|
+{
|
|
+ u8 *ie_ptr;
|
|
+ u8 *end = (u8*)f->hdr + f->len;
|
|
+
|
|
+ f->type = WLAN_FSTYPE_AUTHEN;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->auth_alg = (u16 *) OFFSET(f->hdr, WLAN_AUTHEN_OFF_AUTH_ALG);
|
|
+ f->auth_seq = (u16 *) OFFSET(f->hdr, WLAN_AUTHEN_OFF_AUTH_SEQ);
|
|
+ f->status = (u16 *) OFFSET(f->hdr, WLAN_AUTHEN_OFF_STATUS);
|
|
+
|
|
+ /*-- Information elements */
|
|
+ ie_ptr = OFFSET(f->hdr, WLAN_AUTHEN_OFF_CHALLENGE);
|
|
+ if ((ie_ptr < end) && (IE_EID(ie_ptr) == WLAN_EID_CHALLENGE)) {
|
|
+ f->challenge = (wlan_ie_challenge_t *) ie_ptr;
|
|
+ }
|
|
+}
|
|
+
|
|
+
|
|
+void
|
|
+wlan_mgmt_decode_deauthen(wlan_fr_deauthen_t * f)
|
|
+{
|
|
+ f->type = WLAN_FSTYPE_DEAUTHEN;
|
|
+
|
|
+ /*-- Fixed Fields ----*/
|
|
+ f->reason = (u16 *) OFFSET(f->hdr, WLAN_DEAUTHEN_OFF_REASON);
|
|
+
|
|
+ /*-- Information elements */
|
|
+}
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/wlan_compat.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/wlan_compat.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,260 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** This code is based on elements which are
|
|
+** Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved.
|
|
+** info@linux-wlan.com
|
|
+** http://www.linux-wlan.com
|
|
+*/
|
|
+
|
|
+/*=============================================================*/
|
|
+/*------ Establish Platform Identity --------------------------*/
|
|
+/*=============================================================*/
|
|
+/* Key macros: */
|
|
+/* WLAN_CPU_FAMILY */
|
|
+#define WLAN_Ix86 1
|
|
+#define WLAN_PPC 2
|
|
+#define WLAN_Ix96 3
|
|
+#define WLAN_ARM 4
|
|
+#define WLAN_ALPHA 5
|
|
+#define WLAN_MIPS 6
|
|
+#define WLAN_HPPA 7
|
|
+#define WLAN_SPARC 8
|
|
+#define WLAN_SH 9
|
|
+#define WLAN_x86_64 10
|
|
+/* WLAN_CPU_CORE */
|
|
+#define WLAN_I386CORE 1
|
|
+#define WLAN_PPCCORE 2
|
|
+#define WLAN_I296 3
|
|
+#define WLAN_ARMCORE 4
|
|
+#define WLAN_ALPHACORE 5
|
|
+#define WLAN_MIPSCORE 6
|
|
+#define WLAN_HPPACORE 7
|
|
+/* WLAN_CPU_PART */
|
|
+#define WLAN_I386PART 1
|
|
+#define WLAN_MPC860 2
|
|
+#define WLAN_MPC823 3
|
|
+#define WLAN_I296SA 4
|
|
+#define WLAN_PPCPART 5
|
|
+#define WLAN_ARMPART 6
|
|
+#define WLAN_ALPHAPART 7
|
|
+#define WLAN_MIPSPART 8
|
|
+#define WLAN_HPPAPART 9
|
|
+/* WLAN_SYSARCH */
|
|
+#define WLAN_PCAT 1
|
|
+#define WLAN_MBX 2
|
|
+#define WLAN_RPX 3
|
|
+#define WLAN_LWARCH 4
|
|
+#define WLAN_PMAC 5
|
|
+#define WLAN_SKIFF 6
|
|
+#define WLAN_BITSY 7
|
|
+#define WLAN_ALPHAARCH 7
|
|
+#define WLAN_MIPSARCH 9
|
|
+#define WLAN_HPPAARCH 10
|
|
+/* WLAN_HOSTIF (generally set on the command line, not detected) */
|
|
+#define WLAN_PCMCIA 1
|
|
+#define WLAN_ISA 2
|
|
+#define WLAN_PCI 3
|
|
+#define WLAN_USB 4
|
|
+#define WLAN_PLX 5
|
|
+
|
|
+/* Note: the PLX HOSTIF above refers to some vendors implementations for */
|
|
+/* PCI. It's a PLX chip that is a PCI to PCMCIA adapter, but it */
|
|
+/* isn't a real PCMCIA host interface adapter providing all the */
|
|
+/* card&socket services. */
|
|
+
|
|
+#ifdef __powerpc__
|
|
+#ifndef __ppc__
|
|
+#define __ppc__
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#if (defined(CONFIG_PPC) || defined(CONFIG_8xx))
|
|
+#ifndef __ppc__
|
|
+#define __ppc__
|
|
+#endif
|
|
+#endif
|
|
+
|
|
+#if defined(__x86_64__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_x86_64
|
|
+ #define WLAN_SYSARCH WLAN_PCAT
|
|
+#elif defined(__i386__) || defined(__i486__) || defined(__i586__) || defined(__i686__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_Ix86
|
|
+ #define WLAN_CPU_CORE WLAN_I386CORE
|
|
+ #define WLAN_CPU_PART WLAN_I386PART
|
|
+ #define WLAN_SYSARCH WLAN_PCAT
|
|
+#elif defined(__ppc__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_PPC
|
|
+ #define WLAN_CPU_CORE WLAN_PPCCORE
|
|
+ #if defined(CONFIG_MBX)
|
|
+ #define WLAN_CPU_PART WLAN_MPC860
|
|
+ #define WLAN_SYSARCH WLAN_MBX
|
|
+ #elif defined(CONFIG_RPXLITE)
|
|
+ #define WLAN_CPU_PART WLAN_MPC823
|
|
+ #define WLAN_SYSARCH WLAN_RPX
|
|
+ #elif defined(CONFIG_RPXCLASSIC)
|
|
+ #define WLAN_CPU_PART WLAN_MPC860
|
|
+ #define WLAN_SYSARCH WLAN_RPX
|
|
+ #else
|
|
+ #define WLAN_CPU_PART WLAN_PPCPART
|
|
+ #define WLAN_SYSARCH WLAN_PMAC
|
|
+ #endif
|
|
+#elif defined(__arm__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_ARM
|
|
+ #define WLAN_CPU_CORE WLAN_ARMCORE
|
|
+ #define WLAN_CPU_PART WLAN_ARM_PART
|
|
+ #define WLAN_SYSARCH WLAN_SKIFF
|
|
+#elif defined(__alpha__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_ALPHA
|
|
+ #define WLAN_CPU_CORE WLAN_ALPHACORE
|
|
+ #define WLAN_CPU_PART WLAN_ALPHAPART
|
|
+ #define WLAN_SYSARCH WLAN_ALPHAARCH
|
|
+#elif defined(__mips__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_MIPS
|
|
+ #define WLAN_CPU_CORE WLAN_MIPSCORE
|
|
+ #define WLAN_CPU_PART WLAN_MIPSPART
|
|
+ #define WLAN_SYSARCH WLAN_MIPSARCH
|
|
+#elif defined(__hppa__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_HPPA
|
|
+ #define WLAN_CPU_CORE WLAN_HPPACORE
|
|
+ #define WLAN_CPU_PART WLAN_HPPAPART
|
|
+ #define WLAN_SYSARCH WLAN_HPPAARCH
|
|
+#elif defined(__sparc__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_SPARC
|
|
+ #define WLAN_SYSARCH WLAN_SPARC
|
|
+#elif defined(__sh__)
|
|
+ #define WLAN_CPU_FAMILY WLAN_SH
|
|
+ #define WLAN_SYSARCH WLAN_SHARCH
|
|
+ #ifndef __LITTLE_ENDIAN__
|
|
+ #define __LITTLE_ENDIAN__
|
|
+ #endif
|
|
+#else
|
|
+ #error "No CPU identified!"
|
|
+#endif
|
|
+
|
|
+/*
|
|
+ Some big endian machines implicitly do all I/O in little endian mode.
|
|
+
|
|
+ In particular:
|
|
+ Linux/PPC on PowerMacs (PCI)
|
|
+ Arm/Intel Xscale (PCI)
|
|
+
|
|
+ This may also affect PLX boards and other BE &| PPC platforms;
|
|
+ as new ones are discovered, add them below.
|
|
+*/
|
|
+
|
|
+#if ((WLAN_SYSARCH == WLAN_SKIFF) || (WLAN_SYSARCH == WLAN_PMAC))
|
|
+#define REVERSE_ENDIAN
|
|
+#endif
|
|
+
|
|
+/*=============================================================*/
|
|
+/*------ Hardware Portability Macros --------------------------*/
|
|
+/*=============================================================*/
|
|
+#if (WLAN_CPU_FAMILY == WLAN_PPC)
|
|
+#define wlan_inw(a) in_be16((unsigned short *)((a)+_IO_BASE))
|
|
+#define wlan_inw_le16_to_cpu(a) inw((a))
|
|
+#define wlan_outw(v,a) out_be16((unsigned short *)((a)+_IO_BASE), (v))
|
|
+#define wlan_outw_cpu_to_le16(v,a) outw((v),(a))
|
|
+#else
|
|
+#define wlan_inw(a) inw((a))
|
|
+#define wlan_inw_le16_to_cpu(a) __cpu_to_le16(inw((a)))
|
|
+#define wlan_outw(v,a) outw((v),(a))
|
|
+#define wlan_outw_cpu_to_le16(v,a) outw(__cpu_to_le16((v)),(a))
|
|
+#endif
|
|
+
|
|
+/*=============================================================*/
|
|
+/*------ Bit settings -----------------------------------------*/
|
|
+/*=============================================================*/
|
|
+#define ieee2host16(n) __le16_to_cpu(n)
|
|
+#define ieee2host32(n) __le32_to_cpu(n)
|
|
+#define host2ieee16(n) __cpu_to_le16(n)
|
|
+#define host2ieee32(n) __cpu_to_le32(n)
|
|
+
|
|
+/* for constants */
|
|
+#ifdef __LITTLE_ENDIAN
|
|
+ #define IEEE16(a,n) a = n, a##i = n,
|
|
+#else
|
|
+ #ifdef __BIG_ENDIAN
|
|
+ /* shifts would produce gcc warnings. Oh well... */
|
|
+ #define IEEE16(a,n) a = n, a##i = ((n&0xff)*256 + ((n&0xff00)/256)),
|
|
+ #else
|
|
+ #error give me endianness or give me death
|
|
+ #endif
|
|
+#endif
|
|
+
|
|
+/*=============================================================*/
|
|
+/*------ Compiler Portability Macros --------------------------*/
|
|
+/*=============================================================*/
|
|
+#define WLAN_PACKED __attribute__ ((packed))
|
|
+
|
|
+/* Interrupt handler backwards compatibility stuff */
|
|
+#ifndef IRQ_NONE
|
|
+#define IRQ_NONE
|
|
+#define IRQ_HANDLED
|
|
+typedef void irqreturn_t;
|
|
+#endif
|
|
+
|
|
+#ifndef ARPHRD_IEEE80211_PRISM
|
|
+#define ARPHRD_IEEE80211_PRISM 802
|
|
+#endif
|
|
+
|
|
+#define ETH_P_80211_RAW (ETH_P_ECONET + 1)
|
|
+
|
|
+/*============================================================================*
|
|
+ * Constants *
|
|
+ *============================================================================*/
|
|
+#define WLAN_IEEE_OUI_LEN 3
|
|
+
|
|
+/*============================================================================*
|
|
+ * Types *
|
|
+ *============================================================================*/
|
|
+
|
|
+/* local ether header type */
|
|
+typedef struct wlan_ethhdr {
|
|
+ u8 daddr[ETH_ALEN];
|
|
+ u8 saddr[ETH_ALEN];
|
|
+ u16 type;
|
|
+} WLAN_PACKED wlan_ethhdr_t;
|
|
+
|
|
+/* local llc header type */
|
|
+typedef struct wlan_llc {
|
|
+ u8 dsap;
|
|
+ u8 ssap;
|
|
+ u8 ctl;
|
|
+} WLAN_PACKED wlan_llc_t;
|
|
+
|
|
+/* local snap header type */
|
|
+typedef struct wlan_snap {
|
|
+ u8 oui[WLAN_IEEE_OUI_LEN];
|
|
+ u16 type;
|
|
+} WLAN_PACKED wlan_snap_t;
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/wlan_hdr.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/wlan_hdr.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,497 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** This code is based on elements which are
|
|
+** Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved.
|
|
+** info@linux-wlan.com
|
|
+** http://www.linux-wlan.com
|
|
+*/
|
|
+
|
|
+/* mini-doc
|
|
+
|
|
+Here are all 11b/11g/11a rates and modulations:
|
|
+
|
|
+ 11b 11g 11a
|
|
+ --- --- ---
|
|
+ 1 |B |B |
|
|
+ 2 |Q |Q |
|
|
+ 5.5|Cp |C p|
|
|
+ 6 | |Od |O
|
|
+ 9 | |od |o
|
|
+11 |Cp |C p|
|
|
+12 | |Od |O
|
|
+18 | |od |o
|
|
+22 | | p|
|
|
+24 | |Od |O
|
|
+33 | | p|
|
|
+36 | |od |o
|
|
+48 | |od |o
|
|
+54 | |od |o
|
|
+
|
|
+Mandatory:
|
|
+ B - DBPSK (Differential Binary Phase Shift Keying)
|
|
+ Q - DQPSK (Differential Quaternary Phase Shift Keying)
|
|
+ C - CCK (Complementary Code Keying, a form of DSSS
|
|
+ (Direct Sequence Spread Spectrum) modulation)
|
|
+ O - OFDM (Orthogonal Frequency Division Multiplexing)
|
|
+Optional:
|
|
+ o - OFDM
|
|
+ d - CCK-OFDM (also known as DSSS-OFDM)
|
|
+ p - PBCC (Packet Binary Convolutional Coding)
|
|
+
|
|
+The term CCK-OFDM may be used interchangeably with DSSS-OFDM
|
|
+(the IEEE 802.11g-2003 standard uses the latter terminology).
|
|
+In the CCK-OFDM, the PLCP header of the frame uses the CCK form of DSSS,
|
|
+while the PLCP payload (the MAC frame) is modulated using OFDM.
|
|
+
|
|
+Basically, you must use CCK-OFDM if you have mixed 11b/11g environment,
|
|
+or else (pure OFDM) 11b equipment may not realize that AP
|
|
+is sending a packet and start sending its own one.
|
|
+Sadly, looks like acx111 does not support CCK-OFDM, only pure OFDM.
|
|
+
|
|
+Re PBCC: avoid using it. It makes sense only if you have
|
|
+TI "11b+" hardware. You _must_ use PBCC in order to reach 22Mbps on it.
|
|
+
|
|
+Preambles:
|
|
+
|
|
+Long preamble (at 1Mbit rate, takes 144 us):
|
|
+ 16 bytes ones
|
|
+ 2 bytes 0xF3A0 (lsb sent first)
|
|
+PLCP header follows (at 1Mbit also):
|
|
+ 1 byte Signal: speed, in 0.1Mbit units, except for:
|
|
+ 33Mbit: 33 (instead of 330 - doesn't fit in octet)
|
|
+ all CCK-OFDM rates: 30
|
|
+ 1 byte Service
|
|
+ 0,1,4: reserved
|
|
+ 2: 1=locked clock
|
|
+ 3: 1=PBCC
|
|
+ 5: Length Extension (PBCC 22,33Mbit (11g only)) <-
|
|
+ 6: Length Extension (PBCC 22,33Mbit (11g only)) <- BLACK MAGIC HERE
|
|
+ 7: Length Extension <-
|
|
+ 2 bytes Length (time needed to tx this frame)
|
|
+ a) 5.5 Mbit/s CCK
|
|
+ Length = octets*8/5.5, rounded up to integer
|
|
+ b) 11 Mbit/s CCK
|
|
+ Length = octets*8/11, rounded up to integer
|
|
+ Service bit 7:
|
|
+ 0 = rounding took less than 8/11
|
|
+ 1 = rounding took more than or equal to 8/11
|
|
+ c) 5.5 Mbit/s PBCC
|
|
+ Length = (octets+1)*8/5.5, rounded up to integer
|
|
+ d) 11 Mbit/s PBCC
|
|
+ Length = (octets+1)*8/11, rounded up to integer
|
|
+ Service bit 7:
|
|
+ 0 = rounding took less than 8/11
|
|
+ 1 = rounding took more than or equal to 8/11
|
|
+ e) 22 Mbit/s PBCC
|
|
+ Length = (octets+1)*8/22, rounded up to integer
|
|
+ Service bits 6,7:
|
|
+ 00 = rounding took less than 8/22ths
|
|
+ 01 = rounding took 8/22...15/22ths
|
|
+ 10 = rounding took 16/22ths or more.
|
|
+ f) 33 Mbit/s PBCC
|
|
+ Length = (octets+1)*8/33, rounded up to integer
|
|
+ Service bits 5,6,7:
|
|
+ 000 rounding took less than 8/33
|
|
+ 001 rounding took 8/33...15/33
|
|
+ 010 rounding took 16/33...23/33
|
|
+ 011 rounding took 24/33...31/33
|
|
+ 100 rounding took 32/33 or more
|
|
+ 2 bytes CRC
|
|
+
|
|
+PSDU follows (up to 2346 bytes at selected rate)
|
|
+
|
|
+While Signal value alone is not enough to determine rate and modulation,
|
|
+Signal+Service is always sufficient.
|
|
+
|
|
+Short preamble (at 1Mbit rate, takes 72 us):
|
|
+ 7 bytes zeroes
|
|
+ 2 bytes 0x05CF (lsb sent first)
|
|
+PLCP header follows *at 2Mbit/s*. Format is the same as in long preamble.
|
|
+PSDU follows (up to 2346 bytes at selected rate)
|
|
+
|
|
+OFDM preamble is completely different, uses OFDM
|
|
+modulation from the start and thus easily identifiable.
|
|
+Not shown here.
|
|
+*/
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Constants
|
|
+*/
|
|
+
|
|
+#define WLAN_HDR_A3_LEN 24
|
|
+#define WLAN_HDR_A4_LEN 30
|
|
+/* IV structure:
|
|
+** 3 bytes: Initialization Vector (24 bits)
|
|
+** 1 byte: 0..5: padding, must be 0; 6..7: key selector (0-3)
|
|
+*/
|
|
+#define WLAN_WEP_IV_LEN 4
|
|
+/* 802.11 says 2312 but looks like 2312 is a max size of _WEPed data_ */
|
|
+#define WLAN_DATA_MAXLEN 2304
|
|
+#define WLAN_WEP_ICV_LEN 4
|
|
+#define WLAN_FCS_LEN 4
|
|
+#define WLAN_A3FR_MAXLEN (WLAN_HDR_A3_LEN + WLAN_DATA_MAXLEN)
|
|
+#define WLAN_A4FR_MAXLEN (WLAN_HDR_A4_LEN + WLAN_DATA_MAXLEN)
|
|
+#define WLAN_A3FR_MAXLEN_FCS (WLAN_HDR_A3_LEN + WLAN_DATA_MAXLEN + 4)
|
|
+#define WLAN_A4FR_MAXLEN_FCS (WLAN_HDR_A4_LEN + WLAN_DATA_MAXLEN + 4)
|
|
+#define WLAN_A3FR_MAXLEN_WEP (WLAN_A3FR_MAXLEN + 8)
|
|
+#define WLAN_A4FR_MAXLEN_WEP (WLAN_A4FR_MAXLEN + 8)
|
|
+#define WLAN_A3FR_MAXLEN_WEP_FCS (WLAN_A3FR_MAXLEN_FCS + 8)
|
|
+#define WLAN_A4FR_MAXLEN_WEP_FCS (WLAN_A4FR_MAXLEN_FCS + 8)
|
|
+
|
|
+#define WLAN_BSS_TS_LEN 8
|
|
+#define WLAN_SSID_MAXLEN 32
|
|
+#define WLAN_BEACON_FR_MAXLEN (WLAN_HDR_A3_LEN + 334)
|
|
+#define WLAN_ATIM_FR_MAXLEN (WLAN_HDR_A3_LEN + 0)
|
|
+#define WLAN_DISASSOC_FR_MAXLEN (WLAN_HDR_A3_LEN + 2)
|
|
+#define WLAN_ASSOCREQ_FR_MAXLEN (WLAN_HDR_A3_LEN + 48)
|
|
+#define WLAN_ASSOCRESP_FR_MAXLEN (WLAN_HDR_A3_LEN + 16)
|
|
+#define WLAN_REASSOCREQ_FR_MAXLEN (WLAN_HDR_A3_LEN + 54)
|
|
+#define WLAN_REASSOCRESP_FR_MAXLEN (WLAN_HDR_A3_LEN + 16)
|
|
+#define WLAN_PROBEREQ_FR_MAXLEN (WLAN_HDR_A3_LEN + 44)
|
|
+#define WLAN_PROBERESP_FR_MAXLEN (WLAN_HDR_A3_LEN + 78)
|
|
+#define WLAN_AUTHEN_FR_MAXLEN (WLAN_HDR_A3_LEN + 261)
|
|
+#define WLAN_DEAUTHEN_FR_MAXLEN (WLAN_HDR_A3_LEN + 2)
|
|
+#define WLAN_CHALLENGE_IE_LEN 130
|
|
+#define WLAN_CHALLENGE_LEN 128
|
|
+#define WLAN_WEP_MAXKEYLEN 13
|
|
+#define WLAN_WEP_NKEYS 4
|
|
+
|
|
+/*--- Frame Control Field -------------------------------------*/
|
|
+/* Frame Types */
|
|
+#define WLAN_FTYPE_MGMT 0x00
|
|
+#define WLAN_FTYPE_CTL 0x01
|
|
+#define WLAN_FTYPE_DATA 0x02
|
|
+
|
|
+/* Frame subtypes */
|
|
+/* Management */
|
|
+#define WLAN_FSTYPE_ASSOCREQ 0x00
|
|
+#define WLAN_FSTYPE_ASSOCRESP 0x01
|
|
+#define WLAN_FSTYPE_REASSOCREQ 0x02
|
|
+#define WLAN_FSTYPE_REASSOCRESP 0x03
|
|
+#define WLAN_FSTYPE_PROBEREQ 0x04
|
|
+#define WLAN_FSTYPE_PROBERESP 0x05
|
|
+#define WLAN_FSTYPE_BEACON 0x08
|
|
+#define WLAN_FSTYPE_ATIM 0x09
|
|
+#define WLAN_FSTYPE_DISASSOC 0x0a
|
|
+#define WLAN_FSTYPE_AUTHEN 0x0b
|
|
+#define WLAN_FSTYPE_DEAUTHEN 0x0c
|
|
+
|
|
+/* Control */
|
|
+#define WLAN_FSTYPE_PSPOLL 0x0a
|
|
+#define WLAN_FSTYPE_RTS 0x0b
|
|
+#define WLAN_FSTYPE_CTS 0x0c
|
|
+#define WLAN_FSTYPE_ACK 0x0d
|
|
+#define WLAN_FSTYPE_CFEND 0x0e
|
|
+#define WLAN_FSTYPE_CFENDCFACK 0x0f
|
|
+
|
|
+/* Data */
|
|
+#define WLAN_FSTYPE_DATAONLY 0x00
|
|
+#define WLAN_FSTYPE_DATA_CFACK 0x01
|
|
+#define WLAN_FSTYPE_DATA_CFPOLL 0x02
|
|
+#define WLAN_FSTYPE_DATA_CFACK_CFPOLL 0x03
|
|
+#define WLAN_FSTYPE_NULL 0x04
|
|
+#define WLAN_FSTYPE_CFACK 0x05
|
|
+#define WLAN_FSTYPE_CFPOLL 0x06
|
|
+#define WLAN_FSTYPE_CFACK_CFPOLL 0x07
|
|
+
|
|
+/*--- FC Constants v. 2.0 ------------------------------------*/
|
|
+/* Each constant is defined twice: WF_CONST is in host */
|
|
+/* byteorder, WF_CONSTi is in ieee byteorder. */
|
|
+/* Usage: */
|
|
+/* printf("the frame subtype is %X", WF_FC_FTYPEi & rx.fc); */
|
|
+/* tx.fc = WF_FTYPE_CTLi | WF_FSTYPE_RTSi; */
|
|
+/*------------------------------------------------------------*/
|
|
+
|
|
+enum {
|
|
+/*--- Frame Control Field -------------------------------------*/
|
|
+/* Protocol version: always 0 for current 802.11 standards */
|
|
+IEEE16(WF_FC_PVER, 0x0003)
|
|
+IEEE16(WF_FC_FTYPE, 0x000c)
|
|
+IEEE16(WF_FC_FSTYPE, 0x00f0)
|
|
+IEEE16(WF_FC_TODS, 0x0100)
|
|
+IEEE16(WF_FC_FROMDS, 0x0200)
|
|
+IEEE16(WF_FC_FROMTODS, 0x0300)
|
|
+IEEE16(WF_FC_MOREFRAG, 0x0400)
|
|
+IEEE16(WF_FC_RETRY, 0x0800)
|
|
+/* Indicates PS mode in which STA will be after successful completion
|
|
+** of current frame exchange sequence. Always 0 for AP frames */
|
|
+IEEE16(WF_FC_PWRMGT, 0x1000)
|
|
+/* What MoreData=1 means:
|
|
+** From AP to STA in PS mode: don't sleep yet, I have more frames for you
|
|
+** From Contention-Free (CF) Pollable STA in response to a CF-Poll:
|
|
+** STA has buffered frames for transmission in response to next CF-Poll
|
|
+** Bcast/mcast frames transmitted from AP:
|
|
+** when additional bcast/mcast frames remain to be transmitted by AP
|
|
+** during this beacon interval
|
|
+** In all other cases MoreData=0 */
|
|
+IEEE16(WF_FC_MOREDATA, 0x2000)
|
|
+IEEE16(WF_FC_ISWEP, 0x4000)
|
|
+IEEE16(WF_FC_ORDER, 0x8000)
|
|
+
|
|
+/* Frame Types */
|
|
+IEEE16(WF_FTYPE_MGMT, 0x00)
|
|
+IEEE16(WF_FTYPE_CTL, 0x04)
|
|
+IEEE16(WF_FTYPE_DATA, 0x08)
|
|
+
|
|
+/* Frame subtypes */
|
|
+/* Management */
|
|
+IEEE16(WF_FSTYPE_ASSOCREQ, 0x00)
|
|
+IEEE16(WF_FSTYPE_ASSOCRESP, 0x10)
|
|
+IEEE16(WF_FSTYPE_REASSOCREQ, 0x20)
|
|
+IEEE16(WF_FSTYPE_REASSOCRESP, 0x30)
|
|
+IEEE16(WF_FSTYPE_PROBEREQ, 0x40)
|
|
+IEEE16(WF_FSTYPE_PROBERESP, 0x50)
|
|
+IEEE16(WF_FSTYPE_BEACON, 0x80)
|
|
+IEEE16(WF_FSTYPE_ATIM, 0x90)
|
|
+IEEE16(WF_FSTYPE_DISASSOC, 0xa0)
|
|
+IEEE16(WF_FSTYPE_AUTHEN, 0xb0)
|
|
+IEEE16(WF_FSTYPE_DEAUTHEN, 0xc0)
|
|
+
|
|
+/* Control */
|
|
+IEEE16(WF_FSTYPE_PSPOLL, 0xa0)
|
|
+IEEE16(WF_FSTYPE_RTS, 0xb0)
|
|
+IEEE16(WF_FSTYPE_CTS, 0xc0)
|
|
+IEEE16(WF_FSTYPE_ACK, 0xd0)
|
|
+IEEE16(WF_FSTYPE_CFEND, 0xe0)
|
|
+IEEE16(WF_FSTYPE_CFENDCFACK, 0xf0)
|
|
+
|
|
+/* Data */
|
|
+IEEE16(WF_FSTYPE_DATAONLY, 0x00)
|
|
+IEEE16(WF_FSTYPE_DATA_CFACK, 0x10)
|
|
+IEEE16(WF_FSTYPE_DATA_CFPOLL, 0x20)
|
|
+IEEE16(WF_FSTYPE_DATA_CFACK_CFPOLL, 0x30)
|
|
+IEEE16(WF_FSTYPE_NULL, 0x40)
|
|
+IEEE16(WF_FSTYPE_CFACK, 0x50)
|
|
+IEEE16(WF_FSTYPE_CFPOLL, 0x60)
|
|
+IEEE16(WF_FSTYPE_CFACK_CFPOLL, 0x70)
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Macros
|
|
+*/
|
|
+
|
|
+/*--- Duration Macros ----------------------------------------*/
|
|
+/* Macros to get/set the bitfields of the Duration Field */
|
|
+/* - the duration value is only valid when bit15 is zero */
|
|
+/* - the firmware handles these values, so I'm not going */
|
|
+/* to use these macros right now. */
|
|
+/*------------------------------------------------------------*/
|
|
+
|
|
+/*--- Sequence Control Macros -------------------------------*/
|
|
+/* Macros to get/set the bitfields of the Sequence Control */
|
|
+/* Field. */
|
|
+/*------------------------------------------------------------*/
|
|
+#define WLAN_GET_SEQ_FRGNUM(n) ((u16)(n) & 0x000f)
|
|
+#define WLAN_GET_SEQ_SEQNUM(n) (((u16)(n) & 0xfff0) >> 4)
|
|
+
|
|
+/*--- Data ptr macro -----------------------------------------*/
|
|
+/* Creates a u8* to the data portion of a frame */
|
|
+/* Assumes you're passing in a ptr to the beginning of the hdr*/
|
|
+/*------------------------------------------------------------*/
|
|
+#define WLAN_HDR_A3_DATAP(p) (((u8*)(p)) + WLAN_HDR_A3_LEN)
|
|
+#define WLAN_HDR_A4_DATAP(p) (((u8*)(p)) + WLAN_HDR_A4_LEN)
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Types
|
|
+*/
|
|
+
|
|
+/* 802.11 header type
|
|
+**
|
|
+** Note the following:
|
|
+** a1 *always* is receiver's mac or bcast/mcast
|
|
+** a2 *always* is transmitter's mac, if a2 exists
|
|
+** seq: [0:3] frag#, [4:15] seq# - used for dup detection
|
|
+** (dups from retries have same seq#) */
|
|
+typedef struct wlan_hdr {
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 a1[ETH_ALEN];
|
|
+ u8 a2[ETH_ALEN];
|
|
+ u8 a3[ETH_ALEN];
|
|
+ u16 seq;
|
|
+ u8 a4[ETH_ALEN];
|
|
+} WLAN_PACKED wlan_hdr_t;
|
|
+
|
|
+/* Separate structs for use if frame type is known */
|
|
+typedef struct wlan_hdr_a3 {
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 a1[ETH_ALEN];
|
|
+ u8 a2[ETH_ALEN];
|
|
+ u8 a3[ETH_ALEN];
|
|
+ u16 seq;
|
|
+} WLAN_PACKED wlan_hdr_a3_t;
|
|
+
|
|
+typedef struct wlan_hdr_mgmt {
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 da[ETH_ALEN];
|
|
+ u8 sa[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u16 seq;
|
|
+} WLAN_PACKED wlan_hdr_mgmt_t;
|
|
+
|
|
+#ifdef NOT_NEEDED_YET
|
|
+typedef struct { /* ad-hoc peer->peer (to/from DS = 0/0) */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 da[ETH_ALEN];
|
|
+ u8 sa[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u16 seq;
|
|
+} WLAN_PACKED ibss;
|
|
+typedef struct { /* ap->sta (to/from DS = 0/1) */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 da[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u8 sa[ETH_ALEN];
|
|
+ u16 seq;
|
|
+} WLAN_PACKED fromap;
|
|
+typedef struct { /* sta->ap (to/from DS = 1/0) */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u8 sa[ETH_ALEN];
|
|
+ u8 da[ETH_ALEN];
|
|
+ u16 seq;
|
|
+} WLAN_PACKED toap;
|
|
+typedef struct { /* wds->wds (to/from DS = 1/1), the only 4addr pkt */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 ra[ETH_ALEN];
|
|
+ u8 ta[ETH_ALEN];
|
|
+ u8 da[ETH_ALEN];
|
|
+ u16 seq;
|
|
+ u8 sa[ETH_ALEN];
|
|
+} WLAN_PACKED wds;
|
|
+typedef struct { /* all management packets */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 da[ETH_ALEN];
|
|
+ u8 sa[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u16 seq;
|
|
+} WLAN_PACKED mgmt;
|
|
+typedef struct { /* has no body, just a FCS */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 ra[ETH_ALEN];
|
|
+ u8 ta[ETH_ALEN];
|
|
+} WLAN_PACKED rts;
|
|
+typedef struct { /* has no body, just a FCS */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 ra[ETH_ALEN];
|
|
+} WLAN_PACKED cts;
|
|
+typedef struct { /* has no body, just a FCS */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 ra[ETH_ALEN];
|
|
+} WLAN_PACKED ack;
|
|
+typedef struct { /* has no body, just a FCS */
|
|
+ u16 fc;
|
|
+ /* NB: this one holds Assoc ID in dur field: */
|
|
+ u16 aid;
|
|
+ u8 bssid[ETH_ALEN];
|
|
+ u8 ta[ETH_ALEN];
|
|
+} WLAN_PACKED pspoll;
|
|
+typedef struct { /* has no body, just a FCS */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 ra[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN];
|
|
+} WLAN_PACKED cfend;
|
|
+typedef struct { /* has no body, just a FCS */
|
|
+ u16 fc;
|
|
+ u16 dur;
|
|
+ u8 ra[ETH_ALEN];
|
|
+ u8 bssid[ETH_ALEN];
|
|
+} WLAN_PACKED cfendcfack;
|
|
+#endif
|
|
+
|
|
+/* Prism header emulation (monitor mode) */
|
|
+typedef struct wlanitem_u32 {
|
|
+ u32 did;
|
|
+ u16 status;
|
|
+ u16 len;
|
|
+ u32 data;
|
|
+} WLAN_PACKED wlanitem_u32_t;
|
|
+#define WLANITEM_STATUS_data_ok 0
|
|
+#define WLANITEM_STATUS_no_value 1
|
|
+#define WLANITEM_STATUS_invalid_itemname 2
|
|
+#define WLANITEM_STATUS_invalid_itemdata 3
|
|
+#define WLANITEM_STATUS_missing_itemdata 4
|
|
+#define WLANITEM_STATUS_incomplete_itemdata 5
|
|
+#define WLANITEM_STATUS_invalid_msg_did 6
|
|
+#define WLANITEM_STATUS_invalid_mib_did 7
|
|
+#define WLANITEM_STATUS_missing_conv_func 8
|
|
+#define WLANITEM_STATUS_string_too_long 9
|
|
+#define WLANITEM_STATUS_data_out_of_range 10
|
|
+#define WLANITEM_STATUS_string_too_short 11
|
|
+#define WLANITEM_STATUS_missing_valid_func 12
|
|
+#define WLANITEM_STATUS_unknown 13
|
|
+#define WLANITEM_STATUS_invalid_did 14
|
|
+#define WLANITEM_STATUS_missing_print_func 15
|
|
+
|
|
+#define WLAN_DEVNAMELEN_MAX 16
|
|
+typedef struct wlansniffrm {
|
|
+ u32 msgcode;
|
|
+ u32 msglen;
|
|
+ u8 devname[WLAN_DEVNAMELEN_MAX];
|
|
+ wlanitem_u32_t hosttime;
|
|
+ wlanitem_u32_t mactime;
|
|
+ wlanitem_u32_t channel;
|
|
+ wlanitem_u32_t rssi;
|
|
+ wlanitem_u32_t sq;
|
|
+ wlanitem_u32_t signal;
|
|
+ wlanitem_u32_t noise;
|
|
+ wlanitem_u32_t rate;
|
|
+ wlanitem_u32_t istx; /* tx? 0:no 1:yes */
|
|
+ wlanitem_u32_t frmlen;
|
|
+} WLAN_PACKED wlansniffrm_t;
|
|
+#define WLANSNIFFFRM 0x0041
|
|
+#define WLANSNIFFFRM_hosttime 0x1041
|
|
+#define WLANSNIFFFRM_mactime 0x2041
|
|
+#define WLANSNIFFFRM_channel 0x3041
|
|
+#define WLANSNIFFFRM_rssi 0x4041
|
|
+#define WLANSNIFFFRM_sq 0x5041
|
|
+#define WLANSNIFFFRM_signal 0x6041
|
|
+#define WLANSNIFFFRM_noise 0x7041
|
|
+#define WLANSNIFFFRM_rate 0x8041
|
|
+#define WLANSNIFFFRM_istx 0x9041
|
|
+#define WLANSNIFFFRM_frmlen 0xA041
|
|
Index: linux-2.6.23/drivers/net/wireless/acx/wlan_mgmt.h
|
|
===================================================================
|
|
--- /dev/null 1970-01-01 00:00:00.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/acx/wlan_mgmt.h 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -0,0 +1,582 @@
|
|
+/***********************************************************************
|
|
+** Copyright (C) 2003 ACX100 Open Source Project
|
|
+**
|
|
+** The contents of this file are subject to the Mozilla Public
|
|
+** License Version 1.1 (the "License"); you may not use this file
|
|
+** except in compliance with the License. You may obtain a copy of
|
|
+** the License at http://www.mozilla.org/MPL/
|
|
+**
|
|
+** Software distributed under the License is distributed on an "AS
|
|
+** IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
|
|
+** implied. See the License for the specific language governing
|
|
+** rights and limitations under the License.
|
|
+**
|
|
+** Alternatively, the contents of this file may be used under the
|
|
+** terms of the GNU Public License version 2 (the "GPL"), in which
|
|
+** case the provisions of the GPL are applicable instead of the
|
|
+** above. If you wish to allow the use of your version of this file
|
|
+** only under the terms of the GPL and not to allow others to use
|
|
+** your version of this file under the MPL, indicate your decision
|
|
+** by deleting the provisions above and replace them with the notice
|
|
+** and other provisions required by the GPL. If you do not delete
|
|
+** the provisions above, a recipient may use your version of this
|
|
+** file under either the MPL or the GPL.
|
|
+** ---------------------------------------------------------------------
|
|
+** Inquiries regarding the ACX100 Open Source Project can be
|
|
+** made directly to:
|
|
+**
|
|
+** acx100-users@lists.sf.net
|
|
+** http://acx100.sf.net
|
|
+** ---------------------------------------------------------------------
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** This code is based on elements which are
|
|
+** Copyright (C) 1999 AbsoluteValue Systems, Inc. All Rights Reserved.
|
|
+** info@linux-wlan.com
|
|
+** http://www.linux-wlan.com
|
|
+*/
|
|
+
|
|
+/***********************************************************************
|
|
+** Constants
|
|
+*/
|
|
+
|
|
+/*-- Information Element IDs --------------------*/
|
|
+#define WLAN_EID_SSID 0
|
|
+#define WLAN_EID_SUPP_RATES 1
|
|
+#define WLAN_EID_FH_PARMS 2
|
|
+#define WLAN_EID_DS_PARMS 3
|
|
+#define WLAN_EID_CF_PARMS 4
|
|
+#define WLAN_EID_TIM 5
|
|
+#define WLAN_EID_IBSS_PARMS 6
|
|
+#define WLAN_EID_COUNTRY 7 /* 802.11d */
|
|
+#define WLAN_EID_FH_HOP_PARMS 8 /* 802.11d */
|
|
+#define WLAN_EID_FH_TABLE 9 /* 802.11d */
|
|
+#define WLAN_EID_REQUEST 10 /* 802.11d */
|
|
+/*-- values 11-15 reserved --*/
|
|
+#define WLAN_EID_CHALLENGE 16
|
|
+/*-- values 17-31 reserved for challenge text extension --*/
|
|
+#define WLAN_EID_PWR_CONSTRAINT 32 /* 11h PowerConstraint */
|
|
+#define WLAN_EID_ERP_INFO 42 /* was seen from WRT54GS with OpenWrt */
|
|
+#define WLAN_EID_NONERP 47 /* was seen from WRT54GS with OpenWrt */
|
|
+#define WLAN_EID_RSN 48
|
|
+#define WLAN_EID_EXT_RATES 50
|
|
+#define WLAN_EID_UNKNOWN128 128
|
|
+#define WLAN_EID_UNKNOWN133 133
|
|
+#define WLAN_EID_GENERIC 221 /* was seen from WRT54GS with OpenWrt */
|
|
+#define WLAN_EID_UNKNOWN223 223
|
|
+
|
|
+#if 0
|
|
+#define WLAN_EID_PWR_CAP 33 /* 11h PowerCapability */
|
|
+#define WLAN_EID_TPC_REQUEST 34 /* 11h TPC Request */
|
|
+#define WLAN_EID_TPC_REPORT 35 /* 11h TPC Report */
|
|
+#define WLAN_EID_SUPP_CHANNELS 36 /* 11h Supported Channels */
|
|
+#define WLAN_EID_CHANNEL_SWITCH 37 /* 11h ChannelSwitch */
|
|
+#define WLAN_EID_MEASURE_REQUEST 38 /* 11h MeasurementRequest */
|
|
+#define WLAN_EID_MEASURE_REPORT 39 /* 11h MeasurementReport */
|
|
+#define WLAN_EID_QUIET_ID 40 /* 11h Quiet */
|
|
+#define WLAN_EID_IBSS_DFS_ID 41 /* 11h IBSS_DFS */
|
|
+#endif
|
|
+
|
|
+/*-- Reason Codes -------------------------------*/
|
|
+#define WLAN_MGMT_REASON_RSVD 0
|
|
+#define WLAN_MGMT_REASON_UNSPEC 1
|
|
+#define WLAN_MGMT_REASON_PRIOR_AUTH_INVALID 2
|
|
+#define WLAN_MGMT_REASON_DEAUTH_LEAVING 3
|
|
+#define WLAN_MGMT_REASON_DISASSOC_INACTIVE 4
|
|
+#define WLAN_MGMT_REASON_DISASSOC_AP_BUSY 5
|
|
+#define WLAN_MGMT_REASON_CLASS2_NONAUTH 6
|
|
+#define WLAN_MGMT_REASON_CLASS3_NONASSOC 7
|
|
+#define WLAN_MGMT_REASON_DISASSOC_STA_HASLEFT 8
|
|
+#define WLAN_MGMT_REASON_CANT_ASSOC_NONAUTH 9
|
|
+
|
|
+/*-- Status Codes -------------------------------*/
|
|
+#define WLAN_MGMT_STATUS_SUCCESS 0
|
|
+#define WLAN_MGMT_STATUS_UNSPEC_FAILURE 1
|
|
+#define WLAN_MGMT_STATUS_CAPS_UNSUPPORTED 10
|
|
+#define WLAN_MGMT_STATUS_REASSOC_NO_ASSOC 11
|
|
+#define WLAN_MGMT_STATUS_ASSOC_DENIED_UNSPEC 12
|
|
+#define WLAN_MGMT_STATUS_UNSUPPORTED_AUTHALG 13
|
|
+#define WLAN_MGMT_STATUS_RX_AUTH_NOSEQ 14
|
|
+#define WLAN_MGMT_STATUS_CHALLENGE_FAIL 15
|
|
+#define WLAN_MGMT_STATUS_AUTH_TIMEOUT 16
|
|
+#define WLAN_MGMT_STATUS_ASSOC_DENIED_BUSY 17
|
|
+#define WLAN_MGMT_STATUS_ASSOC_DENIED_RATES 18
|
|
+/* p80211b additions */
|
|
+#define WLAN_MGMT_STATUS_ASSOC_DENIED_NOSHORT 19
|
|
+#define WLAN_MGMT_STATUS_ASSOC_DENIED_NOPBCC 20
|
|
+#define WLAN_MGMT_STATUS_ASSOC_DENIED_NOAGILITY 21
|
|
+
|
|
+/*-- Auth Algorithm Field ---------------------------*/
|
|
+#define WLAN_AUTH_ALG_OPENSYSTEM 0
|
|
+#define WLAN_AUTH_ALG_SHAREDKEY 1
|
|
+
|
|
+/*-- Management Frame Field Offsets -------------*/
|
|
+/* Note: Not all fields are listed because of variable lengths */
|
|
+/* Note: These offsets are from the start of the frame data */
|
|
+
|
|
+#define WLAN_BEACON_OFF_TS 0
|
|
+#define WLAN_BEACON_OFF_BCN_INT 8
|
|
+#define WLAN_BEACON_OFF_CAPINFO 10
|
|
+#define WLAN_BEACON_OFF_SSID 12
|
|
+
|
|
+#define WLAN_DISASSOC_OFF_REASON 0
|
|
+
|
|
+#define WLAN_ASSOCREQ_OFF_CAP_INFO 0
|
|
+#define WLAN_ASSOCREQ_OFF_LISTEN_INT 2
|
|
+#define WLAN_ASSOCREQ_OFF_SSID 4
|
|
+
|
|
+#define WLAN_ASSOCRESP_OFF_CAP_INFO 0
|
|
+#define WLAN_ASSOCRESP_OFF_STATUS 2
|
|
+#define WLAN_ASSOCRESP_OFF_AID 4
|
|
+#define WLAN_ASSOCRESP_OFF_SUPP_RATES 6
|
|
+
|
|
+#define WLAN_REASSOCREQ_OFF_CAP_INFO 0
|
|
+#define WLAN_REASSOCREQ_OFF_LISTEN_INT 2
|
|
+#define WLAN_REASSOCREQ_OFF_CURR_AP 4
|
|
+#define WLAN_REASSOCREQ_OFF_SSID 10
|
|
+
|
|
+#define WLAN_REASSOCRESP_OFF_CAP_INFO 0
|
|
+#define WLAN_REASSOCRESP_OFF_STATUS 2
|
|
+#define WLAN_REASSOCRESP_OFF_AID 4
|
|
+#define WLAN_REASSOCRESP_OFF_SUPP_RATES 6
|
|
+
|
|
+#define WLAN_PROBEREQ_OFF_SSID 0
|
|
+
|
|
+#define WLAN_PROBERESP_OFF_TS 0
|
|
+#define WLAN_PROBERESP_OFF_BCN_INT 8
|
|
+#define WLAN_PROBERESP_OFF_CAP_INFO 10
|
|
+#define WLAN_PROBERESP_OFF_SSID 12
|
|
+
|
|
+#define WLAN_AUTHEN_OFF_AUTH_ALG 0
|
|
+#define WLAN_AUTHEN_OFF_AUTH_SEQ 2
|
|
+#define WLAN_AUTHEN_OFF_STATUS 4
|
|
+#define WLAN_AUTHEN_OFF_CHALLENGE 6
|
|
+
|
|
+#define WLAN_DEAUTHEN_OFF_REASON 0
|
|
+
|
|
+enum {
|
|
+IEEE16(WF_MGMT_CAP_ESS, 0x0001)
|
|
+IEEE16(WF_MGMT_CAP_IBSS, 0x0002)
|
|
+/* In (re)assoc request frames by STA:
|
|
+** Pollable=0, PollReq=0: STA is not CF-Pollable
|
|
+** 0 1: STA is CF-Pollable, not requesting to be placed on the CF-Polling list
|
|
+** 1 0: STA is CF-Pollable, requesting to be placed on the CF-Polling list
|
|
+** 1 1: STA is CF-Pollable, requesting never to be polled
|
|
+** In beacon, proberesp, (re)assoc resp frames by AP:
|
|
+** 0 0: No point coordinator at AP
|
|
+** 0 1: Point coordinator at AP for delivery only (no polling)
|
|
+** 1 0: Point coordinator at AP for delivery and polling
|
|
+** 1 1: Reserved */
|
|
+IEEE16(WF_MGMT_CAP_CFPOLLABLE, 0x0004)
|
|
+IEEE16(WF_MGMT_CAP_CFPOLLREQ, 0x0008)
|
|
+/* 1=non-WEP data frames are disallowed */
|
|
+IEEE16(WF_MGMT_CAP_PRIVACY, 0x0010)
|
|
+/* In beacon, proberesp, (re)assocresp by AP/AdHoc:
|
|
+** 1=use of shortpre is allowed ("I can receive shortpre") */
|
|
+IEEE16(WF_MGMT_CAP_SHORT, 0x0020)
|
|
+IEEE16(WF_MGMT_CAP_PBCC, 0x0040)
|
|
+IEEE16(WF_MGMT_CAP_AGILITY, 0x0080)
|
|
+/* In (re)assoc request frames by STA:
|
|
+** 1=short slot time implemented and enabled
|
|
+** NB: AP shall use long slot time beginning at the next Beacon after assoc
|
|
+** of STA with this bit set to 0
|
|
+** In beacon, proberesp, (re)assoc resp frames by AP:
|
|
+** currently used slot time value: 0/1 - long/short */
|
|
+IEEE16(WF_MGMT_CAP_SHORTSLOT, 0x0400)
|
|
+/* In (re)assoc request frames by STA: 1=CCK-OFDM is implemented and enabled
|
|
+** In beacon, proberesp, (re)assoc resp frames by AP/AdHoc:
|
|
+** 1=CCK-OFDM is allowed */
|
|
+IEEE16(WF_MGMT_CAP_CCKOFDM, 0x2000)
|
|
+};
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Types
|
|
+*/
|
|
+
|
|
+/* Information Element types */
|
|
+
|
|
+/* prototype structure, all IEs start with these members */
|
|
+typedef struct wlan_ie {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+} WLAN_PACKED wlan_ie_t;
|
|
+
|
|
+/*-- Service Set Identity (SSID) -----------------*/
|
|
+typedef struct wlan_ie_ssid {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u8 ssid[1]; /* may be zero */
|
|
+} WLAN_PACKED wlan_ie_ssid_t;
|
|
+
|
|
+/*-- Supported Rates -----------------------------*/
|
|
+typedef struct wlan_ie_supp_rates {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u8 rates[1]; /* had better be at LEAST one! */
|
|
+} WLAN_PACKED wlan_ie_supp_rates_t;
|
|
+
|
|
+/*-- FH Parameter Set ----------------------------*/
|
|
+typedef struct wlan_ie_fh_parms {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u16 dwell;
|
|
+ u8 hopset;
|
|
+ u8 hoppattern;
|
|
+ u8 hopindex;
|
|
+} WLAN_PACKED wlan_ie_fh_parms_t;
|
|
+
|
|
+/*-- DS Parameter Set ----------------------------*/
|
|
+typedef struct wlan_ie_ds_parms {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u8 curr_ch;
|
|
+} WLAN_PACKED wlan_ie_ds_parms_t;
|
|
+
|
|
+/*-- CF Parameter Set ----------------------------*/
|
|
+typedef struct wlan_ie_cf_parms {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u8 cfp_cnt;
|
|
+ u8 cfp_period;
|
|
+ u16 cfp_maxdur;
|
|
+ u16 cfp_durremaining;
|
|
+} WLAN_PACKED wlan_ie_cf_parms_t;
|
|
+
|
|
+/*-- TIM ------------------------------------------*/
|
|
+typedef struct wlan_ie_tim {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u8 dtim_cnt;
|
|
+ u8 dtim_period;
|
|
+ u8 bitmap_ctl;
|
|
+ u8 virt_bm[1];
|
|
+} WLAN_PACKED wlan_ie_tim_t;
|
|
+
|
|
+/*-- IBSS Parameter Set ---------------------------*/
|
|
+typedef struct wlan_ie_ibss_parms {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u16 atim_win;
|
|
+} WLAN_PACKED wlan_ie_ibss_parms_t;
|
|
+
|
|
+/*-- Challenge Text ------------------------------*/
|
|
+typedef struct wlan_ie_challenge {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ u8 challenge[1];
|
|
+} WLAN_PACKED wlan_ie_challenge_t;
|
|
+
|
|
+/*-- ERP (42) -------------------------------------*/
|
|
+typedef struct wlan_ie_erp {
|
|
+ u8 eid;
|
|
+ u8 len;
|
|
+ /* bit 0:Non ERP present
|
|
+ ** 1:Use Protection
|
|
+ ** 2:Barker Preamble mode
|
|
+ ** 3-7:reserved */
|
|
+ u8 erp;
|
|
+} WLAN_PACKED wlan_ie_erp_t;
|
|
+
|
|
+/* Types for parsing mgmt frames */
|
|
+
|
|
+/* prototype structure, all mgmt frame types will start with these members */
|
|
+typedef struct wlan_fr_mgmt {
|
|
+ u16 type;
|
|
+ u16 len; /* DOES NOT include FCS */
|
|
+ wlan_hdr_t *hdr;
|
|
+ /* used for target specific data, skb in Linux */
|
|
+ /*-- fixed fields -----------*/
|
|
+ /*-- info elements ----------*/
|
|
+} WLAN_PACKED wlan_fr_mgmt_t;
|
|
+
|
|
+/*-- Beacon ---------------------------------------*/
|
|
+typedef struct wlan_fr_beacon {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u64 *ts;
|
|
+ u16 *bcn_int;
|
|
+ u16 *cap_info;
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_ssid_t *ssid;
|
|
+ wlan_ie_supp_rates_t *supp_rates;
|
|
+ wlan_ie_supp_rates_t *ext_rates;
|
|
+ wlan_ie_fh_parms_t *fh_parms;
|
|
+ wlan_ie_ds_parms_t *ds_parms;
|
|
+ wlan_ie_cf_parms_t *cf_parms;
|
|
+ wlan_ie_ibss_parms_t *ibss_parms;
|
|
+ wlan_ie_tim_t *tim; /* in beacon only, not proberesp */
|
|
+ wlan_ie_erp_t *erp; /* in beacon only, not proberesp */
|
|
+} wlan_fr_beacon_t;
|
|
+#define wlan_fr_proberesp wlan_fr_beacon
|
|
+#define wlan_fr_proberesp_t wlan_fr_beacon_t
|
|
+
|
|
+/*-- IBSS ATIM ------------------------------------*/
|
|
+typedef struct wlan_fr_ibssatim {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ /*-- info elements ----------*/
|
|
+ /* this frame type has a null body */
|
|
+} wlan_fr_ibssatim_t;
|
|
+
|
|
+/*-- Disassociation -------------------------------*/
|
|
+typedef struct wlan_fr_disassoc {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *reason;
|
|
+ /*-- info elements ----------*/
|
|
+} wlan_fr_disassoc_t;
|
|
+
|
|
+/*-- Association Request --------------------------*/
|
|
+typedef struct wlan_fr_assocreq {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *cap_info;
|
|
+ u16 *listen_int;
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_ssid_t *ssid;
|
|
+ wlan_ie_supp_rates_t *supp_rates;
|
|
+ wlan_ie_supp_rates_t *ext_rates;
|
|
+} wlan_fr_assocreq_t;
|
|
+
|
|
+/*-- Association Response -------------------------*/
|
|
+typedef struct wlan_fr_assocresp {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *cap_info;
|
|
+ u16 *status;
|
|
+ u16 *aid;
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_supp_rates_t *supp_rates;
|
|
+ wlan_ie_supp_rates_t *ext_rates;
|
|
+} wlan_fr_assocresp_t;
|
|
+
|
|
+/*-- Reassociation Request ------------------------*/
|
|
+typedef struct wlan_fr_reassocreq {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *cap_info;
|
|
+ u16 *listen_int;
|
|
+ u8 *curr_ap;
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_ssid_t *ssid;
|
|
+ wlan_ie_supp_rates_t *supp_rates;
|
|
+ wlan_ie_supp_rates_t *ext_rates;
|
|
+} wlan_fr_reassocreq_t;
|
|
+
|
|
+/*-- Reassociation Response -----------------------*/
|
|
+typedef struct wlan_fr_reassocresp {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *cap_info;
|
|
+ u16 *status;
|
|
+ u16 *aid;
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_supp_rates_t *supp_rates;
|
|
+ wlan_ie_supp_rates_t *ext_rates;
|
|
+} wlan_fr_reassocresp_t;
|
|
+
|
|
+/*-- Probe Request --------------------------------*/
|
|
+typedef struct wlan_fr_probereq {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_ssid_t *ssid;
|
|
+ wlan_ie_supp_rates_t *supp_rates;
|
|
+ wlan_ie_supp_rates_t *ext_rates;
|
|
+} wlan_fr_probereq_t;
|
|
+
|
|
+/*-- Authentication -------------------------------*/
|
|
+typedef struct wlan_fr_authen {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *auth_alg;
|
|
+ u16 *auth_seq;
|
|
+ u16 *status;
|
|
+ /*-- info elements ----------*/
|
|
+ wlan_ie_challenge_t *challenge;
|
|
+} wlan_fr_authen_t;
|
|
+
|
|
+/*-- Deauthenication -----------------------------*/
|
|
+typedef struct wlan_fr_deauthen {
|
|
+ u16 type;
|
|
+ u16 len;
|
|
+ wlan_hdr_t *hdr;
|
|
+ /*-- fixed fields -----------*/
|
|
+ u16 *reason;
|
|
+ /*-- info elements ----------*/
|
|
+} wlan_fr_deauthen_t;
|
|
+
|
|
+/* Types for building mgmt frames */
|
|
+
|
|
+/* Warning. Several types used in below structs are
|
|
+** in fact variable length. Use structs with such fields with caution */
|
|
+typedef struct auth_frame_body {
|
|
+ u16 auth_alg;
|
|
+ u16 auth_seq;
|
|
+ u16 status;
|
|
+ wlan_ie_challenge_t challenge;
|
|
+} WLAN_PACKED auth_frame_body_t;
|
|
+
|
|
+typedef struct assocresp_frame_body {
|
|
+ u16 cap_info;
|
|
+ u16 status;
|
|
+ u16 aid;
|
|
+ wlan_ie_supp_rates_t rates;
|
|
+} WLAN_PACKED assocresp_frame_body_t;
|
|
+
|
|
+typedef struct reassocreq_frame_body {
|
|
+ u16 cap_info;
|
|
+ u16 listen_int;
|
|
+ u8 current_ap[ETH_ALEN];
|
|
+ wlan_ie_ssid_t ssid;
|
|
+/* access to this one is disabled since ssid_t is variable length: */
|
|
+ /* wlan_ie_supp_rates_t rates; */
|
|
+} WLAN_PACKED reassocreq_frame_body_t;
|
|
+
|
|
+typedef struct reassocresp_frame_body {
|
|
+ u16 cap_info;
|
|
+ u16 status;
|
|
+ u16 aid;
|
|
+ wlan_ie_supp_rates_t rates;
|
|
+} WLAN_PACKED reassocresp_frame_body_t;
|
|
+
|
|
+typedef struct deauthen_frame_body {
|
|
+ u16 reason;
|
|
+} WLAN_PACKED deauthen_frame_body_t;
|
|
+
|
|
+typedef struct disassoc_frame_body {
|
|
+ u16 reason;
|
|
+} WLAN_PACKED disassoc_frame_body_t;
|
|
+
|
|
+typedef struct probereq_frame_body {
|
|
+ wlan_ie_ssid_t ssid;
|
|
+ wlan_ie_supp_rates_t rates;
|
|
+} WLAN_PACKED probereq_frame_body_t;
|
|
+
|
|
+typedef struct proberesp_frame_body {
|
|
+ u8 timestamp[8];
|
|
+ u16 beacon_int;
|
|
+ u16 cap_info;
|
|
+ wlan_ie_ssid_t ssid;
|
|
+/* access to these is disabled since ssid_t is variable length: */
|
|
+ /* wlan_ie_supp_rates_t rates; */
|
|
+ /* fhps_t fhps; */
|
|
+ /* dsps_t dsps; */
|
|
+ /* cfps_t cfps; */
|
|
+} WLAN_PACKED proberesp_frame_body_t;
|
|
+
|
|
+
|
|
+/***********************************************************************
|
|
+** Functions
|
|
+*/
|
|
+
|
|
+/* Helpers for parsing mgmt frames */
|
|
+void wlan_mgmt_decode_ibssatim(wlan_fr_ibssatim_t *f);
|
|
+void wlan_mgmt_decode_assocreq(wlan_fr_assocreq_t *f);
|
|
+void wlan_mgmt_decode_assocresp(wlan_fr_assocresp_t *f);
|
|
+void wlan_mgmt_decode_authen(wlan_fr_authen_t *f);
|
|
+void wlan_mgmt_decode_beacon(wlan_fr_beacon_t *f);
|
|
+void wlan_mgmt_decode_deauthen(wlan_fr_deauthen_t *f);
|
|
+void wlan_mgmt_decode_disassoc(wlan_fr_disassoc_t *f);
|
|
+void wlan_mgmt_decode_probereq(wlan_fr_probereq_t *f);
|
|
+void wlan_mgmt_decode_proberesp(wlan_fr_proberesp_t *f);
|
|
+void wlan_mgmt_decode_reassocreq(wlan_fr_reassocreq_t *f);
|
|
+void wlan_mgmt_decode_reassocresp(wlan_fr_reassocresp_t *f);
|
|
+
|
|
+/* Helpers for building mgmt frames */
|
|
+static inline u8*
|
|
+wlan_fill_ie_ssid(u8 *p, int len, const char *ssid)
|
|
+{
|
|
+ struct wlan_ie_ssid *ie = (void*)p;
|
|
+ ie->eid = WLAN_EID_SSID;
|
|
+ ie->len = len;
|
|
+ memcpy(ie->ssid, ssid, len);
|
|
+ return p + len + 2;
|
|
+}
|
|
+/* This controls whether we create 802.11g 'ext supported rates' IEs
|
|
+** or just create overlong 'supported rates' IEs instead
|
|
+** (non-11g compliant) */
|
|
+#define WE_OBEY_802_11G 1
|
|
+static inline u8*
|
|
+wlan_fill_ie_rates(u8 *p, int len, const u8 *rates)
|
|
+{
|
|
+ struct wlan_ie_supp_rates *ie = (void*)p;
|
|
+#if WE_OBEY_802_11G
|
|
+ if (len > 8 ) len = 8;
|
|
+#endif
|
|
+ /* supported rates (1 to 8 octets) */
|
|
+ ie->eid = WLAN_EID_SUPP_RATES;
|
|
+ ie->len = len;
|
|
+ memcpy(ie->rates, rates, len);
|
|
+ return p + len + 2;
|
|
+}
|
|
+/* This one wouldn't create an IE at all if not needed */
|
|
+static inline u8*
|
|
+wlan_fill_ie_rates_ext(u8 *p, int len, const u8 *rates)
|
|
+{
|
|
+ struct wlan_ie_supp_rates *ie = (void*)p;
|
|
+#if !WE_OBEY_802_11G
|
|
+ return p;
|
|
+#endif
|
|
+ len -= 8;
|
|
+ if (len <= 0) return p;
|
|
+ /* ext supported rates */
|
|
+ ie->eid = WLAN_EID_EXT_RATES;
|
|
+ ie->len = len;
|
|
+ memcpy(ie->rates, rates+8, len);
|
|
+ return p + len + 2;
|
|
+}
|
|
+static inline u8*
|
|
+wlan_fill_ie_ds_parms(u8 *p, int channel)
|
|
+{
|
|
+ struct wlan_ie_ds_parms *ie = (void*)p;
|
|
+ ie->eid = WLAN_EID_DS_PARMS;
|
|
+ ie->len = 1;
|
|
+ ie->curr_ch = channel;
|
|
+ return p + sizeof(*ie);
|
|
+}
|
|
+static inline u8*
|
|
+wlan_fill_ie_ibss_parms(u8 *p, int atim_win)
|
|
+{
|
|
+ struct wlan_ie_ibss_parms *ie = (void*)p;
|
|
+ ie->eid = WLAN_EID_IBSS_PARMS;
|
|
+ ie->len = 2;
|
|
+ ie->atim_win = atim_win;
|
|
+ return p + sizeof(*ie);
|
|
+}
|
|
+static inline u8*
|
|
+wlan_fill_ie_tim(u8 *p, int rem, int period, int bcast,
|
|
+ int ofs, int len, const u8 *vbm)
|
|
+{
|
|
+ struct wlan_ie_tim *ie = (void*)p;
|
|
+ ie->eid = WLAN_EID_TIM;
|
|
+ ie->len = len + 3;
|
|
+ ie->dtim_cnt = rem;
|
|
+ ie->dtim_period = period;
|
|
+ ie->bitmap_ctl = ofs | (bcast!=0);
|
|
+ if (vbm)
|
|
+ memcpy(ie->virt_bm, vbm, len); /* min 1 byte */
|
|
+ else
|
|
+ ie->virt_bm[0] = 0;
|
|
+ return p + len + 3 + 2;
|
|
+}
|
|
Index: linux-2.6.23/drivers/net/wireless/Kconfig
|
|
===================================================================
|
|
--- linux-2.6.23.orig/drivers/net/wireless/Kconfig 2008-01-20 21:13:17.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/Kconfig 2008-01-20 21:15:12.000000000 +0000
|
|
@@ -5,6 +5,36 @@
|
|
menu "Wireless LAN"
|
|
depends on !S390
|
|
|
|
+config NET_RADIO
|
|
+ bool "Wireless LAN drivers (non-hamradio) & Wireless Extensions"
|
|
+ select WIRELESS_EXT
|
|
+ ---help---
|
|
+ Support for wireless LANs and everything having to do with radio,
|
|
+ but not with amateur radio or FM broadcasting.
|
|
+
|
|
+ Saying Y here also enables the Wireless Extensions (creates
|
|
+ /proc/net/wireless and enables iwconfig access). The Wireless
|
|
+ Extension is a generic API allowing a driver to expose to the user
|
|
+ space configuration and statistics specific to common Wireless LANs.
|
|
+ The beauty of it is that a single set of tool can support all the
|
|
+ variations of Wireless LANs, regardless of their type (as long as
|
|
+ the driver supports Wireless Extension). Another advantage is that
|
|
+ these parameters may be changed on the fly without restarting the
|
|
+ driver (or Linux). If you wish to use Wireless Extensions with
|
|
+ wireless PCMCIA (PC-) cards, you need to say Y here; you can fetch
|
|
+ the tools from
|
|
+ <http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/Tools.html>.
|
|
+
|
|
+config NET_WIRELESS_RTNETLINK
|
|
+ bool "Wireless Extension API over RtNetlink"
|
|
+ depends on NET_RADIO
|
|
+ ---help---
|
|
+ Support the Wireless Extension API over the RtNetlink socket
|
|
+ in addition to the traditional ioctl interface (selected above).
|
|
+
|
|
+ For now, few tools use this facility, but it might grow in the
|
|
+ future. The only downside is that it adds 4.5 kB to your kernel.
|
|
+
|
|
config WLAN_PRE80211
|
|
bool "Wireless LAN (pre-802.11)"
|
|
depends on NETDEVICES
|
|
@@ -650,6 +680,7 @@ config P54_PCI
|
|
|
|
source "drivers/net/wireless/iwlwifi/Kconfig"
|
|
source "drivers/net/wireless/hostap/Kconfig"
|
|
+source "drivers/net/wireless/acx/Kconfig"
|
|
source "drivers/net/wireless/bcm43xx/Kconfig"
|
|
source "drivers/net/wireless/b43/Kconfig"
|
|
source "drivers/net/wireless/b43legacy/Kconfig"
|
|
Index: linux-2.6.23/drivers/net/wireless/Makefile
|
|
===================================================================
|
|
--- linux-2.6.23.orig/drivers/net/wireless/Makefile 2008-01-20 21:13:17.000000000 +0000
|
|
+++ linux-2.6.23/drivers/net/wireless/Makefile 2008-01-20 21:13:40.000000000 +0000
|
|
@@ -34,6 +34,8 @@ obj-$(CONFIG_PCMCIA_ATMEL) += atmel
|
|
|
|
obj-$(CONFIG_PRISM54) += prism54/
|
|
|
|
+obj-$(CONFIG_ACX) += acx/
|
|
+
|
|
obj-$(CONFIG_HOSTAP) += hostap/
|
|
obj-$(CONFIG_BCM43XX) += bcm43xx/
|
|
obj-$(CONFIG_B43) += b43/
|