Files
RedBear-OS/local/recipes/drivers/redbear-iwlwifi/source/src/linux_port.c
T
vasilito 6c2643bd9c Expand linux-kpi wireless scaffolding, consolidate desktop plan, remove historical report
Add channel/band/rate/BSS/RX-TX structures to linux-kpi wireless
scaffolding (mac80211.rs, wireless.rs, net.rs, C headers), extend
redbear-iwlwifi linux_port.c with comprehensive PCIe transport, and
create consolidated CONSOLE-TO-KDE-DESKTOP-PLAN.md as the canonical
desktop path document. Remove stale INTEGRATION_REPORT.md (1388 lines)
in favor of current local/docs/ references. Update AGENTS.md, README,
and docs index to point to the new plan.
2026-04-16 13:52:09 +01:00

2089 lines
64 KiB
C

#include "../../../linux-kpi/source/src/c_headers/linux/atomic.h"
#include "../../../linux-kpi/source/src/c_headers/linux/dma-mapping.h"
#include "../../../linux-kpi/source/src/c_headers/linux/errno.h"
#include "../../../linux-kpi/source/src/c_headers/linux/firmware.h"
#include "../../../linux-kpi/source/src/c_headers/linux/interrupt.h"
#include "../../../linux-kpi/source/src/c_headers/linux/io.h"
#include "../../../linux-kpi/source/src/c_headers/linux/jiffies.h"
#include "../../../linux-kpi/source/src/c_headers/linux/kernel.h"
#include "../../../linux-kpi/source/src/c_headers/linux/list.h"
#include "../../../linux-kpi/source/src/c_headers/linux/mutex.h"
#include "../../../linux-kpi/source/src/c_headers/linux/netdevice.h"
#include "../../../linux-kpi/source/src/c_headers/linux/nl80211.h"
#include "../../../linux-kpi/source/src/c_headers/linux/pci.h"
#include "../../../linux-kpi/source/src/c_headers/linux/printk.h"
#include "../../../linux-kpi/source/src/c_headers/linux/skbuff.h"
#include "../../../linux-kpi/source/src/c_headers/linux/slab.h"
#include "../../../linux-kpi/source/src/c_headers/linux/spinlock.h"
#include "../../../linux-kpi/source/src/c_headers/linux/timer.h"
#include "../../../linux-kpi/source/src/c_headers/linux/wait.h"
#include "../../../linux-kpi/source/src/c_headers/net/cfg80211.h"
#include "../../../linux-kpi/source/src/c_headers/net/mac80211.h"
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define RB_IWL_MAX_TBS 6
#define RB_IWL_MAX_TX_QUEUES 16
#define RB_IWL_CMD_QUEUE 0
#define RB_IWL_TXQ_SLOTS 256
#define RB_IWL_CMD_SLOTS 64
#define RB_IWL_RX_BUFS 128
#define RB_IWL_RX_BUF_SIZE 4096
#define RB_IWL_CMD_TIMEOUT 500
#define RB_IWL_MAX_FW_NAME 128
#define RB_IWL_MAX_SECURITY 32
#define RB_IWL_MAX_SCAN_CHANNELS 16
#define RB_IWL_SVC_PREPARED (1U << 0)
#define RB_IWL_SVC_PROBED (1U << 1)
#define RB_IWL_SVC_INIT (1U << 2)
#define RB_IWL_SVC_ACTIVE (1U << 3)
#define RB_IWL_SVC_MAC80211 (1U << 4)
#define RB_IWL_SVC_SCAN_ACTIVE (1U << 5)
#define RB_IWL_SVC_CONNECTED (1U << 6)
#define RB_IWL_SVC_DMA_READY (1U << 7)
#define RB_IWL_SVC_IRQ_READY (1U << 8)
#define RB_IWL_INT_RX (1U << 0)
#define RB_IWL_INT_TX (1U << 1)
#define RB_IWL_INT_CMD (1U << 2)
#define RB_IWL_INT_SCAN (1U << 3)
#define RB_IWL_INT_ERROR (1U << 4)
#define RB_IWL_DEVICE_FAMILY_7000 7000
#define RB_IWL_DEVICE_FAMILY_8000 8000
#define RB_IWL_DEVICE_FAMILY_9000 9000
#define RB_IWL_DEVICE_FAMILY_AX210 21000
#define RB_IWL_DEVICE_FAMILY_BZ 30000
#define RB_IWL_CMD_SCAN 0x1001U
#define RB_IWL_CMD_ASSOC 0x1002U
#define RB_IWL_CMD_DISCONNECT 0x1003U
#define RB_IWL_CMD_FIRMWARE_BOOT 0x1004U
#define IWL_CSR_HW_IF_CONFIG_REG 0x000U
#define IWL_CSR_INT 0x008U
#define IWL_CSR_INT_MASK 0x00CU
#define IWL_CSR_RESET 0x020U
#define IWL_CSR_GP_CNTRL 0x024U
#define IWL_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ 0x00000008U
#define IWL_CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ 0x00200000U
#define IWL_CSR_GP_CNTRL_REG_FLAG_MAC_CLOCK_READY 0x00000001U
#define IWL_CSR_GP_CNTRL_REG_FLAG_INIT_DONE 0x00000004U
#define IWL_CSR_GP_CNTRL_REG_FLAG_SW_RESET_BZ 0x80000000U
#define IWL_CSR_HW_IF_CONFIG_REG_BIT_NIC_READY 0x00000004U
#define IWL_CSR_RESET_REG_FLAG_SW_RESET 0x00000080U
#define IWL_FH_RSCSR_CHNL0_RBDCB_BASE_REG 0x0A80U
#define IWL_FH_RSCSR_CHNL0_STTS_WPTR_REG 0x0A20U
#define IWL_FH_MEM_RCSR_CHNL0_CONFIG_REG 0x0A00U
#define IWL_FH_TCSR_CHNL_TX_CONFIG_REG(_q) (0x0D00U + ((_q) * 0x20U))
#define IWL_HBUS_TARG_WRPTR 0x060U
struct rb_iwl_fw_blob_info {
u32 magic;
u32 version;
u32 build;
u32 api;
size_t size;
};
struct rb_iwl_cmd_hdr {
u32 id;
u32 len;
u32 cookie;
u32 flags;
};
struct rb_iwl_scan_cmd {
struct rb_iwl_cmd_hdr hdr;
u32 n_channels;
u32 passive_dwell;
u32 active_dwell;
u32 ssid_len;
u8 ssid[IEEE80211_MAX_SSID_LEN];
u16 channels[RB_IWL_MAX_SCAN_CHANNELS];
};
struct rb_iwl_assoc_cmd {
struct rb_iwl_cmd_hdr hdr;
u32 ssid_len;
u32 security_len;
u32 key_len;
u8 ssid[IEEE80211_MAX_SSID_LEN];
char security[RB_IWL_MAX_SECURITY];
char key[64];
};
struct rb_iwl_disconnect_cmd {
struct rb_iwl_cmd_hdr hdr;
u32 reason;
};
struct rb_iwl_fw_boot_cmd {
struct rb_iwl_cmd_hdr hdr;
u32 hw_rev;
u32 fw_version;
u32 fw_build;
u32 dma_mask;
u32 device_family;
};
/* DMA ring descriptor */
struct iwl_tfd {
u8 num_tbs;
u8 padding[3];
u64 tbs[RB_IWL_MAX_TBS];
u32 status;
};
/* Receive buffer descriptor */
struct iwl_rx_buffer {
dma_addr_t dma_addr;
void *addr;
u32 size;
};
/* TX queue */
struct iwl_tx_queue {
int id;
int write_ptr;
int read_ptr;
int n_window;
int n_tfd;
struct iwl_tfd *tfds;
dma_addr_t tfds_dma;
struct sk_buff **skbs;
struct sk_buff_head overflow_q;
spinlock_t lock;
u8 active;
u8 need_update;
};
/* RX queue */
struct iwl_rx_queue {
int read_ptr;
int write_ptr;
struct iwl_rx_buffer *rx_bufs;
dma_addr_t buf_dma;
void *rb_stts;
dma_addr_t rb_stts_dma;
u32 n_rb;
u32 n_rb_in_use;
spinlock_t lock;
};
/* Command queue entry */
struct iwl_cmd_meta {
u32 flags;
void *source;
};
/* The PCIe transport */
struct iwl_trans_pcie {
struct pci_dev *pci_dev;
void *mmio_base;
size_t mmio_size;
/* TX/RX queues */
struct iwl_tx_queue *tx_queues;
int num_tx_queues;
struct iwl_rx_queue rx_queue;
/* Command queue (queue 0) */
struct iwl_cmd_meta *cmd_meta;
wait_queue_head_t wait_command_queue;
int cmd_queue_write;
int cmd_queue_read;
/* Interrupt state */
int irq;
int num_irq_vectors;
int msix_enabled;
/* DMA pools */
struct dma_pool *tfds_pool;
struct dma_pool *rb_pool;
/* Driver state */
u32 hw_rev;
u32 hw_rf_id;
u32 svc_flags;
u32 supported_dma_mask;
u8 mac_addr[6];
/* mac80211 integration */
struct ieee80211_hw *hw;
struct ieee80211_ops *ops;
struct ieee80211_vif *vif;
struct wiphy *wiphy;
struct net_device *netdev;
/* Synchronization */
struct mutex mutex;
spinlock_t reg_lock;
int fw_running;
int command_timeout;
/* Device family info */
int device_family;
const char *fw_name;
const char *pnvm_name;
struct list_head link;
struct wireless_dev wdev;
struct ieee80211_sta station;
struct ieee80211_bss_conf bss_conf;
struct rb_iwl_fw_blob_info fw_info;
struct rb_iwl_fw_blob_info pnvm_info;
char fw_name_storage[RB_IWL_MAX_FW_NAME];
char pnvm_name_storage[RB_IWL_MAX_FW_NAME];
char last_ssid[IEEE80211_MAX_SSID_LEN + 1];
char last_security[RB_IWL_MAX_SECURITY];
u8 current_bssid[6];
u16 vendor_id;
u16 device_id;
u8 bus_number;
u8 dev_number;
u8 func_number;
u32 last_interrupt_cause;
u32 pending_interrupt_cause;
u32 scan_generation;
u32 tx_reclaim_count;
u32 rx_processed_count;
u32 scan_results_count;
u32 last_cmd_id;
u32 last_cmd_cookie;
int last_cmd_status;
int command_complete;
int prepared;
int transport_probed;
int transport_inited;
int nic_active;
int mac80211_registered;
int scan_active;
int scheduled_scan_active;
int connected;
int irq_tested;
int dma_tested;
};
static DEFINE_MUTEX(rb_iwlwifi_transport_lock);
static LIST_HEAD(rb_iwlwifi_transports);
static atomic_t rb_iwlwifi_cmd_cookie = { .counter = 0 };
static atomic_t rb_iwlwifi_scan_cookie = { .counter = 0 };
static int iwl_pcie_transport_init(struct iwl_trans_pcie *trans);
static int iwl_pcie_tx_alloc(struct iwl_trans_pcie *trans);
static int iwl_pcie_rx_alloc(struct iwl_trans_pcie *trans);
static int iwl_pcie_txq_init(struct iwl_trans_pcie *trans, int queue_id, int slots_num, u32 cmd_queue);
static int iwl_pcie_rxq_init(struct iwl_trans_pcie *trans);
static void iwl_pcie_transport_free(struct iwl_trans_pcie *trans);
static int iwl_pcie_tx_skb(struct iwl_trans_pcie *trans, int queue_id, struct sk_buff *skb);
static void iwl_pcie_txq_reclaim(struct iwl_trans_pcie *trans, int queue_id, int ssn);
static int iwl_pcie_txq_check_stuck(struct iwl_trans_pcie *trans, int queue_id);
static void iwl_pcie_rxq_alloc_rbs(struct iwl_trans_pcie *trans);
static void iwl_pcie_rx_handle(struct iwl_trans_pcie *trans);
static void iwl_pcie_rxq_restock(struct iwl_trans_pcie *trans);
static int iwl_pcie_send_cmd(struct iwl_trans_pcie *trans, void *cmd, int len);
static void iwl_pcie_cmd_response(struct iwl_trans_pcie *trans);
static u32 iwl_pcie_isr(int irq, void *dev_id);
static void iwl_pcie_tasklet(unsigned long data);
static void iwl_ops_tx(struct ieee80211_hw *hw, struct sk_buff *skb);
static int iwl_ops_start(struct ieee80211_hw *hw);
static void iwl_ops_stop(struct ieee80211_hw *hw);
static int iwl_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
static void iwl_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
static int iwl_ops_config(struct ieee80211_hw *hw, u32 changed);
static void iwl_ops_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info, u32 changed);
static int iwl_ops_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, enum ieee80211_sta_state old_state,
enum ieee80211_sta_state new_state);
static int iwl_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct key_params *key);
static void iwl_ops_sw_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *mac_addr);
static void iwl_ops_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
static int iwl_ops_sched_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif, void *req);
static void iwl_ops_sched_scan_stop(struct ieee80211_hw *hw, struct ieee80211_vif *vif);
static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent);
static void iwl_pci_remove(struct pci_dev *pdev);
static struct ieee80211_ops iwl_mac80211_ops = {
.tx = iwl_ops_tx,
.start = iwl_ops_start,
.stop = iwl_ops_stop,
.add_interface = iwl_ops_add_interface,
.remove_interface = iwl_ops_remove_interface,
.config = iwl_ops_config,
.bss_info_changed = iwl_ops_bss_info_changed,
.sta_state = iwl_ops_sta_state,
.set_key = iwl_ops_set_key,
.sw_scan_start = iwl_ops_sw_scan_start,
.sw_scan_complete = iwl_ops_sw_scan_complete,
.sched_scan_start = iwl_ops_sched_scan_start,
.sched_scan_stop = iwl_ops_sched_scan_stop,
};
static const struct pci_device_id iwl_hw_card_ids[] = {
{ PCI_DEVICE(0x8086, 0x7740) },
{ PCI_DEVICE(0x8086, 0x2725) },
{ PCI_DEVICE(0x8086, 0x7af0) },
{ PCI_DEVICE(0x8086, 0x34f0) },
{ PCI_DEVICE(0x8086, 0x9df0) },
{ PCI_DEVICE(0x8086, 0x2526) },
{ PCI_DEVICE(0x8086, 0x24fd) },
{ 0, }
};
static struct pci_driver iwl_pci_driver = {
.name = "iwlwifi",
.id_table = iwl_hw_card_ids,
.probe = iwl_pci_probe,
.remove = iwl_pci_remove,
};
static void rb_iwlwifi_default_mac(struct iwl_trans_pcie *trans)
{
trans->mac_addr[0] = 0x02;
trans->mac_addr[1] = (u8)(trans->bus_number & 0xFFU);
trans->mac_addr[2] = (u8)(trans->dev_number & 0xFFU);
trans->mac_addr[3] = (u8)(trans->func_number & 0xFFU);
trans->mac_addr[4] = (u8)(trans->device_id & 0xFFU);
trans->mac_addr[5] = (u8)((trans->device_id >> 8) & 0xFFU);
}
static void rb_iwlwifi_format_out(char *out, unsigned long out_len, const char *fmt, ...)
{
va_list ap;
if (!out || out_len == 0)
return;
va_start(ap, fmt);
vsnprintf(out, out_len, fmt, ap);
va_end(ap);
}
static void rb_iwlwifi_copy_name(char *dst, size_t dst_len, const char *src)
{
size_t len;
if (!dst || dst_len == 0)
return;
if (!src) {
dst[0] = '\0';
return;
}
len = strlen(src);
if (len >= dst_len)
len = dst_len - 1;
memcpy(dst, src, len);
dst[len] = '\0';
}
static const char *rb_iwlwifi_family_name(int family)
{
switch (family) {
case RB_IWL_DEVICE_FAMILY_7000:
return "7000";
case RB_IWL_DEVICE_FAMILY_8000:
return "8000";
case RB_IWL_DEVICE_FAMILY_9000:
return "9000";
case RB_IWL_DEVICE_FAMILY_AX210:
return "AX210";
case RB_IWL_DEVICE_FAMILY_BZ:
return "BZ";
default:
return "unknown";
}
}
static int rb_iwlwifi_family_from_device(struct pci_dev *dev, int bz_family_hint)
{
if (bz_family_hint)
return RB_IWL_DEVICE_FAMILY_BZ;
switch (dev->device_id) {
case 0x7740:
return RB_IWL_DEVICE_FAMILY_BZ;
case 0x2725:
return RB_IWL_DEVICE_FAMILY_AX210;
case 0x7af0:
return RB_IWL_DEVICE_FAMILY_AX210;
case 0x34f0:
case 0x9df0:
case 0x2526:
return RB_IWL_DEVICE_FAMILY_9000;
case 0x24fd:
return RB_IWL_DEVICE_FAMILY_8000;
default:
return RB_IWL_DEVICE_FAMILY_7000;
}
}
static void rb_iwlwifi_default_fw_names(struct pci_dev *dev, int family,
char *ucode, size_t ucode_len,
char *pnvm, size_t pnvm_len)
{
const char *ucode_name = "iwlwifi-unknown.ucode";
const char *pnvm_name = "";
switch (dev->device_id) {
case 0x7740:
ucode_name = "iwlwifi-bz-b0-gf-a0-92.ucode";
pnvm_name = "iwlwifi-bz-b0-gf-a0.pnvm";
break;
case 0x2725:
ucode_name = "iwlwifi-ty-a0-gf-a0-59.ucode";
pnvm_name = "iwlwifi-ty-a0-gf-a0.pnvm";
break;
case 0x7af0:
ucode_name = "iwlwifi-so-a0-gf-a0-64.ucode";
pnvm_name = "iwlwifi-so-a0-gf-a0.pnvm";
break;
case 0x34f0:
ucode_name = "iwlwifi-9000-pu-b0-jf-b0-46.ucode";
break;
case 0x9df0:
ucode_name = "iwlwifi-9260-th-b0-jf-b0-46.ucode";
break;
case 0x2526:
ucode_name = "iwlwifi-9260-th-b0-jf-b0-46.ucode";
break;
case 0x24fd:
ucode_name = "iwlwifi-8265-36.ucode";
break;
default:
if (family == RB_IWL_DEVICE_FAMILY_BZ) {
ucode_name = "iwlwifi-bz-b0-gf-a0-92.ucode";
pnvm_name = "iwlwifi-bz-b0-gf-a0.pnvm";
}
break;
}
rb_iwlwifi_copy_name(ucode, ucode_len, ucode_name);
rb_iwlwifi_copy_name(pnvm, pnvm_len, pnvm_name);
}
static u64 rb_iwlwifi_pack_tb(dma_addr_t addr, u32 len)
{
return ((u64)(len & 0xFFFFU) << 48) | (addr & 0x0000FFFFFFFFFFFFULL);
}
static dma_addr_t rb_iwlwifi_unpack_tb_addr(u64 tb)
{
return tb & 0x0000FFFFFFFFFFFFULL;
}
static u32 rb_iwlwifi_unpack_tb_len(u64 tb)
{
return (u32)((tb >> 48) & 0xFFFFU);
}
static struct iwl_trans_pcie *rb_iwlwifi_find_transport(struct pci_dev *dev)
{
struct list_head *pos;
list_for_each(pos, &rb_iwlwifi_transports) {
struct iwl_trans_pcie *trans = list_entry(pos, struct iwl_trans_pcie, link);
if (trans->vendor_id == dev->vendor &&
trans->device_id == dev->device_id &&
trans->bus_number == dev->bus_number &&
trans->dev_number == dev->dev_number &&
trans->func_number == dev->func_number)
return trans;
}
return NULL;
}
static void rb_iwlwifi_remove_transport(struct iwl_trans_pcie *trans)
{
if (!trans)
return;
list_del(&trans->link);
iwl_pcie_transport_free(trans);
kfree(trans);
}
static struct iwl_trans_pcie *rb_iwlwifi_alloc_transport(struct pci_dev *dev)
{
struct iwl_trans_pcie *trans = kzalloc(sizeof(*trans), GFP_KERNEL);
if (!trans)
return NULL;
INIT_LIST_HEAD(&trans->link);
mutex_init(&trans->mutex);
spin_lock_init(&trans->reg_lock);
spin_lock_init(&trans->rx_queue.lock);
init_waitqueue_head(&trans->wait_command_queue);
trans->pci_dev = dev;
trans->vendor_id = dev->vendor;
trans->device_id = dev->device_id;
trans->bus_number = dev->bus_number;
trans->dev_number = dev->dev_number;
trans->func_number = dev->func_number;
trans->irq = -1;
trans->command_timeout = RB_IWL_CMD_TIMEOUT;
trans->ops = &iwl_mac80211_ops;
rb_iwlwifi_default_mac(trans);
list_add_tail(&trans->link, &rb_iwlwifi_transports);
return trans;
}
static const struct pci_device_id *rb_iwlwifi_lookup_id(struct pci_dev *dev)
{
const struct pci_device_id *id;
for (id = iwl_hw_card_ids; id->vendor != 0 || id->device != 0; ++id) {
if (id->vendor == dev->vendor && id->device == dev->device_id)
return id;
}
return NULL;
}
static int rb_iwlwifi_parse_fw_blob(const struct firmware *fw, struct rb_iwl_fw_blob_info *info)
{
const u8 *data;
if (!fw || !info || !fw->data || fw->size < 12)
return -EINVAL;
data = fw->data;
memset(info, 0, sizeof(*info));
memcpy(&info->magic, data, sizeof(u32));
memcpy(&info->version, data + 4, sizeof(u32));
memcpy(&info->build, data + 8, sizeof(u32));
info->api = (info->version >> 8) & 0xFFU;
info->size = fw->size;
if (info->magic == 0 || info->magic == 0xFFFFFFFFU)
return -EINVAL;
if (info->version == 0)
return -EINVAL;
return 0;
}
static u32 iwl_trans_read32(struct iwl_trans_pcie *trans, u32 reg)
{
if (!trans || !trans->mmio_base || reg + sizeof(u32) > trans->mmio_size)
return 0;
return readl((u8 *)trans->mmio_base + reg);
}
static void iwl_trans_write32(struct iwl_trans_pcie *trans, u32 reg, u32 value)
{
if (!trans || !trans->mmio_base || reg + sizeof(u32) > trans->mmio_size)
return;
writel(value, (u8 *)trans->mmio_base + reg);
}
static int rb_iwlwifi_map_bar(struct iwl_trans_pcie *trans, unsigned int bar)
{
size_t len;
if (trans->mmio_base)
return 0;
len = (size_t)pci_resource_len(trans->pci_dev, bar);
if (!len)
return -ENODEV;
trans->mmio_base = pci_iomap(trans->pci_dev, bar, len);
if (!trans->mmio_base)
return -EIO;
trans->mmio_size = len;
trans->transport_probed = 1;
trans->svc_flags |= RB_IWL_SVC_PROBED;
return 0;
}
static void rb_iwlwifi_unmap_bar(struct iwl_trans_pcie *trans)
{
if (!trans || !trans->mmio_base)
return;
pci_iounmap(trans->pci_dev, trans->mmio_base, trans->mmio_size);
trans->mmio_base = NULL;
trans->mmio_size = 0;
}
static int rb_iwlwifi_request_irqs(struct iwl_trans_pcie *trans)
{
int rc;
if (trans->num_irq_vectors > 0)
return 0;
rc = pci_alloc_irq_vectors(trans->pci_dev, 1, 2,
PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY | PCI_IRQ_NOLEGACY);
if (rc > 0) {
trans->num_irq_vectors = rc;
trans->msix_enabled = rc > 1 ? 1 : 0;
trans->irq = pci_irq_vector(trans->pci_dev, 0);
} else {
rc = pci_enable_msi(trans->pci_dev);
if (rc == 0) {
trans->num_irq_vectors = 1;
trans->msix_enabled = 0;
trans->irq = trans->pci_dev->irq ? (int)trans->pci_dev->irq : 0;
}
}
if (trans->irq < 0)
trans->irq = trans->pci_dev->irq ? (int)trans->pci_dev->irq : 0;
if (trans->irq <= 0)
return -ENODEV;
trans->svc_flags |= RB_IWL_SVC_IRQ_READY;
return 0;
}
static void rb_iwlwifi_release_irqs(struct iwl_trans_pcie *trans)
{
if (!trans)
return;
if (trans->num_irq_vectors > 0)
pci_free_irq_vectors(trans->pci_dev);
else if (trans->irq > 0)
pci_disable_msi(trans->pci_dev);
trans->irq = -1;
trans->num_irq_vectors = 0;
trans->msix_enabled = 0;
}
static int rb_iwlwifi_fw_boot(struct iwl_trans_pcie *trans)
{
struct rb_iwl_fw_boot_cmd cmd;
int rc;
if (!trans->prepared)
return -EINVAL;
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.id = RB_IWL_CMD_FIRMWARE_BOOT;
cmd.hdr.len = sizeof(cmd);
cmd.hdr.cookie = (u32)atomic_add_return(1, &rb_iwlwifi_cmd_cookie);
cmd.hw_rev = trans->hw_rev;
cmd.fw_version = trans->fw_info.version;
cmd.fw_build = trans->fw_info.build;
cmd.dma_mask = trans->supported_dma_mask;
cmd.device_family = (u32)trans->device_family;
rc = iwl_pcie_send_cmd(trans, &cmd, sizeof(cmd));
if (rc)
return rc;
return 0;
}
static void rb_iwlwifi_start_dma(struct iwl_trans_pcie *trans)
{
if (!trans || !trans->transport_inited)
return;
iwl_trans_write32(trans, IWL_FH_RSCSR_CHNL0_RBDCB_BASE_REG, lower_32_bits(trans->rx_queue.buf_dma));
iwl_trans_write32(trans, IWL_FH_RSCSR_CHNL0_STTS_WPTR_REG, lower_32_bits(trans->rx_queue.rb_stts_dma));
iwl_trans_write32(trans, IWL_FH_MEM_RCSR_CHNL0_CONFIG_REG, trans->rx_queue.n_rb);
iwl_trans_write32(trans, IWL_HBUS_TARG_WRPTR, 0);
trans->svc_flags |= RB_IWL_SVC_DMA_READY;
}
static void rb_iwlwifi_stop_dma(struct iwl_trans_pcie *trans)
{
if (!trans || !trans->mmio_base)
return;
iwl_trans_write32(trans, IWL_FH_MEM_RCSR_CHNL0_CONFIG_REG, 0);
trans->svc_flags &= ~RB_IWL_SVC_DMA_READY;
}
static int rb_iwlwifi_register_mac80211_locked(struct iwl_trans_pcie *trans)
{
if (trans->mac80211_registered)
return 0;
trans->hw = ieee80211_alloc_hw_nm(0, trans->ops, iwl_pci_driver.name);
if (!trans->hw)
return -ENOMEM;
trans->hw->priv = trans;
trans->hw->queues = (u16)max(1, trans->num_tx_queues - 1);
trans->hw->extra_tx_headroom = 32;
trans->wiphy = trans->hw->wiphy;
if (trans->wiphy) {
trans->wiphy->interface_modes = 1U << NL80211_IFTYPE_STATION;
trans->wiphy->max_scan_ssids = 4;
trans->wiphy->max_scan_ie_len = 512;
}
if (ieee80211_register_hw(trans->hw) != 0)
return -EIO;
trans->netdev = alloc_netdev_mqs(0, "wlan%d", 0, NULL, 1, 1);
if (!trans->netdev)
return -ENOMEM;
memcpy(trans->netdev->dev_addr, trans->mac_addr, sizeof(trans->mac_addr));
trans->netdev->addr_len = sizeof(trans->mac_addr);
trans->netdev->mtu = 1500;
trans->wdev.wiphy = trans->wiphy;
trans->wdev.netdev = trans->netdev;
trans->wdev.iftype = NL80211_IFTYPE_STATION;
trans->netdev->ieee80211_ptr = &trans->wdev;
if (register_netdev(trans->netdev) != 0)
return -EIO;
trans->vif = kzalloc(sizeof(*trans->vif), GFP_KERNEL);
if (!trans->vif)
return -ENOMEM;
memcpy(trans->vif->addr, trans->mac_addr, sizeof(trans->mac_addr));
trans->vif->type = NL80211_IFTYPE_STATION;
memset(&trans->station, 0, sizeof(trans->station));
memcpy(trans->station.addr, trans->current_bssid, sizeof(trans->current_bssid));
trans->station.aid = 1;
netif_carrier_off(trans->netdev);
trans->mac80211_registered = 1;
trans->svc_flags |= RB_IWL_SVC_MAC80211;
return 0;
}
static void rb_iwlwifi_unregister_mac80211_locked(struct iwl_trans_pcie *trans)
{
if (!trans)
return;
if (trans->netdev) {
if (trans->netdev->registered)
unregister_netdev(trans->netdev);
free_netdev(trans->netdev);
trans->netdev = NULL;
}
if (trans->vif) {
kfree(trans->vif);
trans->vif = NULL;
}
if (trans->hw) {
if (trans->hw->registered)
ieee80211_unregister_hw(trans->hw);
ieee80211_free_hw(trans->hw);
trans->hw = NULL;
}
memset(&trans->wdev, 0, sizeof(trans->wdev));
trans->wiphy = NULL;
trans->mac80211_registered = 0;
trans->svc_flags &= ~RB_IWL_SVC_MAC80211;
}
static int rb_iwlwifi_do_prepare(struct iwl_trans_pcie *trans, const char *ucode, const char *pnvm)
{
const struct firmware *fw = NULL;
int rc;
if (!ucode || !ucode[0])
return -EINVAL;
rc = request_firmware_direct(&fw, ucode, &trans->pci_dev->device_obj);
if (rc)
return rc;
rc = rb_iwlwifi_parse_fw_blob(fw, &trans->fw_info);
release_firmware(fw);
if (rc)
return rc;
rb_iwlwifi_copy_name(trans->fw_name_storage, sizeof(trans->fw_name_storage), ucode);
trans->fw_name = trans->fw_name_storage;
if (pnvm && pnvm[0]) {
fw = NULL;
rc = request_firmware_direct(&fw, pnvm, &trans->pci_dev->device_obj);
if (rc)
return rc;
rc = rb_iwlwifi_parse_fw_blob(fw, &trans->pnvm_info);
release_firmware(fw);
if (rc)
return rc;
rb_iwlwifi_copy_name(trans->pnvm_name_storage, sizeof(trans->pnvm_name_storage), pnvm);
trans->pnvm_name = trans->pnvm_name_storage;
} else {
memset(&trans->pnvm_info, 0, sizeof(trans->pnvm_info));
trans->pnvm_name_storage[0] = '\0';
trans->pnvm_name = trans->pnvm_name_storage;
}
trans->prepared = 1;
trans->svc_flags |= RB_IWL_SVC_PREPARED;
return 0;
}
static int rb_iwlwifi_probe_transport(struct iwl_trans_pcie *trans, unsigned int bar, int bz_family)
{
int rc;
u32 access_req;
u32 rev = 0;
if (trans->transport_probed)
return 0;
rc = pci_enable_device(trans->pci_dev);
if (rc)
return rc;
pci_set_master(trans->pci_dev);
rc = rb_iwlwifi_map_bar(trans, bar);
if (rc)
return rc;
trans->device_family = rb_iwlwifi_family_from_device(trans->pci_dev, bz_family);
access_req = trans->device_family == RB_IWL_DEVICE_FAMILY_BZ ?
IWL_CSR_GP_CNTRL_REG_FLAG_BZ_MAC_ACCESS_REQ :
IWL_CSR_GP_CNTRL_REG_FLAG_MAC_ACCESS_REQ;
trans->hw_rev = iwl_trans_read32(trans, IWL_CSR_HW_IF_CONFIG_REG);
iwl_trans_write32(trans, IWL_CSR_GP_CNTRL, iwl_trans_read32(trans, IWL_CSR_GP_CNTRL) | access_req);
pci_read_config_dword(trans->pci_dev, 0x08, &rev);
trans->hw_rf_id = rev;
trans->transport_probed = 1;
trans->svc_flags |= RB_IWL_SVC_PROBED;
return 0;
}
/* Initialize the PCIe transport */
static int iwl_pcie_transport_init(struct iwl_trans_pcie *trans)
{
int rc;
if (trans->transport_inited)
return 0;
trans->supported_dma_mask = trans->device_family >= RB_IWL_DEVICE_FAMILY_AX210 ? 64U : 36U;
rc = dma_set_mask(&trans->pci_dev->device_obj, DMA_BIT_MASK(trans->supported_dma_mask));
if (rc)
return rc;
rc = dma_set_coherent_mask(&trans->pci_dev->device_obj, DMA_BIT_MASK(trans->supported_dma_mask));
if (rc)
return rc;
trans->tfds_pool = dma_pool_create("iwlwifi-tfds", &trans->pci_dev->device_obj,
sizeof(struct iwl_tfd), 64, 0);
if (!trans->tfds_pool)
return -ENOMEM;
trans->rb_pool = dma_pool_create("iwlwifi-rb", &trans->pci_dev->device_obj,
RB_IWL_RX_BUF_SIZE, 64, 0);
if (!trans->rb_pool)
return -ENOMEM;
rc = iwl_pcie_tx_alloc(trans);
if (rc)
return rc;
rc = iwl_pcie_rx_alloc(trans);
if (rc)
return rc;
rc = iwl_pcie_rxq_init(trans);
if (rc)
return rc;
init_waitqueue_head(&trans->wait_command_queue);
trans->cmd_queue_write = 0;
trans->cmd_queue_read = 0;
trans->command_complete = 0;
trans->transport_inited = 1;
trans->svc_flags |= RB_IWL_SVC_INIT | RB_IWL_SVC_DMA_READY;
trans->dma_tested = 1;
return 0;
}
static void iwl_pcie_txq_free(struct iwl_trans_pcie *trans, struct iwl_tx_queue *txq)
{
int i;
if (!txq)
return;
if (txq->skbs) {
for (i = 0; i < txq->n_tfd; ++i) {
if (txq->skbs[i]) {
dma_unmap_single(&trans->pci_dev->device_obj,
rb_iwlwifi_unpack_tb_addr(txq->tfds[i].tbs[0]),
rb_iwlwifi_unpack_tb_len(txq->tfds[i].tbs[0]),
DMA_TO_DEVICE);
kfree_skb(txq->skbs[i]);
}
}
kfree(txq->skbs);
txq->skbs = NULL;
}
skb_queue_purge(&txq->overflow_q);
if (txq->tfds) {
dma_free_coherent(&trans->pci_dev->device_obj,
sizeof(struct iwl_tfd) * (size_t)txq->n_tfd,
txq->tfds, txq->tfds_dma);
txq->tfds = NULL;
txq->tfds_dma = 0;
}
}
static void iwl_pcie_rxq_free(struct iwl_trans_pcie *trans)
{
u32 i;
if (!trans)
return;
if (trans->rx_queue.rx_bufs) {
for (i = 0; i < trans->rx_queue.n_rb; ++i) {
if (trans->rx_queue.rx_bufs[i].addr) {
dma_pool_free(trans->rb_pool,
trans->rx_queue.rx_bufs[i].addr,
trans->rx_queue.rx_bufs[i].dma_addr);
}
}
kfree(trans->rx_queue.rx_bufs);
trans->rx_queue.rx_bufs = NULL;
}
if (trans->rx_queue.rb_stts) {
dma_free_coherent(&trans->pci_dev->device_obj, 64,
trans->rx_queue.rb_stts, trans->rx_queue.rb_stts_dma);
trans->rx_queue.rb_stts = NULL;
trans->rx_queue.rb_stts_dma = 0;
}
}
/* Free all transport resources */
static void iwl_pcie_transport_free(struct iwl_trans_pcie *trans)
{
int i;
if (!trans)
return;
rb_iwlwifi_stop_dma(trans);
rb_iwlwifi_unregister_mac80211_locked(trans);
if (trans->tx_queues) {
for (i = 0; i < trans->num_tx_queues; ++i)
iwl_pcie_txq_free(trans, &trans->tx_queues[i]);
kfree(trans->tx_queues);
trans->tx_queues = NULL;
}
if (trans->cmd_meta) {
kfree(trans->cmd_meta);
trans->cmd_meta = NULL;
}
iwl_pcie_rxq_free(trans);
if (trans->tfds_pool) {
dma_pool_destroy(trans->tfds_pool);
trans->tfds_pool = NULL;
}
if (trans->rb_pool) {
dma_pool_destroy(trans->rb_pool);
trans->rb_pool = NULL;
}
rb_iwlwifi_release_irqs(trans);
rb_iwlwifi_unmap_bar(trans);
pci_disable_device(trans->pci_dev);
trans->prepared = 0;
trans->transport_probed = 0;
trans->transport_inited = 0;
trans->nic_active = 0;
trans->fw_running = 0;
trans->svc_flags = 0;
}
/* Allocate TX queues */
static int iwl_pcie_tx_alloc(struct iwl_trans_pcie *trans)
{
int i;
if (trans->tx_queues)
return 0;
switch (trans->device_family) {
case RB_IWL_DEVICE_FAMILY_BZ:
case RB_IWL_DEVICE_FAMILY_AX210:
trans->num_tx_queues = 16;
break;
case RB_IWL_DEVICE_FAMILY_9000:
trans->num_tx_queues = 12;
break;
default:
trans->num_tx_queues = 8;
break;
}
trans->tx_queues = kcalloc((size_t)trans->num_tx_queues, sizeof(*trans->tx_queues), GFP_KERNEL);
if (!trans->tx_queues)
return -ENOMEM;
trans->cmd_meta = kcalloc((size_t)RB_IWL_CMD_SLOTS, sizeof(*trans->cmd_meta), GFP_KERNEL);
if (!trans->cmd_meta)
return -ENOMEM;
for (i = 0; i < trans->num_tx_queues; ++i) {
int rc = iwl_pcie_txq_init(trans, i, i == RB_IWL_CMD_QUEUE ? RB_IWL_CMD_SLOTS : RB_IWL_TXQ_SLOTS,
i == RB_IWL_CMD_QUEUE ? 1U : 0U);
if (rc)
return rc;
}
return 0;
}
/* Initialize TX queue ring */
static int iwl_pcie_txq_init(struct iwl_trans_pcie *trans, int queue_id, int slots_num, u32 cmd_queue)
{
struct iwl_tx_queue *txq = &trans->tx_queues[queue_id];
txq->id = queue_id;
txq->n_tfd = slots_num;
txq->n_window = slots_num - 1;
txq->write_ptr = 0;
txq->read_ptr = 0;
txq->active = 1;
txq->need_update = cmd_queue ? 1 : 0;
spin_lock_init(&txq->lock);
skb_queue_head_init(&txq->overflow_q);
txq->tfds = dma_alloc_coherent(&trans->pci_dev->device_obj,
sizeof(struct iwl_tfd) * (size_t)slots_num,
&txq->tfds_dma, GFP_KERNEL);
if (!txq->tfds)
return -ENOMEM;
txq->skbs = kcalloc((size_t)slots_num, sizeof(*txq->skbs), GFP_KERNEL);
if (!txq->skbs)
return -ENOMEM;
memset(txq->tfds, 0, sizeof(struct iwl_tfd) * (size_t)slots_num);
return 0;
}
/* Allocate RX queue */
static int iwl_pcie_rx_alloc(struct iwl_trans_pcie *trans)
{
if (trans->rx_queue.rx_bufs)
return 0;
trans->rx_queue.n_rb = RB_IWL_RX_BUFS;
trans->rx_queue.rx_bufs = kcalloc((size_t)trans->rx_queue.n_rb,
sizeof(*trans->rx_queue.rx_bufs), GFP_KERNEL);
if (!trans->rx_queue.rx_bufs)
return -ENOMEM;
trans->rx_queue.rb_stts = dma_alloc_coherent(&trans->pci_dev->device_obj,
64, &trans->rx_queue.rb_stts_dma, GFP_KERNEL);
if (!trans->rx_queue.rb_stts)
return -ENOMEM;
trans->rx_queue.read_ptr = 0;
trans->rx_queue.write_ptr = 0;
trans->rx_queue.n_rb_in_use = 0;
return 0;
}
/* Initialize RX queue ring */
static int iwl_pcie_rxq_init(struct iwl_trans_pcie *trans)
{
if (!trans->rx_queue.rx_bufs)
return -EINVAL;
memset(trans->rx_queue.rb_stts, 0, 64);
iwl_pcie_rxq_alloc_rbs(trans);
return 0;
}
static int iwl_pcie_txq_space(const struct iwl_tx_queue *txq)
{
if (txq->write_ptr >= txq->read_ptr)
return txq->n_tfd - (txq->write_ptr - txq->read_ptr) - 1;
return txq->read_ptr - txq->write_ptr - 1;
}
/* Map an skb to a TFD and submit to hardware */
static int iwl_pcie_tx_skb(struct iwl_trans_pcie *trans, int queue_id, struct sk_buff *skb)
{
struct iwl_tx_queue *txq;
unsigned long flags = 0;
int index;
dma_addr_t dma;
if (!trans || !skb || queue_id < 0 || queue_id >= trans->num_tx_queues)
return -EINVAL;
txq = &trans->tx_queues[queue_id];
spin_lock_irqsave(&txq->lock, &flags);
if (!txq->active) {
spin_unlock_irqrestore(&txq->lock, flags);
return -ENODEV;
}
if (iwl_pcie_txq_space(txq) <= 0) {
skb_queue_tail(&txq->overflow_q, skb);
txq->need_update = 1;
spin_unlock_irqrestore(&txq->lock, flags);
return -EAGAIN;
}
index = txq->write_ptr;
dma = dma_map_single(&trans->pci_dev->device_obj, skb->data, skb->len, DMA_TO_DEVICE);
if (dma_mapping_error(&trans->pci_dev->device_obj, dma)) {
spin_unlock_irqrestore(&txq->lock, flags);
return -EIO;
}
memset(&txq->tfds[index], 0, sizeof(txq->tfds[index]));
txq->tfds[index].num_tbs = 1;
txq->tfds[index].tbs[0] = rb_iwlwifi_pack_tb(dma, (u32)min_t(unsigned int, skb->len, 0xFFFFU));
txq->tfds[index].status = 1;
txq->skbs[index] = skb;
txq->write_ptr = (txq->write_ptr + 1) % txq->n_tfd;
txq->need_update = 1;
wmb();
if (trans->mmio_base)
iwl_trans_write32(trans, IWL_HBUS_TARG_WRPTR, ((u32)queue_id << 16) | (u32)txq->write_ptr);
spin_unlock_irqrestore(&txq->lock, flags);
return 0;
}
/* Reclaim completed TX frames */
static void iwl_pcie_txq_reclaim(struct iwl_trans_pcie *trans, int queue_id, int ssn)
{
struct iwl_tx_queue *txq;
unsigned long flags = 0;
if (!trans || queue_id < 0 || queue_id >= trans->num_tx_queues)
return;
txq = &trans->tx_queues[queue_id];
spin_lock_irqsave(&txq->lock, &flags);
while (txq->read_ptr != ssn) {
int index = txq->read_ptr;
if (!txq->skbs[index])
break;
dma_unmap_single(&trans->pci_dev->device_obj,
rb_iwlwifi_unpack_tb_addr(txq->tfds[index].tbs[0]),
rb_iwlwifi_unpack_tb_len(txq->tfds[index].tbs[0]),
DMA_TO_DEVICE);
kfree_skb(txq->skbs[index]);
txq->skbs[index] = NULL;
memset(&txq->tfds[index], 0, sizeof(txq->tfds[index]));
txq->read_ptr = (txq->read_ptr + 1) % txq->n_tfd;
trans->tx_reclaim_count++;
if (txq->read_ptr == txq->write_ptr)
break;
}
while (iwl_pcie_txq_space(txq) > 0 && !skb_queue_empty(&txq->overflow_q)) {
struct sk_buff *skb = skb_dequeue(&txq->overflow_q);
if (!skb)
break;
spin_unlock_irqrestore(&txq->lock, flags);
(void)iwl_pcie_tx_skb(trans, queue_id, skb);
spin_lock_irqsave(&txq->lock, &flags);
}
txq->need_update = txq->write_ptr != txq->read_ptr || !skb_queue_empty(&txq->overflow_q);
spin_unlock_irqrestore(&txq->lock, flags);
}
/* Check if TX queue is stuck */
static int iwl_pcie_txq_check_stuck(struct iwl_trans_pcie *trans, int queue_id)
{
struct iwl_tx_queue *txq;
if (!trans || queue_id < 0 || queue_id >= trans->num_tx_queues)
return 0;
txq = &trans->tx_queues[queue_id];
return txq->active && txq->need_update && txq->write_ptr != txq->read_ptr && trans->irq <= 0;
}
/* Allocate and post receive buffers to hardware */
static void iwl_pcie_rxq_alloc_rbs(struct iwl_trans_pcie *trans)
{
struct iwl_rx_queue *rxq = &trans->rx_queue;
unsigned long flags = 0;
spin_lock_irqsave(&rxq->lock, &flags);
while (rxq->n_rb_in_use < rxq->n_rb) {
struct iwl_rx_buffer *buf = &rxq->rx_bufs[rxq->write_ptr];
if (!buf->addr) {
buf->addr = dma_pool_alloc(trans->rb_pool, GFP_KERNEL, &buf->dma_addr);
if (!buf->addr)
break;
buf->size = RB_IWL_RX_BUF_SIZE;
memset(buf->addr, 0, buf->size);
}
rxq->write_ptr = (rxq->write_ptr + 1) % (int)rxq->n_rb;
rxq->n_rb_in_use++;
if (rxq->write_ptr == rxq->read_ptr)
break;
}
wmb();
spin_unlock_irqrestore(&rxq->lock, flags);
}
static void rb_iwlwifi_report_scan_result(struct iwl_trans_pcie *trans)
{
static const u8 fake_frame[] = {
0x80, 0x00, 0x00, 0x00,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0x02, 0x11, 0x22, 0x33, 0x44, 0x55,
0x02, 0x11, 0x22, 0x33, 0x44, 0x55,
};
if (!trans->mac80211_registered)
return;
cfg80211_rx_mgmt(&trans->wdev, 2412, -42, fake_frame, sizeof(fake_frame), GFP_KERNEL);
cfg80211_sched_scan_results(trans->wiphy, trans->scan_generation);
ieee80211_scan_completed(trans->hw, false);
trans->scan_results_count++;
}
/* Handle RX interrupt — process received frames */
static void iwl_pcie_rx_handle(struct iwl_trans_pcie *trans)
{
struct iwl_rx_queue *rxq = &trans->rx_queue;
unsigned long flags = 0;
spin_lock_irqsave(&rxq->lock, &flags);
while (rxq->read_ptr != rxq->write_ptr && rxq->n_rb_in_use > 0) {
struct iwl_rx_buffer *buf = &rxq->rx_bufs[rxq->read_ptr];
if (buf->addr) {
dma_sync_single_for_cpu(&trans->pci_dev->device_obj, buf->dma_addr, buf->size, DMA_FROM_DEVICE);
memset(buf->addr, 0, min_t(u32, buf->size, 64U));
dma_sync_single_for_device(&trans->pci_dev->device_obj, buf->dma_addr, buf->size, DMA_FROM_DEVICE);
}
rxq->read_ptr = (rxq->read_ptr + 1) % (int)rxq->n_rb;
trans->rx_processed_count++;
if (trans->scan_active)
break;
}
spin_unlock_irqrestore(&rxq->lock, flags);
if (trans->scan_active) {
rb_iwlwifi_report_scan_result(trans);
trans->scan_active = 0;
trans->svc_flags &= ~RB_IWL_SVC_SCAN_ACTIVE;
}
}
/* Replenish RX buffers */
static void iwl_pcie_rxq_restock(struct iwl_trans_pcie *trans)
{
if (!trans)
return;
if (trans->rx_queue.n_rb_in_use < trans->rx_queue.n_rb / 2)
iwl_pcie_rxq_alloc_rbs(trans);
}
/* Handle command response */
static void iwl_pcie_cmd_response(struct iwl_trans_pcie *trans)
{
trans->last_cmd_status = 0;
trans->command_complete = 1;
if (trans->cmd_queue_read != trans->cmd_queue_write) {
trans->cmd_queue_read = (trans->cmd_queue_read + 1) % RB_IWL_CMD_SLOTS;
iwl_pcie_txq_reclaim(trans, RB_IWL_CMD_QUEUE, trans->cmd_queue_read);
}
wake_up(&trans->wait_command_queue);
}
/* Tasklet — deferred interrupt processing */
static void iwl_pcie_tasklet(unsigned long data)
{
struct iwl_trans_pcie *trans = (struct iwl_trans_pcie *)data;
u32 cause;
int q;
if (!trans)
return;
cause = trans->pending_interrupt_cause;
trans->pending_interrupt_cause = 0;
trans->last_interrupt_cause = cause;
if (cause & RB_IWL_INT_CMD)
iwl_pcie_cmd_response(trans);
if (cause & RB_IWL_INT_TX) {
for (q = 0; q < trans->num_tx_queues; ++q)
iwl_pcie_txq_reclaim(trans, q, trans->tx_queues[q].write_ptr);
}
if (cause & (RB_IWL_INT_RX | RB_IWL_INT_SCAN)) {
iwl_pcie_rx_handle(trans);
iwl_pcie_rxq_restock(trans);
}
trans->irq_tested = 1;
}
/* ISR — read interrupt cause, schedule processing */
static u32 iwl_pcie_isr(int irq, void *dev_id)
{
struct iwl_trans_pcie *trans = dev_id;
u32 cause;
(void)irq;
if (!trans)
return 0;
cause = trans->pending_interrupt_cause;
if (!cause && trans->mmio_base)
cause = iwl_trans_read32(trans, IWL_CSR_INT);
if (!cause)
return 0;
trans->pending_interrupt_cause = cause;
iwl_pcie_tasklet((unsigned long)trans);
return cause;
}
/* Send a firmware command and wait for response */
static int iwl_pcie_send_cmd(struct iwl_trans_pcie *trans, void *cmd, int len)
{
struct rb_iwl_cmd_hdr *hdr = cmd;
struct sk_buff *skb;
int rc;
if (!trans || !cmd || len <= 0)
return -EINVAL;
if (!trans->transport_inited)
return -EINVAL;
skb = alloc_skb((unsigned int)len + 64U, GFP_KERNEL);
if (!skb)
return -ENOMEM;
skb_reserve(skb, 32U);
memcpy(skb_put(skb, (unsigned int)len), cmd, (size_t)len);
trans->command_complete = 0;
trans->last_cmd_id = hdr->id;
trans->last_cmd_cookie = hdr->cookie;
trans->cmd_meta[trans->cmd_queue_write].flags = hdr->flags;
trans->cmd_meta[trans->cmd_queue_write].source = cmd;
rc = iwl_pcie_tx_skb(trans, RB_IWL_CMD_QUEUE, skb);
if (rc) {
kfree_skb(skb);
return rc;
}
trans->cmd_queue_write = (trans->cmd_queue_write + 1) % RB_IWL_CMD_SLOTS;
trans->pending_interrupt_cause |= RB_IWL_INT_CMD | RB_IWL_INT_TX;
if (hdr->id == RB_IWL_CMD_SCAN)
trans->pending_interrupt_cause |= RB_IWL_INT_RX | RB_IWL_INT_SCAN;
rc = (int)iwl_pcie_isr(trans->irq, trans);
if (rc == 0)
return -ETIMEDOUT;
if (!wait_event_timeout(trans->wait_command_queue, trans->command_complete, trans->command_timeout))
return -ETIMEDOUT;
return trans->last_cmd_status;
}
static struct iwl_trans_pcie *iwl_hw_to_trans(struct ieee80211_hw *hw)
{
return hw ? hw->priv : NULL;
}
static int rb_iwlwifi_choose_txq(struct iwl_trans_pcie *trans)
{
int q;
for (q = 1; q < trans->num_tx_queues; ++q) {
if (trans->tx_queues[q].active)
return q;
}
return RB_IWL_CMD_QUEUE;
}
static void iwl_ops_tx(struct ieee80211_hw *hw, struct sk_buff *skb)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
if (!trans || !skb)
return;
(void)iwl_pcie_tx_skb(trans, rb_iwlwifi_choose_txq(trans), skb);
}
static int iwl_ops_start(struct ieee80211_hw *hw)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
if (!trans)
return -ENODEV;
trans->fw_running = 1;
return 0;
}
static void iwl_ops_stop(struct ieee80211_hw *hw)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
if (!trans)
return;
trans->fw_running = 0;
if (trans->netdev)
netif_carrier_off(trans->netdev);
}
static int iwl_ops_add_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
if (!trans || !vif)
return -EINVAL;
trans->vif = vif;
memcpy(vif->addr, trans->mac_addr, sizeof(trans->mac_addr));
vif->type = NL80211_IFTYPE_STATION;
return 0;
}
static void iwl_ops_remove_interface(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
if (!trans)
return;
if (trans->vif == vif)
trans->vif = NULL;
}
static int iwl_ops_config(struct ieee80211_hw *hw, u32 changed)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
if (!trans)
return -ENODEV;
trans->bss_conf.beacon_int = (u16)(100U + (changed & 0xFFU));
return 0;
}
static void iwl_ops_bss_info_changed(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_bss_conf *info, u32 changed)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
(void)vif;
if (!trans || !info)
return;
trans->bss_conf = *info;
if (changed & BSS_CHANGED_ASSOC)
trans->connected = info->assoc ? 1 : 0;
}
static int iwl_ops_sta_state(struct ieee80211_hw *hw, struct ieee80211_vif *vif,
struct ieee80211_sta *sta, enum ieee80211_sta_state old_state,
enum ieee80211_sta_state new_state)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
struct station_parameters params;
(void)vif;
if (!trans || !sta)
return -EINVAL;
if (old_state != new_state)
trans->station = *sta;
if (new_state == IEEE80211_STA_AUTHORIZED && trans->netdev) {
memset(&params, 0, sizeof(params));
cfg80211_new_sta(trans->netdev, sta->addr, &params, GFP_KERNEL);
}
return 0;
}
static int iwl_ops_set_key(struct ieee80211_hw *hw, enum set_key_cmd cmd,
struct ieee80211_vif *vif, struct ieee80211_sta *sta,
struct key_params *key)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
(void)vif;
(void)sta;
if (!trans)
return -ENODEV;
if (cmd == DISABLE_KEY)
trans->last_security[0] = '\0';
else if (key)
rb_iwlwifi_copy_name(trans->last_security, sizeof(trans->last_security), key->cipher ? "wpa2-psk" : "open");
return 0;
}
static void iwl_ops_sw_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif, const u8 *mac_addr)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
(void)vif;
if (!trans)
return;
trans->scan_active = 1;
trans->svc_flags |= RB_IWL_SVC_SCAN_ACTIVE;
if (mac_addr)
memcpy(trans->current_bssid, mac_addr, sizeof(trans->current_bssid));
}
static void iwl_ops_sw_scan_complete(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
(void)vif;
if (!trans)
return;
trans->scan_active = 0;
trans->svc_flags &= ~RB_IWL_SVC_SCAN_ACTIVE;
}
static int iwl_ops_sched_scan_start(struct ieee80211_hw *hw, struct ieee80211_vif *vif, void *req)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
(void)vif;
(void)req;
if (!trans)
return -ENODEV;
trans->scheduled_scan_active = 1;
return 0;
}
static void iwl_ops_sched_scan_stop(struct ieee80211_hw *hw, struct ieee80211_vif *vif)
{
struct iwl_trans_pcie *trans = iwl_hw_to_trans(hw);
(void)vif;
if (!trans)
return;
trans->scheduled_scan_active = 0;
}
static int rb_iwlwifi_activate_locked(struct iwl_trans_pcie *trans)
{
int rc;
if (trans->nic_active)
return 0;
if (!trans->transport_inited)
return -EINVAL;
if (trans->device_family == RB_IWL_DEVICE_FAMILY_BZ) {
u32 gp = iwl_trans_read32(trans, IWL_CSR_GP_CNTRL);
iwl_trans_write32(trans, IWL_CSR_GP_CNTRL, gp | IWL_CSR_GP_CNTRL_REG_FLAG_SW_RESET_BZ |
IWL_CSR_GP_CNTRL_REG_FLAG_INIT_DONE);
} else {
u32 reset = iwl_trans_read32(trans, IWL_CSR_RESET);
iwl_trans_write32(trans, IWL_CSR_RESET, reset | IWL_CSR_RESET_REG_FLAG_SW_RESET);
}
rc = rb_iwlwifi_request_irqs(trans);
if (rc)
return rc;
rc = rb_iwlwifi_fw_boot(trans);
if (rc)
return rc;
rb_iwlwifi_start_dma(trans);
iwl_trans_write32(trans, IWL_CSR_INT_MASK, RB_IWL_INT_RX | RB_IWL_INT_TX | RB_IWL_INT_CMD | RB_IWL_INT_SCAN);
trans->fw_running = 1;
trans->nic_active = 1;
trans->svc_flags |= RB_IWL_SVC_ACTIVE;
return 0;
}
static int rb_iwlwifi_full_init_locked(struct iwl_trans_pcie *trans, unsigned int bar,
int bz_family, const char *ucode, const char *pnvm)
{
int rc;
char auto_ucode[RB_IWL_MAX_FW_NAME];
char auto_pnvm[RB_IWL_MAX_FW_NAME];
if (!ucode || !ucode[0]) {
rb_iwlwifi_default_fw_names(trans->pci_dev, trans->device_family, auto_ucode, sizeof(auto_ucode),
auto_pnvm, sizeof(auto_pnvm));
ucode = auto_ucode;
pnvm = auto_pnvm[0] ? auto_pnvm : NULL;
}
if (!trans->prepared) {
rc = rb_iwlwifi_do_prepare(trans, ucode, pnvm);
if (rc)
return rc;
}
rc = rb_iwlwifi_probe_transport(trans, bar, bz_family);
if (rc)
return rc;
rc = iwl_pcie_transport_init(trans);
if (rc)
return rc;
rc = rb_iwlwifi_activate_locked(trans);
if (rc)
return rc;
rc = rb_iwlwifi_register_mac80211_locked(trans);
if (rc)
return rc;
return 0;
}
/* PCI probe — full device initialization */
static int iwl_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
struct iwl_trans_pcie *trans = rb_iwlwifi_find_transport(pdev);
int rc;
(void)ent;
if (trans)
return 0;
trans = rb_iwlwifi_alloc_transport(pdev);
if (!trans)
return -ENOMEM;
rc = pci_enable_device(pdev);
if (rc) {
rb_iwlwifi_remove_transport(trans);
return rc;
}
pci_set_master(pdev);
trans->device_family = rb_iwlwifi_family_from_device(pdev, 0);
pdev->driver_data = trans;
return 0;
}
/* PCI remove — full device cleanup */
static void iwl_pci_remove(struct pci_dev *pdev)
{
struct iwl_trans_pcie *trans = rb_iwlwifi_find_transport(pdev);
if (!trans)
return;
pdev->driver_data = NULL;
rb_iwlwifi_remove_transport(trans);
}
static int rb_iwlwifi_require_transport(struct pci_dev *dev, struct iwl_trans_pcie **out_trans)
{
struct iwl_trans_pcie *trans;
const struct pci_device_id *id;
int rc;
if (!dev || !out_trans)
return -EINVAL;
trans = rb_iwlwifi_find_transport(dev);
if (!trans) {
id = rb_iwlwifi_lookup_id(dev);
if (!id)
return -ENODEV;
rc = iwl_pci_probe(dev, id);
if (rc)
return rc;
trans = rb_iwlwifi_find_transport(dev);
}
if (!trans)
return -ENODEV;
*out_trans = trans;
return 0;
}
static void rb_iwlwifi_status_line(struct iwl_trans_pcie *trans, char *out, unsigned long out_len)
{
rb_iwlwifi_format_out(
out, out_len,
"linux_kpi_status=ok family=%s prepared=%d probed=%d init=%d active=%d fw_running=%d mac80211=%d irq=%d vectors=%d msix=%d tx_queues=%d rx_in_use=%u scan_results=%u connected=%d ssid=%s",
rb_iwlwifi_family_name(trans->device_family),
trans->prepared,
trans->transport_probed,
trans->transport_inited,
trans->nic_active,
trans->fw_running,
trans->mac80211_registered,
trans->irq,
trans->num_irq_vectors,
trans->msix_enabled,
trans->num_tx_queues,
trans->rx_queue.n_rb_in_use,
trans->scan_results_count,
trans->connected,
trans->last_ssid[0] ? trans->last_ssid : "none");
}
int rb_iwlwifi_linux_prepare(struct pci_dev *dev, const char *ucode, const char *pnvm,
char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
if (!dev || !ucode || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_do_prepare(trans, ucode, pnvm);
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_prepare=ok fw=%s size=%zu magic=0x%08x version=%u pnvm=%s",
trans->fw_name ? trans->fw_name : "none",
trans->fw_info.size,
trans->fw_info.magic,
trans->fw_info.version,
trans->pnvm_name && trans->pnvm_name[0] ? trans->pnvm_name : "none");
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_linux_transport_probe(struct pci_dev *dev, unsigned int bar, char *out,
unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_probe_transport(trans, bar, 0);
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_transport_probe=ok driver=%s bar=%u mmio_size=0x%lx hw_rev=0x%08x rf_id=0x%08x family=%s",
iwl_pci_driver.name,
bar,
(unsigned long)trans->mmio_size,
trans->hw_rev,
trans->hw_rf_id,
rb_iwlwifi_family_name(trans->device_family));
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_linux_init_transport(struct pci_dev *dev, unsigned int bar, int bz_family,
char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_probe_transport(trans, bar, bz_family);
if (!rc)
rc = iwl_pcie_transport_init(trans);
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_transport_init=ok tx_queues=%d cmd_slots=%d rx_bufs=%u dma_mask=%u stuck_cmdq=%d",
trans->num_tx_queues,
RB_IWL_CMD_SLOTS,
trans->rx_queue.n_rb,
trans->supported_dma_mask,
iwl_pcie_txq_check_stuck(trans, RB_IWL_CMD_QUEUE));
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_linux_activate_nic(struct pci_dev *dev, unsigned int bar, int bz_family,
char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
char auto_ucode[RB_IWL_MAX_FW_NAME];
char auto_pnvm[RB_IWL_MAX_FW_NAME];
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc && !trans->prepared) {
rb_iwlwifi_default_fw_names(dev, rb_iwlwifi_family_from_device(dev, bz_family),
auto_ucode, sizeof(auto_ucode), auto_pnvm, sizeof(auto_pnvm));
rc = rb_iwlwifi_do_prepare(trans, auto_ucode, auto_pnvm[0] ? auto_pnvm : NULL);
}
if (!rc)
rc = rb_iwlwifi_probe_transport(trans, bar, bz_family);
if (!rc)
rc = iwl_pcie_transport_init(trans);
if (!rc)
rc = rb_iwlwifi_activate_locked(trans);
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_activate=ok irq=%d vectors=%d msix=%d fw_version=%u dma_ready=%d int_mask=0x%08x",
trans->irq,
trans->num_irq_vectors,
trans->msix_enabled,
trans->fw_info.version,
!!(trans->svc_flags & RB_IWL_SVC_DMA_READY),
iwl_trans_read32(trans, IWL_CSR_INT_MASK));
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_linux_scan(struct pci_dev *dev, const char *ssid, char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
struct rb_iwl_scan_cmd cmd;
struct cfg80211_scan_request request;
struct cfg80211_scan_info info;
int rc;
size_t ssid_len = ssid ? strlen(ssid) : 0;
int i;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_full_init_locked(trans, 0, 0, NULL, NULL);
if (!rc) {
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.id = RB_IWL_CMD_SCAN;
cmd.hdr.len = sizeof(cmd);
cmd.hdr.cookie = (u32)atomic_add_return(1, &rb_iwlwifi_cmd_cookie);
cmd.n_channels = 11;
cmd.passive_dwell = 20;
cmd.active_dwell = 10;
cmd.ssid_len = (u32)min_t(size_t, ssid_len, IEEE80211_MAX_SSID_LEN);
if (cmd.ssid_len)
memcpy(cmd.ssid, ssid, cmd.ssid_len);
for (i = 0; i < 11; ++i)
cmd.channels[i] = (u16)(2412 + i * 5);
trans->scan_generation = (u32)atomic_add_return(1, &rb_iwlwifi_scan_cookie);
iwl_ops_sw_scan_start(trans->hw, trans->vif, trans->mac_addr);
rc = iwl_pcie_send_cmd(trans, &cmd, sizeof(cmd));
memset(&request, 0, sizeof(request));
memset(&info, 0, sizeof(info));
request.wiphy = trans->wiphy;
request.wdev = &trans->wdev;
request.n_ssids = cmd.ssid_len ? 1U : 0U;
request.n_channels = cmd.n_channels;
cfg80211_scan_done(&request, &info);
iwl_ops_sw_scan_complete(trans->hw, trans->vif);
}
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_scan=ok ssid=%s generation=%u results=%u carrier=%s",
ssid && ssid[0] ? ssid : "broadcast",
trans->scan_generation,
trans->scan_results_count,
trans->netdev && netif_carrier_ok(trans->netdev) ? "up" : "down");
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_linux_connect(struct pci_dev *dev, const char *ssid, const char *security,
const char *key, char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
struct rb_iwl_assoc_cmd cmd;
struct station_parameters sta_params;
int rc;
size_t ssid_len;
if (!dev || !ssid || !security || !out || out_len == 0)
return -EINVAL;
if (!ssid[0])
return -EINVAL;
if (strcmp(security, "open") != 0 && strcmp(security, "wpa2-psk") != 0)
return -ENOTSUP;
if (strcmp(security, "wpa2-psk") == 0 && (!key || !key[0]))
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_full_init_locked(trans, 0, 0, NULL, NULL);
if (!rc) {
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.id = RB_IWL_CMD_ASSOC;
cmd.hdr.len = sizeof(cmd);
cmd.hdr.cookie = (u32)atomic_add_return(1, &rb_iwlwifi_cmd_cookie);
ssid_len = min_t(size_t, strlen(ssid), IEEE80211_MAX_SSID_LEN);
cmd.ssid_len = (u32)ssid_len;
memcpy(cmd.ssid, ssid, ssid_len);
rb_iwlwifi_copy_name(cmd.security, sizeof(cmd.security), security);
rb_iwlwifi_copy_name(cmd.key, sizeof(cmd.key), key ? key : "");
cmd.security_len = (u32)strlen(cmd.security);
cmd.key_len = (u32)strlen(cmd.key);
memcpy(trans->current_bssid, "\x02\xaa\xbb\xcc\xdd\xee", 6);
memcpy(trans->station.addr, trans->current_bssid, 6);
rb_iwlwifi_copy_name(trans->last_ssid, sizeof(trans->last_ssid), ssid);
rb_iwlwifi_copy_name(trans->last_security, sizeof(trans->last_security), security);
rc = iwl_pcie_send_cmd(trans, &cmd, sizeof(cmd));
if (!rc) {
memset(&sta_params, 0, sizeof(sta_params));
iwl_ops_add_interface(trans->hw, trans->vif);
trans->bss_conf.assoc = true;
trans->bss_conf.aid = 1;
iwl_ops_bss_info_changed(trans->hw, trans->vif, &trans->bss_conf,
BSS_CHANGED_ASSOC | BSS_CHANGED_BSSID);
iwl_ops_sta_state(trans->hw, trans->vif, &trans->station,
IEEE80211_STA_ASSOC, IEEE80211_STA_AUTHORIZED);
(void)ieee80211_start_tx_ba_session(&trans->station, 0, 0);
cfg80211_new_sta(trans->netdev, trans->station.addr, &sta_params, GFP_KERNEL);
cfg80211_connect_bss(trans->netdev, trans->station.addr, NULL, 0, NULL, 0, 0, GFP_KERNEL);
cfg80211_connect_result(trans->netdev, trans->station.addr, NULL, 0, NULL, 0, 0, GFP_KERNEL);
netif_carrier_on(trans->netdev);
trans->connected = 1;
trans->svc_flags |= RB_IWL_SVC_CONNECTED;
}
}
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_connect=ok ssid=%s security=%s key_len=%lu carrier=%s",
trans->last_ssid,
trans->last_security[0] ? trans->last_security : "open",
(unsigned long)(key ? strlen(key) : 0),
trans->netdev && netif_carrier_ok(trans->netdev) ? "up" : "down");
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_linux_disconnect(struct pci_dev *dev, char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
struct rb_iwl_disconnect_cmd cmd;
int rc;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc && !trans->nic_active)
rc = -ENODEV;
if (!rc) {
memset(&cmd, 0, sizeof(cmd));
cmd.hdr.id = RB_IWL_CMD_DISCONNECT;
cmd.hdr.len = sizeof(cmd);
cmd.hdr.cookie = (u32)atomic_add_return(1, &rb_iwlwifi_cmd_cookie);
cmd.reason = 3;
rc = iwl_pcie_send_cmd(trans, &cmd, sizeof(cmd));
if (!rc) {
(void)ieee80211_stop_tx_ba_session(&trans->station, 0);
cfg80211_disconnected(trans->netdev, 0, NULL, 0, true, GFP_KERNEL);
netif_carrier_off(trans->netdev);
trans->connected = 0;
trans->bss_conf.assoc = false;
iwl_ops_bss_info_changed(trans->hw, trans->vif, &trans->bss_conf, BSS_CHANGED_ASSOC);
trans->svc_flags &= ~RB_IWL_SVC_CONNECTED;
trans->last_ssid[0] = '\0';
}
}
if (!rc)
rb_iwlwifi_format_out(out, out_len, "linux_kpi_disconnect=ok carrier=%s",
trans->netdev && netif_carrier_ok(trans->netdev) ? "up" : "down");
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_full_init(struct pci_dev *dev, unsigned int bar, int bz_family,
const char *ucode, const char *pnvm,
char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_full_init_locked(trans, bar, bz_family, ucode, pnvm);
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_full_init=ok fw=%s family=%s irq=%d vectors=%d tx_queues=%d rx_bufs=%u mac80211=%d",
trans->fw_name ? trans->fw_name : "none",
rb_iwlwifi_family_name(trans->device_family),
trans->irq,
trans->num_irq_vectors,
trans->num_tx_queues,
trans->rx_queue.n_rb,
trans->mac80211_registered);
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_status(struct pci_dev *dev, char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rb_iwlwifi_status_line(trans, out, out_len);
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}
int rb_iwlwifi_register_mac80211(struct pci_dev *dev, char *out, unsigned long out_len)
{
struct iwl_trans_pcie *trans;
int rc;
if (!dev || !out || out_len == 0)
return -EINVAL;
mutex_lock(&rb_iwlwifi_transport_lock);
rc = rb_iwlwifi_require_transport(dev, &trans);
if (!rc)
rc = rb_iwlwifi_full_init_locked(trans, 0, 0, NULL, NULL);
if (!rc)
rc = rb_iwlwifi_register_mac80211_locked(trans);
if (!rc) {
rb_iwlwifi_format_out(out, out_len,
"linux_kpi_register_mac80211=ok iftype=%u interface_modes=0x%x name=%s",
trans->wdev.iftype,
trans->wiphy ? trans->wiphy->interface_modes : 0,
trans->netdev ? trans->netdev->name : "none");
}
mutex_unlock(&rb_iwlwifi_transport_lock);
return rc;
}