| /* |
| * 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 <linux/etherdevice.h> |
| #include <asm/byteorder.h> |
| |
| #include <linux/ip.h> |
| #include <linux/tcp.h> |
| #include <linux/if_ether.h> |
| |
| #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 = 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 || !tcpudph) |
| 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); |
| } |
| } |