6c2643bd9c
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.
2089 lines
64 KiB
C
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(¶ms, 0, sizeof(params));
|
|
cfg80211_new_sta(trans->netdev, sta->addr, ¶ms, 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;
|
|
}
|