text: /*
   * 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 B2H(x)		__be16_to_cpu(x)
  
  #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);
  
  	entry = kmalloc(sizeof(struct qos_entry_s), GFP_ATOMIC);
  	return entry;
  }
  
  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 = 0;
  	}
  
  	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 = 0;
  	}
  
  	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 u32 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 u32 get_qos_index(struct nic *nic, u8 *iph, u8 *tcpudph)
  {
  	u32	IP_Ver, Header_Len, i;
  	struct qos_cb_s *qcb = &nic->qos;
  
  	if (iph == NULL || tcpudph == NULL)
  		return -1;
  
  	IP_Ver = (iph[0]>>4)&0xf;
  	Header_Len = iph[0]&0xf;
  
  	if (IP_Ver == 4) {
  		for (i = 0; i < QOS_MAX; i++) {
  			if (qcb->csr[i].enabled) {
  				if (qcb->csr[i].classifier_rule_en) {
  					if (chk_ipv4_rule(&qcb->csr[i], iph,
  					tcpudph) == 0)
  						return i;
  				}
  			}
  		}
  	}
  
  	return -1;
  }
  
  static u32 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) {
  			if (qcb->csr[i].qos_buf_count < qcb->qos_limit_size) {
  				if (!list_empty(&qcb->qos_list[i])) {
  					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);
  				}
  			}
  		}
  	}
  
  	return 0;
  }
  
  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);
  		free_qos_entry(entry);
  		gdm_wimax_send_tx(entry->skb, entry->dev);
  	}
  }
  
  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 (B2H(ethh->h_proto) == 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 u32 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 == 0) {
  				qcb->csr[i].enabled = 1;
  				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;
  	u32 i, SFID, index, pos;
  	u8 subCmdEvt;
  	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;
  
  	subCmdEvt = (u8)buf[4];
  
  	if (subCmdEvt == 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;
  	}
  
  	/* subCmdEvt == QOS_ADD || subCmdEvt == 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 (subCmdEvt == 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 (subCmdEvt == 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 = 0;
  		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);
  	}
  }
  