// SPDX-License-Identifier: GPL-2.0+
|
/*
|
* Copyright (c) 1996, 2003 VIA Networking Technologies, Inc.
|
* All rights reserved.
|
*
|
* File: usbpipe.c
|
*
|
* Purpose: Handle USB control endpoint
|
*
|
* Author: Warren Hsu
|
*
|
* Date: Mar. 29, 2005
|
*
|
* Functions:
|
* vnt_control_out - Write variable length bytes to MEM/BB/MAC/EEPROM
|
* vnt_control_in - Read variable length bytes from MEM/BB/MAC/EEPROM
|
* vnt_control_out_u8 - Write one byte to MEM/BB/MAC/EEPROM
|
* vnt_control_in_u8 - Read one byte from MEM/BB/MAC/EEPROM
|
*
|
* Revision History:
|
* 04-05-2004 Jerry Chen: Initial release
|
* 11-24-2004 Warren Hsu: Add ControlvWriteByte,ControlvReadByte,
|
* ControlvMaskByte
|
*
|
*/
|
|
#include "rxtx.h"
|
#include "desc.h"
|
#include "device.h"
|
#include "usbpipe.h"
|
#include "mac.h"
|
#include "rf.h"
|
|
#define USB_CTL_WAIT 500 /* ms */
|
|
int vnt_control_out(struct vnt_private *priv, u8 request, u16 value,
|
u16 index, u16 length, const u8 *buffer)
|
{
|
int ret = 0;
|
u8 *usb_buffer;
|
|
if (test_bit(DEVICE_FLAGS_DISCONNECTED, &priv->flags)) {
|
ret = -EINVAL;
|
goto end;
|
}
|
|
mutex_lock(&priv->usb_lock);
|
|
usb_buffer = kmemdup(buffer, length, GFP_KERNEL);
|
if (!usb_buffer) {
|
ret = -ENOMEM;
|
goto end_unlock;
|
}
|
|
ret = usb_control_msg(priv->usb,
|
usb_sndctrlpipe(priv->usb, 0),
|
request, 0x40, value,
|
index, usb_buffer, length, USB_CTL_WAIT);
|
|
kfree(usb_buffer);
|
|
if (ret == (int)length)
|
ret = 0;
|
else
|
ret = -EIO;
|
|
end_unlock:
|
mutex_unlock(&priv->usb_lock);
|
end:
|
return ret;
|
}
|
|
int vnt_control_out_u8(struct vnt_private *priv, u8 reg, u8 reg_off, u8 data)
|
{
|
return vnt_control_out(priv, MESSAGE_TYPE_WRITE,
|
reg_off, reg, sizeof(u8), &data);
|
}
|
|
int vnt_control_out_blocks(struct vnt_private *priv,
|
u16 block, u8 reg, u16 length, const u8 *data)
|
{
|
int ret = 0, i;
|
|
for (i = 0; i < length; i += block) {
|
u16 len = min_t(int, length - i, block);
|
|
ret = vnt_control_out(priv, MESSAGE_TYPE_WRITE,
|
i, reg, len, data + i);
|
if (ret)
|
goto end;
|
}
|
end:
|
return ret;
|
}
|
|
int vnt_control_in(struct vnt_private *priv, u8 request, u16 value,
|
u16 index, u16 length, u8 *buffer)
|
{
|
int ret = 0;
|
u8 *usb_buffer;
|
|
if (test_bit(DEVICE_FLAGS_DISCONNECTED, &priv->flags)) {
|
ret = -EINVAL;
|
goto end;
|
}
|
|
mutex_lock(&priv->usb_lock);
|
|
usb_buffer = kmalloc(length, GFP_KERNEL);
|
if (!usb_buffer) {
|
ret = -ENOMEM;
|
goto end_unlock;
|
}
|
|
ret = usb_control_msg(priv->usb,
|
usb_rcvctrlpipe(priv->usb, 0),
|
request, 0xc0, value,
|
index, usb_buffer, length, USB_CTL_WAIT);
|
|
if (ret == length)
|
memcpy(buffer, usb_buffer, length);
|
|
kfree(usb_buffer);
|
|
if (ret == (int)length)
|
ret = 0;
|
else
|
ret = -EIO;
|
|
end_unlock:
|
mutex_unlock(&priv->usb_lock);
|
end:
|
return ret;
|
}
|
|
int vnt_control_in_u8(struct vnt_private *priv, u8 reg, u8 reg_off, u8 *data)
|
{
|
return vnt_control_in(priv, MESSAGE_TYPE_READ,
|
reg_off, reg, sizeof(u8), data);
|
}
|
|
static int vnt_int_report_rate(struct vnt_private *priv, u8 pkt_no, u8 tsr)
|
{
|
struct vnt_usb_send_context *context;
|
struct ieee80211_tx_info *info;
|
u8 tx_retry = (tsr & 0xf0) >> 4;
|
s8 idx;
|
|
if (pkt_no >= priv->num_tx_context)
|
return -EINVAL;
|
|
context = priv->tx_context[pkt_no];
|
|
if (!context->skb)
|
return -EINVAL;
|
|
info = IEEE80211_SKB_CB(context->skb);
|
idx = info->control.rates[0].idx;
|
|
ieee80211_tx_info_clear_status(info);
|
|
info->status.rates[0].count = tx_retry;
|
|
if (!(tsr & TSR_TMO)) {
|
info->status.rates[0].idx = idx;
|
|
if (!(info->flags & IEEE80211_TX_CTL_NO_ACK))
|
info->flags |= IEEE80211_TX_STAT_ACK;
|
}
|
|
ieee80211_tx_status_irqsafe(priv->hw, context->skb);
|
|
context->in_use = false;
|
|
return 0;
|
}
|
|
static void vnt_int_process_data(struct vnt_private *priv)
|
{
|
struct vnt_interrupt_data *int_data;
|
struct ieee80211_low_level_stats *low_stats = &priv->low_stats;
|
|
dev_dbg(&priv->usb->dev, "---->s_nsInterruptProcessData\n");
|
|
int_data = (struct vnt_interrupt_data *)priv->int_buf.data_buf;
|
|
if (int_data->tsr0 & TSR_VALID)
|
vnt_int_report_rate(priv, int_data->pkt0, int_data->tsr0);
|
|
if (int_data->tsr1 & TSR_VALID)
|
vnt_int_report_rate(priv, int_data->pkt1, int_data->tsr1);
|
|
if (int_data->tsr2 & TSR_VALID)
|
vnt_int_report_rate(priv, int_data->pkt2, int_data->tsr2);
|
|
if (int_data->tsr3 & TSR_VALID)
|
vnt_int_report_rate(priv, int_data->pkt3, int_data->tsr3);
|
|
if (!int_data->isr0)
|
return;
|
|
if (int_data->isr0 & ISR_BNTX && priv->op_mode == NL80211_IFTYPE_AP)
|
vnt_schedule_command(priv, WLAN_CMD_BECON_SEND);
|
|
priv->current_tsf = le64_to_cpu(int_data->tsf);
|
|
low_stats->dot11RTSSuccessCount += int_data->rts_success;
|
low_stats->dot11RTSFailureCount += int_data->rts_fail;
|
low_stats->dot11ACKFailureCount += int_data->ack_fail;
|
low_stats->dot11FCSErrorCount += int_data->fcs_err;
|
}
|
|
static void vnt_start_interrupt_urb_complete(struct urb *urb)
|
{
|
struct vnt_private *priv = urb->context;
|
int status = urb->status;
|
|
switch (status) {
|
case 0:
|
case -ETIMEDOUT:
|
break;
|
case -ECONNRESET:
|
case -ENOENT:
|
case -ESHUTDOWN:
|
return;
|
default:
|
break;
|
}
|
|
if (status)
|
dev_dbg(&priv->usb->dev, "%s status = %d\n", __func__, status);
|
else
|
vnt_int_process_data(priv);
|
|
status = usb_submit_urb(priv->interrupt_urb, GFP_ATOMIC);
|
if (status)
|
dev_dbg(&priv->usb->dev, "Submit int URB failed %d\n", status);
|
}
|
|
int vnt_start_interrupt_urb(struct vnt_private *priv)
|
{
|
int ret = 0;
|
|
dev_dbg(&priv->usb->dev, "---->Interrupt Polling Thread\n");
|
|
usb_fill_int_urb(priv->interrupt_urb,
|
priv->usb,
|
usb_rcvintpipe(priv->usb, 1),
|
priv->int_buf.data_buf,
|
MAX_INTERRUPT_SIZE,
|
vnt_start_interrupt_urb_complete,
|
priv,
|
priv->int_interval);
|
|
ret = usb_submit_urb(priv->interrupt_urb, GFP_ATOMIC);
|
if (ret)
|
dev_dbg(&priv->usb->dev, "Submit int URB failed %d\n", ret);
|
|
return ret;
|
}
|
|
static int vnt_rx_data(struct vnt_private *priv, struct vnt_rcb *ptr_rcb,
|
unsigned long bytes_received)
|
{
|
struct ieee80211_hw *hw = priv->hw;
|
struct ieee80211_supported_band *sband;
|
struct sk_buff *skb;
|
struct ieee80211_rx_status *rx_status;
|
struct vnt_rx_header *head;
|
struct vnt_rx_tail *tail;
|
u32 frame_size;
|
int ii;
|
u16 rx_bitrate, pay_load_with_padding;
|
u8 rate_idx = 0;
|
long rx_dbm;
|
|
skb = ptr_rcb->skb;
|
rx_status = IEEE80211_SKB_RXCB(skb);
|
|
/* [31:16]RcvByteCount ( not include 4-byte Status ) */
|
head = (struct vnt_rx_header *)skb->data;
|
frame_size = head->wbk_status >> 16;
|
frame_size += 4;
|
|
if (bytes_received != frame_size) {
|
dev_dbg(&priv->usb->dev, "------- WRONG Length 1\n");
|
return false;
|
}
|
|
if ((bytes_received > 2372) || (bytes_received <= 40)) {
|
/* Frame Size error drop this packet.*/
|
dev_dbg(&priv->usb->dev, "------ WRONG Length 2\n");
|
return false;
|
}
|
|
/* real Frame Size = USBframe_size -4WbkStatus - 4RxStatus */
|
/* -8TSF - 4RSR - 4SQ3 - ?Padding */
|
|
/* if SQ3 the range is 24~27, if no SQ3 the range is 20~23 */
|
|
/*Fix hardware bug => PLCP_Length error */
|
if (((bytes_received - head->pay_load_len) > 27) ||
|
((bytes_received - head->pay_load_len) < 24) ||
|
(bytes_received < head->pay_load_len)) {
|
dev_dbg(&priv->usb->dev, "Wrong PLCP Length %x\n",
|
head->pay_load_len);
|
return false;
|
}
|
|
sband = hw->wiphy->bands[hw->conf.chandef.chan->band];
|
rx_bitrate = head->rx_rate * 5; /* rx_rate * 5 */
|
|
for (ii = 0; ii < sband->n_bitrates; ii++) {
|
if (sband->bitrates[ii].bitrate == rx_bitrate) {
|
rate_idx = ii;
|
break;
|
}
|
}
|
|
if (ii == sband->n_bitrates) {
|
dev_dbg(&priv->usb->dev, "Wrong Rx Bit Rate %d\n", rx_bitrate);
|
return false;
|
}
|
|
pay_load_with_padding = ((head->pay_load_len / 4) +
|
((head->pay_load_len % 4) ? 1 : 0)) * 4;
|
|
tail = (struct vnt_rx_tail *)(skb->data +
|
sizeof(*head) + pay_load_with_padding);
|
priv->tsf_time = le64_to_cpu(tail->tsf_time);
|
|
if (tail->rsr & (RSR_IVLDTYP | RSR_IVLDLEN))
|
return false;
|
|
vnt_rf_rssi_to_dbm(priv, tail->rssi, &rx_dbm);
|
|
priv->bb_pre_ed_rssi = (u8)-rx_dbm + 1;
|
priv->current_rssi = priv->bb_pre_ed_rssi;
|
|
skb_pull(skb, sizeof(*head));
|
skb_trim(skb, head->pay_load_len);
|
|
rx_status->mactime = priv->tsf_time;
|
rx_status->band = hw->conf.chandef.chan->band;
|
rx_status->signal = rx_dbm;
|
rx_status->flag = 0;
|
rx_status->freq = hw->conf.chandef.chan->center_freq;
|
|
if (!(tail->rsr & RSR_CRCOK))
|
rx_status->flag |= RX_FLAG_FAILED_FCS_CRC;
|
|
rx_status->rate_idx = rate_idx;
|
|
if (tail->new_rsr & NEWRSR_DECRYPTOK)
|
rx_status->flag |= RX_FLAG_DECRYPTED;
|
|
ieee80211_rx_irqsafe(priv->hw, skb);
|
|
return true;
|
}
|
|
static void vnt_submit_rx_urb_complete(struct urb *urb)
|
{
|
struct vnt_rcb *rcb = urb->context;
|
struct vnt_private *priv = rcb->priv;
|
|
switch (urb->status) {
|
case 0:
|
break;
|
case -ECONNRESET:
|
case -ENOENT:
|
case -ESHUTDOWN:
|
return;
|
case -ETIMEDOUT:
|
default:
|
dev_dbg(&priv->usb->dev, "BULK In failed %d\n", urb->status);
|
break;
|
}
|
|
if (urb->actual_length) {
|
if (vnt_rx_data(priv, rcb, urb->actual_length)) {
|
rcb->skb = dev_alloc_skb(priv->rx_buf_sz);
|
if (!rcb->skb)
|
return;
|
} else {
|
skb_push(rcb->skb, skb_headroom(rcb->skb));
|
skb_trim(rcb->skb, 0);
|
}
|
|
urb->transfer_buffer = skb_put(rcb->skb,
|
skb_tailroom(rcb->skb));
|
}
|
|
if (usb_submit_urb(urb, GFP_ATOMIC))
|
dev_dbg(&priv->usb->dev, "Failed to re submit rx skb\n");
|
}
|
|
int vnt_submit_rx_urb(struct vnt_private *priv, struct vnt_rcb *rcb)
|
{
|
int ret = 0;
|
struct urb *urb = rcb->urb;
|
|
if (!rcb->skb) {
|
dev_dbg(&priv->usb->dev, "rcb->skb is null\n");
|
ret = -EINVAL;
|
goto end;
|
}
|
|
usb_fill_bulk_urb(urb,
|
priv->usb,
|
usb_rcvbulkpipe(priv->usb, 2),
|
skb_put(rcb->skb, skb_tailroom(rcb->skb)),
|
MAX_TOTAL_SIZE_WITH_ALL_HEADERS,
|
vnt_submit_rx_urb_complete,
|
rcb);
|
|
ret = usb_submit_urb(urb, GFP_ATOMIC);
|
if (ret)
|
dev_dbg(&priv->usb->dev, "Submit Rx URB failed %d\n", ret);
|
end:
|
return ret;
|
}
|
|
static void vnt_tx_context_complete(struct urb *urb)
|
{
|
struct vnt_usb_send_context *context = urb->context;
|
struct vnt_private *priv = context->priv;
|
|
switch (urb->status) {
|
case 0:
|
dev_dbg(&priv->usb->dev,
|
"Write %d bytes\n", urb->actual_length);
|
break;
|
case -ECONNRESET:
|
case -ENOENT:
|
case -ESHUTDOWN:
|
context->in_use = false;
|
return;
|
case -ETIMEDOUT:
|
default:
|
dev_dbg(&priv->usb->dev, "BULK Out failed %d\n", urb->status);
|
break;
|
}
|
|
if (context->type == CONTEXT_DATA_PACKET)
|
ieee80211_wake_queues(priv->hw);
|
|
if (urb->status || context->type == CONTEXT_BEACON_PACKET) {
|
if (context->skb)
|
ieee80211_free_txskb(priv->hw, context->skb);
|
|
context->in_use = false;
|
}
|
}
|
|
int vnt_tx_context(struct vnt_private *priv,
|
struct vnt_usb_send_context *context,
|
struct sk_buff *skb)
|
{
|
struct vnt_tx_usb_header *usb;
|
struct urb *urb;
|
int status;
|
u16 count = skb->len;
|
|
usb = skb_push(skb, sizeof(*usb));
|
usb->tx_byte_count = cpu_to_le16(count);
|
usb->pkt_no = context->pkt_no;
|
usb->type = context->type;
|
|
if (test_bit(DEVICE_FLAGS_DISCONNECTED, &priv->flags)) {
|
context->in_use = false;
|
return -ENODEV;
|
}
|
|
if (skb->len > MAX_TOTAL_SIZE_WITH_ALL_HEADERS) {
|
context->in_use = false;
|
return -E2BIG;
|
}
|
|
urb = usb_alloc_urb(0, GFP_ATOMIC);
|
if (!urb) {
|
context->in_use = false;
|
return -ENOMEM;
|
}
|
|
usb_fill_bulk_urb(urb,
|
priv->usb,
|
usb_sndbulkpipe(priv->usb, 3),
|
skb->data,
|
skb->len,
|
vnt_tx_context_complete,
|
context);
|
|
usb_anchor_urb(urb, &priv->tx_submitted);
|
|
status = usb_submit_urb(urb, GFP_ATOMIC);
|
if (status) {
|
dev_dbg(&priv->usb->dev, "Submit Tx URB failed %d\n", status);
|
usb_unanchor_urb(urb);
|
context->in_use = false;
|
}
|
|
usb_free_urb(urb);
|
|
return status;
|
}
|