android_kernel_samsung_on5x.../drivers/net/wireless/scsc/hip4.c
2018-06-19 23:16:04 +02:00

1396 lines
45 KiB
C
Executable file

/******************************************************************************
*
* Copyright (c) 2014 - 2016 Samsung Electronics Co., Ltd. All rights reserved
*
*****************************************************************************/
#include <linux/slab.h>
#include <linux/interrupt.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <scsc/scsc_mx.h>
#include <scsc/scsc_mifram.h>
#include <linux/ktime.h>
#include <linux/kthread.h>
#include <scsc/scsc_logring.h>
#include "hip4.h"
#include "mbulk.h"
#include "dev.h"
#ifdef CONFIG_SCSC_WLAN_DEBUG
#include "hip4_sampler.h"
#endif
#include "debug.h"
#ifdef CONFIG_SCSC_PLATFORM
uint scoreboard_ver = 1;
#else
uint scoreboard_ver;
#endif
static ktime_t intr_received;
static ktime_t bh_init;
static ktime_t bh_end;
static ktime_t wdt;
static ktime_t send;
static ktime_t closing;
module_param(scoreboard_ver, uint, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(scoreboard_ver, "Scoreboard Indexes - 0-FPGA, 1-ASIC (default = 0)");
enum rw {
widx,
ridx,
};
/* Q mapping V3 - V4 */
/*offset of F/W owned indices */
#define FW_OWN_OFS (64)
/**
* HIP queue indices layout in the scoreboard (SC-505612-DD). v3
*
* 3 2 1 0
* +-----------------------------------+
* +0 | Q3R | Q2R | Q1W | Q0W | Owned by the host
* +-----------------------------------+
* +4 | | | Q5W | Q4R | Owned by the host
* +-----------------------------------+
*
* +-----------------------------------+
* +64 | Q3W | Q2W | Q1R | Q0R | Owned by the F/W
* +-----------------------------------+
* +68 | | | Q5R | Q4W | Owned by the F/W
* +-----------------------------------+
*
* The queue indcies which owned by the host are only writable by the host.
* F/W can only read them. And vice versa.
*/
static int q_idx_layout[6][2] = {
{ 0, FW_OWN_OFS + 0}, /* mif_q_fh_ctl : 0 */
{ 1, FW_OWN_OFS + 1}, /* mif_q_fh_dat : 1 */
{ FW_OWN_OFS + 2, 2}, /* mif_q_fh_rfb : 2 */
{ FW_OWN_OFS + 3, 3}, /* mif_q_th_ctl : 3 */
{ FW_OWN_OFS + 4, 4}, /* mif_q_th_dat : 4 */
{ 5, FW_OWN_OFS + 5} /* mif_q_th_rfb : 5 */
};
/*offset of F/W owned VIF Status */
#define FW_OWN_VIF (96)
/**
* HIP Pause state VIF. v4. 2 bits per PEER
*
* +-----------------------------------+
* +96 | VIF[0] Peers [15-1] | Owned by the F/W
* +-----------------------------------+
* +100 | VIF[0] Peers [31-16] | Owned by the F/W
* +-----------------------------------+
* +104 | VIF[1] Peers [15-1] | Owned by the F/W
* +-----------------------------------+
* +108 | VIF[1] Peers [31-16] | Owned by the F/W
* +-----------------------------------+
* +112 | VIF[2] Peers [15-1] | Owned by the F/W
* +-----------------------------------+
* +116 | VIF[2] Peers [31-16] | Owned by the F/W
* +-----------------------------------+
* +120 | VIF[3] Peers [15-1] | Owned by the F/W
* +-----------------------------------+
* +124 | VIF[3] Peers [31-16] | Owned by the F/W
* +-----------------------------------+
*
*/
#ifdef CONFIG_SCSC_WLAN_DEBUG
/* MAX_HISTORY_RECORDS should be power of two */
#define MAX_HISTORY_RECORDS 32
#define FH 0
#define TH 1
struct hip4_history {
bool dir;
u32 signal;
u32 cnt;
ktime_t last_time;
} hip4_signal_history[MAX_HISTORY_RECORDS];
static u32 history_record;
/* This function should be called from atomic context */
static void hip4_history_record_add(bool dir, u32 signal_id)
{
struct hip4_history record;
record = hip4_signal_history[history_record];
if (record.signal == signal_id && record.dir == dir) {
/* If last signal and direction is the same, increment counter */
record.last_time = ktime_get();
record.cnt += 1;
hip4_signal_history[history_record] = record;
return;
}
history_record = (history_record + 1) & (MAX_HISTORY_RECORDS - 1);
record = hip4_signal_history[history_record];
record.dir = dir;
record.signal = signal_id;
record.cnt = 1;
record.last_time = ktime_get();
hip4_signal_history[history_record] = record;
}
static void hip4_history_record_print(void)
{
struct hip4_history record;
u32 i, pos;
ktime_t old;
old = ktime_set(0, 0);
/* Start with the Next record to print history in order */
pos = (history_record + 1) & (MAX_HISTORY_RECORDS - 1);
SLSI_ERR_NODEV("dir\t signal\t cnt\t last_time(ns) \t gap(ns)\n");
SLSI_ERR_NODEV("-----------------------------------------------------------------------------\n");
for (i = 0; i < MAX_HISTORY_RECORDS; i++) {
record = hip4_signal_history[pos];
/*next pos*/
if (record.cnt) {
SLSI_ERR_NODEV("%s\t 0x%04x\t %d\t %lld \t%lld\n", record.dir ? "<--TH" : "FH-->",
record.signal, record.cnt, ktime_to_ns(record.last_time), ktime_to_ns(ktime_sub(record.last_time, old)));
}
old = record.last_time;
pos = (pos + 1) & (MAX_HISTORY_RECORDS - 1);
}
}
#endif
/* Update scoreboard index */
/* Function can be called from BH context */
static void hip4_update_index(struct slsi_hip4 *hip, u32 q, enum rw r_w, u8 value)
{
struct hip4_priv *hip_priv = hip->hip_priv;
write_lock_bh(&hip_priv->rw_scoreboard);
if (hip->hip_priv->version == 3 || hip->hip_priv->version == 4) {
*((u8 *)(hip->hip_priv->scbrd_base + q_idx_layout[q][r_w])) = value;
} else {
SLSI_ERR_NODEV("Incorrect version\n");
goto error;
}
/* Memory barrier when updating shared mailbox/memory */
smp_wmb();
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, q, r_w, value, 0);
#endif
error:
write_unlock_bh(&hip_priv->rw_scoreboard);
}
/* Read scoreboard index */
/* Function can be called from BH context */
static u8 hip4_read_index(struct slsi_hip4 *hip, u32 q, enum rw r_w)
{
struct hip4_priv *hip_priv = hip->hip_priv;
u32 value = 0;
read_lock_bh(&hip_priv->rw_scoreboard);
if (hip->hip_priv->version == 3 || hip->hip_priv->version == 4) {
value = *((u8 *)(hip->hip_priv->scbrd_base + q_idx_layout[q][r_w]));
} else {
SLSI_ERR_NODEV("Incorrect version\n");
goto error;
}
/* Memory barrier when reading shared mailbox/memory */
smp_rmb();
error:
read_unlock_bh(&hip_priv->rw_scoreboard);
return value;
}
#if 0
/* Function can be called from BH context */
static u8 hip4_read_vif_peer_status(struct slsi_hip4 *hip, u16 vif, u16 peer)
{
struct hip4_priv *hip_priv = hip->hip_priv;
u32 value;
read_lock_bh(&hip_priv->rw_scoreboard);
if (hip->hip_priv->version == 3 || hip->hip_priv->version == 4) {
value = *((u8 *)(hip->hip_priv->scbrd_base + ((vif*8) + FW_OWN_VIF)));
value = (value & (0x3 << (peer % 4)*0x2)) >> (peer % 4)*0x2;
} else {
SLSI_ERR_NODEV("Incorrect version\n");
}
/* Memory barrier when reading shared mailbox/memory */
smp_rmb();
read_unlock_bh(&hip_priv->rw_scoreboard);
return value;
}
#endif
static void hip4_dump_dbg(struct slsi_hip4 *hip, struct mbulk *m, struct sk_buff *skb, struct scsc_service *service)
{
unsigned int i = 0;
scsc_mifram_ref ref;
SLSI_ERR_NODEV("rx_intr_tohost 0x%x\n", hip->hip_priv->rx_intr_tohost);
SLSI_ERR_NODEV("rx_intr_fromhost 0x%x\n", hip->hip_priv->rx_intr_fromhost);
/* Print scoreboard */
for (i = 0; i < 6; i++) {
SLSI_ERR_NODEV("Q%dW 0x%x\n", i, hip4_read_index(hip, i, widx));
SLSI_ERR_NODEV("Q%dR 0x%x\n", i, hip4_read_index(hip, i, ridx));
}
if (service)
scsc_mx_service_mif_dump_registers(service);
if (m && service) {
if (scsc_mx_service_mif_ptr_to_addr(service, m, &ref))
return;
SLSI_ERR_NODEV("m: %p 0x%x\n", m, ref);
print_hex_dump(KERN_ERR, "mbulk ", DUMP_PREFIX_NONE, 16, 1, m, sizeof(struct mbulk), 0);
}
if (skb)
print_hex_dump(KERN_ERR, "skb ", DUMP_PREFIX_NONE, 16, 1, skb->data, skb->len & 0xff, 0);
SLSI_ERR_NODEV("time: wdt %lld\n", ktime_to_ns(wdt));
SLSI_ERR_NODEV("time: send %lld\n", ktime_to_ns(send));
SLSI_ERR_NODEV("time: intr %lld\n", ktime_to_ns(intr_received));
SLSI_ERR_NODEV("time: bh_init %lld\n", ktime_to_ns(bh_init));
SLSI_ERR_NODEV("time: bh_end %lld\n", ktime_to_ns(bh_end));
SLSI_ERR_NODEV("time: closing %lld\n", ktime_to_ns(closing));
#ifdef CONFIG_SCSC_WLAN_DEBUG
/* Discard noise if it is a mbulk/skb issue */
if (!skb && !m)
hip4_history_record_print();
#endif
}
#ifdef CONFIG_SCSC_WLAN_DEBUG
static u32 bytes_accum;
static ktime_t to;
#if (LINUX_VERSION_CODE < KERNEL_VERSION(3, 11, 0))
static inline ktime_t ktime_add_ms(const ktime_t kt, const u64 msec)
{
return ktime_add_ns(kt, msec * NSEC_PER_MSEC);
}
#endif
#endif
/* Transform skb to mbulk (fapi_signal + payload) */
static struct mbulk *hip4_skb_to_mbulk(struct hip4_priv *hip, struct sk_buff *skb, bool ctrl_packet)
{
struct mbulk *m = NULL;
void *sig = NULL, *b_data = NULL;
size_t payload = 0;
u8 pool_id = ctrl_packet ? MBULK_CLASS_FROM_HOST_CTL : MBULK_CLASS_FROM_HOST_DAT;
u8 headroom = 0, tailroom = 0;
enum mbulk_class clas = ctrl_packet ? MBULK_CLASS_FROM_HOST_CTL : MBULK_CLASS_FROM_HOST_DAT;
struct slsi_skb_cb *cb = slsi_skb_cb_get(skb);
payload = skb->len - cb->sig_length;
/* Get headroom/tailroom */
headroom = hip->unidat_req_headroom;
tailroom = hip->unidat_req_tailroom;
/* Allocate mbulk */
if (payload) {
/* If signal include payload, add headroom and tailroom */
m = mbulk_with_signal_alloc_by_pool(pool_id, cb->colour, clas, cb->sig_length + 4,
payload + headroom + tailroom);
if (!m)
return NULL;
if (!mbulk_reserve_head(m, headroom))
return NULL;
} else {
/* If it is only a signal do not add headroom */
m = mbulk_with_signal_alloc_by_pool(pool_id, cb->colour, clas, cb->sig_length + 4, 0);
if (!m)
return NULL;
}
/* Get signal handler */
sig = mbulk_get_signal(m);
if (!sig) {
mbulk_free_virt_host(m);
return NULL;
}
/* Copy signal */
/* 4Bytes offset is required for FW fapi header */
memcpy(sig + 4, skb->data, cb->sig_length);
/* Copy payload */
/* If the signal has payload memcpy the data */
if (payload > 0) {
/* Get head pointer */
b_data = mbulk_dat_rw(m);
if (!b_data) {
mbulk_free_virt_host(m);
return NULL;
}
/* Copy payload skipping the signal data */
memcpy(b_data, skb->data + cb->sig_length, payload);
mbulk_append_tail(m, payload);
}
m->flag |= MBULK_F_OBOUND;
#ifdef CONFIG_SCSC_WLAN_DEBUG
if (ktime_to_ms(to) == 0) {
SLSI_DBG1_NODEV(SLSI_HIP, "init timer");
to = ktime_add_ms(ktime_get(), 1000);
}
bytes_accum += payload;
if (ktime_compare(ktime_get(), to) > 0) {
bytes_accum = 8 * bytes_accum;
if (bytes_accum < 1000) {
SCSC_HIP4_SAMPLER_THROUG(hip->minor, (bytes_accum & 0xff00) >> 8, bytes_accum & 0xff);
} else if ((bytes_accum > 1000) && (bytes_accum < (1000 * 1000))) {
bytes_accum = bytes_accum / 1000;
SCSC_HIP4_SAMPLER_THROUG_K(hip->minor, (bytes_accum & 0xff00) >> 8, bytes_accum & 0xff);
} else {
bytes_accum = bytes_accum / (1000 * 1000);
SCSC_HIP4_SAMPLER_THROUG_M(hip->minor, (bytes_accum & 0xff00) >> 8, bytes_accum & 0xff);
}
to = ktime_add_ms(ktime_get(), 1000);
bytes_accum = 0;
}
#endif
return m;
}
/* Transform mbulk to skb (fapi_signal + payload) */
static struct sk_buff *hip4_mbulk_to_skb(struct scsc_service *service, struct mbulk *m, scsc_mifram_ref *to_free, bool cfm)
{
struct slsi_skb_cb *cb;
struct mbulk *next_mbulk[MBULK_MAX_CHAIN];
struct sk_buff *skb = NULL;
scsc_mifram_ref ref;
scsc_mifram_ref m_chain_next;
u8 free = 0;
u8 i = 0, j = 0;
u8 *p;
size_t bytes_to_alloc = 0;
/* Get the mif ref pointer, check for incorrect mbulk */
if (scsc_mx_service_mif_ptr_to_addr(service, m, &ref))
return NULL;
/* Track mbulk that should be freed */
to_free[free++] = ref;
bytes_to_alloc += m->sig_bufsz - 4;
bytes_to_alloc += m->len;
/* Detect Chained mbulk to start building the chain */
if ((MBULK_SEG_IS_CHAIN_HEAD(m)) && (MBULK_SEG_IS_CHAINED(m))) {
m_chain_next = mbulk_chain_next(m);
if (!m_chain_next) {
SLSI_ERR_NODEV("Mbulk is set MBULK_F_CHAIN_HEAD and MBULK_F_CHAIN but m_chain_next is NULL\n");
goto cont;
}
while (1) {
/* increase number mbulks in chain */
i++;
/* Get next_mbulk kernel address space pointer */
next_mbulk[i-1] = scsc_mx_service_mif_addr_to_ptr(service, m_chain_next);
if (!next_mbulk[i-1]) {
SLSI_ERR_NODEV("First Mbulk is set as MBULK_F_CHAIN but next_mbulk is NULL\n");
return NULL;
}
/* Track mbulk to be freed */
to_free[free++] = m_chain_next;
bytes_to_alloc += next_mbulk[i-1]->len;
if (MBULK_SEG_IS_CHAINED(next_mbulk[i-1])) {
/* continue traversing the chain */
m_chain_next = mbulk_chain_next(next_mbulk[i-1]);
if (!m_chain_next)
break;
if (i >= MBULK_MAX_CHAIN) {
SLSI_ERR_NODEV("Max number of chained MBULK reached\n");
return NULL;
}
} else {
break;
}
}
}
cont:
skb = alloc_skb(bytes_to_alloc, GFP_ATOMIC);
if (!skb) {
SLSI_ERR_NODEV("Error allocating skb\n");
return NULL;
}
cb = slsi_skb_cb_init(skb);
cb->sig_length = m->sig_bufsz - 4;
/* fapi_data_append adds to the data_length */
cb->data_length = cb->sig_length;
p = mbulk_get_signal(m);
if (!p) {
SLSI_ERR_NODEV("No signal in Mbulk\n");
print_hex_dump(KERN_ERR, "mbulk ", DUMP_PREFIX_NONE, 16, 1, m, sizeof(struct mbulk), 0);
kfree_skb(skb);
return NULL;
}
/* Remove 4Bytes offset coming from FW */
p += 4;
/* Don't need to copy the 4Bytes header coming from the FW */
memcpy(skb_put(skb, cb->sig_length), p, cb->sig_length);
if (m->len)
fapi_append_data(skb, mbulk_dat_r(m), m->len);
for (j = 0; j < i; j++)
fapi_append_data(skb, mbulk_dat_r(next_mbulk[j]), next_mbulk[j]->len);
return skb;
}
/* Add signal reference (offset in shared memory) in the selected queue */
/* This function should be called in atomic context. Callers should supply proper locking mechanism */
static int hip4_q_add_signal(struct slsi_hip4 *hip, enum hip4_hip_q_conf conf, scsc_mifram_ref phy_m, struct scsc_service *service)
{
struct hip4_hip_control *ctrl = hip->hip_control;
struct hip4_priv *hip_priv = hip->hip_priv;
u8 idx_w;
u8 idx_r;
/* Read the current q write pointer */
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
idx_w = QW(hip_priv, conf);
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, widx, idx_w, 1);
#endif
} else {
idx_w = hip4_read_index(hip, conf, widx);
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, widx, idx_w, 1);
#endif
}
/* Read the current q read pointer */
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
idx_r = QR(hip_priv, conf);
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, ridx, idx_r, 1);
#endif
} else {
idx_r = hip4_read_index(hip, conf, ridx);
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, ridx, idx_r, 1);
#endif
}
/* Queueu is full */
if (idx_r == ((idx_w + 1) & (MAX_NUM - 1)))
return -ENOSPC;
/* Update array */
ctrl->q[conf].array[idx_w] = phy_m;
/* Memory barrier before updating shared mailbox */
smp_wmb();
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, phy_m, conf);
#endif
/* Increase index */
idx_w++;
idx_w &= (MAX_NUM - 1);
/* Update the scoreboard */
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
QW(hip_priv, conf) = idx_w;
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, conf, widx, idx_w, 0);
#endif
/* Memory barrier before updating shared mailbox */
smp_wmb();
} else {
hip4_update_index(hip, conf, widx, idx_w);
}
send = ktime_get();
scsc_service_mifintrbit_bit_set(service, hip_priv->rx_intr_fromhost, SCSC_MIFINTR_TARGET_R4);
return 0;
}
static void hip4_watchdog(unsigned long data)
{
struct slsi_hip4 *hip = (struct slsi_hip4 *)data;
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
struct scsc_service *service;
unsigned long flags;
if (!sdev)
return;
if (!atomic_read(&hip->hip_priv->watchdog_timer_active))
return;
wdt = ktime_get();
hip4_dump_dbg(hip, NULL, NULL, sdev->service);
spin_lock_irqsave(&hip->hip_priv->watchdog_lock, flags);
service = sdev->service;
SLSI_INFO_NODEV("Hip4 watchdog triggered\n");
if (scsc_service_mifintrbit_get(service) & (1 << hip->hip_priv->rx_intr_tohost) &&
!(scsc_service_mifintrbit_bit_mask_status_get(service) & (1 << hip->hip_priv->rx_intr_tohost))) {
/* Interrupt is pending! */
SLSI_INFO_NODEV("Interrupt is set and Masked. Unmask to try to trigger ISR\n");
scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->rx_intr_tohost);
}
spin_unlock_irqrestore(&hip->hip_priv->watchdog_lock, flags);
}
/* Tasklet: high priority, low latency atomic tasks
* cannot sleep (run atomically in soft IRQ context and are guaranteed to
* never run on more than one CPU of a given processor, for a given tasklet)
*/
/* Worqueue: Lower priority, run in process context. Can run simultaneously on
* different CPUs
*/
#ifdef TASKLET
static void hip4_tasklet(unsigned long data)
#else
static void hip4_wq(struct work_struct *data)
#endif
{
#ifdef TASKLET
struct slsi_hip4 *hip = (struct slsi_hip4 *)data;
struct hip4_priv *hip_priv = hip->hip_priv;
struct hip4_hip_control *ctrl = hip->hip_control;
#else
struct hip4_priv *hip_priv = container_of(data, struct hip4_priv, intr_wq);
struct slsi_hip4 *hip = hip_priv->hip;
struct hip4_hip_control *ctrl = hip->hip_control;
#endif
scsc_mifram_ref ref;
void *mem;
struct mbulk *m;
u8 idx_r;
u8 idx_w;
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
struct scsc_service *service;
bool update = false;
bool no_change = true;
#ifdef CONFIG_SCSC_WLAN_DEBUG
int id;
#endif
if (!sdev || !sdev->service) {
BUG();
return;
}
service = sdev->service;
#ifndef TASKLET
spin_lock_bh(&hip_priv->rx_lock);
#endif
atomic_set(&hip->hip_priv->in_rx, 1);
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_INT(hip_priv->minor);
#endif
bh_init = ktime_get();
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
idx_r = QR(hip_priv, HIP4_MIF_Q_FH_RFB);
idx_w = QW(hip_priv, HIP4_MIF_Q_FH_RFB);
} else {
idx_r = hip4_read_index(hip, HIP4_MIF_Q_FH_RFB, ridx);
idx_w = hip4_read_index(hip, HIP4_MIF_Q_FH_RFB, widx);
update = false;
}
if (idx_r != idx_w) {
no_change = false;
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, ridx, idx_r, 1);
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, widx, idx_w, 1);
#endif
}
while (idx_r != idx_w) {
struct mbulk *m;
u16 colour;
ref = ctrl->q[HIP4_MIF_Q_FH_RFB].array[idx_r];
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_FH_RFB);
#endif
mem = scsc_mx_service_mif_addr_to_ptr(service, ref);
m = (struct mbulk *)mem;
if (m == NULL) {
SLSI_ERR_NODEV("FB: Mbulk is NULL\n");
goto consume_fb_mbulk;
}
/* colour is defined as: */
/* u16 register bits:
* 0 - do not use
* [2:1] - vif
* [7:3] - peer_index
* [10:8] - ac queue
*/
colour = ((m->clas & 0xc0) << 2) | (m->pid & 0xfe);
/* Account ONLY for data RFB */
if ((m->pid & 0x1) == MBULK_CLASS_FROM_HOST_DAT) {
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_VIF_PEER(hip->hip_priv->minor, 0, (colour & 0x6) >> 1, (colour & 0xf8) >> 3);
/* to profile round-trip */
{
u16 host_tag;
u8 *get_host_tag;
/* This is a nasty way of getting the host_tag without involving mbulk processing
* This hostag value should also be include in the cb descriptor which goes to
* mbulk descriptor (no room left at the moment)
*/
get_host_tag = (u8 *)m;
host_tag = get_host_tag[37] << 8 | get_host_tag[36];
SCSC_HIP4_SAMPLER_PKT_TX_FB(hip->hip_priv->minor, host_tag);
}
#endif
/* Ignore return value */
slsi_hip_tx_done(sdev, colour);
}
mbulk_free_virt_host(m);
consume_fb_mbulk:
/* Increase index */
idx_r++;
idx_r &= (MAX_NUM - 1);
update = true;
}
/* Update the scoreboard */
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
QR(hip_priv, HIP4_MIF_Q_FH_RFB) = (u8)idx_r;
#ifdef CONFIG_SCSC_WLAN_DEBUG
if (update)
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_FH_RFB, ridx, idx_r, 0);
#endif
} else if (update) {
hip4_update_index(hip, HIP4_MIF_Q_FH_RFB, ridx, idx_r);
}
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
idx_r = QR(hip_priv, HIP4_MIF_Q_TH_CTRL);
idx_w = QW(hip_priv, HIP4_MIF_Q_TH_CTRL);
} else {
idx_r = hip4_read_index(hip, HIP4_MIF_Q_TH_CTRL, ridx);
idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_CTRL, widx);
update = false;
}
if (idx_r != idx_w) {
no_change = false;
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, ridx, idx_r, 1);
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, widx, idx_w, 1);
#endif
}
while (idx_r != idx_w) {
struct sk_buff *skb;
/* TODO: currently the max number to be freed is 2. In future
* implementations (i.e. AMPDU) this number may be bigger
* list of mbulks to be freedi
*/
scsc_mifram_ref to_free[MBULK_MAX_CHAIN + 1] = { 0 };
u8 i = 0;
/* Catch-up with idx_w */
ref = ctrl->q[HIP4_MIF_Q_TH_CTRL].array[idx_r];
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_TH_CTRL);
#endif
mem = scsc_mx_service_mif_addr_to_ptr(service, ref);
m = (struct mbulk *)(mem);
/* Process Control Signal */
skb = hip4_mbulk_to_skb(service, m, to_free, false);
if (!skb) {
SLSI_ERR_NODEV("Ctrl: Error parsing skb\n");
hip4_dump_dbg(hip, m, skb, service);
goto consume_ctl_mbulk;
}
#ifdef CONFIG_SCSC_WLAN_DEBUG
id = fapi_get_sigid(skb);
/* log control signal, not unidata not debug */
if (fapi_is_mlme(skb))
SCSC_HIP4_SAMPLER_SIGNAL_CTRLRX(hip_priv->minor, (id & 0xff00) >> 8, id & 0xff);
hip4_history_record_add(TH, id);
#endif
if (slsi_hip_rx(sdev, skb) < 0) {
SLSI_ERR_NODEV("Ctrl: Error detected slsi_hip_rx\n");
hip4_dump_dbg(hip, m, skb, service);
slsi_kfree_skb(skb);
}
consume_ctl_mbulk:
/* Increase index */
idx_r++;
idx_r &= (MAX_NUM - 1);
/* Go through the list of references to free */
while ((ref = to_free[i++]))
/* return to the firmware */
if (hip4_q_add_signal(hip, HIP4_MIF_Q_TH_RFB, ref, service)) {
/* TODO: We need to know when this happens. We need to wait until space becomes free.
* We need to engineer the size of the ring buffers to ensure this is a rare event.
* After retrying once, we could panic or something.
*/
/* need to retry later */
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QFULL(hip_priv->minor, HIP4_MIF_Q_TH_RFB);
#endif
}
#ifdef CONFIG_SCSC_WLAN_DEBUG
if (i > 2)
SCSC_HIP4_SAMPLER_TOFREE(hip_priv->minor, i-1);
#endif
update = true;
}
/* Update the scoreboard */
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
QR(hip_priv, HIP4_MIF_Q_TH_CTRL) = (u8)idx_r;
#ifdef CONFIG_SCSC_WLAN_DEBUG
if (update)
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_CTRL, ridx, idx_r, 0);
#endif
} else if (update) {
hip4_update_index(hip, HIP4_MIF_Q_TH_CTRL, ridx, idx_r);
}
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
/* Continue Q[HIP4_MIF_Q_TH_DAT] (4) */
idx_r = QR(hip_priv, HIP4_MIF_Q_TH_DAT);
idx_w = QW(hip_priv, HIP4_MIF_Q_TH_DAT);
} else {
idx_r = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, ridx);
idx_w = hip4_read_index(hip, HIP4_MIF_Q_TH_DAT, widx);
update = false;
}
if (idx_r != idx_w) {
no_change = false;
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, ridx, idx_r, 1);
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, widx, idx_w, 1);
#endif
}
while (idx_r != idx_w) {
struct sk_buff *skb;
/* TODO: currently the max number to be freed is 2. In future
* implementations (i.e. AMPDU) this number may be bigger
*/
/* list of mbulks to be freed */
scsc_mifram_ref to_free[MBULK_MAX_CHAIN + 1] = { 0 };
u8 i = 0;
/* Catch-up with idx_w */
ref = ctrl->q[HIP4_MIF_Q_TH_DAT].array[idx_r];
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QREF(hip_priv->minor, ref, HIP4_MIF_Q_TH_DAT);
#endif
mem = scsc_mx_service_mif_addr_to_ptr(service, ref);
m = (struct mbulk *)(mem);
skb = hip4_mbulk_to_skb(service, m, to_free, false);
if (!skb) {
SLSI_ERR_NODEV("Dat: Error parsing skb\n");
hip4_dump_dbg(hip, m, skb, service);
goto consume_dat_mbulk;
}
#ifdef CONFIG_SCSC_WLAN_DEBUG
id = fapi_get_sigid(skb);
hip4_history_record_add(TH, id);
#endif
if (slsi_hip_rx(sdev, skb) < 0) {
SLSI_ERR_NODEV("Dat: Error detected slsi_hip_rx\n");
hip4_dump_dbg(hip, m, skb, service);
slsi_kfree_skb(skb);
}
consume_dat_mbulk:
/* Increase index */
idx_r++;
idx_r &= (MAX_NUM - 1);
/* Go through the list of references to free */
while ((ref = to_free[i++]))
/* return to the firmware */
if (hip4_q_add_signal(hip, HIP4_MIF_Q_TH_RFB, ref, service)) {
/* TODO: We need to know when this happens. We need to wait until space becomes free.
* We need to engineer the size of the ring buffers to ensure this is a rare event.
* After retrying once, we could panic or something.
*/
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QFULL(hip_priv->minor, HIP4_MIF_Q_TH_RFB);
#endif
}
#ifdef CONFIG_SCSC_WLAN_DEBUG
if (i > 2)
SCSC_HIP4_SAMPLER_TOFREE(hip_priv->minor, i-1);
#endif
update = true;
}
/* Update the scoreboard */
/* TODO: This is a temporary fix until FW is updated to scoreboard_ver 1 */
if (scoreboard_ver == 0) {
QR(hip_priv, HIP4_MIF_Q_TH_DAT) = (u8)idx_r;
#ifdef CONFIG_SCSC_WLAN_DEBUG
if (update)
SCSC_HIP4_SAMPLER_Q(hip_priv->minor, HIP4_MIF_Q_TH_DAT, ridx, idx_r, 0);
#endif
} else if (update) {
hip4_update_index(hip, HIP4_MIF_Q_TH_DAT, ridx, idx_r);
}
if (no_change)
atomic_inc(&hip->hip_priv->stats.spurious_irqs);
if (!atomic_read(&hip->hip_priv->closing)) {
/* Reset status variable. DO THIS BEFORE UNMASKING!!!*/
atomic_set(&hip->hip_priv->watchdog_timer_active, 0);
scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->rx_intr_tohost);
}
if (wake_lock_active(&hip->hip_priv->hip4_wake_lock))
wake_unlock(&hip->hip_priv->hip4_wake_lock);
bh_end = ktime_get();
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_INT_OUT(hip_priv->minor);
#endif
atomic_set(&hip->hip_priv->in_rx, 0);
#ifndef TASKLET
spin_unlock_bh(&hip_priv->rx_lock);
#endif
}
/* IRQ handler for hip4. The function runs in Interrupt context, so all the
* asssumptions related to interrupt should be applied (sleep, fast,...)
*/
static void hip4_irq_handler(int irq, void *data)
{
struct slsi_hip4 *hip = (struct slsi_hip4 *)data;
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
(void)irq; /* unused */
if (!hip || !sdev || !sdev->service || !hip->hip_priv)
return;
if (!atomic_read(&hip->hip_priv->rx_ready))
goto end;
intr_received = ktime_get();
if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock))
wake_lock(&hip->hip_priv->hip4_wake_lock);
atomic_set(&hip->hip_priv->watchdog_timer_active, 1);
mod_timer(&hip->hip_priv->watchdog, jiffies + HZ);
/* Mask interrupt to avoid interrupt storm and let BH run */
scsc_service_mifintrbit_bit_mask(sdev->service, hip->hip_priv->rx_intr_tohost);
atomic_inc(&hip->hip_priv->stats.irqs);
#ifdef TASKLET
tasklet_schedule(&hip->hip_priv->intr_tq);
#else
schedule_work(&hip->hip_priv->intr_wq);
#endif
end:
/* Clear interrupt */
scsc_service_mifintrbit_bit_clear(sdev->service, hip->hip_priv->rx_intr_tohost);
}
/**** OFFSET SHOULD BE 4096 BYTES ALIGNED ***/
#define IMG_MGR_SEC_WLAN_CONFIG_OFFSET 0x00000
#define IMG_MGR_SEC_WLAN_CONFIG_SIZE 0x02000 /* 8 kB*/
#define IMG_MGR_SEC_WLAN_MIB_OFFSET 0x02000
#define IMG_MGR_SEC_WLAN_MIB_SIZE 0x01000 /* 4 kB*/
#define IMG_MGR_SEC_WLAN_TX_OFFSET 0x03000
#define IMG_MGR_SEC_WLAN_TX_SIZE 0x7d000 /* 500 kB*/
#define IMG_MGR_SEC_WLAN_TX_DAT_OFFSET 0x03000 /* = */
#define IMG_MGR_SEC_WLAN_TX_DAT_SIZE 0x6d000 /* 436 kB*/
#define IMG_MGR_SEC_WLAN_TX_CTL_OFFSET 0x70000 /* + */
#define IMG_MGR_SEC_WLAN_TX_CTL_SIZE 0x10000 /* 64 kB*/
#define IMG_MGR_SEC_WLAN_RX_OFFSET 0x80000
#define IMG_MGR_SEC_WLAN_RX_SIZE 0x80000 /* 512 kB*/
int hip4_init(struct slsi_hip4 *hip)
{
void *hip_ptr;
struct hip4_hip_control *hip_control;
struct scsc_service *service;
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
scsc_mifram_ref ref, ref_scoreboard;
int i;
int ret;
if (!sdev || !sdev->service)
return -EINVAL;
hip->hip_priv = kzalloc(sizeof(*hip->hip_priv), GFP_ATOMIC);
if (!hip->hip_priv)
return -ENOMEM;
#ifdef CONFIG_SCSC_WLAN_DEBUG
hip->hip_priv->minor = hip4_sampler_register_hip(sdev->maxwell_core);
if (hip->hip_priv->minor < SCSC_HIP4_INTERFACES) {
SLSI_DBG1_NODEV(SLSI_HIP, "registered with minor %d\n", hip->hip_priv->minor);
sdev->minor_prof = hip->hip_priv->minor;
} else {
SLSI_DBG1_NODEV(SLSI_HIP, "hip4_sampler is not enabled\n");
}
#endif
/* Used in the workqueue */
hip->hip_priv->hip = hip;
service = sdev->service;
hip->hip_priv->host_pool_id_dat = MBULK_CLASS_FROM_HOST_DAT;
hip->hip_priv->host_pool_id_ctl = MBULK_CLASS_FROM_HOST_CTL;
/* hip_ref contains the reference of the start of shared memory allocated for WLAN */
/* hip_ptr is the kernel address of hip_ref*/
hip_ptr = scsc_mx_service_mif_addr_to_ptr(service, hip->hip_ref);
#ifdef CONFIG_SCSC_WLAN_DEBUG
/* Configure mbulk allocator - Data QUEUES */
ret = mbulk_pool_add(MBULK_CLASS_FROM_HOST_DAT, hip_ptr + IMG_MGR_SEC_WLAN_TX_DAT_OFFSET,
hip_ptr + IMG_MGR_SEC_WLAN_TX_DAT_OFFSET + IMG_MGR_SEC_WLAN_TX_DAT_SIZE,
(IMG_MGR_SEC_WLAN_TX_DAT_SIZE / HIP4_DAT_SLOTS) - sizeof(struct mbulk), 5,
hip->hip_priv->minor);
if (ret)
return ret;
/* Configure mbulk allocator - Control QUEUES */
ret = mbulk_pool_add(MBULK_CLASS_FROM_HOST_CTL, hip_ptr + IMG_MGR_SEC_WLAN_TX_CTL_OFFSET,
hip_ptr + IMG_MGR_SEC_WLAN_TX_CTL_OFFSET + IMG_MGR_SEC_WLAN_TX_CTL_SIZE,
(IMG_MGR_SEC_WLAN_TX_CTL_SIZE / HIP4_CTL_SLOTS) - sizeof(struct mbulk), 0,
hip->hip_priv->minor);
if (ret)
return ret;
#else
/* Configure mbulk allocator - Data QUEUES */
ret = mbulk_pool_add(MBULK_CLASS_FROM_HOST_DAT, hip_ptr + IMG_MGR_SEC_WLAN_TX_DAT_OFFSET,
hip_ptr + IMG_MGR_SEC_WLAN_TX_DAT_OFFSET + IMG_MGR_SEC_WLAN_TX_DAT_SIZE,
(IMG_MGR_SEC_WLAN_TX_DAT_SIZE / HIP4_DAT_SLOTS) - sizeof(struct mbulk), 5);
if (ret)
return ret;
/* Configure mbulk allocator - Control QUEUES */
ret = mbulk_pool_add(MBULK_CLASS_FROM_HOST_CTL, hip_ptr + IMG_MGR_SEC_WLAN_TX_CTL_OFFSET,
hip_ptr + IMG_MGR_SEC_WLAN_TX_CTL_OFFSET + IMG_MGR_SEC_WLAN_TX_CTL_SIZE,
(IMG_MGR_SEC_WLAN_TX_CTL_SIZE / HIP4_CTL_SLOTS) - sizeof(struct mbulk), 0);
if (ret)
return ret;
#endif
/* Reset hip_control table */
memset(hip_ptr, 0, sizeof(struct hip4_hip_control));
#ifdef CONFIG_SCSC_WLAN_DEBUG
/* Reset Sample q values sending 0xff */
SCSC_HIP4_SAMPLER_RESET(hip->hip_priv->minor);
#endif
/* Set driver is not ready to receive interrupts */
atomic_set(&hip->hip_priv->rx_ready, 0);
/* TOHOST Handler allocator */
hip->hip_priv->rx_intr_tohost =
scsc_service_mifintrbit_register_tohost(service, hip4_irq_handler, hip);
/* Mask the interrupt to prevent intr been kicked during start */
scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->rx_intr_tohost);
/* FROMHOST Handler allocator */
hip->hip_priv->rx_intr_fromhost =
scsc_service_mifintrbit_alloc_fromhost(service, SCSC_MIFINTR_TARGET_R4);
/* Get hip_control pointer on shared memory */
hip_control = (struct hip4_hip_control *)(hip_ptr +
IMG_MGR_SEC_WLAN_CONFIG_OFFSET);
/* Initialize scoreboard */
if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->scoreboard, &ref_scoreboard))
return -EFAULT;
/* Initialize hip_control table for version 4 */
/***** VERSION 4 *******/
hip_control->config_v4.magic_number = 0xcaba0401;
hip_control->config_v4.hip_config_ver = 4;
hip_control->config_v4.config_len = sizeof(struct hip4_hip_config_version_4);
hip_control->config_v4.host_cache_line = 64;
hip_control->config_v4.host_buf_loc = hip->hip_ref + IMG_MGR_SEC_WLAN_TX_OFFSET;
hip_control->config_v4.host_buf_sz = IMG_MGR_SEC_WLAN_TX_SIZE;
hip_control->config_v4.fw_buf_loc = hip->hip_ref + IMG_MGR_SEC_WLAN_RX_OFFSET;
hip_control->config_v4.fw_buf_sz = IMG_MGR_SEC_WLAN_RX_SIZE;
/* Copy MIB content in shared memory if any */
/* Clear the area to avoid picking up old values */
memset(hip_ptr + IMG_MGR_SEC_WLAN_MIB_OFFSET, 0, IMG_MGR_SEC_WLAN_MIB_SIZE);
if (sdev->mib_len > IMG_MGR_SEC_WLAN_MIB_SIZE) {
SLSI_ERR_NODEV("MIB size (%d), is bigger than the MIB AREA (%d). Aborting memcpy\n", sdev->mib_len, IMG_MGR_SEC_WLAN_MIB_SIZE);
hip_control->config_v4.mib_loc = 0;
hip_control->config_v4.mib_sz = 0;
} else if (sdev->mib_len) {
SLSI_INFO_NODEV("Loading MIB into shared memory, size (%d)\n", sdev->mib_len);
memcpy((u8 *)hip_ptr + IMG_MGR_SEC_WLAN_MIB_OFFSET, sdev->mib_data, sdev->mib_len);
hip_control->config_v4.mib_loc = hip->hip_ref + IMG_MGR_SEC_WLAN_MIB_OFFSET;
hip_control->config_v4.mib_sz = sdev->mib_len;
} else {
hip_control->config_v4.mib_loc = 0;
hip_control->config_v4.mib_sz = 0;
}
hip_control->config_v4.log_config_loc = 0;
hip_control->config_v4.mif_fh_int_n = hip->hip_priv->rx_intr_fromhost;
hip_control->config_v4.mif_th_int_n = hip->hip_priv->rx_intr_tohost;
hip_control->config_v4.scbrd_loc = (u32)ref_scoreboard;
hip_control->config_v4.q_num = 6;
hip_control->config_v4.q_len = 256;
hip_control->config_v4.q_idx_sz = 1;
/* Initialize q relative positions */
for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) {
if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->q[i].array, &ref))
return -EFAULT;
hip_control->config_v4.q_loc[i] = (u32)ref;
}
/***** END VERSION 2 *******/
/* Initialize hip_control table for version 2 */
/***** VERSION 3 *******/
hip_control->config_v3.magic_number = 0xcaba0401;
hip_control->config_v3.hip_config_ver = 3;
hip_control->config_v3.config_len = sizeof(struct hip4_hip_config_version_3);
hip_control->config_v3.host_cache_line = 64;
hip_control->config_v3.host_buf_loc = hip->hip_ref + IMG_MGR_SEC_WLAN_TX_OFFSET;
hip_control->config_v3.host_buf_sz = IMG_MGR_SEC_WLAN_TX_SIZE;
hip_control->config_v3.fw_buf_loc = hip->hip_ref + IMG_MGR_SEC_WLAN_RX_OFFSET;
hip_control->config_v3.fw_buf_sz = IMG_MGR_SEC_WLAN_RX_SIZE;
/* Copy MIB content in shared memory if any */
/* Clear the area to avoid picking up old values */
memset(hip_ptr + IMG_MGR_SEC_WLAN_MIB_OFFSET, 0, IMG_MGR_SEC_WLAN_MIB_SIZE);
if (sdev->mib_len > IMG_MGR_SEC_WLAN_MIB_SIZE) {
SLSI_ERR_NODEV("MIB size (%d), is bigger than the MIB AREA (%d). Aborting memcpy\n", sdev->mib_len, IMG_MGR_SEC_WLAN_MIB_SIZE);
hip_control->config_v3.mib_loc = 0;
hip_control->config_v3.mib_sz = 0;
} else if (sdev->mib_len) {
SLSI_INFO_NODEV("Loading MIB into shared memory, size (%d)\n", sdev->mib_len);
memcpy((u8 *)hip_ptr + IMG_MGR_SEC_WLAN_MIB_OFFSET, sdev->mib_data, sdev->mib_len);
hip_control->config_v3.mib_loc = hip->hip_ref + IMG_MGR_SEC_WLAN_MIB_OFFSET;
hip_control->config_v3.mib_sz = sdev->mib_len;
} else {
hip_control->config_v3.mib_loc = 0;
hip_control->config_v3.mib_sz = 0;
}
hip_control->config_v3.log_config_loc = 0;
hip_control->config_v3.mif_fh_int_n = hip->hip_priv->rx_intr_fromhost;
hip_control->config_v3.mif_th_int_n = hip->hip_priv->rx_intr_tohost;
hip_control->config_v3.q_num = 6;
hip_control->config_v3.q_len = 256;
hip_control->config_v3.q_idx_sz = 1;
hip_control->config_v3.scbrd_loc = (u32)ref_scoreboard; /* scoreborad location */
/* Initialize q relative positions */
for (i = 0; i < MIF_HIP_CFG_Q_NUM; i++) {
if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->q[i].array, &ref))
return -EFAULT;
hip_control->config_v3.q_loc[i] = (u32)ref;
}
/***** END VERSION 3 *******/
/* Initialzie hip_init configuration */
hip_control->init.magic_number = 0xcaaa0400;
if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->config_v4, &ref))
return -EFAULT;
hip_control->init.version_a_ref = ref;
if (scsc_mx_service_mif_ptr_to_addr(service, &hip_control->config_v3, &ref))
return -EFAULT;
hip_control->init.version_b_ref = ref;
/* End hip_init configuration */
hip->hip_control = hip_control;
hip->hip_priv->q_indice = (struct hip_q_indice *)(&hip_control->q[0].array);
hip->hip_priv->scbrd_base = &hip_control->scoreboard;
#ifndef TASKLET
spin_lock_init(&hip->hip_priv->rx_lock);
#endif
atomic_set(&hip->hip_priv->in_rx, 0);
spin_lock_init(&hip->hip_priv->tx_lock);
atomic_set(&hip->hip_priv->in_tx, 0);
#ifdef TASKLET
/* Init tasklet */
tasklet_init(&hip->hip_priv->intr_tq, hip4_tasklet, (unsigned long)hip);
#else
/* Init wq */
INIT_WORK(&hip->hip_priv->intr_wq, hip4_wq);
#endif
rwlock_init(&hip->hip_priv->rw_scoreboard);
/* Setup watchdog timer */
atomic_set(&hip->hip_priv->watchdog_timer_active, 0);
spin_lock_init(&hip->hip_priv->watchdog_lock);
setup_timer(&hip->hip_priv->watchdog, hip4_watchdog, (unsigned long)hip);
atomic_set(&hip->hip_priv->gmod, HIP4_DAT_SLOTS);
atomic_set(&hip->hip_priv->gactive, 1);
wake_lock_init(&hip->hip_priv->hip4_wake_lock, WAKE_LOCK_SUSPEND, "hip4_wake_lock");
return 0;
}
/**
* This function is in charge to transmit a frame through the HIP.
* It does NOT take ownership of the SKB unless it successfully transmit it;
* as a consequence skb is NOT freed on error.
* We return ENOSPC on queue related troubles in order to trigger upper
* layers of kernel to requeue/retry.
* We free ONLY locally-allocated stuff.
*/
int scsc_wifi_transmit_frame(struct slsi_hip4 *hip, bool ctrl_packet, struct sk_buff *skb)
{
struct scsc_service *service;
scsc_mifram_ref offset;
struct mbulk *m;
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
struct fapi_signal_header *fapi_header;
int ret = 0;
#ifdef CONFIG_SCSC_WLAN_DEBUG
struct slsi_skb_cb *cb = slsi_skb_cb_get(skb);
#endif
if (!hip || !sdev || !sdev->service || !skb || !hip->hip_priv)
return -EINVAL;
spin_lock_bh(&hip->hip_priv->tx_lock);
atomic_set(&hip->hip_priv->in_tx, 1);
if (!wake_lock_active(&hip->hip_priv->hip4_wake_lock))
wake_lock(&hip->hip_priv->hip4_wake_lock);
service = sdev->service;
fapi_header = (struct fapi_signal_header *)skb->data;
m = hip4_skb_to_mbulk(hip->hip_priv, skb, ctrl_packet);
if (m == NULL) {
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_MFULL(hip->hip_priv->minor);
#endif
ret = -ENOSPC;
SLSI_ERR_NODEV("mbulk is NULL\n");
goto error;
}
if (scsc_mx_service_mif_ptr_to_addr(service, m, &offset) < 0) {
mbulk_free_virt_host(m);
ret = -EFAULT;
SLSI_ERR_NODEV("Incorrect reference memory\n");
goto error;
}
if (hip4_q_add_signal(hip, ctrl_packet ? HIP4_MIF_Q_FH_CTRL : HIP4_MIF_Q_FH_DAT, offset, service)) {
#ifdef CONFIG_SCSC_WLAN_DEBUG
SCSC_HIP4_SAMPLER_QFULL(hip->hip_priv->minor, ctrl_packet ? HIP4_MIF_Q_FH_CTRL : HIP4_MIF_Q_FH_DAT);
#endif
mbulk_free_virt_host(m);
ret = -ENOSPC;
SLSI_ERR_NODEV("No space\n");
goto error;
}
#ifdef CONFIG_SCSC_WLAN_DEBUG
/* colour is defined as: */
/* u8 register bits:
* 0 - do not use
* [2:1] - vif
* [7:3] - peer_index
*/
if (ctrl_packet) {
/* Record control signal */
SCSC_HIP4_SAMPLER_SIGNAL_CTRLTX(hip->hip_priv->minor, (fapi_header->id & 0xff00) >> 8, fapi_header->id & 0xff);
} else {
SCSC_HIP4_SAMPLER_PKT_TX_HIP4(hip->hip_priv->minor, fapi_get_u16(skb, u.ma_unitdata_req.host_tag));
SCSC_HIP4_SAMPLER_VIF_PEER(hip->hip_priv->minor, 1, (cb->colour & 0x6) >> 1, (cb->colour & 0xf8) >> 3);
}
hip4_history_record_add(FH, fapi_header->id);
#endif
/* Here we push a copy of the bare skb TRANSMITTED data also to the logring
* as a binary record. Note that bypassing UDI subsystem as a whole
* means we are losing:
* UDI filtering / UDI Header INFO / UDI QueuesFrames Throttling /
* UDI Skb Asynchronous processing
* We keep separated DATA/CTRL paths.
*/
if (ctrl_packet)
SCSC_BIN_TAG_DEBUG(BIN_WIFI_CTRL_TX, skb->data, skb->len);
else
SCSC_BIN_TAG_DEBUG(BIN_WIFI_DATA_TX, skb->data, skb->len);
/* slsi_log_clients_log_signal_fast: skb is copied to all the log clients */
slsi_log_clients_log_signal_fast(sdev, &sdev->log_clients, skb, SLSI_LOG_DIRECTION_FROM_HOST);
slsi_kfree_skb(skb);
atomic_set(&hip->hip_priv->in_tx, 0);
spin_unlock_bh(&hip->hip_priv->tx_lock);
return 0;
error:
if (wake_lock_active(&hip->hip_priv->hip4_wake_lock))
wake_unlock(&hip->hip_priv->hip4_wake_lock);
atomic_set(&hip->hip_priv->in_tx, 0);
spin_unlock_bh(&hip->hip_priv->tx_lock);
return ret;
}
/* HIP4 has been initialize, setup with values
* provided by FW
*/
int hip4_setup(struct slsi_hip4 *hip)
{
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
struct scsc_service *service;
u32 conf_hip4_ver = 0;
if (!sdev || !sdev->service)
return -EIO;
service = sdev->service;
/* Get the Version reported by the FW */
conf_hip4_ver = scsc_wifi_get_hip_config_version(&hip->hip_control->init);
/* Check if the version is supported. And get the index */
/* This is hardcoded and may change in future versions */
if (conf_hip4_ver != 4 && conf_hip4_ver != 3) {
SLSI_ERR_NODEV("FW Version %d not supported\n", conf_hip4_ver);
return -EIO;
}
/* If version 4 is used */
if (conf_hip4_ver == 4) {
hip->hip_priv->unidat_req_headroom =
scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_headroom, 4);
hip->hip_priv->unidat_req_tailroom =
scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_tailroom, 4);
hip->hip_priv->version = 4;
} else {
/* version 3 */
hip->hip_priv->unidat_req_headroom =
scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_headroom, 3);
hip->hip_priv->unidat_req_tailroom =
scsc_wifi_get_hip_config_u8(&hip->hip_control, unidat_req_tailroom, 3);
hip->hip_priv->version = 3;
}
/* Unmask interrupts - now host should handle them */
atomic_set(&hip->hip_priv->stats.irqs, 0);
atomic_set(&hip->hip_priv->stats.spurious_irqs, 0);
atomic_set(&sdev->debug_inds, 0);
atomic_set(&hip->hip_priv->closing, 0);
/* Driver is ready to process IRQ */
atomic_set(&hip->hip_priv->rx_ready, 1);
scsc_service_mifintrbit_bit_unmask(service, hip->hip_priv->rx_intr_tohost);
return 0;
}
void hip4_freeze(struct slsi_hip4 *hip)
{
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
struct scsc_service *service;
if (!sdev || !sdev->service)
return;
service = sdev->service;
closing = ktime_get();
atomic_set(&hip->hip_priv->closing, 1);
hip4_dump_dbg(hip, NULL, NULL, service);
scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->rx_intr_tohost);
#ifdef TASKLET
tasklet_kill(&hip->hip_priv->intr_tq);
#else
cancel_work_sync(&hip->hip_priv->intr_wq);
#endif
atomic_set(&hip->hip_priv->rx_ready, 0);
atomic_set(&hip->hip_priv->watchdog_timer_active, 0);
/* Deactive the wd timer prior its expiration */
del_timer_sync(&hip->hip_priv->watchdog);
}
void hip4_deinit(struct slsi_hip4 *hip)
{
struct slsi_dev *sdev = container_of(hip, struct slsi_dev, hip4_inst);
struct scsc_service *service;
if (!sdev || !sdev->service)
return;
wake_lock_destroy(&hip->hip_priv->hip4_wake_lock);
service = sdev->service;
closing = ktime_get();
atomic_set(&hip->hip_priv->closing, 1);
scsc_service_mifintrbit_bit_mask(service, hip->hip_priv->rx_intr_tohost);
#ifdef TASKLET
tasklet_kill(&hip->hip_priv->intr_tq);
#else
cancel_work_sync(&hip->hip_priv->intr_wq);
#endif
scsc_service_mifintrbit_unregister_tohost(service, hip->hip_priv->rx_intr_tohost);
scsc_service_mifintrbit_free_fromhost(service, hip->hip_priv->rx_intr_fromhost, SCSC_MIFINTR_TARGET_R4);
/* If we get to that point with rx_lock/tx_lock claimed, trigger BUG() */
WARN_ON(atomic_read(&hip->hip_priv->in_tx));
WARN_ON(atomic_read(&hip->hip_priv->in_rx));
atomic_set(&hip->hip_priv->rx_ready, 0);
atomic_set(&hip->hip_priv->watchdog_timer_active, 0);
/* Deactive the wd timer prior its expiration */
del_timer_sync(&hip->hip_priv->watchdog);
kfree(hip->hip_priv);
}