From 9ca8dbcc65cfc63d6f5ef3312a33184e1d726e00 Mon Sep 17 00:00:00 2001 From: Yunhong Jiang Date: Tue, 4 Aug 2015 12:17:53 -0700 Subject: Add the rt linux 4.1.3-rt3 as base Import the rt linux 4.1.3-rt3 as OPNFV kvm base. It's from git://git.kernel.org/pub/scm/linux/kernel/git/rt/linux-rt-devel.git linux-4.1.y-rt and the base is: commit 0917f823c59692d751951bf5ea699a2d1e2f26a2 Author: Sebastian Andrzej Siewior Date: Sat Jul 25 12:13:34 2015 +0200 Prepare v4.1.3-rt3 Signed-off-by: Sebastian Andrzej Siewior We lose all the git history this way and it's not good. We should apply another opnfv project repo in future. Change-Id: I87543d81c9df70d99c5001fbdf646b202c19f423 Signed-off-by: Yunhong Jiang --- kernel/drivers/staging/gdm72xx/Kconfig | 63 +++ kernel/drivers/staging/gdm72xx/Makefile | 6 + kernel/drivers/staging/gdm72xx/TODO | 2 + kernel/drivers/staging/gdm72xx/gdm_qos.c | 438 ++++++++++++++++ kernel/drivers/staging/gdm72xx/gdm_qos.h | 74 +++ kernel/drivers/staging/gdm72xx/gdm_sdio.c | 701 +++++++++++++++++++++++++ kernel/drivers/staging/gdm72xx/gdm_sdio.h | 63 +++ kernel/drivers/staging/gdm72xx/gdm_usb.c | 789 ++++++++++++++++++++++++++++ kernel/drivers/staging/gdm72xx/gdm_usb.h | 78 +++ kernel/drivers/staging/gdm72xx/gdm_wimax.c | 816 +++++++++++++++++++++++++++++ kernel/drivers/staging/gdm72xx/gdm_wimax.h | 49 ++ kernel/drivers/staging/gdm72xx/hci.h | 213 ++++++++ kernel/drivers/staging/gdm72xx/netlink_k.c | 156 ++++++ kernel/drivers/staging/gdm72xx/netlink_k.h | 25 + kernel/drivers/staging/gdm72xx/sdio_boot.c | 158 ++++++ kernel/drivers/staging/gdm72xx/sdio_boot.h | 21 + kernel/drivers/staging/gdm72xx/usb_boot.c | 359 +++++++++++++ kernel/drivers/staging/gdm72xx/usb_boot.h | 22 + kernel/drivers/staging/gdm72xx/usb_ids.h | 82 +++ kernel/drivers/staging/gdm72xx/wm_ioctl.h | 96 ++++ 20 files changed, 4211 insertions(+) create mode 100644 kernel/drivers/staging/gdm72xx/Kconfig create mode 100644 kernel/drivers/staging/gdm72xx/Makefile create mode 100644 kernel/drivers/staging/gdm72xx/TODO create mode 100644 kernel/drivers/staging/gdm72xx/gdm_qos.c create mode 100644 kernel/drivers/staging/gdm72xx/gdm_qos.h create mode 100644 kernel/drivers/staging/gdm72xx/gdm_sdio.c create mode 100644 kernel/drivers/staging/gdm72xx/gdm_sdio.h create mode 100644 kernel/drivers/staging/gdm72xx/gdm_usb.c create mode 100644 kernel/drivers/staging/gdm72xx/gdm_usb.h create mode 100644 kernel/drivers/staging/gdm72xx/gdm_wimax.c create mode 100644 kernel/drivers/staging/gdm72xx/gdm_wimax.h create mode 100644 kernel/drivers/staging/gdm72xx/hci.h create mode 100644 kernel/drivers/staging/gdm72xx/netlink_k.c create mode 100644 kernel/drivers/staging/gdm72xx/netlink_k.h create mode 100644 kernel/drivers/staging/gdm72xx/sdio_boot.c create mode 100644 kernel/drivers/staging/gdm72xx/sdio_boot.h create mode 100644 kernel/drivers/staging/gdm72xx/usb_boot.c create mode 100644 kernel/drivers/staging/gdm72xx/usb_boot.h create mode 100644 kernel/drivers/staging/gdm72xx/usb_ids.h create mode 100644 kernel/drivers/staging/gdm72xx/wm_ioctl.h (limited to 'kernel/drivers/staging/gdm72xx') diff --git a/kernel/drivers/staging/gdm72xx/Kconfig b/kernel/drivers/staging/gdm72xx/Kconfig new file mode 100644 index 000000000..bf11a7fbf --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/Kconfig @@ -0,0 +1,63 @@ +# +# GCT GDM72xx WiMAX driver configuration +# + +menuconfig WIMAX_GDM72XX + tristate "GCT GDM72xx WiMAX support" + depends on NET && (USB || MMC) + help + Support a WiMAX module based on the GCT GDM72xx WiMAX chip. + +if WIMAX_GDM72XX + +config WIMAX_GDM72XX_QOS + bool "Enable QoS support" + default n + help + Enable Quality of Service support based on the data protocol of + transmitting packets. + +config WIMAX_GDM72XX_K_MODE + bool "Enable K mode" + default n + help + Enable support for proprietary functions for KT (Korea Telecom). + +config WIMAX_GDM72XX_WIMAX2 + bool "Enable WiMAX2 support" + default n + help + Enable support for transmitting multiple packets (packet + aggregation) from the WiMAX module to the host processor. + +choice + prompt "Select interface" + +config WIMAX_GDM72XX_USB + bool "USB interface" + depends on (USB = y || USB = WIMAX_GDM72XX) + help + Select this option if the WiMAX module interfaces with the host + processor via USB. + +config WIMAX_GDM72XX_SDIO + bool "SDIO interface" + depends on (MMC = y || MMC = WIMAX_GDM72XX) + help + Select this option if the WiMAX module interfaces with the host + processor via SDIO. + +endchoice + +if WIMAX_GDM72XX_USB + +config WIMAX_GDM72XX_USB_PM + bool "Enable power management support" + depends on PM + help + Enable USB power management in order to reduce power consumption + while the interface is not in use. + +endif # WIMAX_GDM72XX_USB + +endif # WIMAX_GDM72XX diff --git a/kernel/drivers/staging/gdm72xx/Makefile b/kernel/drivers/staging/gdm72xx/Makefile new file mode 100644 index 000000000..35da7b90b --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/Makefile @@ -0,0 +1,6 @@ +obj-$(CONFIG_WIMAX_GDM72XX) := gdmwm.o + +gdmwm-y += gdm_wimax.o netlink_k.o +gdmwm-$(CONFIG_WIMAX_GDM72XX_QOS) += gdm_qos.o +gdmwm-$(CONFIG_WIMAX_GDM72XX_SDIO) += gdm_sdio.o sdio_boot.o +gdmwm-$(CONFIG_WIMAX_GDM72XX_USB) += gdm_usb.o usb_boot.o diff --git a/kernel/drivers/staging/gdm72xx/TODO b/kernel/drivers/staging/gdm72xx/TODO new file mode 100644 index 000000000..62d0cd622 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/TODO @@ -0,0 +1,2 @@ +TODO: +- Clean up coding style to meet kernel standard. diff --git a/kernel/drivers/staging/gdm72xx/gdm_qos.c b/kernel/drivers/staging/gdm72xx/gdm_qos.c new file mode 100644 index 000000000..96bf2bf87 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_qos.c @@ -0,0 +1,438 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include + +#include +#include +#include + +#include "gdm_wimax.h" +#include "hci.h" +#include "gdm_qos.h" + +#define MAX_FREE_LIST_CNT 32 +static struct { + struct list_head head; + int cnt; + spinlock_t lock; +} qos_free_list; + +static void init_qos_entry_list(void) +{ + qos_free_list.cnt = 0; + INIT_LIST_HEAD(&qos_free_list.head); + spin_lock_init(&qos_free_list.lock); +} + +static void *alloc_qos_entry(void) +{ + struct qos_entry_s *entry; + unsigned long flags; + + spin_lock_irqsave(&qos_free_list.lock, flags); + if (qos_free_list.cnt) { + entry = list_entry(qos_free_list.head.prev, struct qos_entry_s, + list); + list_del(&entry->list); + qos_free_list.cnt--; + spin_unlock_irqrestore(&qos_free_list.lock, flags); + return entry; + } + spin_unlock_irqrestore(&qos_free_list.lock, flags); + + return kmalloc(sizeof(*entry), GFP_ATOMIC); +} + +static void free_qos_entry(void *entry) +{ + struct qos_entry_s *qentry = (struct qos_entry_s *)entry; + unsigned long flags; + + spin_lock_irqsave(&qos_free_list.lock, flags); + if (qos_free_list.cnt < MAX_FREE_LIST_CNT) { + list_add(&qentry->list, &qos_free_list.head); + qos_free_list.cnt++; + spin_unlock_irqrestore(&qos_free_list.lock, flags); + return; + } + spin_unlock_irqrestore(&qos_free_list.lock, flags); + + kfree(entry); +} + +static void free_qos_entry_list(struct list_head *free_list) +{ + struct qos_entry_s *entry, *n; + int total_free = 0; + + list_for_each_entry_safe(entry, n, free_list, list) { + list_del(&entry->list); + kfree(entry); + total_free++; + } + + pr_debug("%s: total_free_cnt=%d\n", __func__, total_free); +} + +void gdm_qos_init(void *nic_ptr) +{ + struct nic *nic = nic_ptr; + struct qos_cb_s *qcb = &nic->qos; + int i; + + for (i = 0; i < QOS_MAX; i++) { + INIT_LIST_HEAD(&qcb->qos_list[i]); + qcb->csr[i].qos_buf_count = 0; + qcb->csr[i].enabled = false; + } + + qcb->qos_list_cnt = 0; + qcb->qos_null_idx = QOS_MAX-1; + qcb->qos_limit_size = 255; + + spin_lock_init(&qcb->qos_lock); + + init_qos_entry_list(); +} + +void gdm_qos_release_list(void *nic_ptr) +{ + struct nic *nic = nic_ptr; + struct qos_cb_s *qcb = &nic->qos; + unsigned long flags; + struct qos_entry_s *entry, *n; + struct list_head free_list; + int i; + + INIT_LIST_HEAD(&free_list); + + spin_lock_irqsave(&qcb->qos_lock, flags); + + for (i = 0; i < QOS_MAX; i++) { + qcb->csr[i].qos_buf_count = 0; + qcb->csr[i].enabled = false; + } + + qcb->qos_list_cnt = 0; + qcb->qos_null_idx = QOS_MAX-1; + + for (i = 0; i < QOS_MAX; i++) { + list_for_each_entry_safe(entry, n, &qcb->qos_list[i], list) { + list_move_tail(&entry->list, &free_list); + } + } + spin_unlock_irqrestore(&qcb->qos_lock, flags); + free_qos_entry_list(&free_list); +} + +static int chk_ipv4_rule(struct gdm_wimax_csr_s *csr, u8 *stream, u8 *port) +{ + int i; + + if (csr->classifier_rule_en&IPTYPEOFSERVICE) { + if (((stream[1] & csr->ip2s_mask) < csr->ip2s_lo) || + ((stream[1] & csr->ip2s_mask) > csr->ip2s_hi)) + return 1; + } + + if (csr->classifier_rule_en&PROTOCOL) { + if (stream[9] != csr->protocol) + return 1; + } + + if (csr->classifier_rule_en&IPMASKEDSRCADDRESS) { + for (i = 0; i < 4; i++) { + if ((stream[12 + i] & csr->ipsrc_addrmask[i]) != + (csr->ipsrc_addr[i] & csr->ipsrc_addrmask[i])) + return 1; + } + } + + if (csr->classifier_rule_en&IPMASKEDDSTADDRESS) { + for (i = 0; i < 4; i++) { + if ((stream[16 + i] & csr->ipdst_addrmask[i]) != + (csr->ipdst_addr[i] & csr->ipdst_addrmask[i])) + return 1; + } + } + + if (csr->classifier_rule_en&PROTOCOLSRCPORTRANGE) { + i = ((port[0]<<8)&0xff00)+port[1]; + if ((i < csr->srcport_lo) || (i > csr->srcport_hi)) + return 1; + } + + if (csr->classifier_rule_en&PROTOCOLDSTPORTRANGE) { + i = ((port[2]<<8)&0xff00)+port[3]; + if ((i < csr->dstport_lo) || (i > csr->dstport_hi)) + return 1; + } + + return 0; +} + +static int get_qos_index(struct nic *nic, u8 *iph, u8 *tcpudph) +{ + int ip_ver, i; + struct qos_cb_s *qcb = &nic->qos; + + if (iph == NULL || tcpudph == NULL) + return -1; + + ip_ver = (iph[0]>>4)&0xf; + + if (ip_ver != 4) + return -1; + + for (i = 0; i < QOS_MAX; i++) { + if (!qcb->csr[i].enabled) + continue; + if (!qcb->csr[i].classifier_rule_en) + continue; + if (chk_ipv4_rule(&qcb->csr[i], iph, tcpudph) == 0) + return i; + } + + return -1; +} + +static void extract_qos_list(struct nic *nic, struct list_head *head) +{ + struct qos_cb_s *qcb = &nic->qos; + struct qos_entry_s *entry; + int i; + + INIT_LIST_HEAD(head); + + for (i = 0; i < QOS_MAX; i++) { + if (!qcb->csr[i].enabled) + continue; + if (qcb->csr[i].qos_buf_count >= qcb->qos_limit_size) + continue; + if (list_empty(&qcb->qos_list[i])) + continue; + + entry = list_entry(qcb->qos_list[i].prev, struct qos_entry_s, + list); + + list_move_tail(&entry->list, head); + qcb->csr[i].qos_buf_count++; + + if (!list_empty(&qcb->qos_list[i])) + netdev_warn(nic->netdev, "Index(%d) is piled!!\n", i); + } +} + +static void send_qos_list(struct nic *nic, struct list_head *head) +{ + struct qos_entry_s *entry, *n; + + list_for_each_entry_safe(entry, n, head, list) { + list_del(&entry->list); + gdm_wimax_send_tx(entry->skb, entry->dev); + free_qos_entry(entry); + } +} + +int gdm_qos_send_hci_pkt(struct sk_buff *skb, struct net_device *dev) +{ + struct nic *nic = netdev_priv(dev); + int index; + struct qos_cb_s *qcb = &nic->qos; + unsigned long flags; + struct ethhdr *ethh = (struct ethhdr *)(skb->data + HCI_HEADER_SIZE); + struct iphdr *iph = (struct iphdr *)((char *)ethh + ETH_HLEN); + struct tcphdr *tcph; + struct qos_entry_s *entry = NULL; + struct list_head send_list; + int ret = 0; + + tcph = (struct tcphdr *)iph + iph->ihl*4; + + if (ethh->h_proto == cpu_to_be16(ETH_P_IP)) { + if (qcb->qos_list_cnt && !qos_free_list.cnt) { + entry = alloc_qos_entry(); + entry->skb = skb; + entry->dev = dev; + netdev_dbg(dev, "qcb->qos_list_cnt=%d\n", + qcb->qos_list_cnt); + } + + spin_lock_irqsave(&qcb->qos_lock, flags); + if (qcb->qos_list_cnt) { + index = get_qos_index(nic, (u8 *)iph, (u8 *)tcph); + if (index == -1) + index = qcb->qos_null_idx; + + if (!entry) { + entry = alloc_qos_entry(); + entry->skb = skb; + entry->dev = dev; + } + + list_add_tail(&entry->list, &qcb->qos_list[index]); + extract_qos_list(nic, &send_list); + spin_unlock_irqrestore(&qcb->qos_lock, flags); + send_qos_list(nic, &send_list); + goto out; + } + spin_unlock_irqrestore(&qcb->qos_lock, flags); + if (entry) + free_qos_entry(entry); + } + + ret = gdm_wimax_send_tx(skb, dev); +out: + return ret; +} + +static int get_csr(struct qos_cb_s *qcb, u32 sfid, int mode) +{ + int i; + + for (i = 0; i < qcb->qos_list_cnt; i++) { + if (qcb->csr[i].sfid == sfid) + return i; + } + + if (mode) { + for (i = 0; i < QOS_MAX; i++) { + if (!qcb->csr[i].enabled) { + qcb->csr[i].enabled = true; + qcb->qos_list_cnt++; + return i; + } + } + } + return -1; +} + +#define QOS_CHANGE_DEL 0xFC +#define QOS_ADD 0xFD +#define QOS_REPORT 0xFE + +void gdm_recv_qos_hci_packet(void *nic_ptr, u8 *buf, int size) +{ + struct nic *nic = nic_ptr; + int i, index, pos; + u32 sfid; + u8 sub_cmd_evt; + struct qos_cb_s *qcb = &nic->qos; + struct qos_entry_s *entry, *n; + struct list_head send_list; + struct list_head free_list; + unsigned long flags; + + sub_cmd_evt = (u8)buf[4]; + + if (sub_cmd_evt == QOS_REPORT) { + spin_lock_irqsave(&qcb->qos_lock, flags); + for (i = 0; i < qcb->qos_list_cnt; i++) { + sfid = ((buf[(i*5)+6]<<24)&0xff000000); + sfid += ((buf[(i*5)+7]<<16)&0xff0000); + sfid += ((buf[(i*5)+8]<<8)&0xff00); + sfid += (buf[(i*5)+9]); + index = get_csr(qcb, sfid, 0); + if (index == -1) { + spin_unlock_irqrestore(&qcb->qos_lock, flags); + netdev_err(nic->netdev, "QoS ERROR: No SF\n"); + return; + } + qcb->csr[index].qos_buf_count = buf[(i*5)+10]; + } + + extract_qos_list(nic, &send_list); + spin_unlock_irqrestore(&qcb->qos_lock, flags); + send_qos_list(nic, &send_list); + return; + } + + /* sub_cmd_evt == QOS_ADD || sub_cmd_evt == QOS_CHANG_DEL */ + pos = 6; + sfid = ((buf[pos++]<<24)&0xff000000); + sfid += ((buf[pos++]<<16)&0xff0000); + sfid += ((buf[pos++]<<8)&0xff00); + sfid += (buf[pos++]); + + index = get_csr(qcb, sfid, 1); + if (index == -1) { + netdev_err(nic->netdev, + "QoS ERROR: csr Update Error / Wrong index (%d)\n", + index); + return; + } + + if (sub_cmd_evt == QOS_ADD) { + netdev_dbg(nic->netdev, "QOS_ADD SFID = 0x%x, index=%d\n", + sfid, index); + + spin_lock_irqsave(&qcb->qos_lock, flags); + qcb->csr[index].sfid = sfid; + qcb->csr[index].classifier_rule_en = ((buf[pos++]<<8)&0xff00); + qcb->csr[index].classifier_rule_en += buf[pos++]; + if (qcb->csr[index].classifier_rule_en == 0) + qcb->qos_null_idx = index; + qcb->csr[index].ip2s_mask = buf[pos++]; + qcb->csr[index].ip2s_lo = buf[pos++]; + qcb->csr[index].ip2s_hi = buf[pos++]; + qcb->csr[index].protocol = buf[pos++]; + qcb->csr[index].ipsrc_addrmask[0] = buf[pos++]; + qcb->csr[index].ipsrc_addrmask[1] = buf[pos++]; + qcb->csr[index].ipsrc_addrmask[2] = buf[pos++]; + qcb->csr[index].ipsrc_addrmask[3] = buf[pos++]; + qcb->csr[index].ipsrc_addr[0] = buf[pos++]; + qcb->csr[index].ipsrc_addr[1] = buf[pos++]; + qcb->csr[index].ipsrc_addr[2] = buf[pos++]; + qcb->csr[index].ipsrc_addr[3] = buf[pos++]; + qcb->csr[index].ipdst_addrmask[0] = buf[pos++]; + qcb->csr[index].ipdst_addrmask[1] = buf[pos++]; + qcb->csr[index].ipdst_addrmask[2] = buf[pos++]; + qcb->csr[index].ipdst_addrmask[3] = buf[pos++]; + qcb->csr[index].ipdst_addr[0] = buf[pos++]; + qcb->csr[index].ipdst_addr[1] = buf[pos++]; + qcb->csr[index].ipdst_addr[2] = buf[pos++]; + qcb->csr[index].ipdst_addr[3] = buf[pos++]; + qcb->csr[index].srcport_lo = ((buf[pos++]<<8)&0xff00); + qcb->csr[index].srcport_lo += buf[pos++]; + qcb->csr[index].srcport_hi = ((buf[pos++]<<8)&0xff00); + qcb->csr[index].srcport_hi += buf[pos++]; + qcb->csr[index].dstport_lo = ((buf[pos++]<<8)&0xff00); + qcb->csr[index].dstport_lo += buf[pos++]; + qcb->csr[index].dstport_hi = ((buf[pos++]<<8)&0xff00); + qcb->csr[index].dstport_hi += buf[pos++]; + + qcb->qos_limit_size = 254/qcb->qos_list_cnt; + spin_unlock_irqrestore(&qcb->qos_lock, flags); + } else if (sub_cmd_evt == QOS_CHANGE_DEL) { + netdev_dbg(nic->netdev, "QOS_CHANGE_DEL SFID = 0x%x, index=%d\n", + sfid, index); + + INIT_LIST_HEAD(&free_list); + + spin_lock_irqsave(&qcb->qos_lock, flags); + qcb->csr[index].enabled = false; + qcb->qos_list_cnt--; + qcb->qos_limit_size = 254/qcb->qos_list_cnt; + + list_for_each_entry_safe(entry, n, &qcb->qos_list[index], + list) { + list_move_tail(&entry->list, &free_list); + } + spin_unlock_irqrestore(&qcb->qos_lock, flags); + free_qos_entry_list(&free_list); + } +} diff --git a/kernel/drivers/staging/gdm72xx/gdm_qos.h b/kernel/drivers/staging/gdm72xx/gdm_qos.h new file mode 100644 index 000000000..bbc8aab33 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_qos.h @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_GDM_QOS_H__ +#define __GDM72XX_GDM_QOS_H__ + +#include +#include +#include + +#define QOS_MAX 16 +#define IPTYPEOFSERVICE 0x8000 +#define PROTOCOL 0x4000 +#define IPMASKEDSRCADDRESS 0x2000 +#define IPMASKEDDSTADDRESS 0x1000 +#define PROTOCOLSRCPORTRANGE 0x800 +#define PROTOCOLDSTPORTRANGE 0x400 +#define DSTMACADDR 0x200 +#define SRCMACADDR 0x100 +#define ETHERTYPE 0x80 +#define IEEE802_1DUSERPRIORITY 0x40 +#define IEEE802_1QVLANID 0x10 + +struct gdm_wimax_csr_s { + bool enabled; + u32 sfid; + u8 qos_buf_count; + u16 classifier_rule_en; + u8 ip2s_lo; + u8 ip2s_hi; + u8 ip2s_mask; + u8 protocol; + u8 ipsrc_addr[16]; + u8 ipsrc_addrmask[16]; + u8 ipdst_addr[16]; + u8 ipdst_addrmask[16]; + u16 srcport_lo; + u16 srcport_hi; + u16 dstport_lo; + u16 dstport_hi; +}; + +struct qos_entry_s { + struct list_head list; + struct sk_buff *skb; + struct net_device *dev; + +}; + +struct qos_cb_s { + struct list_head qos_list[QOS_MAX]; + int qos_list_cnt; + int qos_null_idx; + struct gdm_wimax_csr_s csr[QOS_MAX]; + spinlock_t qos_lock; + int qos_limit_size; +}; + +void gdm_qos_init(void *nic_ptr); +void gdm_qos_release_list(void *nic_ptr); +int gdm_qos_send_hci_pkt(struct sk_buff *skb, struct net_device *dev); +void gdm_recv_qos_hci_packet(void *nic_ptr, u8 *buf, int size); + +#endif /* __GDM72XX_GDM_QOS_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/gdm_sdio.c b/kernel/drivers/staging/gdm72xx/gdm_sdio.c new file mode 100644 index 000000000..a5fd07948 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_sdio.c @@ -0,0 +1,701 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include + +#include +#include +#include +#include + +#include "gdm_sdio.h" +#include "gdm_wimax.h" +#include "sdio_boot.h" +#include "hci.h" + +#define TYPE_A_HEADER_SIZE 4 +#define TYPE_A_LOOKAHEAD_SIZE 16 + +#define MAX_NR_RX_BUF 4 + +#define SDU_TX_BUF_SIZE 2048 +#define TX_BUF_SIZE 2048 +#define TX_CHUNK_SIZE (2048 - TYPE_A_HEADER_SIZE) +#define RX_BUF_SIZE (25*1024) + +#define TX_HZ 2000 +#define TX_INTERVAL (1000000/TX_HZ) + +static struct sdio_tx *alloc_tx_struct(struct tx_cxt *tx) +{ + struct sdio_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC); + + if (!t) + return NULL; + + t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC); + if (!t->buf) { + kfree(t); + return NULL; + } + + t->tx_cxt = tx; + + return t; +} + +static void free_tx_struct(struct sdio_tx *t) +{ + if (t) { + kfree(t->buf); + kfree(t); + } +} + +static struct sdio_rx *alloc_rx_struct(struct rx_cxt *rx) +{ + struct sdio_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC); + + if (r) + r->rx_cxt = rx; + + return r; +} + +static void free_rx_struct(struct sdio_rx *r) +{ + kfree(r); +} + +/* Before this function is called, spin lock should be locked. */ +static struct sdio_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc) +{ + struct sdio_tx *t; + + if (list_empty(&tx->free_list)) + return NULL; + + t = list_entry(tx->free_list.prev, struct sdio_tx, list); + list_del(&t->list); + + *no_spc = list_empty(&tx->free_list) ? 1 : 0; + + return t; +} + +/* Before this function is called, spin lock should be locked. */ +static void put_tx_struct(struct tx_cxt *tx, struct sdio_tx *t) +{ + list_add_tail(&t->list, &tx->free_list); +} + +/* Before this function is called, spin lock should be locked. */ +static struct sdio_rx *get_rx_struct(struct rx_cxt *rx) +{ + struct sdio_rx *r; + + if (list_empty(&rx->free_list)) + return NULL; + + r = list_entry(rx->free_list.prev, struct sdio_rx, list); + list_del(&r->list); + + return r; +} + +/* Before this function is called, spin lock should be locked. */ +static void put_rx_struct(struct rx_cxt *rx, struct sdio_rx *r) +{ + list_add_tail(&r->list, &rx->free_list); +} + +static void release_sdio(struct sdiowm_dev *sdev) +{ + struct tx_cxt *tx = &sdev->tx; + struct rx_cxt *rx = &sdev->rx; + struct sdio_tx *t, *t_next; + struct sdio_rx *r, *r_next; + + kfree(tx->sdu_buf); + + list_for_each_entry_safe(t, t_next, &tx->free_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + list_for_each_entry_safe(t, t_next, &tx->hci_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + kfree(rx->rx_buf); + + list_for_each_entry_safe(r, r_next, &rx->free_list, list) { + list_del(&r->list); + free_rx_struct(r); + } + + list_for_each_entry_safe(r, r_next, &rx->req_list, list) { + list_del(&r->list); + free_rx_struct(r); + } +} + +static int init_sdio(struct sdiowm_dev *sdev) +{ + int ret = 0, i; + struct tx_cxt *tx = &sdev->tx; + struct rx_cxt *rx = &sdev->rx; + struct sdio_tx *t; + struct sdio_rx *r; + + INIT_LIST_HEAD(&tx->free_list); + INIT_LIST_HEAD(&tx->sdu_list); + INIT_LIST_HEAD(&tx->hci_list); + + spin_lock_init(&tx->lock); + + tx->sdu_buf = kmalloc(SDU_TX_BUF_SIZE, GFP_KERNEL); + if (tx->sdu_buf == NULL) + goto fail; + + for (i = 0; i < MAX_NR_SDU_BUF; i++) { + t = alloc_tx_struct(tx); + if (t == NULL) { + ret = -ENOMEM; + goto fail; + } + list_add(&t->list, &tx->free_list); + } + + INIT_LIST_HEAD(&rx->free_list); + INIT_LIST_HEAD(&rx->req_list); + + spin_lock_init(&rx->lock); + + for (i = 0; i < MAX_NR_RX_BUF; i++) { + r = alloc_rx_struct(rx); + if (r == NULL) { + ret = -ENOMEM; + goto fail; + } + list_add(&r->list, &rx->free_list); + } + + rx->rx_buf = kmalloc(RX_BUF_SIZE, GFP_KERNEL); + if (rx->rx_buf == NULL) + goto fail; + + return 0; + +fail: + release_sdio(sdev); + return ret; +} + +static void send_sdio_pkt(struct sdio_func *func, u8 *data, int len) +{ + int n, blocks, ret, remain; + + sdio_claim_host(func); + + blocks = len / func->cur_blksize; + n = blocks * func->cur_blksize; + if (blocks) { + ret = sdio_memcpy_toio(func, 0, data, n); + if (ret < 0) { + if (ret != -ENOMEDIUM) + dev_err(&func->dev, + "gdmwms: error: ret = %d\n", ret); + goto end_io; + } + } + + remain = len - n; + remain = (remain + 3) & ~3; + + if (remain) { + ret = sdio_memcpy_toio(func, 0, data + n, remain); + if (ret < 0) { + if (ret != -ENOMEDIUM) + dev_err(&func->dev, + "gdmwms: error: ret = %d\n", ret); + goto end_io; + } + } + +end_io: + sdio_release_host(func); +} + +static void send_sdu(struct sdio_func *func, struct tx_cxt *tx) +{ + struct list_head *l, *next; + struct hci_s *hci; + struct sdio_tx *t; + int pos, len, i, estlen, aggr_num = 0, aggr_len; + u8 *buf; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + + pos = TYPE_A_HEADER_SIZE + HCI_HEADER_SIZE; + list_for_each_entry(t, &tx->sdu_list, list) { + estlen = ((t->len + 3) & ~3) + 4; + if ((pos + estlen) > SDU_TX_BUF_SIZE) + break; + + aggr_num++; + memcpy(tx->sdu_buf + pos, t->buf, t->len); + memset(tx->sdu_buf + pos + t->len, 0, estlen - t->len); + pos += estlen; + } + aggr_len = pos; + + hci = (struct hci_s *)(tx->sdu_buf + TYPE_A_HEADER_SIZE); + hci->cmd_evt = cpu_to_be16(WIMAX_TX_SDU_AGGR); + hci->length = cpu_to_be16(aggr_len - TYPE_A_HEADER_SIZE - + HCI_HEADER_SIZE); + + spin_unlock_irqrestore(&tx->lock, flags); + + dev_dbg(&func->dev, "sdio_send: %*ph\n", aggr_len - TYPE_A_HEADER_SIZE, + tx->sdu_buf + TYPE_A_HEADER_SIZE); + + for (pos = TYPE_A_HEADER_SIZE; pos < aggr_len; pos += TX_CHUNK_SIZE) { + len = aggr_len - pos; + len = len > TX_CHUNK_SIZE ? TX_CHUNK_SIZE : len; + buf = tx->sdu_buf + pos - TYPE_A_HEADER_SIZE; + + buf[0] = len & 0xff; + buf[1] = (len >> 8) & 0xff; + buf[2] = (len >> 16) & 0xff; + buf[3] = (pos + len) >= aggr_len ? 0 : 1; + send_sdio_pkt(func, buf, len + TYPE_A_HEADER_SIZE); + } + + spin_lock_irqsave(&tx->lock, flags); + + for (l = tx->sdu_list.next, i = 0; i < aggr_num; i++, l = next) { + next = l->next; + t = list_entry(l, struct sdio_tx, list); + if (t->callback) + t->callback(t->cb_data); + + list_del(l); + put_tx_struct(t->tx_cxt, t); + } + + do_gettimeofday(&tx->sdu_stamp); + spin_unlock_irqrestore(&tx->lock, flags); +} + +static void send_hci(struct sdio_func *func, struct tx_cxt *tx, + struct sdio_tx *t) +{ + unsigned long flags; + + dev_dbg(&func->dev, "sdio_send: %*ph\n", t->len - TYPE_A_HEADER_SIZE, + t->buf + TYPE_A_HEADER_SIZE); + + send_sdio_pkt(func, t->buf, t->len); + + spin_lock_irqsave(&tx->lock, flags); + if (t->callback) + t->callback(t->cb_data); + free_tx_struct(t); + spin_unlock_irqrestore(&tx->lock, flags); +} + +static void do_tx(struct work_struct *work) +{ + struct sdiowm_dev *sdev = container_of(work, struct sdiowm_dev, ws); + struct sdio_func *func = sdev->func; + struct tx_cxt *tx = &sdev->tx; + struct sdio_tx *t = NULL; + struct timeval now, *before; + int is_sdu = 0; + long diff; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + if (!tx->can_send) { + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + + if (!list_empty(&tx->hci_list)) { + t = list_entry(tx->hci_list.next, struct sdio_tx, list); + list_del(&t->list); + is_sdu = 0; + } else if (!tx->stop_sdu_tx && !list_empty(&tx->sdu_list)) { + do_gettimeofday(&now); + before = &tx->sdu_stamp; + + diff = (now.tv_sec - before->tv_sec) * 1000000 + + (now.tv_usec - before->tv_usec); + if (diff >= 0 && diff < TX_INTERVAL) { + schedule_work(&sdev->ws); + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + is_sdu = 1; + } + + if (!is_sdu && t == NULL) { + spin_unlock_irqrestore(&tx->lock, flags); + return; + } + + tx->can_send = 0; + + spin_unlock_irqrestore(&tx->lock, flags); + + if (is_sdu) + send_sdu(func, tx); + else + send_hci(func, tx, t); +} + +static int gdm_sdio_send(void *priv_dev, void *data, int len, + void (*cb)(void *data), void *cb_data) +{ + struct sdiowm_dev *sdev = priv_dev; + struct tx_cxt *tx = &sdev->tx; + struct sdio_tx *t; + u8 *pkt = data; + int no_spc = 0; + u16 cmd_evt; + unsigned long flags; + + if (len > TX_BUF_SIZE - TYPE_A_HEADER_SIZE) + return -EINVAL; + + spin_lock_irqsave(&tx->lock, flags); + + cmd_evt = (pkt[0] << 8) | pkt[1]; + if (cmd_evt == WIMAX_TX_SDU) { + t = get_tx_struct(tx, &no_spc); + if (t == NULL) { + /* This case must not happen. */ + spin_unlock_irqrestore(&tx->lock, flags); + return -ENOSPC; + } + list_add_tail(&t->list, &tx->sdu_list); + + memcpy(t->buf, data, len); + + t->len = len; + t->callback = cb; + t->cb_data = cb_data; + } else { + t = alloc_tx_struct(tx); + if (t == NULL) { + spin_unlock_irqrestore(&tx->lock, flags); + return -ENOMEM; + } + list_add_tail(&t->list, &tx->hci_list); + + t->buf[0] = len & 0xff; + t->buf[1] = (len >> 8) & 0xff; + t->buf[2] = (len >> 16) & 0xff; + t->buf[3] = 2; + memcpy(t->buf + TYPE_A_HEADER_SIZE, data, len); + + t->len = len + TYPE_A_HEADER_SIZE; + t->callback = cb; + t->cb_data = cb_data; + } + + if (tx->can_send) + schedule_work(&sdev->ws); + + spin_unlock_irqrestore(&tx->lock, flags); + + if (no_spc) + return -ENOSPC; + + return 0; +} + +/* Handle the HCI, WIMAX_SDU_TX_FLOW. */ +static int control_sdu_tx_flow(struct sdiowm_dev *sdev, u8 *hci_data, int len) +{ + struct tx_cxt *tx = &sdev->tx; + u16 cmd_evt; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + + cmd_evt = (hci_data[0] << 8) | (hci_data[1]); + if (cmd_evt != WIMAX_SDU_TX_FLOW) + goto out; + + if (hci_data[4] == 0) { + dev_dbg(&sdev->func->dev, "WIMAX ==> STOP SDU TX\n"); + tx->stop_sdu_tx = 1; + } else if (hci_data[4] == 1) { + dev_dbg(&sdev->func->dev, "WIMAX ==> START SDU TX\n"); + tx->stop_sdu_tx = 0; + if (tx->can_send) + schedule_work(&sdev->ws); + /* If free buffer for sdu tx doesn't exist, then tx queue + * should not be woken. For this reason, don't pass the command, + * START_SDU_TX. + */ + if (list_empty(&tx->free_list)) + len = 0; + } + +out: + spin_unlock_irqrestore(&tx->lock, flags); + return len; +} + +static void gdm_sdio_irq(struct sdio_func *func) +{ + struct phy_dev *phy_dev = sdio_get_drvdata(func); + struct sdiowm_dev *sdev = phy_dev->priv_dev; + struct tx_cxt *tx = &sdev->tx; + struct rx_cxt *rx = &sdev->rx; + struct sdio_rx *r; + unsigned long flags; + u8 val, hdr[TYPE_A_LOOKAHEAD_SIZE], *buf; + u32 len, blocks, n; + int ret, remain; + + /* Check interrupt */ + val = sdio_readb(func, 0x13, &ret); + if (val & 0x01) + sdio_writeb(func, 0x01, 0x13, &ret); /* clear interrupt */ + else + return; + + ret = sdio_memcpy_fromio(func, hdr, 0x0, TYPE_A_LOOKAHEAD_SIZE); + if (ret) { + dev_err(&func->dev, + "Cannot read from function %d\n", func->num); + goto done; + } + + len = (hdr[2] << 16) | (hdr[1] << 8) | hdr[0]; + if (len > (RX_BUF_SIZE - TYPE_A_HEADER_SIZE)) { + dev_err(&func->dev, "Too big Type-A size: %d\n", len); + goto done; + } + + if (hdr[3] == 1) { /* Ack */ + u32 *ack_seq = (u32 *)&hdr[4]; + + spin_lock_irqsave(&tx->lock, flags); + tx->can_send = 1; + + if (!list_empty(&tx->sdu_list) || !list_empty(&tx->hci_list)) + schedule_work(&sdev->ws); + spin_unlock_irqrestore(&tx->lock, flags); + dev_dbg(&func->dev, "Ack... %0x\n", ntohl(*ack_seq)); + goto done; + } + + memcpy(rx->rx_buf, hdr + TYPE_A_HEADER_SIZE, + TYPE_A_LOOKAHEAD_SIZE - TYPE_A_HEADER_SIZE); + + buf = rx->rx_buf + TYPE_A_LOOKAHEAD_SIZE - TYPE_A_HEADER_SIZE; + remain = len - TYPE_A_LOOKAHEAD_SIZE + TYPE_A_HEADER_SIZE; + if (remain <= 0) + goto end_io; + + blocks = remain / func->cur_blksize; + + if (blocks) { + n = blocks * func->cur_blksize; + ret = sdio_memcpy_fromio(func, buf, 0x0, n); + if (ret) { + dev_err(&func->dev, + "Cannot read from function %d\n", func->num); + goto done; + } + buf += n; + remain -= n; + } + + if (remain) { + ret = sdio_memcpy_fromio(func, buf, 0x0, remain); + if (ret) { + dev_err(&func->dev, + "Cannot read from function %d\n", func->num); + goto done; + } + } + +end_io: + dev_dbg(&func->dev, "sdio_receive: %*ph\n", len, rx->rx_buf); + + len = control_sdu_tx_flow(sdev, rx->rx_buf, len); + + spin_lock_irqsave(&rx->lock, flags); + + if (!list_empty(&rx->req_list)) { + r = list_entry(rx->req_list.next, struct sdio_rx, list); + spin_unlock_irqrestore(&rx->lock, flags); + if (r->callback) + r->callback(r->cb_data, rx->rx_buf, len); + spin_lock_irqsave(&rx->lock, flags); + list_del(&r->list); + put_rx_struct(rx, r); + } + + spin_unlock_irqrestore(&rx->lock, flags); + +done: + sdio_writeb(func, 0x00, 0x10, &ret); /* PCRRT */ + if (!phy_dev->netdev) + register_wimax_device(phy_dev, &func->dev); +} + +static int gdm_sdio_receive(void *priv_dev, + void (*cb)(void *cb_data, void *data, int len), + void *cb_data) +{ + struct sdiowm_dev *sdev = priv_dev; + struct rx_cxt *rx = &sdev->rx; + struct sdio_rx *r; + unsigned long flags; + + spin_lock_irqsave(&rx->lock, flags); + r = get_rx_struct(rx); + if (r == NULL) { + spin_unlock_irqrestore(&rx->lock, flags); + return -ENOMEM; + } + + r->callback = cb; + r->cb_data = cb_data; + + list_add_tail(&r->list, &rx->req_list); + spin_unlock_irqrestore(&rx->lock, flags); + + return 0; +} + +static int sdio_wimax_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int ret; + struct phy_dev *phy_dev = NULL; + struct sdiowm_dev *sdev = NULL; + + dev_info(&func->dev, "Found GDM SDIO VID = 0x%04x PID = 0x%04x...\n", + func->vendor, func->device); + dev_info(&func->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION); + + sdio_claim_host(func); + sdio_enable_func(func); + sdio_claim_irq(func, gdm_sdio_irq); + + ret = sdio_boot(func); + if (ret) + return ret; + + phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL); + if (phy_dev == NULL) { + ret = -ENOMEM; + goto out; + } + sdev = kzalloc(sizeof(*sdev), GFP_KERNEL); + if (sdev == NULL) { + ret = -ENOMEM; + goto out; + } + + phy_dev->priv_dev = (void *)sdev; + phy_dev->send_func = gdm_sdio_send; + phy_dev->rcv_func = gdm_sdio_receive; + + ret = init_sdio(sdev); + if (ret < 0) + goto out; + + sdev->func = func; + + sdio_writeb(func, 1, 0x14, &ret); /* Enable interrupt */ + sdio_release_host(func); + + INIT_WORK(&sdev->ws, do_tx); + + sdio_set_drvdata(func, phy_dev); +out: + if (ret) { + kfree(phy_dev); + kfree(sdev); + } + + return ret; +} + +static void sdio_wimax_remove(struct sdio_func *func) +{ + struct phy_dev *phy_dev = sdio_get_drvdata(func); + struct sdiowm_dev *sdev = phy_dev->priv_dev; + + cancel_work_sync(&sdev->ws); + if (phy_dev->netdev) + unregister_wimax_device(phy_dev); + sdio_claim_host(func); + sdio_release_irq(func); + sdio_disable_func(func); + sdio_release_host(func); + release_sdio(sdev); + + kfree(sdev); + kfree(phy_dev); +} + +static const struct sdio_device_id sdio_wimax_ids[] = { + { SDIO_DEVICE(0x0296, 0x5347) }, + {0} +}; + +MODULE_DEVICE_TABLE(sdio, sdio_wimax_ids); + +static struct sdio_driver sdio_wimax_driver = { + .probe = sdio_wimax_probe, + .remove = sdio_wimax_remove, + .name = "sdio_wimax", + .id_table = sdio_wimax_ids, +}; + +static int __init sdio_gdm_wimax_init(void) +{ + return sdio_register_driver(&sdio_wimax_driver); +} + +static void __exit sdio_gdm_wimax_exit(void) +{ + sdio_unregister_driver(&sdio_wimax_driver); +} + +module_init(sdio_gdm_wimax_init); +module_exit(sdio_gdm_wimax_exit); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_DESCRIPTION("GCT WiMax SDIO Device Driver"); +MODULE_AUTHOR("Ethan Park"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/gdm72xx/gdm_sdio.h b/kernel/drivers/staging/gdm72xx/gdm_sdio.h new file mode 100644 index 000000000..77ad9d686 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_sdio.h @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_GDM_SDIO_H__ +#define __GDM72XX_GDM_SDIO_H__ + +#include +#include + +#define MAX_NR_SDU_BUF 64 + +struct sdio_tx { + struct list_head list; + struct tx_cxt *tx_cxt; + u8 *buf; + int len; + void (*callback)(void *cb_data); + void *cb_data; +}; + +struct tx_cxt { + struct list_head free_list; + struct list_head sdu_list; + struct list_head hci_list; + struct timeval sdu_stamp; + u8 *sdu_buf; + spinlock_t lock; + int can_send; + int stop_sdu_tx; +}; + +struct sdio_rx { + struct list_head list; + struct rx_cxt *rx_cxt; + void (*callback)(void *cb_data, void *data, int len); + void *cb_data; +}; + +struct rx_cxt { + struct list_head free_list; + struct list_head req_list; + u8 *rx_buf; + spinlock_t lock; +}; + +struct sdiowm_dev { + struct sdio_func *func; + struct tx_cxt tx; + struct rx_cxt rx; + struct work_struct ws; +}; + +#endif /* __GDM72XX_GDM_SDIO_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/gdm_usb.c b/kernel/drivers/staging/gdm72xx/gdm_usb.c new file mode 100644 index 000000000..eac2f3478 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_usb.c @@ -0,0 +1,789 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include + +#include "gdm_usb.h" +#include "gdm_wimax.h" +#include "usb_boot.h" +#include "hci.h" + +#include "usb_ids.h" + +MODULE_DEVICE_TABLE(usb, id_table); + +#define TX_BUF_SIZE 2048 + +#if defined(CONFIG_WIMAX_GDM72XX_WIMAX2) +#define RX_BUF_SIZE (128*1024) /* For packet aggregation */ +#else +#define RX_BUF_SIZE 2048 +#endif + +#define GDM7205_PADDING 256 + +#define DOWNLOAD_CONF_VALUE 0x21 + +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE + +static DECLARE_WAIT_QUEUE_HEAD(k_wait); +static LIST_HEAD(k_list); +static DEFINE_SPINLOCK(k_lock); +static int k_mode_stop; + +#define K_WAIT_TIME (2 * HZ / 100) + +#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ + +static struct usb_tx *alloc_tx_struct(struct tx_cxt *tx) +{ + struct usb_tx *t = kzalloc(sizeof(*t), GFP_ATOMIC); + + if (!t) + return NULL; + + t->urb = usb_alloc_urb(0, GFP_ATOMIC); + t->buf = kmalloc(TX_BUF_SIZE, GFP_ATOMIC); + if (!t->urb || !t->buf) { + usb_free_urb(t->urb); + kfree(t->buf); + kfree(t); + return NULL; + } + + t->tx_cxt = tx; + + return t; +} + +static void free_tx_struct(struct usb_tx *t) +{ + if (t) { + usb_free_urb(t->urb); + kfree(t->buf); + kfree(t); + } +} + +static struct usb_rx *alloc_rx_struct(struct rx_cxt *rx) +{ + struct usb_rx *r = kzalloc(sizeof(*r), GFP_ATOMIC); + + if (!r) + return NULL; + + r->urb = usb_alloc_urb(0, GFP_ATOMIC); + r->buf = kmalloc(RX_BUF_SIZE, GFP_ATOMIC); + if (!r->urb || !r->buf) { + usb_free_urb(r->urb); + kfree(r->buf); + kfree(r); + return NULL; + } + + r->rx_cxt = rx; + return r; +} + +static void free_rx_struct(struct usb_rx *r) +{ + if (r) { + usb_free_urb(r->urb); + kfree(r->buf); + kfree(r); + } +} + +/* Before this function is called, spin lock should be locked. */ +static struct usb_tx *get_tx_struct(struct tx_cxt *tx, int *no_spc) +{ + struct usb_tx *t; + + if (list_empty(&tx->free_list)) { + *no_spc = 1; + return NULL; + } + + t = list_entry(tx->free_list.next, struct usb_tx, list); + list_del(&t->list); + + *no_spc = list_empty(&tx->free_list) ? 1 : 0; + + return t; +} + +/* Before this function is called, spin lock should be locked. */ +static void put_tx_struct(struct tx_cxt *tx, struct usb_tx *t) +{ + list_add_tail(&t->list, &tx->free_list); +} + +/* Before this function is called, spin lock should be locked. */ +static struct usb_rx *get_rx_struct(struct rx_cxt *rx) +{ + struct usb_rx *r; + + if (list_empty(&rx->free_list)) { + r = alloc_rx_struct(rx); + if (r == NULL) + return NULL; + + list_add(&r->list, &rx->free_list); + } + + r = list_entry(rx->free_list.next, struct usb_rx, list); + list_move_tail(&r->list, &rx->used_list); + + return r; +} + +/* Before this function is called, spin lock should be locked. */ +static void put_rx_struct(struct rx_cxt *rx, struct usb_rx *r) +{ + list_move(&r->list, &rx->free_list); +} + +static void release_usb(struct usbwm_dev *udev) +{ + struct tx_cxt *tx = &udev->tx; + struct rx_cxt *rx = &udev->rx; + struct usb_tx *t, *t_next; + struct usb_rx *r, *r_next; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + + list_for_each_entry_safe(t, t_next, &tx->sdu_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + list_for_each_entry_safe(t, t_next, &tx->hci_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + list_for_each_entry_safe(t, t_next, &tx->free_list, list) { + list_del(&t->list); + free_tx_struct(t); + } + + spin_unlock_irqrestore(&tx->lock, flags); + + spin_lock_irqsave(&rx->lock, flags); + + list_for_each_entry_safe(r, r_next, &rx->free_list, list) { + list_del(&r->list); + free_rx_struct(r); + } + + list_for_each_entry_safe(r, r_next, &rx->used_list, list) { + list_del(&r->list); + free_rx_struct(r); + } + + spin_unlock_irqrestore(&rx->lock, flags); +} + +static int init_usb(struct usbwm_dev *udev) +{ + int ret = 0, i; + struct tx_cxt *tx = &udev->tx; + struct rx_cxt *rx = &udev->rx; + struct usb_tx *t; + struct usb_rx *r; + unsigned long flags; + + INIT_LIST_HEAD(&tx->free_list); + INIT_LIST_HEAD(&tx->sdu_list); + INIT_LIST_HEAD(&tx->hci_list); +#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE) + INIT_LIST_HEAD(&tx->pending_list); +#endif + + INIT_LIST_HEAD(&rx->free_list); + INIT_LIST_HEAD(&rx->used_list); + + spin_lock_init(&tx->lock); + spin_lock_init(&rx->lock); + + spin_lock_irqsave(&tx->lock, flags); + for (i = 0; i < MAX_NR_SDU_BUF; i++) { + t = alloc_tx_struct(tx); + if (t == NULL) { + spin_unlock_irqrestore(&tx->lock, flags); + ret = -ENOMEM; + goto fail; + } + list_add(&t->list, &tx->free_list); + } + spin_unlock_irqrestore(&tx->lock, flags); + + r = alloc_rx_struct(rx); + if (r == NULL) { + ret = -ENOMEM; + goto fail; + } + + spin_lock_irqsave(&rx->lock, flags); + list_add(&r->list, &rx->free_list); + spin_unlock_irqrestore(&rx->lock, flags); + return ret; + +fail: + release_usb(udev); + return ret; +} + +static void __gdm_usb_send_complete(struct urb *urb) +{ + struct usb_tx *t = urb->context; + struct tx_cxt *tx = t->tx_cxt; + u8 *pkt = t->buf; + u16 cmd_evt; + + /* Completion by usb_unlink_urb */ + if (urb->status == -ECONNRESET) + return; + + if (t->callback) + t->callback(t->cb_data); + + /* Delete from sdu list or hci list. */ + list_del(&t->list); + + cmd_evt = (pkt[0] << 8) | pkt[1]; + if (cmd_evt == WIMAX_TX_SDU) + put_tx_struct(tx, t); + else + free_tx_struct(t); +} + +static void gdm_usb_send_complete(struct urb *urb) +{ + struct usb_tx *t = urb->context; + struct tx_cxt *tx = t->tx_cxt; + unsigned long flags; + + spin_lock_irqsave(&tx->lock, flags); + __gdm_usb_send_complete(urb); + spin_unlock_irqrestore(&tx->lock, flags); +} + +static int gdm_usb_send(void *priv_dev, void *data, int len, + void (*cb)(void *data), void *cb_data) +{ + struct usbwm_dev *udev = priv_dev; + struct usb_device *usbdev = udev->usbdev; + struct tx_cxt *tx = &udev->tx; + struct usb_tx *t; + int padding = udev->padding; + int no_spc = 0, ret; + u8 *pkt = data; + u16 cmd_evt; + unsigned long flags; +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE + unsigned long flags2; +#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ + + if (!udev->usbdev) { + dev_err(&usbdev->dev, "%s: No such device\n", __func__); + return -ENODEV; + } + + if (len > TX_BUF_SIZE - padding - 1) + return -EINVAL; + + spin_lock_irqsave(&tx->lock, flags); + + cmd_evt = (pkt[0] << 8) | pkt[1]; + if (cmd_evt == WIMAX_TX_SDU) { + t = get_tx_struct(tx, &no_spc); + if (t == NULL) { + /* This case must not happen. */ + spin_unlock_irqrestore(&tx->lock, flags); + return -ENOSPC; + } + list_add_tail(&t->list, &tx->sdu_list); + } else { + t = alloc_tx_struct(tx); + if (t == NULL) { + spin_unlock_irqrestore(&tx->lock, flags); + return -ENOMEM; + } + list_add_tail(&t->list, &tx->hci_list); + } + + memcpy(t->buf + padding, data, len); + t->callback = cb; + t->cb_data = cb_data; + + /* In some cases, USB Module of WiMax is blocked when data size is + * the multiple of 512. So, increment length by one in that case. + */ + if ((len % 512) == 0) + len++; + + usb_fill_bulk_urb(t->urb, usbdev, usb_sndbulkpipe(usbdev, 1), t->buf, + len + padding, gdm_usb_send_complete, t); + + dev_dbg(&usbdev->dev, "usb_send: %*ph\n", len + padding, t->buf); + +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM + if (usbdev->state & USB_STATE_SUSPENDED) { + list_add_tail(&t->p_list, &tx->pending_list); + schedule_work(&udev->pm_ws); + goto out; + } +#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ + +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE + if (udev->bw_switch) { + list_add_tail(&t->p_list, &tx->pending_list); + goto out; + } else if (cmd_evt == WIMAX_SCAN) { + struct rx_cxt *rx; + struct usb_rx *r; + + rx = &udev->rx; + + spin_lock_irqsave(&rx->lock, flags2); + list_for_each_entry(r, &rx->used_list, list) + usb_unlink_urb(r->urb); + spin_unlock_irqrestore(&rx->lock, flags2); + + udev->bw_switch = 1; + + spin_lock_irqsave(&k_lock, flags2); + list_add_tail(&udev->list, &k_list); + spin_unlock_irqrestore(&k_lock, flags2); + + wake_up(&k_wait); + } +#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ + + ret = usb_submit_urb(t->urb, GFP_ATOMIC); + if (ret) + goto send_fail; + +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM + usb_mark_last_busy(usbdev); +#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ + +#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE) +out: +#endif + spin_unlock_irqrestore(&tx->lock, flags); + + if (no_spc) + return -ENOSPC; + + return 0; + +send_fail: + t->callback = NULL; + __gdm_usb_send_complete(t->urb); + spin_unlock_irqrestore(&tx->lock, flags); + return ret; +} + +static void gdm_usb_rcv_complete(struct urb *urb) +{ + struct usb_rx *r = urb->context; + struct rx_cxt *rx = r->rx_cxt; + struct usbwm_dev *udev = container_of(r->rx_cxt, struct usbwm_dev, rx); + struct tx_cxt *tx = &udev->tx; + struct usb_tx *t; + u16 cmd_evt; + unsigned long flags, flags2; + struct usb_device *dev = urb->dev; + + /* Completion by usb_unlink_urb */ + if (urb->status == -ECONNRESET) + return; + + spin_lock_irqsave(&tx->lock, flags); + + if (!urb->status) { + cmd_evt = (r->buf[0] << 8) | (r->buf[1]); + + dev_dbg(&dev->dev, "usb_receive: %*ph\n", urb->actual_length, + r->buf); + + if (cmd_evt == WIMAX_SDU_TX_FLOW) { + if (r->buf[4] == 0) { + dev_dbg(&dev->dev, "WIMAX ==> STOP SDU TX\n"); + list_for_each_entry(t, &tx->sdu_list, list) + usb_unlink_urb(t->urb); + } else if (r->buf[4] == 1) { + dev_dbg(&dev->dev, "WIMAX ==> START SDU TX\n"); + list_for_each_entry(t, &tx->sdu_list, list) { + usb_submit_urb(t->urb, GFP_ATOMIC); + } + /* If free buffer for sdu tx doesn't + * exist, then tx queue should not be + * woken. For this reason, don't pass + * the command, START_SDU_TX. + */ + if (list_empty(&tx->free_list)) + urb->actual_length = 0; + } + } + } + + if (!urb->status && r->callback) + r->callback(r->cb_data, r->buf, urb->actual_length); + + spin_lock_irqsave(&rx->lock, flags2); + put_rx_struct(rx, r); + spin_unlock_irqrestore(&rx->lock, flags2); + + spin_unlock_irqrestore(&tx->lock, flags); + +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM + usb_mark_last_busy(dev); +#endif +} + +static int gdm_usb_receive(void *priv_dev, + void (*cb)(void *cb_data, void *data, int len), + void *cb_data) +{ + struct usbwm_dev *udev = priv_dev; + struct usb_device *usbdev = udev->usbdev; + struct rx_cxt *rx = &udev->rx; + struct usb_rx *r; + unsigned long flags; + + if (!udev->usbdev) { + dev_err(&usbdev->dev, "%s: No such device\n", __func__); + return -ENODEV; + } + + spin_lock_irqsave(&rx->lock, flags); + r = get_rx_struct(rx); + spin_unlock_irqrestore(&rx->lock, flags); + + if (r == NULL) + return -ENOMEM; + + r->callback = cb; + r->cb_data = cb_data; + + usb_fill_bulk_urb(r->urb, usbdev, usb_rcvbulkpipe(usbdev, 0x82), r->buf, + RX_BUF_SIZE, gdm_usb_rcv_complete, r); + + return usb_submit_urb(r->urb, GFP_ATOMIC); +} + +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM +static void do_pm_control(struct work_struct *work) +{ + struct usbwm_dev *udev = container_of(work, struct usbwm_dev, pm_ws); + struct tx_cxt *tx = &udev->tx; + int ret; + unsigned long flags; + + ret = usb_autopm_get_interface(udev->intf); + if (!ret) + usb_autopm_put_interface(udev->intf); + + spin_lock_irqsave(&tx->lock, flags); + if (!(udev->usbdev->state & USB_STATE_SUSPENDED) && + (!list_empty(&tx->hci_list) || !list_empty(&tx->sdu_list))) { + struct usb_tx *t, *temp; + + list_for_each_entry_safe(t, temp, &tx->pending_list, p_list) { + list_del(&t->p_list); + ret = usb_submit_urb(t->urb, GFP_ATOMIC); + + if (ret) { + t->callback = NULL; + __gdm_usb_send_complete(t->urb); + } + } + } + spin_unlock_irqrestore(&tx->lock, flags); +} +#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ + +static int gdm_usb_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + int ret = 0; + u8 bConfigurationValue; + struct phy_dev *phy_dev = NULL; + struct usbwm_dev *udev = NULL; + u16 idVendor, idProduct, bcdDevice; + + struct usb_device *usbdev = interface_to_usbdev(intf); + + usb_get_dev(usbdev); + bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue; + + /*USB description is set up with Little-Endian*/ + idVendor = le16_to_cpu(usbdev->descriptor.idVendor); + idProduct = le16_to_cpu(usbdev->descriptor.idProduct); + bcdDevice = le16_to_cpu(usbdev->descriptor.bcdDevice); + + dev_info(&intf->dev, "Found GDM USB VID = 0x%04x PID = 0x%04x...\n", + idVendor, idProduct); + dev_info(&intf->dev, "GCT WiMax driver version %s\n", DRIVER_VERSION); + + + if (idProduct == EMERGENCY_PID) { + ret = usb_emergency(usbdev); + goto out; + } + + /* Support for EEPROM bootloader */ + if (bConfigurationValue == DOWNLOAD_CONF_VALUE || + idProduct & B_DOWNLOAD) { + ret = usb_boot(usbdev, bcdDevice); + goto out; + } + + phy_dev = kzalloc(sizeof(*phy_dev), GFP_KERNEL); + if (phy_dev == NULL) { + ret = -ENOMEM; + goto out; + } + udev = kzalloc(sizeof(*udev), GFP_KERNEL); + if (udev == NULL) { + ret = -ENOMEM; + goto out; + } + + if (idProduct == 0x7205 || idProduct == 0x7206) + udev->padding = GDM7205_PADDING; + else + udev->padding = 0; + + phy_dev->priv_dev = (void *)udev; + phy_dev->send_func = gdm_usb_send; + phy_dev->rcv_func = gdm_usb_receive; + + ret = init_usb(udev); + if (ret < 0) + goto out; + + udev->usbdev = usbdev; + +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM + udev->intf = intf; + + intf->needs_remote_wakeup = 1; + device_init_wakeup(&intf->dev, 1); + + pm_runtime_set_autosuspend_delay(&usbdev->dev, 10 * 1000); /* msec */ + + INIT_WORK(&udev->pm_ws, do_pm_control); +#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ + + ret = register_wimax_device(phy_dev, &intf->dev); + if (ret) + release_usb(udev); + +out: + if (ret) { + kfree(phy_dev); + kfree(udev); + usb_put_dev(usbdev); + } else { + usb_set_intfdata(intf, phy_dev); + } + return ret; +} + +static void gdm_usb_disconnect(struct usb_interface *intf) +{ + u8 bConfigurationValue; + struct phy_dev *phy_dev; + struct usbwm_dev *udev; + u16 idProduct; + struct usb_device *usbdev = interface_to_usbdev(intf); + + bConfigurationValue = usbdev->actconfig->desc.bConfigurationValue; + phy_dev = usb_get_intfdata(intf); + + /*USB description is set up with Little-Endian*/ + idProduct = le16_to_cpu(usbdev->descriptor.idProduct); + + if (idProduct != EMERGENCY_PID && + bConfigurationValue != DOWNLOAD_CONF_VALUE && + (idProduct & B_DOWNLOAD) == 0) { + udev = phy_dev->priv_dev; + udev->usbdev = NULL; + + unregister_wimax_device(phy_dev); + release_usb(udev); + kfree(udev); + kfree(phy_dev); + } + + usb_put_dev(usbdev); +} + +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM +static int gdm_suspend(struct usb_interface *intf, pm_message_t pm_msg) +{ + struct phy_dev *phy_dev; + struct usbwm_dev *udev; + struct rx_cxt *rx; + struct usb_rx *r; + unsigned long flags; + + phy_dev = usb_get_intfdata(intf); + if (!phy_dev) + return 0; + + udev = phy_dev->priv_dev; + rx = &udev->rx; + + spin_lock_irqsave(&rx->lock, flags); + + list_for_each_entry(r, &rx->used_list, list) + usb_unlink_urb(r->urb); + + spin_unlock_irqrestore(&rx->lock, flags); + + return 0; +} + +static int gdm_resume(struct usb_interface *intf) +{ + struct phy_dev *phy_dev; + struct usbwm_dev *udev; + struct rx_cxt *rx; + struct usb_rx *r; + unsigned long flags; + + phy_dev = usb_get_intfdata(intf); + if (!phy_dev) + return 0; + + udev = phy_dev->priv_dev; + rx = &udev->rx; + + spin_lock_irqsave(&rx->lock, flags); + + list_for_each_entry(r, &rx->used_list, list) + usb_submit_urb(r->urb, GFP_ATOMIC); + + spin_unlock_irqrestore(&rx->lock, flags); + + return 0; +} + +#endif /* CONFIG_WIMAX_GDM72XX_USB_PM */ + +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE +static int k_mode_thread(void *arg) +{ + struct usbwm_dev *udev; + struct tx_cxt *tx; + struct rx_cxt *rx; + struct usb_tx *t, *temp; + struct usb_rx *r; + unsigned long flags, flags2, expire; + int ret; + + while (!k_mode_stop) { + spin_lock_irqsave(&k_lock, flags2); + while (!list_empty(&k_list)) { + udev = list_entry(k_list.next, struct usbwm_dev, list); + tx = &udev->tx; + rx = &udev->rx; + + list_del(&udev->list); + spin_unlock_irqrestore(&k_lock, flags2); + + expire = jiffies + K_WAIT_TIME; + while (time_before(jiffies, expire)) + schedule_timeout(K_WAIT_TIME); + + spin_lock_irqsave(&rx->lock, flags); + + list_for_each_entry(r, &rx->used_list, list) + usb_submit_urb(r->urb, GFP_ATOMIC); + + spin_unlock_irqrestore(&rx->lock, flags); + + spin_lock_irqsave(&tx->lock, flags); + + list_for_each_entry_safe(t, temp, &tx->pending_list, + p_list) { + list_del(&t->p_list); + ret = usb_submit_urb(t->urb, GFP_ATOMIC); + + if (ret) { + t->callback = NULL; + __gdm_usb_send_complete(t->urb); + } + } + + udev->bw_switch = 0; + spin_unlock_irqrestore(&tx->lock, flags); + + spin_lock_irqsave(&k_lock, flags2); + } + wait_event_interruptible_lock_irq(k_wait, + !list_empty(&k_list) || + k_mode_stop, k_lock); + spin_unlock_irqrestore(&k_lock, flags2); + } + return 0; +} +#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ + +static struct usb_driver gdm_usb_driver = { + .name = "gdm_wimax", + .probe = gdm_usb_probe, + .disconnect = gdm_usb_disconnect, + .id_table = id_table, +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM + .supports_autosuspend = 1, + .suspend = gdm_suspend, + .resume = gdm_resume, + .reset_resume = gdm_resume, +#endif +}; + +static int __init usb_gdm_wimax_init(void) +{ +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE + kthread_run(k_mode_thread, NULL, "k_mode_wimax"); +#endif /* CONFIG_WIMAX_GDM72XX_K_MODE */ + return usb_register(&gdm_usb_driver); +} + +static void __exit usb_gdm_wimax_exit(void) +{ +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE + k_mode_stop = 1; + wake_up(&k_wait); +#endif + usb_deregister(&gdm_usb_driver); +} + +module_init(usb_gdm_wimax_init); +module_exit(usb_gdm_wimax_exit); + +MODULE_VERSION(DRIVER_VERSION); +MODULE_DESCRIPTION("GCT WiMax Device Driver"); +MODULE_AUTHOR("Ethan Park"); +MODULE_LICENSE("GPL"); diff --git a/kernel/drivers/staging/gdm72xx/gdm_usb.h b/kernel/drivers/staging/gdm72xx/gdm_usb.h new file mode 100644 index 000000000..8e58a25e7 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_usb.h @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_GDM_USB_H__ +#define __GDM72XX_GDM_USB_H__ + +#include +#include +#include + +#define B_DIFF_DL_DRV (1 << 4) +#define B_DOWNLOAD (1 << 5) +#define MAX_NR_SDU_BUF 64 + +struct usb_tx { + struct list_head list; +#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE) + struct list_head p_list; +#endif + struct tx_cxt *tx_cxt; + struct urb *urb; + u8 *buf; + void (*callback)(void *cb_data); + void *cb_data; +}; + +struct tx_cxt { + struct list_head free_list; + struct list_head sdu_list; + struct list_head hci_list; +#if defined(CONFIG_WIMAX_GDM72XX_USB_PM) || defined(CONFIG_WIMAX_GDM72XX_K_MODE) + struct list_head pending_list; +#endif + spinlock_t lock; +}; + +struct usb_rx { + struct list_head list; + struct rx_cxt *rx_cxt; + struct urb *urb; + u8 *buf; + void (*callback)(void *cb_data, void *data, int len); + void *cb_data; +}; + +struct rx_cxt { + struct list_head free_list; + struct list_head used_list; + spinlock_t lock; +}; + +struct usbwm_dev { + struct usb_device *usbdev; +#ifdef CONFIG_WIMAX_GDM72XX_USB_PM + struct work_struct pm_ws; + + struct usb_interface *intf; +#endif +#ifdef CONFIG_WIMAX_GDM72XX_K_MODE + int bw_switch; + struct list_head list; +#endif + struct tx_cxt tx; + struct rx_cxt rx; + int padding; +}; + +#endif /* __GDM72XX_GDM_USB_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/gdm_wimax.c b/kernel/drivers/staging/gdm72xx/gdm_wimax.c new file mode 100644 index 000000000..61d168e82 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_wimax.c @@ -0,0 +1,816 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include + +#include "gdm_wimax.h" +#include "hci.h" +#include "wm_ioctl.h" +#include "netlink_k.h" + +#define gdm_wimax_send(n, d, l) \ + (n->phy_dev->send_func)(n->phy_dev->priv_dev, d, l, NULL, NULL) +#define gdm_wimax_send_with_cb(n, d, l, c, b) \ + (n->phy_dev->send_func)(n->phy_dev->priv_dev, d, l, c, b) +#define gdm_wimax_rcv_with_cb(n, c, b) \ + (n->phy_dev->rcv_func)(n->phy_dev->priv_dev, c, b) + +#define EVT_MAX_SIZE 2048 + +struct evt_entry { + struct list_head list; + struct net_device *dev; + char evt_data[EVT_MAX_SIZE]; + int size; +}; + +static struct { + int ref_cnt; + struct sock *sock; + struct list_head evtq; + spinlock_t evt_lock; + struct list_head freeq; + struct work_struct ws; +} wm_event; + +static u8 gdm_wimax_macaddr[6] = {0x00, 0x0a, 0x3b, 0xf0, 0x01, 0x30}; + +static inline int gdm_wimax_header(struct sk_buff **pskb) +{ + u16 buf[HCI_HEADER_SIZE / sizeof(u16)]; + struct hci_s *hci = (struct hci_s *)buf; + struct sk_buff *skb = *pskb; + + if (unlikely(skb_headroom(skb) < HCI_HEADER_SIZE)) { + struct sk_buff *skb2; + + skb2 = skb_realloc_headroom(skb, HCI_HEADER_SIZE); + if (skb2 == NULL) + return -ENOMEM; + if (skb->sk) + skb_set_owner_w(skb2, skb->sk); + kfree_skb(skb); + skb = skb2; + } + + skb_push(skb, HCI_HEADER_SIZE); + hci->cmd_evt = cpu_to_be16(WIMAX_TX_SDU); + hci->length = cpu_to_be16(skb->len - HCI_HEADER_SIZE); + memcpy(skb->data, buf, HCI_HEADER_SIZE); + + *pskb = skb; + return 0; +} + +static inline struct evt_entry *alloc_event_entry(void) +{ + return kmalloc(sizeof(struct evt_entry), GFP_ATOMIC); +} + +static inline void free_event_entry(struct evt_entry *e) +{ + kfree(e); +} + +static struct evt_entry *get_event_entry(void) +{ + struct evt_entry *e; + + if (list_empty(&wm_event.freeq)) { + e = alloc_event_entry(); + } else { + e = list_entry(wm_event.freeq.next, struct evt_entry, list); + list_del(&e->list); + } + + return e; +} + +static void put_event_entry(struct evt_entry *e) +{ + BUG_ON(!e); + + list_add_tail(&e->list, &wm_event.freeq); +} + +static void gdm_wimax_event_rcv(struct net_device *dev, u16 type, void *msg, + int len) +{ + struct nic *nic = netdev_priv(dev); + + u8 *buf = msg; + u16 hci_cmd = (buf[0]<<8) | buf[1]; + u16 hci_len = (buf[2]<<8) | buf[3]; + + netdev_dbg(dev, "H=>D: 0x%04x(%d)\n", hci_cmd, hci_len); + + gdm_wimax_send(nic, msg, len); +} + +static void __gdm_wimax_event_send(struct work_struct *work) +{ + int idx; + unsigned long flags; + struct evt_entry *e; + struct evt_entry *tmp; + + spin_lock_irqsave(&wm_event.evt_lock, flags); + + list_for_each_entry_safe(e, tmp, &wm_event.evtq, list) { + spin_unlock_irqrestore(&wm_event.evt_lock, flags); + + if (sscanf(e->dev->name, "wm%d", &idx) == 1) + netlink_send(wm_event.sock, idx, 0, e->evt_data, + e->size); + + spin_lock_irqsave(&wm_event.evt_lock, flags); + list_del(&e->list); + put_event_entry(e); + } + + spin_unlock_irqrestore(&wm_event.evt_lock, flags); +} + +static int gdm_wimax_event_init(void) +{ + if (!wm_event.ref_cnt) { + wm_event.sock = netlink_init(NETLINK_WIMAX, + gdm_wimax_event_rcv); + if (wm_event.sock) { + INIT_LIST_HEAD(&wm_event.evtq); + INIT_LIST_HEAD(&wm_event.freeq); + INIT_WORK(&wm_event.ws, __gdm_wimax_event_send); + spin_lock_init(&wm_event.evt_lock); + } + } + + if (wm_event.sock) { + wm_event.ref_cnt++; + return 0; + } + + pr_err("Creating WiMax Event netlink is failed\n"); + return -1; +} + +static void gdm_wimax_event_exit(void) +{ + if (wm_event.sock && --wm_event.ref_cnt == 0) { + struct evt_entry *e, *temp; + unsigned long flags; + + spin_lock_irqsave(&wm_event.evt_lock, flags); + + list_for_each_entry_safe(e, temp, &wm_event.evtq, list) { + list_del(&e->list); + free_event_entry(e); + } + list_for_each_entry_safe(e, temp, &wm_event.freeq, list) { + list_del(&e->list); + free_event_entry(e); + } + + spin_unlock_irqrestore(&wm_event.evt_lock, flags); + netlink_exit(wm_event.sock); + wm_event.sock = NULL; + } +} + +static int gdm_wimax_event_send(struct net_device *dev, char *buf, int size) +{ + struct evt_entry *e; + unsigned long flags; + + u16 hci_cmd = ((u8)buf[0]<<8) | (u8)buf[1]; + u16 hci_len = ((u8)buf[2]<<8) | (u8)buf[3]; + + netdev_dbg(dev, "D=>H: 0x%04x(%d)\n", hci_cmd, hci_len); + + spin_lock_irqsave(&wm_event.evt_lock, flags); + + e = get_event_entry(); + if (!e) { + netdev_err(dev, "%s: No memory for event\n", __func__); + spin_unlock_irqrestore(&wm_event.evt_lock, flags); + return -ENOMEM; + } + + e->dev = dev; + e->size = size; + memcpy(e->evt_data, buf, size); + + list_add_tail(&e->list, &wm_event.evtq); + spin_unlock_irqrestore(&wm_event.evt_lock, flags); + + schedule_work(&wm_event.ws); + + return 0; +} + +static void tx_complete(void *arg) +{ + struct nic *nic = arg; + + if (netif_queue_stopped(nic->netdev)) + netif_wake_queue(nic->netdev); +} + +int gdm_wimax_send_tx(struct sk_buff *skb, struct net_device *dev) +{ + int ret = 0; + struct nic *nic = netdev_priv(dev); + + ret = gdm_wimax_send_with_cb(nic, skb->data, skb->len, tx_complete, + nic); + if (ret == -ENOSPC) { + netif_stop_queue(dev); + ret = 0; + } + + if (ret) { + skb_pull(skb, HCI_HEADER_SIZE); + return ret; + } + + dev->stats.tx_packets++; + dev->stats.tx_bytes += skb->len - HCI_HEADER_SIZE; + kfree_skb(skb); + return ret; +} + +static int gdm_wimax_tx(struct sk_buff *skb, struct net_device *dev) +{ + int ret = 0; + + ret = gdm_wimax_header(&skb); + if (ret < 0) { + skb_pull(skb, HCI_HEADER_SIZE); + return ret; + } + +#if defined(CONFIG_WIMAX_GDM72XX_QOS) + ret = gdm_qos_send_hci_pkt(skb, dev); +#else + ret = gdm_wimax_send_tx(skb, dev); +#endif + return ret; +} + +static int gdm_wimax_set_config(struct net_device *dev, struct ifmap *map) +{ + if (dev->flags & IFF_UP) + return -EBUSY; + + return 0; +} + +static void __gdm_wimax_set_mac_addr(struct net_device *dev, char *mac_addr) +{ + u16 hci_pkt_buf[32 / sizeof(u16)]; + struct hci_s *hci = (struct hci_s *)hci_pkt_buf; + struct nic *nic = netdev_priv(dev); + + /* Since dev is registered as a ethernet device, + * ether_setup has made dev->addr_len to be ETH_ALEN + */ + memcpy(dev->dev_addr, mac_addr, dev->addr_len); + + /* Let lower layer know of this change by sending + * SetInformation(MAC Address) + */ + hci->cmd_evt = cpu_to_be16(WIMAX_SET_INFO); + hci->length = cpu_to_be16(8); + hci->data[0] = 0; /* T */ + hci->data[1] = 6; /* L */ + memcpy(&hci->data[2], mac_addr, dev->addr_len); /* V */ + + gdm_wimax_send(nic, hci, HCI_HEADER_SIZE + 8); +} + +/* A driver function */ +static int gdm_wimax_set_mac_addr(struct net_device *dev, void *p) +{ + struct sockaddr *addr = p; + + if (netif_running(dev)) + return -EBUSY; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EADDRNOTAVAIL; + + __gdm_wimax_set_mac_addr(dev, addr->sa_data); + + return 0; +} + +static void gdm_wimax_ind_if_updown(struct net_device *dev, int if_up) +{ + u16 buf[32 / sizeof(u16)]; + struct hci_s *hci = (struct hci_s *)buf; + unsigned char up_down; + + up_down = if_up ? WIMAX_IF_UP : WIMAX_IF_DOWN; + + /* Indicate updating fsm */ + hci->cmd_evt = cpu_to_be16(WIMAX_IF_UPDOWN); + hci->length = cpu_to_be16(sizeof(up_down)); + hci->data[0] = up_down; + + gdm_wimax_event_send(dev, (char *)hci, HCI_HEADER_SIZE+sizeof(up_down)); +} + +static int gdm_wimax_open(struct net_device *dev) +{ + struct nic *nic = netdev_priv(dev); + struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf; + + netif_start_queue(dev); + + if (fsm && fsm->m_status != M_INIT) + gdm_wimax_ind_if_updown(dev, 1); + return 0; +} + +static int gdm_wimax_close(struct net_device *dev) +{ + struct nic *nic = netdev_priv(dev); + struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf; + + netif_stop_queue(dev); + + if (fsm && fsm->m_status != M_INIT) + gdm_wimax_ind_if_updown(dev, 0); + return 0; +} + +static void kdelete(void **buf) +{ + if (buf && *buf) { + kfree(*buf); + *buf = NULL; + } +} + +static int gdm_wimax_ioctl_get_data(struct data_s *dst, struct data_s *src) +{ + int size; + + size = dst->size < src->size ? dst->size : src->size; + + dst->size = size; + if (src->size) { + if (!dst->buf) + return -EINVAL; + if (copy_to_user((void __user *)dst->buf, src->buf, size)) + return -EFAULT; + } + return 0; +} + +static int gdm_wimax_ioctl_set_data(struct data_s *dst, struct data_s *src) +{ + if (!src->size) { + dst->size = 0; + return 0; + } + + if (!src->buf) + return -EINVAL; + + if (!(dst->buf && dst->size == src->size)) { + kdelete(&dst->buf); + dst->buf = kmalloc(src->size, GFP_KERNEL); + if (dst->buf == NULL) + return -ENOMEM; + } + + if (copy_from_user(dst->buf, (void __user *)src->buf, src->size)) { + kdelete(&dst->buf); + return -EFAULT; + } + dst->size = src->size; + return 0; +} + +static void gdm_wimax_cleanup_ioctl(struct net_device *dev) +{ + struct nic *nic = netdev_priv(dev); + int i; + + for (i = 0; i < SIOC_DATA_MAX; i++) + kdelete(&nic->sdk_data[i].buf); +} + +static void gdm_wimax_ind_fsm_update(struct net_device *dev, struct fsm_s *fsm) +{ + u16 buf[32 / sizeof(u16)]; + struct hci_s *hci = (struct hci_s *)buf; + + /* Indicate updating fsm */ + hci->cmd_evt = cpu_to_be16(WIMAX_FSM_UPDATE); + hci->length = cpu_to_be16(sizeof(struct fsm_s)); + memcpy(&hci->data[0], fsm, sizeof(struct fsm_s)); + + gdm_wimax_event_send(dev, (char *)hci, + HCI_HEADER_SIZE + sizeof(struct fsm_s)); +} + +static void gdm_update_fsm(struct net_device *dev, struct fsm_s *new_fsm) +{ + struct nic *nic = netdev_priv(dev); + struct fsm_s *cur_fsm = (struct fsm_s *) + nic->sdk_data[SIOC_DATA_FSM].buf; + + if (!cur_fsm) + return; + + if (cur_fsm->m_status != new_fsm->m_status || + cur_fsm->c_status != new_fsm->c_status) { + if (new_fsm->m_status == M_CONNECTED) { + netif_carrier_on(dev); + } else if (cur_fsm->m_status == M_CONNECTED) { + netif_carrier_off(dev); + #if defined(CONFIG_WIMAX_GDM72XX_QOS) + gdm_qos_release_list(nic); + #endif + } + gdm_wimax_ind_fsm_update(dev, new_fsm); + } +} + +static int gdm_wimax_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd) +{ + struct wm_req_s *req = (struct wm_req_s *)ifr; + struct nic *nic = netdev_priv(dev); + int ret; + + if (cmd != SIOCWMIOCTL) + return -EOPNOTSUPP; + + switch (req->cmd) { + case SIOCG_DATA: + case SIOCS_DATA: + if (req->data_id >= SIOC_DATA_MAX) { + netdev_err(dev, "%s error: data-index(%d) is invalid!!\n", + __func__, req->data_id); + return -EOPNOTSUPP; + } + if (req->cmd == SIOCG_DATA) { + ret = gdm_wimax_ioctl_get_data( + &req->data, &nic->sdk_data[req->data_id]); + if (ret < 0) + return ret; + } else if (req->cmd == SIOCS_DATA) { + if (req->data_id == SIOC_DATA_FSM) { + /* NOTE: gdm_update_fsm should be called + * before gdm_wimax_ioctl_set_data is called. + */ + gdm_update_fsm(dev, + (struct fsm_s *)req->data.buf); + } + ret = gdm_wimax_ioctl_set_data( + &nic->sdk_data[req->data_id], &req->data); + if (ret < 0) + return ret; + } + break; + default: + netdev_err(dev, "%s: %x unknown ioctl\n", __func__, cmd); + return -EOPNOTSUPP; + } + + return 0; +} + +static void gdm_wimax_prepare_device(struct net_device *dev) +{ + struct nic *nic = netdev_priv(dev); + u16 buf[32 / sizeof(u16)]; + struct hci_s *hci = (struct hci_s *)buf; + u16 len = 0; + u32 val = 0; + __be32 val_be32; + + /* GetInformation mac address */ + len = 0; + hci->cmd_evt = cpu_to_be16(WIMAX_GET_INFO); + hci->data[len++] = TLV_T(T_MAC_ADDRESS); + hci->length = cpu_to_be16(len); + gdm_wimax_send(nic, hci, HCI_HEADER_SIZE+len); + + val = T_CAPABILITY_WIMAX | T_CAPABILITY_MULTI_CS; + #if defined(CONFIG_WIMAX_GDM72XX_QOS) + val |= T_CAPABILITY_QOS; + #endif + #if defined(CONFIG_WIMAX_GDM72XX_WIMAX2) + val |= T_CAPABILITY_AGGREGATION; + #endif + + /* Set capability */ + len = 0; + hci->cmd_evt = cpu_to_be16(WIMAX_SET_INFO); + hci->data[len++] = TLV_T(T_CAPABILITY); + hci->data[len++] = TLV_L(T_CAPABILITY); + val_be32 = cpu_to_be32(val); + memcpy(&hci->data[len], &val_be32, TLV_L(T_CAPABILITY)); + len += TLV_L(T_CAPABILITY); + hci->length = cpu_to_be16(len); + gdm_wimax_send(nic, hci, HCI_HEADER_SIZE+len); + + netdev_info(dev, "GDM WiMax Set CAPABILITY: 0x%08X\n", val); +} + +static int gdm_wimax_hci_get_tlv(u8 *buf, u8 *T, u16 *L, u8 **V) +{ + #define __U82U16(b) ((u16)((u8 *)(b))[0] | ((u16)((u8 *)(b))[1] << 8)) + int next_pos; + + *T = buf[0]; + if (buf[1] == 0x82) { + *L = be16_to_cpu(__U82U16(&buf[2])); + next_pos = 1/*type*/+3/*len*/; + } else { + *L = buf[1]; + next_pos = 1/*type*/+1/*len*/; + } + *V = &buf[next_pos]; + + next_pos += *L/*length of val*/; + return next_pos; +} + +static int gdm_wimax_get_prepared_info(struct net_device *dev, char *buf, + int len) +{ + u8 T, *V; + u16 L; + u16 cmd_evt, cmd_len; + int pos = HCI_HEADER_SIZE; + + cmd_evt = be16_to_cpup((const __be16 *)&buf[0]); + cmd_len = be16_to_cpup((const __be16 *)&buf[2]); + + if (len < cmd_len + HCI_HEADER_SIZE) { + netdev_err(dev, "%s: invalid length [%d/%d]\n", __func__, + cmd_len + HCI_HEADER_SIZE, len); + return -1; + } + + if (cmd_evt == WIMAX_GET_INFO_RESULT) { + if (cmd_len < 2) { + netdev_err(dev, "%s: len is too short [%x/%d]\n", + __func__, cmd_evt, len); + return -1; + } + + pos += gdm_wimax_hci_get_tlv(&buf[pos], &T, &L, &V); + if (T == TLV_T(T_MAC_ADDRESS)) { + if (L != dev->addr_len) { + netdev_err(dev, + "%s Invalid inofrmation result T/L [%x/%d]\n", + __func__, T, L); + return -1; + } + netdev_info(dev, "MAC change [%pM]->[%pM]\n", + dev->dev_addr, V); + memcpy(dev->dev_addr, V, dev->addr_len); + return 1; + } + } + + gdm_wimax_event_send(dev, buf, len); + return 0; +} + +static void gdm_wimax_netif_rx(struct net_device *dev, char *buf, int len) +{ + struct sk_buff *skb; + int ret; + + skb = dev_alloc_skb(len + 2); + if (!skb) + return; + skb_reserve(skb, 2); + + dev->stats.rx_packets++; + dev->stats.rx_bytes += len; + + memcpy(skb_put(skb, len), buf, len); + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); /* what will happen? */ + + ret = in_interrupt() ? netif_rx(skb) : netif_rx_ni(skb); + if (ret == NET_RX_DROP) + netdev_err(dev, "%s skb dropped\n", __func__); +} + +static void gdm_wimax_transmit_aggr_pkt(struct net_device *dev, char *buf, + int len) +{ + #define HCI_PADDING_BYTE 4 + #define HCI_RESERVED_BYTE 4 + struct hci_s *hci; + int length; + + while (len > 0) { + hci = (struct hci_s *)buf; + + if (hci->cmd_evt != cpu_to_be16(WIMAX_RX_SDU)) { + netdev_err(dev, "Wrong cmd_evt(0x%04X)\n", + be16_to_cpu(hci->cmd_evt)); + break; + } + + length = be16_to_cpu(hci->length); + gdm_wimax_netif_rx(dev, hci->data, length); + + if (length & 0x3) { + /* Add padding size */ + length += HCI_PADDING_BYTE - (length & 0x3); + } + + length += HCI_HEADER_SIZE + HCI_RESERVED_BYTE; + len -= length; + buf += length; + } +} + +static void gdm_wimax_transmit_pkt(struct net_device *dev, char *buf, int len) +{ + #if defined(CONFIG_WIMAX_GDM72XX_QOS) + struct nic *nic = netdev_priv(dev); + #endif + u16 cmd_evt, cmd_len; + + /* This code is added for certain rx packet to be ignored. */ + if (len == 0) + return; + + cmd_evt = be16_to_cpup((const __be16 *)&buf[0]); + cmd_len = be16_to_cpup((const __be16 *)&buf[2]); + + if (len < cmd_len + HCI_HEADER_SIZE) { + if (len) + netdev_err(dev, "%s: invalid length [%d/%d]\n", + __func__, cmd_len + HCI_HEADER_SIZE, len); + return; + } + + switch (cmd_evt) { + case WIMAX_RX_SDU_AGGR: + gdm_wimax_transmit_aggr_pkt(dev, &buf[HCI_HEADER_SIZE], + cmd_len); + break; + case WIMAX_RX_SDU: + gdm_wimax_netif_rx(dev, &buf[HCI_HEADER_SIZE], cmd_len); + break; + #if defined(CONFIG_WIMAX_GDM72XX_QOS) + case WIMAX_EVT_MODEM_REPORT: + gdm_recv_qos_hci_packet(nic, buf, len); + break; + #endif + case WIMAX_SDU_TX_FLOW: + if (buf[4] == 0) { + if (!netif_queue_stopped(dev)) + netif_stop_queue(dev); + } else if (buf[4] == 1) { + if (netif_queue_stopped(dev)) + netif_wake_queue(dev); + } + break; + default: + gdm_wimax_event_send(dev, buf, len); + break; + } +} + +static void rx_complete(void *arg, void *data, int len) +{ + struct nic *nic = arg; + + gdm_wimax_transmit_pkt(nic->netdev, data, len); + gdm_wimax_rcv_with_cb(nic, rx_complete, nic); +} + +static void prepare_rx_complete(void *arg, void *data, int len) +{ + struct nic *nic = arg; + int ret; + + ret = gdm_wimax_get_prepared_info(nic->netdev, data, len); + if (ret == 1) { + gdm_wimax_rcv_with_cb(nic, rx_complete, nic); + } else { + if (ret < 0) + netdev_err(nic->netdev, + "get_prepared_info failed(%d)\n", ret); + gdm_wimax_rcv_with_cb(nic, prepare_rx_complete, nic); + } +} + +static void start_rx_proc(struct nic *nic) +{ + gdm_wimax_rcv_with_cb(nic, prepare_rx_complete, nic); +} + +static struct net_device_ops gdm_netdev_ops = { + .ndo_open = gdm_wimax_open, + .ndo_stop = gdm_wimax_close, + .ndo_set_config = gdm_wimax_set_config, + .ndo_start_xmit = gdm_wimax_tx, + .ndo_set_mac_address = gdm_wimax_set_mac_addr, + .ndo_do_ioctl = gdm_wimax_ioctl, +}; + +int register_wimax_device(struct phy_dev *phy_dev, struct device *pdev) +{ + struct nic *nic = NULL; + struct net_device *dev; + int ret; + + dev = alloc_netdev(sizeof(*nic), "wm%d", NET_NAME_UNKNOWN, + ether_setup); + + if (!dev) { + pr_err("alloc_etherdev failed\n"); + return -ENOMEM; + } + + SET_NETDEV_DEV(dev, pdev); + dev->mtu = 1400; + dev->netdev_ops = &gdm_netdev_ops; + dev->flags &= ~IFF_MULTICAST; + memcpy(dev->dev_addr, gdm_wimax_macaddr, sizeof(gdm_wimax_macaddr)); + + nic = netdev_priv(dev); + nic->netdev = dev; + nic->phy_dev = phy_dev; + phy_dev->netdev = dev; + + /* event socket init */ + ret = gdm_wimax_event_init(); + if (ret < 0) { + pr_err("Cannot create event.\n"); + goto cleanup; + } + + ret = register_netdev(dev); + if (ret) + goto cleanup; + + netif_carrier_off(dev); + +#ifdef CONFIG_WIMAX_GDM72XX_QOS + gdm_qos_init(nic); +#endif + + start_rx_proc(nic); + + /* Prepare WiMax device */ + gdm_wimax_prepare_device(dev); + + return 0; + +cleanup: + pr_err("register_netdev failed\n"); + free_netdev(dev); + return ret; +} + +void unregister_wimax_device(struct phy_dev *phy_dev) +{ + struct nic *nic = netdev_priv(phy_dev->netdev); + struct fsm_s *fsm = (struct fsm_s *)nic->sdk_data[SIOC_DATA_FSM].buf; + + if (fsm) + fsm->m_status = M_INIT; + unregister_netdev(nic->netdev); + + gdm_wimax_event_exit(); + +#if defined(CONFIG_WIMAX_GDM72XX_QOS) + gdm_qos_release_list(nic); +#endif + + gdm_wimax_cleanup_ioctl(phy_dev->netdev); + + free_netdev(nic->netdev); +} diff --git a/kernel/drivers/staging/gdm72xx/gdm_wimax.h b/kernel/drivers/staging/gdm72xx/gdm_wimax.h new file mode 100644 index 000000000..3330cd798 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/gdm_wimax.h @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_GDM_WIMAX_H__ +#define __GDM72XX_GDM_WIMAX_H__ + +#include +#include +#include "wm_ioctl.h" +#if defined(CONFIG_WIMAX_GDM72XX_QOS) +#include "gdm_qos.h" +#endif + +#define DRIVER_VERSION "3.2.3" + +struct phy_dev { + void *priv_dev; + struct net_device *netdev; + int (*send_func)(void *priv_dev, void *data, int len, + void (*cb)(void *cb_data), void *cb_data); + int (*rcv_func)(void *priv_dev, + void (*cb)(void *cb_data, void *data, int len), + void *cb_data); +}; + +struct nic { + struct net_device *netdev; + struct phy_dev *phy_dev; + struct data_s sdk_data[SIOC_DATA_MAX]; +#if defined(CONFIG_WIMAX_GDM72XX_QOS) + struct qos_cb_s qos; +#endif +}; + +int register_wimax_device(struct phy_dev *phy_dev, struct device *pdev); +int gdm_wimax_send_tx(struct sk_buff *skb, struct net_device *dev); +void unregister_wimax_device(struct phy_dev *phy_dev); + +#endif /* __GDM72XX_GDM_WIMAX_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/hci.h b/kernel/drivers/staging/gdm72xx/hci.h new file mode 100644 index 000000000..10a6bfa6e --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/hci.h @@ -0,0 +1,213 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_HCI_H__ +#define __GDM72XX_HCI_H__ + +#define HCI_HEADER_SIZE 4 +#define HCI_VALUE_OFFS (HCI_HEADER_SIZE) +#define HCI_MAX_PACKET 2048 +#define HCI_MAX_PARAM (HCI_MAX_PACKET-HCI_HEADER_SIZE) +#define HCI_MAX_TLV 32 + +/* CMD-EVT */ + +/* Category 0 */ +#define WIMAX_RESET 0x0000 +#define WIMAX_SET_INFO 0x0001 +#define WIMAX_GET_INFO 0x0002 +#define WIMAX_GET_INFO_RESULT 0x8003 +#define WIMAX_RADIO_OFF 0x0004 +#define WIMAX_RADIO_ON 0x0006 +#define WIMAX_WIMAX_RESET 0x0007 /* Is this still here */ + +/* Category 1 */ +#define WIMAX_NET_ENTRY 0x0100 +#define WIMAX_NET_DISCONN 0x0102 +#define WIMAX_ENTER_SLEEP 0x0103 +#define WIMAX_EXIT_SLEEP 0x0104 +#define WIMAX_ENTER_IDLE 0x0105 +#define WIMAX_EXIT_IDLE 0x0106 +#define WIMAX_MODE_CHANGE 0x8108 +#define WIMAX_HANDOVER 0x8109 /* obsolete */ +#define WIMAX_SCAN 0x010d +#define WIMAX_SCAN_COMPLETE 0x810e +#define WIMAX_SCAN_RESULT 0x810f +#define WIMAX_CONNECT 0x0110 +#define WIMAX_CONNECT_START 0x8111 +#define WIMAX_CONNECT_COMPLETE 0x8112 +#define WIMAX_ASSOC_START 0x8113 +#define WIMAX_ASSOC_COMPLETE 0x8114 +#define WIMAX_DISCONN_IND 0x8115 +#define WIMAX_ENTRY_IND 0x8116 +#define WIMAX_HO_START 0x8117 +#define WIMAX_HO_COMPLETE 0x8118 +#define WIMAX_RADIO_STATE_IND 0x8119 +#define WIMAX_IP_RENEW_IND 0x811a +#define WIMAX_DISCOVER_NSP 0x011d +#define WIMAX_DISCOVER_NSP_RESULT 0x811e +#define WIMAX_SDU_TX_FLOW 0x8125 + +/* Category 2 */ +#define WIMAX_TX_EAP 0x0200 +#define WIMAX_RX_EAP 0x8201 +#define WIMAX_TX_SDU 0x0202 +#define WIMAX_RX_SDU 0x8203 +#define WIMAX_RX_SDU_AGGR 0x8204 +#define WIMAX_TX_SDU_AGGR 0x0205 + +/* Category 3 */ +#define WIMAX_DM_CMD 0x030a +#define WIMAX_DM_RSP 0x830b + +#define WIMAX_CLI_CMD 0x030c +#define WIMAX_CLI_RSP 0x830d + +#define WIMAX_DL_IMAGE 0x0310 +#define WIMAX_DL_IMAGE_STATUS 0x8311 +#define WIMAX_UL_IMAGE 0x0312 +#define WIMAX_UL_IMAGE_RESULT 0x8313 +#define WIMAX_UL_IMAGE_STATUS 0x0314 +#define WIMAX_EVT_MODEM_REPORT 0x8325 + +/* Category 0xF */ +#define WIMAX_FSM_UPDATE 0x8F01 +#define WIMAX_IF_UPDOWN 0x8F02 +#define WIMAX_IF_UP 1 +#define WIMAX_IF_DOWN 2 + +/* WIMAX mode */ +#define W_NULL 0 +#define W_STANDBY 1 +#define W_OOZ 2 +#define W_AWAKE 3 +#define W_IDLE 4 +#define W_SLEEP 5 +#define W_WAIT 6 + +#define W_NET_ENTRY_RNG 0x80 +#define W_NET_ENTRY_SBC 0x81 +#define W_NET_ENTRY_PKM 0x82 +#define W_NET_ENTRY_REG 0x83 +#define W_NET_ENTRY_DSX 0x84 + +#define W_NET_ENTRY_RNG_FAIL 0x1100100 +#define W_NET_ENTRY_SBC_FAIL 0x1100200 +#define W_NET_ENTRY_PKM_FAIL 0x1102000 +#define W_NET_ENTRY_REG_FAIL 0x1103000 +#define W_NET_ENTRY_DSX_FAIL 0x1104000 + +/* Scan Type */ +#define W_SCAN_ALL_CHANNEL 0 +#define W_SCAN_ALL_SUBSCRIPTION 1 +#define W_SCAN_SPECIFIED_SUBSCRIPTION 2 + +/* TLV + * + * [31:31] indicates the type is composite. + * [30:16] is the length of the type. 0 length means length is variable. + * [15:0] is the actual type. + */ +#define TLV_L(x) (((x) >> 16) & 0xff) +#define TLV_T(x) ((x) & 0xff) +#define TLV_COMPOSITE(x) ((x) >> 31) + +/* GENERAL */ +#define T_MAC_ADDRESS (0x00 | (6 << 16)) +#define T_BSID (0x01 | (6 << 16)) +#define T_MSK (0x02 | (64 << 16)) +#define T_RSSI_THRSHLD (0x03 | (1 << 16)) +#define T_FREQUENCY (0x04 | (4 << 16)) +#define T_CONN_CS_TYPE (0x05 | (1 << 16)) +#define T_HOST_IP_VER (0x06 | (1 << 16)) +#define T_STBY_SCAN_INTERVAL (0x07 | (4 << 16)) +#define T_OOZ_SCAN_INTERVAL (0x08 | (4 << 16)) +#define T_IMEI (0x09 | (8 << 16)) +#define T_PID (0x0a | (12 << 16)) +#define T_CAPABILITY (0x1a | (4 << 16)) +#define T_RELEASE_NUMBER (0x1b | (4 << 16)) +#define T_DRIVER_REVISION (0x1c | (4 << 16)) +#define T_FW_REVISION (0x1d | (4 << 16)) +#define T_MAC_HW_REVISION (0x1e | (4 << 16)) +#define T_PHY_HW_REVISION (0x1f | (4 << 16)) + +/* HANDOVER */ +#define T_SCAN_INTERVAL (0x20 | (1 << 16)) +#define T_RSC_RETAIN_TIME (0x2f | (2 << 16)) + +/* SLEEP */ +#define T_TYPE1_ISW (0x40 | (1 << 16)) +#define T_SLP_START_TO (0x4a | (2 << 16)) + +/* IDLE */ +#define T_IDLE_MODE_TO (0x50 | (2 << 16)) +#define T_IDLE_START_TO (0x54 | (2 << 16)) + +/* MONITOR */ +#define T_RSSI (0x60 | (1 << 16)) +#define T_CINR (0x61 | (1 << 16)) +#define T_TX_POWER (0x6a | (1 << 16)) +#define T_CUR_FREQ (0x7f | (4 << 16)) + + +/* WIMAX */ +#define T_MAX_SUBSCRIPTION (0xa1 | (1 << 16)) +#define T_MAX_SF (0xa2 | (1 << 16)) +#define T_PHY_TYPE (0xa3 | (1 << 16)) +#define T_PKM (0xa4 | (1 << 16)) +#define T_AUTH_POLICY (0xa5 | (1 << 16)) +#define T_CS_TYPE (0xa6 | (2 << 16)) +#define T_VENDOR_NAME (0xa7 | (0 << 16)) +#define T_MOD_NAME (0xa8 | (0 << 16)) +#define T_PACKET_FILTER (0xa9 | (1 << 16)) +#define T_NSP_CHANGE_COUNT (0xaa | (4 << 16)) +#define T_RADIO_STATE (0xab | (1 << 16)) +#define T_URI_CONTACT_TYPE (0xac | (1 << 16)) +#define T_URI_TEXT (0xad | (0 << 16)) +#define T_URI (0xae | (0 << 16)) +#define T_ENABLE_AUTH (0xaf | (1 << 16)) +#define T_TIMEOUT (0xb0 | (2 << 16)) +#define T_RUN_MODE (0xb1 | (1 << 16)) +#define T_OMADMT_VER (0xb2 | (4 << 16)) +/* This is measured in seconds from 00:00:00 GMT January 1, 1970. */ +#define T_RTC_TIME (0xb3 | (4 << 16)) +#define T_CERT_STATUS (0xb4 | (4 << 16)) +#define T_CERT_MASK (0xb5 | (4 << 16)) +#define T_EMSK (0xb6 | (64 << 16)) + +/* Subscription TLV */ +#define T_SUBSCRIPTION_LIST (0xd1 | (0 << 16) | (1 << 31)) +#define T_H_NSPID (0xd2 | (3 << 16)) +#define T_NSP_NAME (0xd3 | (0 << 16)) +#define T_SUBSCRIPTION_NAME (0xd4 | (0 << 16)) +#define T_SUBSCRIPTION_FLAG (0xd5 | (2 << 16)) +#define T_V_NSPID (0xd6 | (3 << 16)) +#define T_NAP_ID (0xd7 | (3 << 16)) +#define T_PREAMBLES (0xd8 | (15 << 16)) +#define T_BW (0xd9 | (4 << 16)) +#define T_FFTSIZE (0xda | (4 << 16)) +#define T_DUPLEX_MODE (0xdb | (4 << 16)) + +/* T_CAPABILITY */ +#define T_CAPABILITY_MULTI_CS (1 << 0) +#define T_CAPABILITY_WIMAX (1 << 1) +#define T_CAPABILITY_QOS (1 << 2) +#define T_CAPABILITY_AGGREGATION (1 << 3) + +struct hci_s { + __be16 cmd_evt; + __be16 length; + u8 data[0]; +} __packed; + +#endif /* __GDM72XX_HCI_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/netlink_k.c b/kernel/drivers/staging/gdm72xx/netlink_k.c new file mode 100644 index 000000000..9d78bfcdb --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/netlink_k.c @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include +#include +#include +#include +#include +#include "netlink_k.h" + +#if !defined(NLMSG_HDRLEN) +#define NLMSG_HDRLEN ((int) NLMSG_ALIGN(sizeof(struct nlmsghdr))) +#endif + +#define ND_MAX_GROUP 30 +#define ND_IFINDEX_LEN sizeof(int) +#define ND_NLMSG_SPACE(len) (nlmsg_total_size(len) + ND_IFINDEX_LEN) +#define ND_NLMSG_DATA(nlh) \ + ((void *)((char *)nlmsg_data(nlh) + ND_IFINDEX_LEN)) +#define ND_NLMSG_S_LEN(len) (len+ND_IFINDEX_LEN) +#define ND_NLMSG_R_LEN(nlh) (nlh->nlmsg_len-ND_IFINDEX_LEN) +#define ND_NLMSG_IFIDX(nlh) nlmsg_data(nlh) +#define ND_MAX_MSG_LEN 8096 + +#if defined(DEFINE_MUTEX) +static DEFINE_MUTEX(netlink_mutex); +#else +static struct semaphore netlink_mutex; +#define mutex_lock(x) down(x) +#define mutex_unlock(x) up(x) +#endif + +static void (*rcv_cb)(struct net_device *dev, u16 type, void *msg, int len); + +static void netlink_rcv_cb(struct sk_buff *skb) +{ + struct nlmsghdr *nlh; + struct net_device *dev; + u32 mlen; + void *msg; + int ifindex; + + if (skb->len >= NLMSG_HDRLEN) { + nlh = (struct nlmsghdr *)skb->data; + + if (skb->len < nlh->nlmsg_len || + nlh->nlmsg_len > ND_MAX_MSG_LEN) { + netdev_err(skb->dev, "Invalid length (%d,%d)\n", + skb->len, nlh->nlmsg_len); + return; + } + + memcpy(&ifindex, ND_NLMSG_IFIDX(nlh), ND_IFINDEX_LEN); + msg = ND_NLMSG_DATA(nlh); + mlen = ND_NLMSG_R_LEN(nlh); + + if (rcv_cb) { + dev = dev_get_by_index(&init_net, ifindex); + if (dev) { + rcv_cb(dev, nlh->nlmsg_type, msg, mlen); + dev_put(dev); + } else + netdev_err(skb->dev, + "dev_get_by_index(%d) is not found.\n", + ifindex); + } else { + netdev_err(skb->dev, "Unregistered Callback\n"); + } + } +} + +static void netlink_rcv(struct sk_buff *skb) +{ + mutex_lock(&netlink_mutex); + netlink_rcv_cb(skb); + mutex_unlock(&netlink_mutex); +} + +struct sock *netlink_init(int unit, void (*cb)(struct net_device *dev, u16 type, + void *msg, int len)) +{ + struct sock *sock; + struct netlink_kernel_cfg cfg = { + .input = netlink_rcv, + }; + +#if !defined(DEFINE_MUTEX) + init_MUTEX(&netlink_mutex); +#endif + + sock = netlink_kernel_create(&init_net, unit, &cfg); + + if (sock) + rcv_cb = cb; + + return sock; +} + +void netlink_exit(struct sock *sock) +{ + netlink_kernel_release(sock); +} + +int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len) +{ + static u32 seq; + struct sk_buff *skb = NULL; + struct nlmsghdr *nlh; + int ret = 0; + + if (group > ND_MAX_GROUP) { + pr_err("Group %d is invalied.\n", group); + pr_err("Valid group is 0 ~ %d.\n", ND_MAX_GROUP); + return -EINVAL; + } + + skb = nlmsg_new(len, GFP_ATOMIC); + if (!skb) { + pr_err("netlink_broadcast ret=%d\n", ret); + return -ENOMEM; + } + + seq++; + nlh = nlmsg_put(skb, 0, seq, type, len, 0); + if (!nlh) { + kfree_skb(skb); + return -EMSGSIZE; + } + memcpy(nlmsg_data(nlh), msg, len); + + NETLINK_CB(skb).portid = 0; + NETLINK_CB(skb).dst_group = 0; + + ret = netlink_broadcast(sock, skb, 0, group+1, GFP_ATOMIC); + + if (!ret) + return len; + if (ret != -ESRCH) { + pr_err("netlink_broadcast g=%d, t=%d, l=%d, r=%d\n", + group, type, len, ret); + } + ret = 0; + return ret; +} diff --git a/kernel/drivers/staging/gdm72xx/netlink_k.h b/kernel/drivers/staging/gdm72xx/netlink_k.h new file mode 100644 index 000000000..1fe7198d5 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/netlink_k.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_NETLINK_K_H__ +#define __GDM72XX_NETLINK_K_H__ + +#include +#include + +struct sock *netlink_init(int unit, void (*cb)(struct net_device *dev, u16 type, + void *msg, int len)); +void netlink_exit(struct sock *sock); +int netlink_send(struct sock *sock, int group, u16 type, void *msg, int len); + +#endif /* __GDM72XX_NETLINK_K_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/sdio_boot.c b/kernel/drivers/staging/gdm72xx/sdio_boot.c new file mode 100644 index 000000000..2c02842ac --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/sdio_boot.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "gdm_sdio.h" +#include "sdio_boot.h" + +#define TYPE_A_HEADER_SIZE 4 +#define TYPE_A_LOOKAHEAD_SIZE 16 +#define YMEM0_SIZE 0x8000 /* 32kbytes */ +#define DOWNLOAD_SIZE (YMEM0_SIZE - TYPE_A_HEADER_SIZE) + +#define FW_DIR "gdm72xx/" +#define FW_KRN "gdmskrn.bin" +#define FW_RFS "gdmsrfs.bin" + +static u8 *tx_buf; + +static int ack_ready(struct sdio_func *func) +{ + unsigned long wait = jiffies + HZ; + u8 val; + int ret; + + while (time_before(jiffies, wait)) { + val = sdio_readb(func, 0x13, &ret); + if (val & 0x01) + return 1; + schedule(); + } + + return 0; +} + +static int download_image(struct sdio_func *func, const char *img_name) +{ + int ret = 0, len, pno; + u8 *buf = tx_buf; + loff_t pos = 0; + int img_len; + const struct firmware *firm; + + ret = request_firmware(&firm, img_name, &func->dev); + if (ret < 0) { + dev_err(&func->dev, + "requesting firmware %s failed with error %d\n", + img_name, ret); + return ret; + } + + buf = kmalloc(DOWNLOAD_SIZE + TYPE_A_HEADER_SIZE, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + img_len = firm->size; + + if (img_len <= 0) { + ret = -1; + goto out; + } + + pno = 0; + while (img_len > 0) { + if (img_len > DOWNLOAD_SIZE) { + len = DOWNLOAD_SIZE; + buf[3] = 0; + } else { + len = img_len; /* the last packet */ + buf[3] = 2; + } + + buf[0] = len & 0xff; + buf[1] = (len >> 8) & 0xff; + buf[2] = (len >> 16) & 0xff; + + memcpy(buf+TYPE_A_HEADER_SIZE, firm->data + pos, len); + ret = sdio_memcpy_toio(func, 0, buf, len + TYPE_A_HEADER_SIZE); + if (ret < 0) { + dev_err(&func->dev, + "send image error: packet number = %d ret = %d\n", + pno, ret); + goto out; + } + + if (buf[3] == 2) /* The last packet */ + break; + if (!ack_ready(func)) { + ret = -EIO; + dev_err(&func->dev, "Ack is not ready.\n"); + goto out; + } + ret = sdio_memcpy_fromio(func, buf, 0, TYPE_A_LOOKAHEAD_SIZE); + if (ret < 0) { + dev_err(&func->dev, + "receive ack error: packet number = %d ret = %d\n", + pno, ret); + goto out; + } + sdio_writeb(func, 0x01, 0x13, &ret); + sdio_writeb(func, 0x00, 0x10, &ret); /* PCRRT */ + + img_len -= DOWNLOAD_SIZE; + pos += DOWNLOAD_SIZE; + pno++; + } + +out: + kfree(buf); + return ret; +} + +int sdio_boot(struct sdio_func *func) +{ + int ret; + const char *krn_name = FW_DIR FW_KRN; + const char *rfs_name = FW_DIR FW_RFS; + + tx_buf = kmalloc(YMEM0_SIZE, GFP_KERNEL); + if (tx_buf == NULL) + return -ENOMEM; + + ret = download_image(func, krn_name); + if (ret) + goto restore_fs; + dev_info(&func->dev, "GCT: Kernel download success.\n"); + + ret = download_image(func, rfs_name); + if (ret) + goto restore_fs; + dev_info(&func->dev, "GCT: Filesystem download success.\n"); + +restore_fs: + kfree(tx_buf); + return ret; +} diff --git a/kernel/drivers/staging/gdm72xx/sdio_boot.h b/kernel/drivers/staging/gdm72xx/sdio_boot.h new file mode 100644 index 000000000..e0800c6fe --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/sdio_boot.h @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_SDIO_BOOT_H__ +#define __GDM72XX_SDIO_BOOT_H__ + +struct sdio_func; + +int sdio_boot(struct sdio_func *func); + +#endif /* __GDM72XX_SDIO_BOOT_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/usb_boot.c b/kernel/drivers/staging/gdm72xx/usb_boot.c new file mode 100644 index 000000000..3ccc44773 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/usb_boot.c @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include "gdm_usb.h" +#include "usb_boot.h" + +#define DN_KERNEL_MAGIC_NUMBER 0x10760001 +#define DN_ROOTFS_MAGIC_NUMBER 0x10760002 + +#define DOWNLOAD_SIZE 1024 + +#define MAX_IMG_CNT 16 +#define FW_DIR "gdm72xx/" +#define FW_UIMG "gdmuimg.bin" +#define FW_KERN "zImage" +#define FW_FS "ramdisk.jffs2" + +struct dn_header { + __be32 magic_num; + __be32 file_size; +}; + +struct img_header { + u32 magic_code; + u32 count; + u32 len; + u32 offset[MAX_IMG_CNT]; + char hostname[32]; + char date[32]; +}; + +struct fw_info { + u32 id; + u32 len; + u32 kernel_len; + u32 rootfs_len; + u32 kernel_offset; + u32 rootfs_offset; + u32 fw_ver; + u32 mac_ver; + char hostname[32]; + char userid[16]; + char date[32]; + char user_desc[128]; +}; + +static void array_le32_to_cpu(u32 *arr, int num) +{ + int i; + + for (i = 0; i < num; i++, arr++) + le32_to_cpus(arr); +} + +static u8 *tx_buf; + +static int gdm_wibro_send(struct usb_device *usbdev, void *data, int len) +{ + int ret; + int actual; + + ret = usb_bulk_msg(usbdev, usb_sndbulkpipe(usbdev, 1), data, len, + &actual, 1000); + + if (ret < 0) { + dev_err(&usbdev->dev, "Error : usb_bulk_msg ( result = %d )\n", + ret); + return ret; + } + return 0; +} + +static int gdm_wibro_recv(struct usb_device *usbdev, void *data, int len) +{ + int ret; + int actual; + + ret = usb_bulk_msg(usbdev, usb_rcvbulkpipe(usbdev, 2), data, len, + &actual, 5000); + + if (ret < 0) { + dev_err(&usbdev->dev, + "Error : usb_bulk_msg(recv) ( result = %d )\n", ret); + return ret; + } + return 0; +} + +static int download_image(struct usb_device *usbdev, + const struct firmware *firm, + loff_t pos, u32 img_len, u32 magic_num) +{ + struct dn_header h; + int ret = 0; + u32 size; + + size = ALIGN(img_len, DOWNLOAD_SIZE); + h.magic_num = cpu_to_be32(magic_num); + h.file_size = cpu_to_be32(size); + + ret = gdm_wibro_send(usbdev, &h, sizeof(h)); + if (ret < 0) + return ret; + + while (img_len > 0) { + if (img_len > DOWNLOAD_SIZE) + size = DOWNLOAD_SIZE; + else + size = img_len; /* the last chunk of data */ + + memcpy(tx_buf, firm->data + pos, size); + ret = gdm_wibro_send(usbdev, tx_buf, size); + + if (ret < 0) + return ret; + + img_len -= size; + pos += size; + } + + return ret; +} + +int usb_boot(struct usb_device *usbdev, u16 pid) +{ + int i, ret = 0; + struct img_header hdr; + struct fw_info fw_info; + loff_t pos = 0; + char *img_name = FW_DIR FW_UIMG; + const struct firmware *firm; + + ret = request_firmware(&firm, img_name, &usbdev->dev); + if (ret < 0) { + dev_err(&usbdev->dev, + "requesting firmware %s failed with error %d\n", + img_name, ret); + return ret; + } + + tx_buf = kmalloc(DOWNLOAD_SIZE, GFP_KERNEL); + if (tx_buf == NULL) + return -ENOMEM; + + if (firm->size < sizeof(hdr)) { + dev_err(&usbdev->dev, "Cannot read the image info.\n"); + ret = -EIO; + goto out; + } + memcpy(&hdr, firm->data, sizeof(hdr)); + + array_le32_to_cpu((u32 *)&hdr, 19); + + if (hdr.count > MAX_IMG_CNT) { + dev_err(&usbdev->dev, "Too many images. %d\n", hdr.count); + ret = -EINVAL; + goto out; + } + + for (i = 0; i < hdr.count; i++) { + if (hdr.offset[i] > hdr.len) { + dev_err(&usbdev->dev, + "Invalid offset. Entry = %d Offset = 0x%08x Image length = 0x%08x\n", + i, hdr.offset[i], hdr.len); + ret = -EINVAL; + goto out; + } + + pos = hdr.offset[i]; + if (firm->size < sizeof(fw_info) + pos) { + dev_err(&usbdev->dev, "Cannot read the FW info.\n"); + ret = -EIO; + goto out; + } + memcpy(&fw_info, firm->data + pos, sizeof(fw_info)); + + array_le32_to_cpu((u32 *)&fw_info, 8); + + if ((fw_info.id & 0xffff) != pid) + continue; + + pos = hdr.offset[i] + fw_info.kernel_offset; + if (firm->size < fw_info.kernel_len + pos) { + dev_err(&usbdev->dev, "Kernel FW is too small.\n"); + goto out; + } + + ret = download_image(usbdev, firm, pos, fw_info.kernel_len, + DN_KERNEL_MAGIC_NUMBER); + if (ret < 0) + goto out; + dev_info(&usbdev->dev, "GCT: Kernel download success.\n"); + + pos = hdr.offset[i] + fw_info.rootfs_offset; + if (firm->size < fw_info.rootfs_len + pos) { + dev_err(&usbdev->dev, "Filesystem FW is too small.\n"); + goto out; + } + ret = download_image(usbdev, firm, pos, fw_info.rootfs_len, + DN_ROOTFS_MAGIC_NUMBER); + if (ret < 0) + goto out; + dev_info(&usbdev->dev, "GCT: Filesystem download success.\n"); + + break; + } + + if (i == hdr.count) { + dev_err(&usbdev->dev, "Firmware for gsk%x is not installed.\n", + pid); + ret = -EINVAL; + } +out: + release_firmware(firm); + kfree(tx_buf); + return ret; +} + +/*#define GDM7205_PADDING 256 */ +#define DOWNLOAD_CHUCK 2048 +#define KERNEL_TYPE_STRING "linux" +#define FS_TYPE_STRING "rootfs" + +static int em_wait_ack(struct usb_device *usbdev, int send_zlp) +{ + int ack; + int ret = -1; + + if (send_zlp) { + /*Send ZLP*/ + ret = gdm_wibro_send(usbdev, NULL, 0); + if (ret < 0) + goto out; + } + + /*Wait for ACK*/ + ret = gdm_wibro_recv(usbdev, &ack, sizeof(ack)); + if (ret < 0) + goto out; +out: + return ret; +} + +static int em_download_image(struct usb_device *usbdev, const char *img_name, + char *type_string) +{ + char *buf = NULL; + loff_t pos = 0; + int ret = 0; + int len; + int img_len; + const struct firmware *firm; + #if defined(GDM7205_PADDING) + const int pad_size = GDM7205_PADDING; + #else + const int pad_size = 0; + #endif + + ret = request_firmware(&firm, img_name, &usbdev->dev); + if (ret < 0) { + dev_err(&usbdev->dev, + "requesting firmware %s failed with error %d\n", + img_name, ret); + return ret; + } + + buf = kmalloc(DOWNLOAD_CHUCK + pad_size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + strcpy(buf+pad_size, type_string); + ret = gdm_wibro_send(usbdev, buf, strlen(type_string)+pad_size); + if (ret < 0) + goto out; + + img_len = firm->size; + + if (img_len <= 0) { + ret = -1; + goto out; + } + + while (img_len > 0) { + if (img_len > DOWNLOAD_CHUCK) + len = DOWNLOAD_CHUCK; + else + len = img_len; /* the last chunk of data */ + + memcpy(buf+pad_size, firm->data + pos, len); + ret = gdm_wibro_send(usbdev, buf, len+pad_size); + + if (ret < 0) + goto out; + + img_len -= DOWNLOAD_CHUCK; + pos += DOWNLOAD_CHUCK; + + ret = em_wait_ack(usbdev, ((len+pad_size) % 512 == 0)); + if (ret < 0) + goto out; + } + + ret = em_wait_ack(usbdev, 1); + if (ret < 0) + goto out; + +out: + release_firmware(firm); + kfree(buf); + + return ret; +} + +static int em_fw_reset(struct usb_device *usbdev) +{ + /*Send ZLP*/ + return gdm_wibro_send(usbdev, NULL, 0); +} + +int usb_emergency(struct usb_device *usbdev) +{ + int ret; + const char *kern_name = FW_DIR FW_KERN; + const char *fs_name = FW_DIR FW_FS; + + ret = em_download_image(usbdev, kern_name, KERNEL_TYPE_STRING); + if (ret < 0) + return ret; + dev_err(&usbdev->dev, "GCT Emergency: Kernel download success.\n"); + + ret = em_download_image(usbdev, fs_name, FS_TYPE_STRING); + if (ret < 0) + return ret; + dev_info(&usbdev->dev, "GCT Emergency: Filesystem download success.\n"); + + ret = em_fw_reset(usbdev); + + return ret; +} diff --git a/kernel/drivers/staging/gdm72xx/usb_boot.h b/kernel/drivers/staging/gdm72xx/usb_boot.h new file mode 100644 index 000000000..5bf719037 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/usb_boot.h @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_USB_BOOT_H__ +#define __GDM72XX_USB_BOOT_H__ + +struct usb_device; + +int usb_boot(struct usb_device *usbdev, u16 pid); +int usb_emergency(struct usb_device *usbdev); + +#endif /* __GDM72XX_USB_BOOT_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/usb_ids.h b/kernel/drivers/staging/gdm72xx/usb_ids.h new file mode 100644 index 000000000..8ce544de7 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/usb_ids.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_USB_IDS_H__ +#define __GDM72XX_USB_IDS_H__ + +/*You can replace vendor-ID as yours.*/ +#define GCT_VID 0x1076 + +/*You can replace product-ID as yours.*/ +#define GCT_PID1 0x7e00 +#define GCT_PID2 0x7f00 + +#define USB_DEVICE_ID_MATCH_DEVICE_INTERFACE \ + (USB_DEVICE_ID_MATCH_DEVICE | USB_DEVICE_ID_MATCH_INT_CLASS) + +#define USB_DEVICE_INTF(vend, prod, intf) \ + .match_flags = USB_DEVICE_ID_MATCH_DEVICE_INTERFACE, \ + .idVendor = (vend), .idProduct = (prod), .bInterfaceClass = (intf) + +#define EMERGENCY_PID 0x720f +#define BL_PID_MASK 0xffc0 + +#define USB_DEVICE_BOOTLOADER(vid, pid) \ + {USB_DEVICE((vid), ((pid)&BL_PID_MASK)|B_DOWNLOAD)}, \ + {USB_DEVICE((vid), ((pid)&BL_PID_MASK)|B_DOWNLOAD|B_DIFF_DL_DRV)} + +#define USB_DEVICE_CDC_DATA(vid, pid) \ + {USB_DEVICE_INTF((vid), (pid), USB_CLASS_CDC_DATA)} + +static const struct usb_device_id id_table[] = { + USB_DEVICE_BOOTLOADER(GCT_VID, GCT_PID1), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x1), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x2), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x3), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x4), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x5), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x6), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x7), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x8), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0x9), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xa), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xb), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xc), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xd), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xe), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID1+0xf), + + USB_DEVICE_BOOTLOADER(GCT_VID, GCT_PID2), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x1), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x2), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x3), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x4), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x5), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x6), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x7), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x8), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0x9), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xa), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xb), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xc), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xd), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xe), + USB_DEVICE_CDC_DATA(GCT_VID, GCT_PID2+0xf), + + {USB_DEVICE(GCT_VID, EMERGENCY_PID)}, + { } +}; + +#endif /* __GDM72XX_USB_IDS_H__ */ diff --git a/kernel/drivers/staging/gdm72xx/wm_ioctl.h b/kernel/drivers/staging/gdm72xx/wm_ioctl.h new file mode 100644 index 000000000..ed8f649c0 --- /dev/null +++ b/kernel/drivers/staging/gdm72xx/wm_ioctl.h @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2012 GCT Semiconductor, Inc. All rights reserved. + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + */ + +#ifndef __GDM72XX_WM_IOCTL_H__ +#define __GDM72XX_WM_IOCTL_H__ + +#if !defined(__KERNEL__) +#include +#endif + +#define NETLINK_WIMAX 31 + +#define SIOCWMIOCTL SIOCDEVPRIVATE + +#define SIOCG_DATA 0x8D10 +#define SIOCS_DATA 0x8D11 + +enum { + SIOC_DATA_FSM, + SIOC_DATA_NETLIST, + SIOC_DATA_CONNNSP, + SIOC_DATA_CONNCOMP, + SIOC_DATA_PROFILEID, + + SIOC_DATA_END +}; + +#define SIOC_DATA_MAX 16 + +/* FSM */ +enum { + M_INIT = 0, + M_OPEN_OFF, + M_OPEN_ON, + M_SCAN, + M_CONNECTING, + M_CONNECTED, + M_FSM_END, + + C_INIT = 0, + C_CONNSTART, + C_ASSOCSTART, + C_RNG, + C_SBC, + C_AUTH, + C_REG, + C_DSX, + C_ASSOCCOMPLETE, + C_CONNCOMPLETE, + C_FSM_END, + + D_INIT = 0, + D_READY, + D_LISTEN, + D_IPACQUISITION, + + END_FSM +}; + +struct fsm_s { + int m_status; /*main status*/ + int c_status; /*connection status*/ + int d_status; /*oma-dm status*/ +}; + +struct data_s { + int size; + void *buf; +}; + +struct wm_req_s { + union { + char ifrn_name[IFNAMSIZ]; + } ifr_ifrn; + unsigned short cmd; + unsigned short data_id; + struct data_s data; + +/* NOTE: sizeof(struct wm_req_s) must be less than sizeof(struct ifreq). */ +}; + +#ifndef ifr_name +#define ifr_name ifr_ifrn.ifrn_name +#endif + +#endif /* __GDM72XX_WM_IOCTL_H__ */ -- cgit 1.2.3-korg