* [PATCH wireless-next v3 08/33] wifi: mm81x: add core.h
From: Lachlan Hodges @ 2026-06-26 6:29 UTC (permalink / raw)
To: johannes, Lachlan Hodges, Dan Callaghan, Arien Judge
Cc: ayman.grais, linux-wireless, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
drivers/net/wireless/morsemicro/mm81x/core.h | 464 +++++++++++++++++++
1 file changed, 464 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/core.h
diff --git a/drivers/net/wireless/morsemicro/mm81x/core.h b/drivers/net/wireless/morsemicro/mm81x/core.h
new file mode 100644
index 000000000000..d5b6dae59bcf
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/core.h
@@ -0,0 +1,464 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_CORE_H_
+#define _MM81X_CORE_H_
+
+#include <net/mac80211.h>
+#include <linux/workqueue.h>
+#include <linux/interrupt.h>
+#include <linux/kfifo.h>
+#include <linux/types.h>
+#include <linux/version.h>
+#include <linux/crc32.h>
+#include <linux/notifier.h>
+#include <linux/nospec.h>
+#include <linux/wait.h>
+#include "yaps.h"
+#include "yaps_hw.h"
+#include "hw.h"
+#include "fw.h"
+#include "rc.h"
+
+#define MM81X_DRIVER_SEMVER_MAJOR 56
+#define MM81X_DRIVER_SEMVER_MINOR 3
+#define MM81X_DRIVER_SEMVER_PATCH 0
+
+#define MM81X_SEMVER_GET_MAJOR(x) (((x) >> 22) & 0x3FF)
+#define MM81X_SEMVER_GET_MINOR(x) (((x) >> 10) & 0xFFF)
+#define MM81X_SEMVER_GET_PATCH(x) ((x) & 0x3FF)
+
+#define DRV_VERSION __stringify(MM81X_VERSION)
+
+#define MM8108_FW_BASE "mm8108"
+
+#define BCF_SIZE_MAX 48
+
+/* Generate a device ID from chip ID, revision, and chip type */
+#define MM81X_DEVICE_ID(chip_id, chip_rev, chip_type) \
+ ((chip_id) | ((chip_rev) << 8) | ((chip_type) << 12))
+
+/* Get constituents of the device ID */
+#define MM81X_DEVICE_GET_CHIP_ID(device_id) ((device_id) & 0xff)
+#define MM81X_DEVICE_GET_CHIP_REV(device_id) ((((device_id) >> 8) & 0xf))
+#define MM81X_DEVICE_GET_CHIP_TYPE(device_id) ((((device_id) >> 12) & 0xf))
+
+#define KHZ100_TO_MHZ(x) ((x) / 10)
+#define KHZ100_TO_KHZ(freq) ((freq) * 100)
+#define KHZ100_TO_HZ(freq) ((freq) * 100000)
+
+#define QDBM_TO_MBM(gain) (((gain) * 100) >> 2)
+#define MBM_TO_QDBM(gain) (((gain) << 2) / 100)
+#define QDBM_TO_DBM(gain) ((gain) / 4)
+
+#define BPS_TO_KBPS(x) ((x) / 1000)
+
+#define NSS_IDX_TO_NSS(x) ((x) + 1)
+#define NSS_TO_NSS_IDX(x) ((x) - 1)
+
+#define ROUND_BYTES_TO_WORD(_nbytes) \
+ (((_nbytes) + 3) & ~((typeof(_nbytes))0x03))
+
+struct mm81x_bus_ops;
+struct mm81x_hif_ops;
+
+#define MM81X_CAPS_MAX_FW_VAL (128)
+
+/* Max number of interfaces */
+#define MM81X_MAX_IF (2)
+
+enum mm81x_caps_flags {
+ MM81X_CAPS_FW_START = 0,
+ MM81X_CAPS_2MHZ = MM81X_CAPS_FW_START,
+ MM81X_CAPS_4MHZ,
+ MM81X_CAPS_8MHZ,
+ MM81X_CAPS_16MHZ,
+ MM81X_CAPS_SGI,
+ MM81X_CAPS_S1G_LONG,
+ MM81X_CAPS_TRAVELING_PILOT_ONE_STREAM,
+ MM81X_CAPS_TRAVELING_PILOT_TWO_STREAM,
+ MM81X_CAPS_MU_BEAMFORMEE,
+ MM81X_CAPS_MU_BEAMFORMER,
+ MM81X_CAPS_RD_RESPONDER,
+ MM81X_CAPS_STA_TYPE_SENSOR,
+ MM81X_CAPS_STA_TYPE_NON_SENSOR,
+ MM81X_CAPS_GROUP_AID,
+ MM81X_CAPS_NON_TIM,
+ MM81X_CAPS_TIM_ADE,
+ MM81X_CAPS_BAT,
+ MM81X_CAPS_DYNAMIC_AID,
+ MM81X_CAPS_UPLINK_SYNC,
+ MM81X_CAPS_FLOW_CONTROL,
+ MM81X_CAPS_AMPDU,
+ MM81X_CAPS_AMSDU,
+ MM81X_CAPS_1MHZ_CONTROL_RESPONSE_PREAMBLE,
+ MM81X_CAPS_PAGE_SLICING,
+ MM81X_CAPS_RAW,
+ MM81X_CAPS_MCS8,
+ MM81X_CAPS_MCS9,
+ MM81X_CAPS_ASYMMETRIC_BA_SUPPORT,
+ MM81X_CAPS_DAC,
+ MM81X_CAPS_CAC,
+ MM81X_CAPS_TXOP_SHARING_IMPLICIT_ACK,
+ MM81X_CAPS_NDP_PSPOLL,
+ MM81X_CAPS_FRAGMENT_BA,
+ MM81X_CAPS_OBSS_MITIGATION,
+ MM81X_CAPS_TMP_PS_MODE_SWITCH,
+ MM81X_CAPS_SECTOR_TRAINING,
+ MM81X_CAPS_UNSOLICIT_DYNAMIC_AID,
+ MM81X_CAPS_NDP_BEAMFORMING_REPORT,
+ MM81X_CAPS_MCS_NEGOTIATION,
+ MM81X_CAPS_DUPLICATE_1MHZ,
+ MM81X_CAPS_TACK_AS_PSPOLL,
+ MM81X_CAPS_PV1,
+ MM81X_CAPS_TWT_RESPONDER,
+ MM81X_CAPS_TWT_REQUESTER,
+ MM81X_CAPS_BDT,
+ MM81X_CAPS_TWT_GROUPING,
+ MM81X_CAPS_LINK_ADAPTATION_WO_NDP_CMAC,
+ MM81X_CAPS_LONG_MPDU,
+ MM81X_CAPS_TXOP_SECTORIZATION,
+ MM81X_CAPS_GROUP_SECTORIZATION,
+ MM81X_CAPS_HTC_VHT,
+ MM81X_CAPS_HTC_VHT_MFB,
+ MM81X_CAPS_HTC_VHT_MRQ,
+ MM81X_CAPS_2SS,
+ MM81X_CAPS_3SS,
+ MM81X_CAPS_4SS,
+ MM81X_CAPS_SU_BEAMFORMEE,
+ MM81X_CAPS_SU_BEAMFORMER,
+ MM81X_CAPS_RX_STBC,
+ MM81X_CAPS_TX_STBC,
+ MM81X_CAPS_RX_LDPC,
+ MM81X_CAPS_HW_FRAGMENT,
+
+ MM81X_CAPS_FW_END = MM81X_CAPS_MAX_FW_VAL,
+ MM81X_CAPS_LAST = MM81X_CAPS_FW_END,
+};
+
+struct mm81x_fw_caps {
+ u32 flags[FW_CAPABILITIES_FLAGS_WIDTH];
+ u8 ampdu_mss;
+ u8 beamformee_sts_capability;
+ u8 number_sounding_dimensions;
+ u8 maximum_ampdu_length_exponent;
+ u8 mm81x_mmss_offset;
+};
+
+#define MM81X_FW_SUPP(MM81X_CAPS, CAPABILITY) \
+ mm81x_caps_supported(MM81X_CAPS, MM81X_CAPS_##CAPABILITY)
+
+static inline bool mm81x_caps_supported(struct mm81x_fw_caps *caps,
+ enum mm81x_caps_flags flag)
+{
+ const unsigned long *flags_ptr = (unsigned long *)caps->flags;
+
+ return test_bit(flag, flags_ptr);
+}
+
+struct mm81x_ps {
+ u32 wakers;
+ bool enable;
+ bool suspended;
+ /* PS state lock */
+ struct mutex lock;
+ struct delayed_work delayed_eval_work;
+};
+
+enum mm81x_page_aci {
+ MM81X_ACI_BE = 0,
+ MM81X_ACI_BK = 1,
+ MM81X_ACI_VI = 2,
+ MM81X_ACI_VO = 3,
+};
+
+enum mm81x_qos_tid_up_index {
+ MM81X_QOS_TID_UP_BK = 1,
+ MM81X_QOS_TID_UP_XX = 2,
+ MM81X_QOS_TID_UP_BE = 0,
+ MM81X_QOS_TID_UP_EE = 3,
+ MM81X_QOS_TID_UP_CL = 4,
+ MM81X_QOS_TID_UP_VI = 5,
+ MM81X_QOS_TID_UP_VO = 6,
+ MM81X_QOS_TID_UP_NC = 7,
+
+ MM81X_QOS_TID_UP_LOWEST = MM81X_QOS_TID_UP_BK,
+ MM81X_QOS_TID_UP_HIGHEST = MM81X_QOS_TID_UP_NC
+};
+
+struct mm81x_sw_version {
+ u8 major;
+ u8 minor;
+ u8 patch;
+};
+
+struct mm81x_sta {
+ const struct ieee80211_vif *vif;
+ u8 addr[ETH_ALEN];
+ enum ieee80211_sta_state state;
+ bool tid_tx[IEEE80211_NUM_TIDS];
+ bool tid_start_tx[IEEE80211_NUM_TIDS];
+ u8 tid_params[IEEE80211_NUM_TIDS];
+ int max_bw_mhz;
+ struct mm81x_rc_sta rc;
+ struct mmrc_rate last_sta_tx_rate;
+ s16 avg_rssi;
+ bool tx_ps_filter_en;
+};
+
+struct mm81x_vif {
+ struct mm81x *mors;
+ u16 id;
+
+ union {
+ struct {
+ bool is_assoc;
+ } sta;
+ struct {
+ u32 num_stas;
+ struct work_struct beacon_work;
+ } ap;
+ } u;
+};
+
+struct mm81x_stale_tx_status {
+ /* Stale Tx lock */
+ spinlock_t lock;
+ struct timer_list timer;
+};
+
+struct mcast_filter {
+ u8 count;
+ /*
+ * Integer representation of the last four bytes of a multicast MAC
+ * address. The first two bytes are always 0x0100 (IPv4) or 0x3333
+ * (IPv6).
+ */
+ __le32 addr_list[];
+};
+
+enum mm81x_hw_scan_op {
+ MM81X_HW_SCAN_OP_START,
+ MM81X_HW_SCAN_OP_STOP,
+};
+
+struct mm81x_hw_scan_params {
+ struct ieee80211_hw *hw;
+
+ /* vif which initiated the scan */
+ struct ieee80211_vif *vif;
+ bool has_directed_ssid;
+ u32 dwell_time_ms;
+ u32 dwell_on_home_ms;
+ enum mm81x_hw_scan_op operation;
+ bool store;
+ struct sk_buff *probe_req;
+ u16 num_chans;
+ u16 allocated_chans;
+
+ struct {
+ struct ieee80211_channel *channel;
+ /* Index into @ref powers_qdbm for the power of this channel */
+ u8 power_idx;
+ } *channels;
+
+ s32 *powers_qdbm;
+ u8 n_powers;
+};
+
+enum mm81x_hw_scan_state {
+ HW_SCAN_STATE_IDLE,
+ HW_SCAN_STATE_RUNNING,
+ HW_SCAN_STATE_ABORTING,
+};
+
+struct mm81x_hw_scan {
+ enum mm81x_hw_scan_state state;
+ struct completion scan_done;
+ struct mm81x_hw_scan_params *params;
+ struct delayed_work timeout;
+ u32 home_dwell_ms;
+};
+
+enum mm81x_hif_event_flags {
+ MM81X_HIF_EVT_RX_PEND,
+ MM81X_HIF_EVT_PAGE_RETURN_PEND,
+ MM81X_HIF_EVT_TX_COMMAND_PEND,
+ MM81X_HIF_EVT_TX_BEACON_PEND,
+ MM81X_HIF_EVT_TX_MGMT_PEND,
+ MM81X_HIF_EVT_TX_DATA_PEND,
+ MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
+ MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,
+ MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
+ MM81X_HIF_EVT_UPDATE_HW_CLOCK_REFERENCE,
+};
+
+enum mm81x_state_flags {
+ MM81X_STATE_CHIP_UNRESPONSIVE,
+ MM81X_STATE_DATA_QS_STOPPED,
+ MM81X_STATE_DATA_TX_STOPPED,
+ MM81X_STATE_REGDOM_SET_BY_USER,
+ MM81X_STATE_REGDOM_SET_BY_OTP,
+ MM81X_STATE_RELOAD_FW_AFTER_START,
+ MM81X_STATE_HOST_TO_CHIP_TX_BLOCKED,
+ MM81X_STATE_HOST_TO_CHIP_CMD_BLOCKED,
+};
+
+#define MM81X_COUNTRY_LEN (3)
+#define INVALID_VIF_INDEX 0xFF
+
+struct mm81x {
+ u32 chip_id;
+ u32 host_table_ptr;
+
+ /* Refer to @enum mm81x_bus_type */
+ u32 bus_type;
+ u32 bcf_address;
+
+ /*
+ * Parsed from the release tag, which should be in the format
+ * 'rel_<major>_<minor>_<patch>'. If the tag is not in this format
+ * then corresponding version field will be 0.
+ */
+ struct mm81x_sw_version sw_ver;
+ u8 macaddr[ETH_ALEN];
+ u8 country[MM81X_COUNTRY_LEN];
+
+ /* Mask of type @enum host_table_firmware_flags */
+ u32 firmware_flags;
+ struct mm81x_fw_caps fw_caps;
+ bool started;
+ bool chip_was_reset;
+ struct wiphy *wiphy;
+ struct mm81x_hw_scan hw_scan;
+ struct ieee80211_hw *hw;
+ struct device *dev;
+
+ struct ieee80211_vif __rcu *vifs[MM81X_MAX_IF];
+
+ /* @mm81x_state_flags */
+ unsigned long state_flags;
+
+ u16 cmd_seq;
+ struct completion *cmd_comp;
+ /* Serialises commands */
+ struct mutex cmd_lock;
+
+ /* Serialises command completion */
+ struct mutex cmd_wait;
+
+ const struct mm81x_regs *regs;
+
+ struct {
+ union {
+ struct mm81x_yaps yaps;
+ } u;
+ const struct mm81x_hif_ops *ops;
+ /* See @enum mm81x_hif_event_flags for values */
+ unsigned long event_flags;
+ bool validate_skb_checksum;
+ } hif;
+
+ struct workqueue_struct *chip_wq;
+ struct work_struct hif_work;
+ struct work_struct usb_irq_work;
+ struct mm81x_stale_tx_status stale_status;
+ bool config_ps;
+ struct mm81x_ps ps;
+
+ /* Tx power in mBm received from the FW before association */
+ s32 tx_power_mbm;
+ s32 tx_max_power_mbm;
+
+ const struct mm81x_bus_ops *bus_ops;
+ struct mm81x_rc mrc;
+ int rts_threshold;
+ struct workqueue_struct *net_wq;
+ struct work_struct tx_stale_work;
+ wait_queue_head_t tx_empty_waitq;
+
+ struct cfg80211_chan_def chandef;
+ struct mcast_filter *mcast_filter;
+ atomic_t num_bcn_vifs;
+ unsigned long beacon_irqs_enabled;
+ u8 drv_priv[] __aligned(sizeof(void *));
+};
+
+/* Map from mac80211 queue to Morse ACI value for page metadata */
+static inline u8 map_mac80211q_2_mm81x_aci(u16 mac80211queue)
+{
+ switch (mac80211queue) {
+ case IEEE80211_AC_VO:
+ return MM81X_ACI_VO;
+ case IEEE80211_AC_VI:
+ return MM81X_ACI_VI;
+ case IEEE80211_AC_BK:
+ return MM81X_ACI_BK;
+ default:
+ return MM81X_ACI_BE;
+ }
+}
+
+static inline enum mm81x_page_aci
+dot11_tid_to_ac(enum mm81x_qos_tid_up_index tid)
+{
+ switch (tid) {
+ case MM81X_QOS_TID_UP_BK:
+ case MM81X_QOS_TID_UP_XX:
+ return MM81X_ACI_BK;
+ case MM81X_QOS_TID_UP_CL:
+ case MM81X_QOS_TID_UP_VI:
+ return MM81X_ACI_VI;
+ case MM81X_QOS_TID_UP_VO:
+ case MM81X_QOS_TID_UP_NC:
+ return MM81X_ACI_VO;
+ case MM81X_QOS_TID_UP_BE:
+ case MM81X_QOS_TID_UP_EE:
+ default:
+ return MM81X_ACI_BE;
+ }
+}
+
+static inline bool mm81x_is_data_tx_allowed(struct mm81x *mors)
+{
+ return !test_bit(MM81X_STATE_DATA_TX_STOPPED, &mors->state_flags) &&
+ !test_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND,
+ &mors->hif.event_flags);
+}
+
+static inline struct ieee80211_vif *
+mm81x_vif_to_ieee80211_vif(struct mm81x_vif *mors_vif)
+{
+ return container_of((void *)mors_vif, struct ieee80211_vif, drv_priv);
+}
+
+static inline struct mm81x_vif *
+ieee80211_vif_to_mors_vif(struct ieee80211_vif *vif)
+{
+ return (struct mm81x_vif *)vif->drv_priv;
+}
+
+static inline struct mm81x *mm81x_vif_to_mors(struct mm81x_vif *mors_vif)
+{
+ return mors_vif->mors;
+}
+
+static inline u32 mm81x_generate_cssid(const u8 *ssid, u8 len)
+{
+ return ~crc32(~0, ssid, len);
+}
+
+int mm81x_beacon_init(struct mm81x_vif *mors_vif);
+void mm81x_beacon_finish(struct mm81x_vif *mors_vif);
+void mm81x_beacon_irq_handle(struct mm81x *mors, u32 status);
+char *mm81x_core_get_fw_path(u32 chip_id);
+struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev);
+int mm81x_core_init(struct mm81x *mors);
+int mm81x_core_register(struct mm81x *mors);
+void mm81x_core_unregister(struct mm81x *mors);
+void mm81x_core_deinit(struct mm81x *mors);
+void mm81x_core_free(struct mm81x *mors);
+
+#endif /* !_MM81X_MM81X_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 07/33] wifi: mm81x: add core.c
From: Lachlan Hodges @ 2026-06-26 6:29 UTC (permalink / raw)
To: johannes, Lachlan Hodges, Dan Callaghan, Arien Judge
Cc: ayman.grais, linux-wireless, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
drivers/net/wireless/morsemicro/mm81x/core.c | 142 +++++++++++++++++++
1 file changed, 142 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/core.c
diff --git a/drivers/net/wireless/morsemicro/mm81x/core.c b/drivers/net/wireless/morsemicro/mm81x/core.c
new file mode 100644
index 000000000000..33548a6aea6c
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/core.c
@@ -0,0 +1,142 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#include <linux/module.h>
+#include "core.h"
+#include "bus.h"
+#include "hif.h"
+#include "mac.h"
+
+static int mm81x_core_attach_regs(struct mm81x *mors)
+{
+ int ret = 0;
+
+ mm81x_claim_bus(mors);
+ ret = mm81x_reg32_read(mors, MM8108_REG_CHIP_ID, &mors->chip_id);
+ mm81x_release_bus(mors);
+
+ if (ret < 0) {
+ dev_err(mors->dev, "failed to read chip id %d", ret);
+ return ret;
+ }
+
+ switch (mors->chip_id) {
+ case (MM8108B2_ID):
+ mors->regs = &mm8108_regs;
+ mors->hif.ops = &mm81x_yaps_ops;
+ break;
+ default:
+ return -ENODEV;
+ }
+
+ return ret;
+}
+
+static char *mm81x_core_get_revision_string(u32 chip_id)
+{
+ u8 chip_rev = MM81X_DEVICE_GET_CHIP_REV(chip_id);
+
+ switch (chip_rev) {
+ case MM8108B2_REV:
+ return MM8108B2_REV_STRING;
+ default:
+ return "??";
+ }
+}
+
+static void mm81x_core_init_mac_addr(struct mm81x *mors)
+{
+ int ret = mm81x_hw_otp_get_mac_addr(mors);
+
+ if (ret || !is_valid_ether_addr(mors->macaddr))
+ eth_random_addr(mors->macaddr);
+}
+
+char *mm81x_core_get_fw_path(u32 chip_id)
+{
+ return kasprintf(GFP_KERNEL,
+ MM81X_FW_DIR "/" MM8108_FW_BASE
+ "%s" FW_ROM_LINKED_STRING MM81X_FW_EXT,
+ mm81x_core_get_revision_string(chip_id));
+}
+EXPORT_SYMBOL_GPL(mm81x_core_get_fw_path);
+
+struct mm81x *mm81x_core_alloc(size_t priv_size, struct device *dev)
+{
+ return mm81x_mac_alloc(priv_size, dev);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_alloc);
+
+int mm81x_core_init(struct mm81x *mors)
+{
+ int ret;
+
+ set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
+ set_bit(MM81X_STATE_RELOAD_FW_AFTER_START, &mors->state_flags);
+
+ mm81x_core_init_mac_addr(mors);
+
+ ret = mm81x_core_attach_regs(mors);
+ if (ret)
+ return ret;
+
+ mors->chip_wq = create_singlethread_workqueue("chip_wq");
+ if (!mors->chip_wq)
+ return -ENOMEM;
+
+ mors->net_wq = create_singlethread_workqueue("net_wq");
+ if (!mors->net_wq) {
+ ret = -ENOMEM;
+ goto err_chip_wq;
+ }
+
+ ret = mm81x_hif_init(mors);
+ if (ret)
+ goto err_wqs;
+
+ return 0;
+
+err_wqs:
+ flush_workqueue(mors->net_wq);
+ destroy_workqueue(mors->net_wq);
+
+err_chip_wq:
+ flush_workqueue(mors->chip_wq);
+ destroy_workqueue(mors->chip_wq);
+
+ return ret;
+}
+EXPORT_SYMBOL_GPL(mm81x_core_init);
+
+int mm81x_core_register(struct mm81x *mors)
+{
+ return mm81x_mac_register(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_register);
+
+void mm81x_core_unregister(struct mm81x *mors)
+{
+ mm81x_mac_unregister(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_unregister);
+
+void mm81x_core_deinit(struct mm81x *mors)
+{
+ mm81x_hif_finish(mors);
+ flush_workqueue(mors->net_wq);
+ destroy_workqueue(mors->net_wq);
+ flush_workqueue(mors->chip_wq);
+ destroy_workqueue(mors->chip_wq);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_deinit);
+
+void mm81x_core_free(struct mm81x *mors)
+{
+ mm81x_mac_free(mors);
+}
+EXPORT_SYMBOL_GPL(mm81x_core_free);
+
+MODULE_AUTHOR("Morse Micro");
+MODULE_DESCRIPTION("Driver support for Morse Micro MM81X core");
+MODULE_LICENSE("Dual BSD/GPL");
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 06/33] wifi: mm81x: add command.h
From: Lachlan Hodges @ 2026-06-26 6:29 UTC (permalink / raw)
To: johannes, Lachlan Hodges, Dan Callaghan, Arien Judge
Cc: ayman.grais, linux-wireless, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
.../net/wireless/morsemicro/mm81x/command.h | 85 +++++++++++++++++++
1 file changed, 85 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/command.h
diff --git a/drivers/net/wireless/morsemicro/mm81x/command.h b/drivers/net/wireless/morsemicro/mm81x/command.h
new file mode 100644
index 000000000000..0ea796f1d878
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/command.h
@@ -0,0 +1,85 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_COMMAND_H_
+#define _MM81X_COMMAND_H_
+
+#include <linux/skbuff.h>
+#include <linux/workqueue.h>
+#include "core.h"
+#include "command_defs.h"
+
+#define HOST_CMD_IS_REQ(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_REQ)
+#define HOST_CMD_IS_RESP(cmd) \
+ (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_RESP)
+#define HOST_CMD_IS_EVT(cmd) (le16_to_cpu((cmd)->hdr.flags) & HOST_CMD_TYPE_EVT)
+
+struct mm81x_queue_params;
+
+enum mm81x_cmd_return_code {
+ MM81X_RET_SUCCESS = 0,
+ MM81X_RET_EPERM = -1,
+ MM81X_RET_ENOMEM = -12,
+ MM81X_RET_CMD_NOT_HANDLED = -32757,
+};
+
+#define HOST_CMD_HOST_ID_SEQ_MAX 0xFFF
+#define HOST_CMD_HOST_ID_RETRY_MASK 0x000F
+#define HOST_CMD_HOST_ID_SEQ_SHIFT 4
+#define HOST_CMD_HOST_ID_SEQ_MASK 0xFFF0
+
+struct host_cmd_req {
+ struct host_cmd_header hdr;
+ u8 data[];
+} __packed;
+
+struct host_cmd_resp {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 data[];
+} __packed;
+
+struct host_cmd_event {
+ struct host_cmd_header hdr;
+ u8 data[];
+} __packed;
+
+int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb);
+int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,
+ enum nl80211_iftype type);
+int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,
+ struct mm81x_fw_caps *capabilities);
+int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params);
+int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,
+ bool enabled);
+int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,
+ u16 dtim_period, u32 cssid);
+int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
+ u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,
+ s32 *power_mbm);
+int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm);
+int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,
+ int txpower_mbm);
+int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,
+ bool store);
+int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled);
+int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,
+ struct mm81x_vif *mors_vif);
+int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,
+ struct ieee80211_sta *sta,
+ enum ieee80211_sta_state state);
+int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+ u16 aid, struct ieee80211_key_conf *key,
+ enum host_cmd_key_cipher cipher,
+ enum host_cmd_aes_key_len length);
+int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+ u16 aid, struct ieee80211_key_conf *key);
+int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id);
+int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold);
+int mm81x_cmd_get_disabled_channels(
+ struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,
+ uint resp_len);
+
+#endif /* !_MM81X_COMMAND_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 05/33] wifi: mm81x: add command_defs.h
From: Lachlan Hodges @ 2026-06-26 6:29 UTC (permalink / raw)
To: johannes, Lachlan Hodges, Dan Callaghan, Arien Judge
Cc: ayman.grais, linux-wireless, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
.../wireless/morsemicro/mm81x/command_defs.h | 1658 +++++++++++++++++
1 file changed, 1658 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/command_defs.h
diff --git a/drivers/net/wireless/morsemicro/mm81x/command_defs.h b/drivers/net/wireless/morsemicro/mm81x/command_defs.h
new file mode 100644
index 000000000000..91a4ac09ad80
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/command_defs.h
@@ -0,0 +1,1658 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+#ifndef _MM81X_COMMAND_DEFS_H_
+#define _MM81X_COMMAND_DEFS_H_
+
+#include <linux/types.h>
+
+#define __sle16 __le16
+#define __sle32 __le32
+#define __sle64 __le64
+
+#define HOST_CMD_SEMVER_MAJOR 56
+#define HOST_CMD_SEMVER_MINOR 17
+#define HOST_CMD_SEMVER_PATCH 0
+
+#define HOST_CMD_TYPE_REQ BIT(0)
+#define HOST_CMD_TYPE_RESP BIT(1)
+#define HOST_CMD_TYPE_EVT BIT(2)
+
+#define HOST_CMD_SSID_MAX_LEN 32
+#define HOST_CMD_MAC_ADDR_LEN 6
+
+enum host_cmd_id {
+ HOST_CMD_ID_SET_CHANNEL = 0x0001,
+ HOST_CMD_ID_GET_CHANNEL = 0x001D,
+ HOST_CMD_ID_GET_CHANNEL_FULL = 0x0013,
+ HOST_CMD_ID_GET_CHANNEL_DTIM = 0x001C,
+ HOST_CMD_ID_GET_VERSION = 0x0002,
+ HOST_CMD_ID_SET_TXPOWER = 0x0003,
+ HOST_CMD_ID_GET_MAX_TXPOWER = 0x0024,
+ HOST_CMD_ID_ADD_INTERFACE = 0x0004,
+ HOST_CMD_ID_REMOVE_INTERFACE = 0x0005,
+ HOST_CMD_ID_BSS_CONFIG = 0x0006,
+ HOST_CMD_ID_SCAN_CONFIG = 0x0010,
+ HOST_CMD_ID_SET_QOS_PARAMS = 0x0011,
+ HOST_CMD_ID_GET_QOS_PARAMS = 0x0012,
+ HOST_CMD_ID_SET_STA_STATE = 0x0014,
+ HOST_CMD_ID_SET_BSS_COLOR = 0x0015,
+ HOST_CMD_ID_CONFIG_PS = 0x0016,
+ HOST_CMD_ID_HEALTH_CHECK = 0x0019,
+ HOST_CMD_ID_CTS_SELF_PS = 0x001A,
+ HOST_CMD_ID_DTIM_CHANNEL_ENABLE = 0x001B,
+ HOST_CMD_ID_ARP_OFFLOAD = 0x0020,
+ HOST_CMD_ID_SET_LONG_SLEEP_CONFIG = 0x0021,
+ HOST_CMD_ID_SET_DUTY_CYCLE = 0x0022,
+ HOST_CMD_ID_GET_DUTY_CYCLE = 0x0023,
+ HOST_CMD_ID_GET_CAPABILITIES = 0x0025,
+ HOST_CMD_ID_TWT_AGREEMENT_INSTALL = 0x0026,
+ HOST_CMD_ID_TWT_AGREEMENT_VALIDATE = 0x0036,
+ HOST_CMD_ID_TWT_AGREEMENT_REMOVE = 0x0027,
+ HOST_CMD_ID_GET_TSF = 0x0028,
+ HOST_CMD_ID_MAC_ADDR = 0x0029,
+ HOST_CMD_ID_MPSW_CONFIG = 0x0030,
+ HOST_CMD_ID_INSTALL_KEY = 0x000A,
+ HOST_CMD_ID_DISABLE_KEY = 0x000B,
+ HOST_CMD_ID_DHCP_OFFLOAD = 0x0032,
+ HOST_CMD_ID_SET_KEEP_ALIVE_OFFLOAD = 0x0033,
+ HOST_CMD_ID_UPDATE_OUI_FILTER = 0x0034,
+ HOST_CMD_ID_IBSS_CONFIG = 0x0035,
+ HOST_CMD_ID_OCS = 0x0038,
+ HOST_CMD_ID_MESH_CONFIG = 0x0039,
+ HOST_CMD_ID_SET_OFFSET_TSF = 0x003A,
+ HOST_CMD_ID_GET_CHANNEL_USAGE = 0x003B,
+ HOST_CMD_ID_MCAST_FILTER = 0x003C,
+ HOST_CMD_ID_BSS_BEACON_CONFIG = 0x003D,
+ HOST_CMD_ID_UAPSD_CONFIG = 0x0040,
+ HOST_CMD_ID_PAGE_SLICING_CONFIG = 0x0043,
+ HOST_CMD_ID_HW_SCAN = 0x0044,
+ HOST_CMD_ID_SET_WHITELIST = 0x0045,
+ HOST_CMD_ID_ARP_PERIODIC_REFRESH = 0x0046,
+ HOST_CMD_ID_SET_TCP_KEEPALIVE = 0x0047,
+ HOST_CMD_ID_FORCE_POWER_MODE = 0x0048,
+ HOST_CMD_ID_LI_SLEEP = 0x0049,
+ HOST_CMD_ID_GET_DISABLED_CHANNELS = 0x004A,
+ HOST_CMD_ID_SET_CQM_RSSI = 0x004F,
+ HOST_CMD_ID_GET_APF_CAPABILITIES = 0x0050,
+ HOST_CMD_ID_READ_WRITE_APF = 0x0051,
+ HOST_CMD_ID_BSSID_SET = 0x0052,
+ HOST_CMD_ID_BEACON_OFFLOAD = 0x0053,
+ HOST_CMD_ID_PROBE_RESPONSE_OFFLOAD = 0x0054,
+ HOST_CMD_ID_HOST_STATS_LOG = 0x2007,
+ HOST_CMD_ID_HOST_STATS_RESET = 0x2008,
+ HOST_CMD_ID_MAC_STATS_LOG = 0x200C,
+ HOST_CMD_ID_MAC_STATS_RESET = 0x200D,
+ HOST_CMD_ID_UPHY_STATS_LOG = 0x200E,
+ HOST_CMD_ID_UPHY_STATS_RESET = 0x200F,
+ HOST_CMD_ID_SET_STA_TYPE = 0xA000,
+ HOST_CMD_ID_SET_ENC_MODE = 0xA001,
+ HOST_CMD_ID_TEST_BA = 0xA002,
+ HOST_CMD_ID_SET_LISTEN_INTERVAL = 0xA003,
+ HOST_CMD_ID_SET_AMPDU = 0xA004,
+ HOST_CMD_ID_COREDUMP = 0xA006,
+ HOST_CMD_ID_SET_S1G_OP_CLASS = 0xA007,
+ HOST_CMD_ID_SEND_WAKE_ACTION_FRAME = 0xA008,
+ HOST_CMD_ID_VENDOR_IE_CONFIG = 0xA009,
+ HOST_CMD_ID_SET_TWT_CONF = 0xA010,
+ HOST_CMD_ID_GET_AVAILABLE_CHANNELS = 0xA011,
+ HOST_CMD_ID_SET_ECSA_S1G_INFO = 0xA012,
+ HOST_CMD_ID_GET_HW_VERSION = 0xA013,
+ HOST_CMD_ID_CAC = 0xA014,
+ HOST_CMD_ID_DRIVER_SET_DUTY_CYCLE = 0xA015,
+ HOST_CMD_ID_OCS_DRIVER = 0xA017,
+ HOST_CMD_ID_MBSSID = 0xA016,
+ HOST_CMD_ID_SET_MESH_CONFIG = 0xA018,
+ HOST_CMD_ID_SET_MCBA_CONF = 0xA019,
+ HOST_CMD_ID_DYNAMIC_PEERING_CONFIG = 0xA020,
+ HOST_CMD_ID_CONFIG_RAW = 0xA021,
+ HOST_CMD_ID_CONFIG_BSS_STATS = 0xA022,
+ HOST_CMD_ID_GET_RSSI = 0x1002,
+ HOST_CMD_ID_SET_IFS = 0x1003,
+ HOST_CMD_ID_SET_FEM_SETTINGS = 0x1005,
+ HOST_CMD_ID_SET_TXOP = 0x1008,
+ HOST_CMD_ID_SET_CONTROL_RESPONSE = 0x1009,
+ HOST_CMD_ID_SET_PERIODIC_CAL = 0x100A,
+ HOST_CMD_ID_SET_BCN_RSSI_THRESHOLD = 0x100B,
+ HOST_CMD_ID_SET_TX_PKT_LIFETIME_USECS = 0x100C,
+ HOST_CMD_ID_SET_PHYSM_WATCHDOG = 0x100D,
+ HOST_CMD_ID_TX_POLAR = 0x100E,
+ HOST_CMD_ID_EVT_STA_STATE = 0x4001,
+ HOST_CMD_ID_EVT_BEACON_LOSS = 0x4002,
+ HOST_CMD_ID_EVT_SIG_FIELD_ERROR = 0x4003,
+ HOST_CMD_ID_EVT_UMAC_TRAFFIC_CONTROL = 0x4004,
+ HOST_CMD_ID_EVT_DHCP_LEASE_UPDATE = 0x4005,
+ HOST_CMD_ID_EVT_OCS_DONE = 0x4006,
+ HOST_CMD_ID_EVT_HW_SCAN_DONE = 0x4011,
+ HOST_CMD_ID_EVT_CHANNEL_USAGE = 0x4012,
+ HOST_CMD_ID_EVT_CONNECTION_LOSS = 0x4013,
+ HOST_CMD_ID_EVT_SCHED_SCAN_RESULTS = 0x4014,
+ HOST_CMD_ID_EVT_CQM_RSSI_NOTIFY = 0x4015,
+ HOST_CMD_ID_EVT_SCAN_DONE = 0x4007,
+ HOST_CMD_ID_EVT_SCAN_RESULT = 0x4008,
+ HOST_CMD_ID_EVT_CONNECTED = 0x4009,
+ HOST_CMD_ID_EVT_DISCONNECTED = 0x4010,
+ HOST_CMD_ID_EVT_BEACON_FILTER_MATCH = 0x4016,
+ HOST_CMD_ID_SET_CAPABILITIES = 0x8118,
+ HOST_CMD_ID_SET_TRANSMISSION_RATE = 0x8009,
+ HOST_CMD_ID_FORCE_ASSERT = 0x800E,
+ HOST_CMD_ID_GET_SET_GENERIC_PARAM = 0x003E,
+};
+
+struct host_cmd_mac_addr {
+ u8 octet[HOST_CMD_MAC_ADDR_LEN];
+};
+
+enum host_cmd_ocs_subcmd {
+ HOST_CMD_OCS_SUBCMD_CONFIG = 1,
+ HOST_CMD_OCS_SUBCMD_STATUS = 2,
+};
+
+enum host_cmd_headless_cfg_option {
+ HOST_CMD_HEADLESS_CFG_OPTION_KEEP_IFACES = BIT(0),
+ HOST_CMD_HEADLESS_CFG_OPTION_BUFFER_RX = BIT(1),
+ HOST_CMD_HEADLESS_CFG_OPTION_NOTIFY_ON_ANY_RX = BIT(2),
+};
+
+struct host_cmd_header {
+ __le16 flags;
+ __le16 message_id;
+ __le16 len;
+ __le16 host_id;
+ __le16 vif_id;
+ __le16 pad;
+};
+
+#define HOST_CMD_CHANNEL_BW_NOT_SET 0xFF
+#define HOST_CMD_CHANNEL_IDX_NOT_SET 0xFF
+#define HOST_CMD_CHANNEL_FREQ_NOT_SET 0xFFFFFFFF
+
+enum host_cmd_dot11_proto_mode {
+ HOST_CMD_DOT11_PROTO_MODE_AH = 0,
+};
+
+struct host_cmd_req_set_channel {
+ struct host_cmd_header hdr;
+ __le32 op_chan_freq_hz;
+ u8 op_bw_mhz;
+ u8 pri_bw_mhz;
+ u8 pri_1mhz_chan_idx;
+ u8 dot11_mode;
+ u8 __deprecated_reg_tx_power_set;
+ u8 is_off_channel;
+} __packed;
+
+struct host_cmd_resp_set_channel {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __sle32 power_qdbm;
+} __packed;
+
+struct host_cmd_req_get_channel {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_channel {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 op_chan_freq_hz;
+ u8 op_chan_bw_mhz;
+ u8 pri_chan_bw_mhz;
+ u8 pri_1mhz_chan_idx;
+} __packed;
+
+#define HOST_CMD_MAX_VERSION_LEN 128
+
+struct host_cmd_req_get_version {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_version {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __sle32 length;
+ u8 version[];
+} __packed;
+
+struct host_cmd_req_set_txpower {
+ struct host_cmd_header hdr;
+ __sle32 power_qdbm;
+} __packed;
+
+struct host_cmd_resp_set_txpower {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __sle32 power_qdbm;
+} __packed;
+
+struct host_cmd_req_get_max_txpower {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_max_txpower {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __sle32 power_qdbm;
+} __packed;
+
+enum host_cmd_interface_type {
+ HOST_CMD_INTERFACE_TYPE_INVALID = 0,
+ HOST_CMD_INTERFACE_TYPE_STA = 1,
+ HOST_CMD_INTERFACE_TYPE_AP = 2,
+ HOST_CMD_INTERFACE_TYPE_MON = 3,
+ HOST_CMD_INTERFACE_TYPE_ADHOC = 4,
+ HOST_CMD_INTERFACE_TYPE_MESH = 5,
+ HOST_CMD_INTERFACE_TYPE_LAST = HOST_CMD_INTERFACE_TYPE_MESH,
+};
+
+struct host_cmd_req_add_interface {
+ struct host_cmd_header hdr;
+ struct host_cmd_mac_addr addr;
+ __le32 interface_type;
+} __packed;
+
+struct host_cmd_resp_add_interface {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_remove_interface {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_remove_interface {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_bss_config {
+ struct host_cmd_header hdr;
+ __le16 beacon_interval_tu;
+ __le16 dtim_period;
+ u8 __padding[2];
+ __le32 cssid;
+} __packed;
+
+struct host_cmd_resp_bss_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_scan_config {
+ struct host_cmd_header hdr;
+ u8 enabled;
+ u8 is_survey;
+} __packed;
+
+struct host_cmd_resp_scan_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_qos_params {
+ struct host_cmd_header hdr;
+ u8 uapsd;
+ u8 queue_idx;
+ u8 aifs_slot_count;
+ __le16 contention_window_min;
+ __le16 contention_window_max;
+ __le32 max_txop_usec;
+} __packed;
+
+struct host_cmd_resp_set_qos_params {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_get_qos_params {
+ struct host_cmd_header hdr;
+ u8 queue_idx;
+} __packed;
+
+struct host_cmd_resp_get_qos_params {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 aifs_slot_count;
+ __le16 contention_window_min;
+ __le16 contention_window_max;
+ __le32 max_txop_usec;
+} __packed;
+
+struct host_cmd_req_set_sta_state {
+ struct host_cmd_header hdr;
+ u8 sta_addr[HOST_CMD_MAC_ADDR_LEN];
+ __le16 aid;
+ __le16 state;
+ u8 uapsd_queues;
+ __le32 flags;
+} __packed;
+
+struct host_cmd_resp_set_sta_state {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_bss_color {
+ struct host_cmd_header hdr;
+ u8 bss_color;
+} __packed;
+
+struct host_cmd_resp_set_bss_color {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_config_ps {
+ struct host_cmd_header hdr;
+ u8 enabled;
+ u8 dynamic_ps_offload;
+} __packed;
+
+struct host_cmd_resp_config_ps {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_health_check {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_health_check {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_cts_self_ps {
+ struct host_cmd_header hdr;
+ u8 enable;
+} __packed;
+
+struct host_cmd_resp_cts_self_ps {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_dtim_channel_enable {
+ struct host_cmd_header hdr;
+ u8 enable;
+} __packed;
+
+struct host_cmd_resp_dtim_channel_enable {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+#define HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES 4
+
+struct host_cmd_req_arp_offload {
+ struct host_cmd_header hdr;
+ __be32 ip_table[HOST_CMD_ARP_OFFLOAD_MAX_IP_ADDRESSES];
+} __packed;
+
+struct host_cmd_resp_arp_offload {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_long_sleep_config {
+ struct host_cmd_header hdr;
+ u8 enabled;
+} __packed;
+
+struct host_cmd_resp_set_long_sleep_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_DUTY_CYCLE BIT(0)
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_OMIT_CONTROL_RESP BIT(1)
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_EXT BIT(2)
+#define HOST_CMD_DUTY_CYCLE_SET_CFG_BURST_RECORD_UNIT BIT(3)
+
+enum host_cmd_duty_cycle_mode {
+ HOST_CMD_DUTY_CYCLE_MODE_SPREAD = 0,
+ HOST_CMD_DUTY_CYCLE_MODE_BURST = 1,
+ HOST_CMD_DUTY_CYCLE_MODE_LAST = HOST_CMD_DUTY_CYCLE_MODE_BURST,
+};
+
+struct host_cmd_duty_cycle_configuration {
+ u8 omit_control_responses;
+ __le32 duty_cycle;
+} __packed;
+
+struct host_cmd_duty_cycle_set_configuration_ext {
+ __le32 burst_record_unit_us;
+ u8 mode;
+} __packed;
+
+struct host_cmd_duty_cycle_configuration_ext {
+ __le32 airtime_remaining_us;
+ __le32 burst_window_duration_us;
+ struct host_cmd_duty_cycle_set_configuration_ext set;
+} __packed;
+
+struct host_cmd_req_set_duty_cycle {
+ struct host_cmd_header hdr;
+ struct host_cmd_duty_cycle_configuration config;
+ u8 set_cfgs;
+ struct host_cmd_duty_cycle_set_configuration_ext config_ext;
+} __packed;
+
+struct host_cmd_resp_get_duty_cycle {
+ struct host_cmd_header hdr;
+ __le32 status;
+ struct host_cmd_duty_cycle_configuration config;
+ struct host_cmd_duty_cycle_configuration_ext config_ext;
+} __packed;
+
+#define HOST_CMD_SET_S1G_CAP_FLAGS BIT(0)
+#define HOST_CMD_SET_S1G_CAP_AMPDU_MSS BIT(1)
+#define HOST_CMD_SET_S1G_CAP_BEAM_STS BIT(2)
+#define HOST_CMD_SET_S1G_CAP_NUM_SOUND_DIMS BIT(3)
+#define HOST_CMD_SET_S1G_CAP_MAX_AMPDU_LEXP BIT(4)
+#define HOST_CMD_SET_MORSE_CAP_MMSS_OFFSET BIT(5)
+#define HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH 4
+
+struct host_cmd_mm_capabilities {
+ __le32 flags[HOST_CMD_S1G_CAPABILITY_FLAGS_WIDTH];
+ u8 ampdu_mss;
+ u8 beamformee_sts_capability;
+ u8 number_sounding_dimensions;
+ u8 maximum_ampdu_length_exponent;
+} __packed;
+
+struct host_cmd_req_get_capabilities {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_capabilities {
+ struct host_cmd_header hdr;
+ __le32 status;
+ struct host_cmd_mm_capabilities capabilities;
+ u8 morse_mmss_offset;
+} __packed;
+
+#define HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN 20
+
+struct host_cmd_req_twt_agreement_install {
+ struct host_cmd_header hdr;
+ u8 flow_id;
+ u8 agreement_len;
+ u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN];
+} __packed;
+
+struct host_cmd_resp_twt_agreement_install {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_twt_agreement_validate {
+ struct host_cmd_header hdr;
+ u8 flow_id;
+ u8 agreement_len;
+ u8 agreement[HOST_CMD_DOT11_TWT_AGREEMENT_MAX_LEN];
+} __packed;
+
+struct host_cmd_resp_twt_agreement_validate {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_twt_agreement_remove {
+ struct host_cmd_header hdr;
+ u8 flow_id;
+} __packed;
+
+struct host_cmd_req_get_tsf {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_tsf {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le64 now_tsf;
+ __le64 now_chip_ts;
+} __packed;
+
+struct host_cmd_req_mac_addr {
+ struct host_cmd_header hdr;
+ u8 write;
+ u8 octet[HOST_CMD_MAC_ADDR_LEN];
+} __packed;
+
+struct host_cmd_resp_mac_addr {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 octet[HOST_CMD_MAC_ADDR_LEN];
+} __packed;
+
+#define HOST_CMD_SET_MPSW_CFG_AIRTIME_BOUNDS BIT(0)
+#define HOST_CMD_SET_MPSW_CFG_PKT_SPC_WIN_LEN BIT(1)
+#define HOST_CMD_SET_MPSW_CFG_ENABLED BIT(2)
+
+struct host_cmd_mpsw_configuration {
+ __le32 airtime_max_us;
+ __le32 airtime_min_us;
+ __le32 packet_space_window_length_us;
+ u8 enable;
+} __packed;
+
+struct host_cmd_req_mpsw_config {
+ struct host_cmd_header hdr;
+ struct host_cmd_mpsw_configuration config;
+ u8 set_cfgs;
+} __packed;
+
+struct host_cmd_resp_mpsw_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+ struct host_cmd_mpsw_configuration config;
+} __packed;
+
+#define HOST_CMD_MAX_KEY_LEN 32
+
+enum host_cmd_key_cipher {
+ HOST_CMD_KEY_CIPHER_INVALID = 0,
+ HOST_CMD_KEY_CIPHER_AES_CCM = 1,
+ HOST_CMD_KEY_CIPHER_AES_GCM = 2,
+ HOST_CMD_KEY_CIPHER_AES_CMAC = 3,
+ HOST_CMD_KEY_CIPHER_AES_GMAC = 4,
+ HOST_CMD_KEY_CIPHER_LAST = HOST_CMD_KEY_CIPHER_AES_GMAC,
+};
+
+enum host_cmd_aes_key_len {
+ HOST_CMD_AES_KEY_LEN_INVALID = 0,
+ HOST_CMD_AES_KEY_LEN_LENGTH_128 = 1,
+ HOST_CMD_AES_KEY_LEN_LENGTH_256 = 2,
+ HOST_CMD_AES_KEY_LEN_LENGTH_LAST = HOST_CMD_AES_KEY_LEN_LENGTH_256,
+};
+
+enum host_cmd_temporal_key_type {
+ HOST_CMD_TEMPORAL_KEY_TYPE_INVALID = 0,
+ HOST_CMD_TEMPORAL_KEY_TYPE_GTK = 1,
+ HOST_CMD_TEMPORAL_KEY_TYPE_PTK = 2,
+ HOST_CMD_TEMPORAL_KEY_TYPE_IGTK = 3,
+ HOST_CMD_TEMPORAL_KEY_TYPE_LAST = HOST_CMD_TEMPORAL_KEY_TYPE_IGTK,
+};
+
+struct host_cmd_req_install_key {
+ struct host_cmd_header hdr;
+ __le64 pn;
+ __le32 aid;
+ u8 key_idx;
+ u8 cipher;
+ u8 key_length;
+ u8 key_type;
+ u8 __padding[2];
+ u8 key[HOST_CMD_MAX_KEY_LEN];
+} __packed;
+
+struct host_cmd_resp_install_key {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 key_idx;
+} __packed;
+
+struct host_cmd_req_disable_key {
+ struct host_cmd_header hdr;
+ __le32 key_type;
+ __le32 aid;
+ u8 key_idx;
+} __packed;
+
+struct host_cmd_resp_disable_key {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+enum host_cmd_dhcp_opcode {
+ HOST_CMD_DHCP_OPCODE_ENABLE = 0,
+ HOST_CMD_DHCP_OPCODE_DO_DISCOVERY = 1,
+ HOST_CMD_DHCP_OPCODE_GET_LEASE = 2,
+ HOST_CMD_DHCP_OPCODE_CLEAR_LEASE = 3,
+ HOST_CMD_DHCP_OPCODE_RENEW_LEASE = 4,
+ HOST_CMD_DHCP_OPCODE_REBIND_LEASE = 5,
+ HOST_CMD_DHCP_OPCODE_SEND_LEASE_UPDATE = 6,
+};
+
+enum host_cmd_dhcp_retcode {
+ HOST_CMD_DHCP_RETCODE_SUCCESS = 0,
+ HOST_CMD_DHCP_RETCODE_NOT_ENABLED = 1,
+ HOST_CMD_DHCP_RETCODE_ALREADY_ENABLED = 2,
+ HOST_CMD_DHCP_RETCODE_NO_LEASE = 3,
+ HOST_CMD_DHCP_RETCODE_HAVE_LEASE = 4,
+ HOST_CMD_DHCP_RETCODE_BUSY = 5,
+ HOST_CMD_DHCP_RETCODE_BAD_VIF = 6,
+};
+
+struct host_cmd_req_dhcp_offload {
+ struct host_cmd_header hdr;
+ __le32 opcode;
+} __packed;
+
+struct host_cmd_resp_dhcp_offload {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 retcode;
+ __le32 my_ip;
+ __le32 netmask;
+ __le32 router;
+ __le32 dns;
+} __packed;
+
+struct host_cmd_req_set_keep_alive_offload {
+ struct host_cmd_header hdr;
+ __le16 bss_max_idle_period;
+ u8 interpret_as_11ah;
+} __packed;
+
+#define HOST_CMD_MAX_OUI_FILTERS 5
+#define HOST_CMD_OUI_SIZE 3
+#define HOST_CMD_MAX_OUI_FILTER_ARRAY_SIZE 15
+
+struct host_cmd_req_update_oui_filter {
+ struct host_cmd_header hdr;
+ u8 n_ouis;
+ u8 ouis[HOST_CMD_MAX_OUI_FILTERS][HOST_CMD_OUI_SIZE];
+} __packed;
+
+enum host_cmd_ibss_config_opcode {
+ HOST_CMD_IBSS_CONFIG_OPCODE_CREATE = 0,
+ HOST_CMD_IBSS_CONFIG_OPCODE_JOIN = 1,
+ HOST_CMD_IBSS_CONFIG_OPCODE_STOP = 2,
+};
+
+struct host_cmd_req_ibss_config {
+ struct host_cmd_header hdr;
+ u8 ibss_bssid[HOST_CMD_MAC_ADDR_LEN];
+ u8 ibss_cfg_opcode;
+ u8 ibss_probe_filtering;
+} __packed;
+
+enum host_cmd_ocs_type {
+ HOST_CMD_OCS_TYPE_QNULL = 0,
+ HOST_CMD_OCS_TYPE_RAW = 1,
+};
+
+struct host_cmd_ocs_config_req {
+ __le32 op_channel_freq_hz;
+ u8 op_channel_bw_mhz;
+ u8 pri_channel_bw_mhz;
+ u8 pri_1mhz_channel_index;
+ __le16 aid;
+ u8 type;
+} __packed;
+
+struct host_cmd_ocs_status_resp {
+ u8 running;
+} __packed;
+
+struct host_cmd_req_ocs {
+ struct host_cmd_header hdr;
+ __le32 subcmd;
+ union {
+ u8 opaque[0];
+ struct host_cmd_ocs_config_req config;
+ };
+} __packed;
+
+struct host_cmd_resp_ocs {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 subcmd;
+ union {
+ u8 opaque[0];
+ struct host_cmd_ocs_status_resp ocs_status;
+ };
+} __packed;
+
+enum host_cmd_mesh_config_opcode {
+ HOST_CMD_MESH_CONFIG_OPCODE_START = 0,
+ HOST_CMD_MESH_CONFIG_OPCODE_STOP = 1,
+};
+
+struct host_cmd_req_mesh_config {
+ struct host_cmd_header hdr;
+ u8 mesh_cfg_opcode;
+ u8 enable_beaconing;
+ u8 mbca_config;
+ u8 min_beacon_gap_ms;
+ __le16 mbss_start_scan_duration_ms;
+ __le16 tbtt_adj_timer_interval_ms;
+} __packed;
+
+struct host_cmd_req_set_offset_tsf {
+ struct host_cmd_header hdr;
+ __sle64 offset_tsf;
+} __packed;
+
+struct host_cmd_req_get_channel_usage {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_channel_usage {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le64 time_listen;
+ __le64 busy_time;
+ __le32 freq_hz;
+ s8 noise;
+ u8 bw_mhz;
+} __packed;
+
+#define HOST_CMD_MAX_MCAST_FILTERS 12
+
+struct host_cmd_req_mcast_filter {
+ struct host_cmd_header hdr;
+ u8 count;
+ __le32 hw_addr[];
+} __packed;
+
+struct host_cmd_req_bss_beacon_config {
+ struct host_cmd_header hdr;
+ u8 enable;
+} __packed;
+
+struct host_cmd_resp_bss_beacon_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le16 interface_id;
+} __packed;
+
+struct host_cmd_req_uapsd_config {
+ struct host_cmd_header hdr;
+ u8 auto_trigger_enabled;
+ __le32 auto_trigger_timeout;
+} __packed;
+
+struct host_cmd_resp_uapsd_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 auto_trigger_enabled;
+} __packed;
+
+struct host_cmd_req_page_slicing_config {
+ struct host_cmd_header hdr;
+ u8 enable;
+} __packed;
+
+#define HOST_CMD_HW_SCAN_FLAGS_START BIT(0)
+#define HOST_CMD_HW_SCAN_FLAGS_ABORT BIT(1)
+#define HOST_CMD_HW_SCAN_FLAGS_SURVEY BIT(2)
+#define HOST_CMD_HW_SCAN_FLAGS_STORE BIT(3)
+#define HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES BIT(4)
+#define HOST_CMD_HW_SCAN_FLAGS_SCHED_START BIT(5)
+#define HOST_CMD_HW_SCAN_FLAGS_SCHED_STOP BIT(6)
+#define HOST_CMD_HW_SCAN_FLAGS_PROBE_ON_DOZE_BEACON BIT(7)
+
+enum host_cmd_hw_scan_tlv_tag {
+ HOST_CMD_HW_SCAN_TLV_TAG_PAD = 0,
+ HOST_CMD_HW_SCAN_TLV_TAG_PROBE_REQ = 1,
+ HOST_CMD_HW_SCAN_TLV_TAG_CHAN_LIST = 2,
+ HOST_CMD_HW_SCAN_TLV_TAG_POWER_LIST = 3,
+ HOST_CMD_HW_SCAN_TLV_TAG_DWELL_ON_HOME = 4,
+ HOST_CMD_HW_SCAN_TLV_TAG_SCHED = 5,
+ HOST_CMD_HW_SCAN_TLV_TAG_FILTER = 6,
+ HOST_CMD_HW_SCAN_TLV_TAG_SCHED_PARAMS = 7,
+};
+
+struct host_cmd_hw_scan_tlv {
+ __le16 tag;
+ __le16 len;
+ u8 value[];
+} __packed;
+
+struct host_cmd_req_hw_scan {
+ struct host_cmd_header hdr;
+ __le32 flags;
+ __le32 dwell_time_ms;
+ u8 variable[];
+} __packed;
+
+#define HOST_CMD_WHITELIST_FLAGS_CLEAR BIT(0)
+
+struct host_cmd_req_set_whitelist {
+ struct host_cmd_header hdr;
+ u8 flags;
+ u8 ip_protocol;
+ __be16 llc_protocol;
+ __be32 src_ip;
+ __be32 dest_ip;
+ __be32 netmask;
+ __be16 src_port;
+ __be16 dest_port;
+} __packed;
+
+struct host_cmd_arp_periodic_params {
+ __le32 refresh_period_s;
+ __le32 destination_ip;
+ u8 send_as_garp;
+} __packed;
+
+struct host_cmd_req_arp_periodic_refresh {
+ struct host_cmd_header hdr;
+ struct host_cmd_arp_periodic_params config;
+} __packed;
+
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_PERIOD BIT(0)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_COUNT BIT(1)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_RETRY_INTERVAL BIT(2)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_IP_ADDR BIT(3)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_IP_ADDR BIT(4)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_SRC_PORT BIT(5)
+#define HOST_CMD_TCP_KEEPALIVE_SET_CFG_DEST_PORT BIT(6)
+
+struct host_cmd_req_set_tcp_keepalive {
+ struct host_cmd_header hdr;
+ u8 enabled;
+ u8 retry_count;
+ u8 retry_interval_s;
+ u8 set_cfgs;
+ __be32 src_ip;
+ __be32 dest_ip;
+ __be16 src_port;
+ __be16 dest_port;
+ __le16 period_s;
+} __packed;
+
+enum host_cmd_power_mode {
+ HOST_CMD_POWER_MODE_SNOOZE = 0,
+ HOST_CMD_POWER_MODE_DEEP_SLEEP = 1,
+ HOST_CMD_POWER_MODE_HIBERNATE = 2,
+};
+
+struct host_cmd_req_force_power_mode {
+ struct host_cmd_header hdr;
+ __le32 mode;
+} __packed;
+
+struct host_cmd_req_li_sleep {
+ struct host_cmd_header hdr;
+ __le32 listen_interval;
+} __packed;
+
+struct host_cmd_disabled_channel_entry {
+ __le16 freq_100khz;
+ u8 bw_mhz;
+} __packed;
+
+struct host_cmd_resp_get_disabled_channels {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 n_channels;
+ struct host_cmd_disabled_channel_entry channels[];
+} __packed;
+
+struct host_cmd_req_set_cqm_rssi {
+ struct host_cmd_header hdr;
+ __sle32 threshold;
+ __le32 hysteresis;
+} __packed;
+
+struct host_cmd_req_get_apf_capabilities {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_apf_capabilities {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 max_length;
+ u8 version;
+} __packed;
+
+struct host_cmd_req_read_write_apf {
+ struct host_cmd_header hdr;
+ __le32 offset;
+ __le16 program_length;
+ u8 write;
+ u8 program[];
+} __packed;
+
+struct host_cmd_resp_read_write_apf {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le16 program_length;
+ u8 program[];
+} __packed;
+
+struct host_cmd_req_bssid_set {
+ struct host_cmd_header hdr;
+ struct host_cmd_mac_addr bssid;
+} __packed;
+
+#define HOST_CMD_BEACON_OFFLOAD_FLAGS_START BIT(0)
+#define HOST_CMD_BEACON_OFFLOAD_FLAGS_STOP BIT(1)
+#define HOST_CMD_BEACON_OFFLOAD_CSSID_LEN 4
+
+enum host_cmd_beacon_offload_tlv_tag {
+ HOST_CMD_BEACON_OFFLOAD_TLV_TAG_DTIM_CNT = 0,
+ HOST_CMD_BEACON_OFFLOAD_TLV_TAG_FRAME_CTRL = 1,
+ HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CHANGE_SEQ = 2,
+ HOST_CMD_BEACON_OFFLOAD_TLV_TAG_CSSID = 3,
+ HOST_CMD_BEACON_OFFLOAD_TLV_TAG_IES = 4,
+ HOST_CMD_BEACON_OFFLOAD_TLV_TAG_TX_INFO = 5,
+};
+
+struct host_cmd_beacon_offload_tlv_hdr {
+ __le16 tag;
+ __le16 len;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_generic {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ u8 value[];
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_dtim_cnt {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ __le16 dtim_cnt;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_frame_ctrl {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ u8 frame_ctrl[2];
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_change_seq {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ __le16 change_seq;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_tx_info {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ u8 bw_mhz;
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_cssid {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ u8 cssid[HOST_CMD_BEACON_OFFLOAD_CSSID_LEN];
+} __packed;
+
+struct host_cmd_beacon_offload_tlv_ies {
+ struct host_cmd_beacon_offload_tlv_hdr hdr;
+ u8 buf[];
+} __packed;
+
+struct host_cmd_req_beacon_offload {
+ struct host_cmd_header hdr;
+ __le32 flags;
+ u8 variable[];
+} __packed;
+
+struct host_cmd_resp_beacon_offload {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le16 dtim_count;
+} __packed;
+
+struct host_cmd_req_probe_response_offload {
+ struct host_cmd_header hdr;
+ u8 enable;
+ __le16 probe_resp_len;
+ u8 probe_resp_buf[];
+} __packed;
+
+struct host_cmd_resp_probe_response_offload {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_sta_type {
+ struct host_cmd_header hdr;
+ u8 sta_type;
+} __packed;
+
+struct host_cmd_req_set_enc_mode {
+ struct host_cmd_header hdr;
+ u8 enc_mode;
+} __packed;
+
+struct host_cmd_req_test_ba {
+ struct host_cmd_header hdr;
+ u8 addr[HOST_CMD_MAC_ADDR_LEN];
+ u8 start;
+ u8 tx;
+ __le32 tid;
+} __packed;
+
+struct host_cmd_req_set_listen_interval {
+ struct host_cmd_header hdr;
+ __le16 listen_interval;
+} __packed;
+
+struct host_cmd_req_set_ampdu {
+ struct host_cmd_header hdr;
+ u8 ampdu_enabled;
+} __packed;
+
+struct host_cmd_req_set_s1g_op_class {
+ struct host_cmd_header hdr;
+ u8 opclass;
+ u8 prim_opclass;
+} __packed;
+
+struct host_cmd_req_send_wake_action_frame {
+ struct host_cmd_header hdr;
+ u8 dest_addr[HOST_CMD_MAC_ADDR_LEN];
+ __le32 payload_size;
+ u8 payload[];
+} __packed;
+
+#define HOST_CMD_MAX_VENDOR_IE_LENGTH 255
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_BEACON BIT(0)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_REQ BIT(1)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_PROBE_RESP BIT(2)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_REQ BIT(3)
+#define HOST_CMD_VENDOR_IE_TYPE_FLAG_ASSOC_RESP BIT(4)
+
+enum host_cmd_vendor_ie_op {
+ HOST_CMD_VENDOR_IE_OP_ADD_ELEMENT = 0,
+ HOST_CMD_VENDOR_IE_OP_CLEAR_ELEMENTS = 1,
+ HOST_CMD_VENDOR_IE_OP_ADD_FILTER = 2,
+ HOST_CMD_VENDOR_IE_OP_CLEAR_FILTERS = 3,
+ HOST_CMD_VENDOR_IE_OP_INVALID = U16_MAX,
+};
+
+struct host_cmd_req_vendor_ie_config {
+ struct host_cmd_header hdr;
+ __le16 opcode;
+ __le16 mgmt_type_mask;
+ u8 data[HOST_CMD_MAX_VENDOR_IE_LENGTH];
+} __packed;
+
+struct host_cmd_resp_vendor_ie_config {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+enum host_cmd_twt_conf_op {
+ HOST_CMD_TWT_CONF_OP_CONFIGURE = 0,
+ HOST_CMD_TWT_CONF_OP_FORCE_INSTALL_AGREEMENT = 1,
+ HOST_CMD_TWT_CONF_OP_REMOVE_AGREEMENT = 2,
+ HOST_CMD_TWT_CONF_OP_CONFIGURE_EXPLICIT = 3,
+};
+
+struct host_cmd_explicit_twt_wake_interval {
+ __le16 wake_interval_mantissa;
+ u8 wake_interval_exponent;
+ u8 __padding[5];
+} __packed;
+
+union host_cmd_wake_interval {
+ __le64 wake_interval_us;
+ struct host_cmd_explicit_twt_wake_interval explicit_twt;
+} __packed;
+
+struct host_cmd_req_set_twt_conf {
+ struct host_cmd_header hdr;
+ u8 opcode;
+ u8 flow_id;
+ __le64 target_wake_time;
+ union host_cmd_wake_interval wake_interval;
+ __le32 wake_duration_us;
+ u8 twt_setup_command;
+ u8 __padding[3];
+} __packed;
+
+#define HOST_CMD_MAX_AVAILABLE_CHANNELS 255
+
+struct host_cmd_channel_info {
+ __le32 frequency_khz;
+ u8 channel_5g;
+ u8 channel_s1g;
+ u8 bandwidth_mhz;
+} __packed;
+
+struct host_cmd_resp_get_available_channels {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 num_channels;
+ struct host_cmd_channel_info channels[HOST_CMD_MAX_AVAILABLE_CHANNELS];
+} __packed;
+
+#define HOST_CMD_S1G_CAP0_S1G_LONG BIT(0)
+#define HOST_CMD_S1G_CAP0_SGI_1MHZ BIT(1)
+#define HOST_CMD_S1G_CAP0_SGI_2MHZ BIT(2)
+#define HOST_CMD_S1G_CAP0_SGI_4MHZ BIT(3)
+#define HOST_CMD_S1G_CAP0_SGI_8MHZ BIT(4)
+#define HOST_CMD_S1G_CAP0_SGI_16MHZ BIT(5)
+
+struct host_cmd_req_set_ecsa_s1g_info {
+ struct host_cmd_header hdr;
+ __le32 operating_channel_freq_hz;
+ u8 opclass;
+ u8 primary_channel_bw_mhz;
+ u8 prim_1mhz_ch_idx;
+ u8 operating_channel_bw_mhz;
+ u8 prim_opclass;
+ u8 s1g_cap0;
+ u8 s1g_cap1;
+ u8 s1g_cap2;
+ u8 s1g_cap3;
+} __packed;
+
+struct host_cmd_resp_get_hw_version {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 hw_version[64];
+} __packed;
+
+#define HOST_CMD_CAC_CFG_CHANGE_RULE_MAX 8
+#define HOST_CMD_CAC_CFG_ARFS_MAX 99
+#define HOST_CMD_CAC_CFG_CHANGE_MAX 99
+#define HOST_CMD_CAC_CFG_CHANGE_STEP 5
+
+enum host_cmd_cac_op {
+ HOST_CMD_CAC_OP_DISABLE = 0,
+ HOST_CMD_CAC_OP_ENABLE = 1,
+ HOST_CMD_CAC_OP_CFG_GET = 2,
+ HOST_CMD_CAC_OP_CFG_SET = 3,
+};
+
+struct host_cmd_cac_change_rule {
+ __le16 arfs;
+ __sle16 threshold_change;
+} __packed;
+
+struct host_cmd_req_cac {
+ struct host_cmd_header hdr;
+ u8 opcode;
+ u8 rule_tot;
+ struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX];
+} __packed;
+
+struct host_cmd_resp_cac {
+ struct host_cmd_header hdr;
+ __le32 status;
+ u8 rule_tot;
+ struct host_cmd_cac_change_rule rule[HOST_CMD_CAC_CFG_CHANGE_RULE_MAX];
+} __packed;
+
+struct host_cmd_ocs_driver_req {
+ __le32 op_channel_freq_hz;
+ u8 op_channel_bw_mhz;
+ u8 pri_channel_bw_mhz;
+ u8 pri_1mhz_channel_index;
+} __packed;
+
+struct host_cmd_ocs_driver_resp {
+ u8 running;
+} __packed;
+
+struct host_cmd_req_ocs_driver {
+ struct host_cmd_header hdr;
+ __le32 subcmd;
+ union {
+ u8 opaque[0];
+ struct host_cmd_ocs_driver_req config;
+ };
+} __packed;
+
+struct host_cmd_resp_ocs_driver {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 subcmd;
+ union {
+ u8 opaque[0];
+ struct host_cmd_ocs_driver_resp ocs_status;
+ };
+} __packed;
+
+#define HOST_CMD_IFNAMSIZ 16
+
+struct host_cmd_req_mbssid {
+ struct host_cmd_header hdr;
+ u8 max_bssid_indicator;
+ s8 transmitter_iface[HOST_CMD_IFNAMSIZ];
+} __packed;
+
+#define HOST_CMD_MESH_ID_LEN_MAX 32
+#define HOST_CMD_MESH_BEACONLESS_MODE_DISABLE 0
+#define HOST_CMD_MESH_BEACONLESS_MODE_ENABLE 1
+#define HOST_CMD_MESH_PEER_LINKS_MIN 0
+#define HOST_CMD_MESH_PEER_LINKS_MAX 10
+
+struct host_cmd_req_set_mesh_config {
+ struct host_cmd_header hdr;
+ u8 mesh_id_len;
+ u8 mesh_id[HOST_CMD_MESH_ID_LEN_MAX];
+ u8 mesh_beaconless_mode;
+ u8 max_plinks;
+} __packed;
+
+struct host_cmd_req_set_mcba_conf {
+ struct host_cmd_header hdr;
+ u8 mbca_config;
+ u8 beacon_timing_report_interval;
+ u8 min_beacon_gap_ms;
+ __le16 mbss_start_scan_duration_ms;
+ __le16 tbtt_adj_interval_ms;
+} __packed;
+
+struct host_cmd_req_dynamic_peering_config {
+ struct host_cmd_header hdr;
+ u8 enabled;
+ u8 rssi_margin;
+ __le32 blacklist_timeout;
+} __packed;
+
+#define HOST_CMD_CFG_RAW_FLAG_ENABLE BIT(0)
+#define HOST_CMD_CFG_RAW_FLAG_DELETE BIT(1)
+#define HOST_CMD_CFG_RAW_FLAG_UPDATE BIT(2)
+#define HOST_CMD_CFG_RAW_FLAG_DYNAMIC BIT(3)
+#define HOST_CMD_RAW_RESERVED_AID_DCS 2008
+#define HOST_CMD_RAW_RESERVED_AID_DOWNLINK 2009
+
+enum host_cmd_raw_tlv_tag {
+ HOST_CMD_RAW_TLV_TAG_SLOT_DEF = 0,
+ HOST_CMD_RAW_TLV_TAG_GROUP = 1,
+ HOST_CMD_RAW_TLV_TAG_START_TIME = 2,
+ HOST_CMD_RAW_TLV_TAG_PRAW = 3,
+ HOST_CMD_RAW_TLV_TAG_BCN_SPREAD = 4,
+ HOST_CMD_RAW_TLV_TAG_DYN_GLOBAL = 5,
+ HOST_CMD_RAW_TLV_TAG_DYN_CONFIG = 6,
+ HOST_CMD_RAW_TLV_TAG_LAST = 7,
+};
+
+struct host_cmd_raw_tlv_slot_def {
+ u8 tag;
+ __le32 raw_duration_us;
+ u8 num_slots;
+ u8 cross_slot_bleed;
+} __packed;
+
+struct host_cmd_raw_tlv_group {
+ u8 tag;
+ __le16 aid_start;
+ __le16 aid_end;
+} __packed;
+
+struct host_cmd_raw_tlv_start_time {
+ u8 tag;
+ __le32 start_time_us;
+} __packed;
+
+struct host_cmd_raw_tlv_praw {
+ u8 tag;
+ u8 periodicity;
+ u8 validity;
+ u8 start_offset;
+ u8 refresh_on_expiry;
+} __packed;
+
+struct host_cmd_raw_tlv_bcn_spread {
+ u8 tag;
+ __le16 max_spread;
+ __le16 nominal_sta_per_bcn;
+} __packed;
+
+struct host_cmd_raw_tlv_dyn_global {
+ u8 tag;
+ __le16 num_configs;
+ __le16 num_bcn_indexes;
+} __packed;
+
+struct host_cmd_raw_tlv_dyn_config {
+ u8 tag;
+ __le16 id;
+ __le16 index;
+ __le16 len;
+ u8 variable[];
+} __packed;
+
+union host_cmd_raw_tlvs {
+ u8 tag;
+ struct host_cmd_raw_tlv_slot_def slot_def;
+ struct host_cmd_raw_tlv_group group;
+ struct host_cmd_raw_tlv_start_time start_time;
+ struct host_cmd_raw_tlv_praw praw;
+ struct host_cmd_raw_tlv_bcn_spread bcn_spread;
+ struct host_cmd_raw_tlv_dyn_global dyn_global;
+ struct host_cmd_raw_tlv_dyn_config dyn_config;
+} __packed;
+
+struct host_cmd_req_config_raw {
+ struct host_cmd_header hdr;
+ __le32 flags;
+ __le16 id;
+ u8 variable[];
+} __packed;
+
+struct host_cmd_req_config_bss_stats {
+ struct host_cmd_header hdr;
+ u8 enable;
+ __le32 monitor_window_ms;
+} __packed;
+
+struct host_cmd_req_get_rssi {
+ struct host_cmd_header hdr;
+} __packed;
+
+struct host_cmd_resp_get_rssi {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __sle32 rssi0;
+ __sle32 rssi1;
+ __sle32 rssi2;
+ __sle32 rssi3;
+ __sle32 rssi4;
+ __sle32 rssi5;
+ __sle32 rssi6;
+ __sle32 rssi7;
+} __packed;
+
+#define HOST_CMD_SET_IFS_MIN_USECS 160
+
+struct host_cmd_req_set_ifs {
+ struct host_cmd_header hdr;
+ __le32 period_usecs;
+} __packed;
+
+struct host_cmd_resp_set_ifs {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_fem_settings {
+ struct host_cmd_header hdr;
+ __le32 tx_antenna;
+ __le32 rx_antenna;
+ __le32 lna_enabled;
+ __le32 pa_enabled;
+} __packed;
+
+struct host_cmd_resp_set_fem_settings {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_txop {
+ struct host_cmd_header hdr;
+ u8 min_packet_count;
+} __packed;
+
+struct host_cmd_resp_set_txop {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_control_response {
+ struct host_cmd_header hdr;
+ u8 direction;
+ u8 control_response_1mhz_en;
+} __packed;
+
+struct host_cmd_resp_set_control_response {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_periodic_cal {
+ struct host_cmd_header hdr;
+ __le32 periodic_cal_en_mask;
+} __packed;
+
+struct host_cmd_resp_set_periodic_cal {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_bcn_rssi_threshold {
+ struct host_cmd_header hdr;
+ u8 threshold_db;
+} __packed;
+
+struct host_cmd_resp_set_bcn_rssi_threshold {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_tx_pkt_lifetime_usecs {
+ struct host_cmd_header hdr;
+ __le32 lifetime_usecs;
+} __packed;
+
+struct host_cmd_resp_set_tx_pkt_lifetime_usecs {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_physm_watchdog {
+ struct host_cmd_header hdr;
+ u8 physm_watchdog_en;
+} __packed;
+
+struct host_cmd_req_tx_polar {
+ struct host_cmd_header hdr;
+ u8 enable;
+} __packed;
+
+struct host_cmd_evt_sta_state {
+ struct host_cmd_header hdr;
+ u8 sta_addr[HOST_CMD_MAC_ADDR_LEN];
+ __le16 aid;
+ __le16 state;
+} __packed;
+
+struct host_cmd_evt_beacon_loss {
+ struct host_cmd_header hdr;
+ __le32 num_bcns;
+} __packed;
+
+struct host_cmd_evt_sig_field_error {
+ struct host_cmd_header hdr;
+ __le64 start_timestamp;
+ __le64 end_timestamp;
+} __packed;
+
+#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_TWT BIT(0)
+#define HOST_CMD_UMAC_TRAFFIC_CONTROL_SOURCE_DUTY_CYCLE BIT(1)
+
+struct host_cmd_evt_umac_traffic_control {
+ struct host_cmd_header hdr;
+ u8 pause_data_traffic;
+ __le32 sources;
+} __packed;
+
+struct host_cmd_evt_dhcp_lease_update {
+ struct host_cmd_header hdr;
+ __le32 my_ip;
+ __le32 netmask;
+ __le32 router;
+ __le32 dns;
+} __packed;
+
+struct host_cmd_evt_ocs_done {
+ struct host_cmd_header hdr;
+ __le64 time_listen;
+ __le64 time_rx;
+ s8 noise;
+ u8 metric;
+} __packed;
+
+struct host_cmd_evt_hw_scan_done {
+ struct host_cmd_header hdr;
+ u8 aborted;
+} __packed;
+
+struct host_cmd_evt_channel_usage {
+ struct host_cmd_header hdr;
+ __le64 time_listen;
+ __le64 busy_time;
+ __le32 freq_hz;
+ u8 noise;
+ u8 bw_mhz;
+} __packed;
+
+enum host_cmd_connection_loss_reason {
+ HOST_CMD_CONNECTION_LOSS_REASON_TSF_RESET = 0,
+};
+
+struct host_cmd_evt_connection_loss {
+ struct host_cmd_header hdr;
+ __le32 reason;
+} __packed;
+
+struct host_cmd_evt_sched_scan_results {
+ struct host_cmd_header hdr;
+} __packed;
+
+enum host_cmd_cqm_rssi_threshold_event {
+ HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_LOW = 0,
+ HOST_CMD_CQM_RSSI_THRESHOLD_EVENT_HIGH = 1,
+};
+
+struct host_cmd_evt_cqm_rssi_notify {
+ struct host_cmd_header hdr;
+ __sle16 rssi;
+ __le16 event;
+} __packed;
+
+struct host_cmd_evt_scan_done {
+ struct host_cmd_header hdr;
+ u8 aborted;
+} __packed;
+
+enum host_cmd_scan_result_frame {
+ HOST_CMD_SCAN_RESULT_FRAME_UNKNOWN = 0,
+ HOST_CMD_SCAN_RESULT_FRAME_BEACON = 1,
+ HOST_CMD_SCAN_RESULT_FRAME_PROBE_RESPONSE = 2,
+};
+
+struct host_cmd_evt_scan_result {
+ struct host_cmd_header hdr;
+ __le32 channel_freq_hz;
+ u8 bw_mhz;
+ u8 frame_type;
+ __sle16 rssi;
+ u8 bssid[HOST_CMD_MAC_ADDR_LEN];
+ __le16 beacon_interval;
+ __le16 capability_info;
+ __le64 tsf;
+ __le16 ies_len;
+ u8 ies[];
+} __packed;
+
+struct host_cmd_evt_connected {
+ struct host_cmd_header hdr;
+ u8 bssid[HOST_CMD_MAC_ADDR_LEN];
+ __sle16 rssi;
+ u8 padding_0[8];
+ __le16 assoc_resp_ies_len;
+ u8 assoc_resp_ies[];
+} __packed;
+
+struct host_cmd_evt_beacon_filter_match {
+ struct host_cmd_header hdr;
+ u8 padding_0[4];
+ __le32 ies_len;
+ u8 ies[];
+} __packed;
+
+struct host_cmd_req_set_capabilities {
+ struct host_cmd_header hdr;
+ struct host_cmd_mm_capabilities capabilities;
+ u8 set_caps;
+ u8 morse_mmss_offset;
+} __packed;
+
+struct host_cmd_resp_set_capabilities {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+struct host_cmd_req_set_transmission_rate {
+ struct host_cmd_header hdr;
+ __sle32 mcs_index;
+ __sle32 bandwidth_mhz;
+ __sle32 tx_80211ah_format;
+ s8 use_traveling_pilots;
+ s8 use_sgi;
+ u8 enabled;
+ s8 nss_idx;
+ s8 use_ldpc;
+ s8 use_stbc;
+} __packed;
+
+struct host_cmd_resp_set_transmission_rate {
+ struct host_cmd_header hdr;
+ __le32 status;
+} __packed;
+
+enum host_cmd_hart_id {
+ HOST_CMD_HART_ID_HOST = 0,
+ HOST_CMD_HART_ID_MAC = 1,
+ HOST_CMD_HART_ID_UPHY = 2,
+ HOST_CMD_HART_ID_LPHY = 3,
+};
+
+struct host_cmd_req_force_assert {
+ struct host_cmd_header hdr;
+ __le32 hart_id;
+} __packed;
+
+#define HOST_CMD_HOST_BLOCK_TX_FRAMES BIT(0)
+#define HOST_CMD_HOST_BLOCK_TX_CMD BIT(1)
+
+enum host_cmd_param_action {
+ HOST_CMD_PARAM_ACTION_SET = 0,
+ HOST_CMD_PARAM_ACTION_GET = 1,
+ HOST_CMD_PARAM_ACTION_LAST = 2,
+};
+
+enum host_cmd_slow_clock_mode {
+ HOST_CMD_SLOW_CLOCK_MODE_AUTO = 0,
+ HOST_CMD_SLOW_CLOCK_MODE_INTERNAL = 1,
+};
+
+enum host_cmd_param_id {
+ HOST_CMD_PARAM_ID_MAX_TRAFFIC_DELIVERY_WAIT_US = 0,
+ HOST_CMD_PARAM_ID_EXTRA_ACK_TIMEOUT_ADJUST_US = 1,
+ HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_WATERMARK = 2,
+ HOST_CMD_PARAM_ID_TX_STATUS_FLUSH_MIN_AMPDU_SIZE = 3,
+ HOST_CMD_PARAM_ID_POWERSAVE_TYPE = 4,
+ HOST_CMD_PARAM_ID_SNOOZE_DURATION_ADJUST_US = 5,
+ HOST_CMD_PARAM_ID_TX_BLOCK = 6,
+ HOST_CMD_PARAM_ID_FORCED_SNOOZE_PERIOD_US = 7,
+ HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO = 8,
+ HOST_CMD_PARAM_ID_WAKE_ACTION_GPIO_PULSE_MS = 9,
+ HOST_CMD_PARAM_ID_CONNECTION_MONITOR_GPIO = 10,
+ HOST_CMD_PARAM_ID_INPUT_TRIGGER_GPIO = 11,
+ HOST_CMD_PARAM_ID_INPUT_TRIGGER_MODE = 12,
+ HOST_CMD_PARAM_ID_COUNTRY = 13,
+ HOST_CMD_PARAM_ID_RTS_THRESHOLD = 14,
+ HOST_CMD_PARAM_ID_HOST_TX_BLOCK = 15,
+ HOST_CMD_PARAM_ID_MEM_RETENTION_CODE = 16,
+ HOST_CMD_PARAM_ID_NON_TIM_MODE = 17,
+ HOST_CMD_PARAM_ID_DYNAMIC_PS_TIMEOUT_MS = 18,
+ HOST_CMD_PARAM_ID_HOME_CHANNEL_DWELL_MS = 19,
+ HOST_CMD_PARAM_ID_SLOW_CLOCK_MODE = 20,
+ HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD = 21,
+ HOST_CMD_PARAM_ID_BEACON_LOSS_COUNT = 22,
+ HOST_CMD_PARAM_ID_AP_POWER_SAVE = 23,
+ HOST_CMD_PARAM_ID_BEACON_OFFLOAD = 24,
+ HOST_CMD_PARAM_ID_PROBE_RESP_OFFLOAD = 25,
+ HOST_CMD_PARAM_ID_BSS_MAX_AWAY_DURATION = 26,
+ HOST_CMD_PARAM_ID_DEFAULT_ACTIVE_SCAN_DWELL_MS = 27,
+ HOST_CMD_PARAM_ID_CTS_TO_SELF = 28,
+ HOST_CMD_PARAM_ID_CHANNELIZATION = 29,
+ HOST_CMD_PARAM_ID_LAST = 30,
+};
+
+struct host_cmd_req_get_set_generic_param {
+ struct host_cmd_header hdr;
+ __le32 param_id;
+ __le32 action;
+ __le32 flags;
+ __le32 value;
+} __packed;
+
+struct host_cmd_resp_get_set_generic_param {
+ struct host_cmd_header hdr;
+ __le32 status;
+ __le32 flags;
+ __le32 value;
+} __packed;
+
+#endif
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 04/33] wifi: mm81x: add command.c
From: Lachlan Hodges @ 2026-06-26 6:29 UTC (permalink / raw)
To: johannes, Lachlan Hodges, Dan Callaghan, Arien Judge
Cc: ayman.grais, linux-wireless, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
.../net/wireless/morsemicro/mm81x/command.c | 563 ++++++++++++++++++
1 file changed, 563 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/command.c
diff --git a/drivers/net/wireless/morsemicro/mm81x/command.c b/drivers/net/wireless/morsemicro/mm81x/command.c
new file mode 100644
index 000000000000..afb7ee9bb236
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/command.c
@@ -0,0 +1,563 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#include <linux/types.h>
+#include <linux/atomic.h>
+#include <linux/slab.h>
+#include <linux/workqueue.h>
+
+#include "command.h"
+#include "mac.h"
+#include "ps.h"
+#include "hif.h"
+
+#define MM_MAX_COMMAND_RETRY 2
+#define HOST_CMD_DEFAULT_TIMEOUT_MS 600
+#define HOST_CMD_POWERSAVE_TIMEOUT_MS 2000
+
+#define INIT_CMD_HDR(_req, _cmd, _vif_id) \
+ ((struct host_cmd_header){ \
+ .message_id = cpu_to_le16(_cmd), \
+ .len = cpu_to_le16(sizeof(_req) - sizeof((_req).hdr)), \
+ .vif_id = cpu_to_le16(_vif_id), \
+ })
+
+struct host_cmd_resp_cb {
+ int ret;
+ u32 length;
+ struct host_cmd_resp *dest_resp;
+};
+
+static int mm81x_cmd_tx(struct mm81x *mors, struct host_cmd_resp *resp,
+ struct host_cmd_req *req, u32 length, u32 timeout)
+{
+ int cmd_len;
+ int ret = 0;
+ u16 host_id;
+ int retry = 0;
+ unsigned long wait_ret = 0;
+ struct sk_buff *skb;
+ struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);
+ struct host_cmd_resp_cb *resp_cb;
+ DECLARE_COMPLETION_ONSTACK(cmd_comp);
+
+ BUILD_BUG_ON(sizeof(struct host_cmd_resp_cb) >
+ IEEE80211_TX_INFO_DRIVER_DATA_SIZE);
+
+ cmd_len = sizeof(*req) + le16_to_cpu(req->hdr.len);
+ req->hdr.flags = cpu_to_le16(HOST_CMD_TYPE_REQ);
+
+ mutex_lock(&mors->cmd_wait);
+ mors->cmd_seq++;
+ if (mors->cmd_seq > HOST_CMD_HOST_ID_SEQ_MAX)
+ mors->cmd_seq = 1;
+ host_id = mors->cmd_seq << HOST_CMD_HOST_ID_SEQ_SHIFT;
+
+ mm81x_ps_disable(mors);
+
+ do {
+ req->hdr.host_id = cpu_to_le16(host_id | retry);
+
+ skb = mm81x_skbq_alloc_skb(cmd_q, cmd_len);
+ if (!skb) {
+ ret = -ENOMEM;
+ break;
+ }
+
+ memcpy(skb->data, req, cmd_len);
+ resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(skb)
+ ->driver_data;
+ resp_cb->length = length;
+ resp_cb->dest_resp = resp;
+
+ dev_dbg(mors->dev, "CMD 0x%04x:%04x",
+ le16_to_cpu(req->hdr.message_id),
+ le16_to_cpu(req->hdr.host_id));
+
+ mutex_lock(&mors->cmd_lock);
+ mors->cmd_comp = &cmd_comp;
+ if (retry > 0)
+ reinit_completion(&cmd_comp);
+ timeout = timeout ? timeout : HOST_CMD_DEFAULT_TIMEOUT_MS;
+ ret = mm81x_skbq_skb_tx(cmd_q, &skb, NULL,
+ MM81X_SKB_CHAN_COMMAND);
+ mutex_unlock(&mors->cmd_lock);
+
+ if (ret) {
+ dev_err(mors->dev, "mm81x_skbq_tx fail: %d", ret);
+ break;
+ }
+
+ wait_ret = wait_for_completion_timeout(
+ &cmd_comp, msecs_to_jiffies(timeout));
+ mutex_lock(&mors->cmd_lock);
+ mors->cmd_comp = NULL;
+
+ if (!wait_ret) {
+ dev_err(mors->dev,
+ "Try:%d Command %04x:%04x timeout after %u ms",
+ retry, le16_to_cpu(req->hdr.message_id),
+ le16_to_cpu(req->hdr.host_id), timeout);
+ ret = -ETIMEDOUT;
+ } else {
+ ret = (length && resp) ? le32_to_cpu(resp->status) :
+ resp_cb->ret;
+ if (ret > 0 || ret < -MAX_ERRNO)
+ ret = -EIO;
+
+ dev_dbg(mors->dev, "Command 0x%04x:%04x status 0x%08x",
+ le16_to_cpu(req->hdr.message_id),
+ le16_to_cpu(req->hdr.host_id), ret);
+ if (ret) {
+ dev_err(mors->dev,
+ "Command 0x%04x:%04x error %d",
+ le16_to_cpu(req->hdr.message_id),
+ le16_to_cpu(req->hdr.host_id), ret);
+ }
+ }
+ /* Free the command request */
+ spin_lock_bh(&cmd_q->lock);
+ mm81x_skbq_skb_finish(cmd_q, skb, NULL);
+ spin_unlock_bh(&cmd_q->lock);
+ mutex_unlock(&mors->cmd_lock);
+
+ retry++;
+ } while ((ret == -ETIMEDOUT) && retry < MM_MAX_COMMAND_RETRY);
+
+ mm81x_ps_enable(mors);
+ mutex_unlock(&mors->cmd_wait);
+
+ if (ret == -ETIMEDOUT) {
+ dev_err(mors->dev, "Command %02x:%02x timed out",
+ le16_to_cpu(req->hdr.message_id),
+ le16_to_cpu(req->hdr.host_id));
+ } else if (ret != 0) {
+ dev_err(mors->dev,
+ "Command %02x:%02x failed with rc %d (0x%x)\n",
+ le16_to_cpu(req->hdr.message_id),
+ le16_to_cpu(req->hdr.host_id), ret, ret);
+ }
+
+ return ret;
+}
+
+int mm81x_cmd_resp_process(struct mm81x *mors, struct sk_buff *skb)
+{
+ int length, ret = -ESRCH; /* No such process */
+ struct mm81x_skbq *cmd_q = mm81x_hif_get_tx_cmd_queue(mors);
+ struct host_cmd_resp *src_resp = (struct host_cmd_resp *)(skb->data);
+ struct sk_buff *cmd_skb = NULL;
+ struct host_cmd_resp_cb *resp_cb;
+ struct host_cmd_resp *dest_resp;
+ struct host_cmd_req *req;
+ u16 message_id = 0;
+ u16 host_id = 0;
+ u16 resp_message_id = le16_to_cpu(src_resp->hdr.message_id);
+ u16 resp_host_id = le16_to_cpu(src_resp->hdr.host_id);
+ bool is_late_response = false;
+
+ dev_dbg(mors->dev, "EVT 0x%04x:0x%04x", resp_message_id, resp_host_id);
+
+ if (!HOST_CMD_IS_RESP(src_resp)) {
+ ret = mm81x_mac_event_recv(mors, skb);
+ goto exit_free;
+ }
+
+ mutex_lock(&mors->cmd_lock);
+
+ cmd_skb = mm81x_skbq_tx_pending(cmd_q);
+ if (cmd_skb) {
+ mm81x_skbq_pull_hdr_post_tx(cmd_skb);
+ req = (struct host_cmd_req *)cmd_skb->data;
+ message_id = le16_to_cpu(req->hdr.message_id);
+ host_id = le16_to_cpu(req->hdr.host_id);
+ }
+
+ /*
+ * If there is no pending command or the sequence ID does not match,
+ * this is a late response for a timed out command which has been
+ * cleaned up, so just free up the response. If a command was retried,
+ * the response may be from the retry or from the original command
+ * (late response) but not from both because the firmware will silently
+ * drop a retry if it received the initial request. So a mismatched
+ * retry counter is treated as a matched command and response.
+ */
+ if (!cmd_skb || message_id != resp_message_id ||
+ (host_id & HOST_CMD_HOST_ID_SEQ_MASK) !=
+ (resp_host_id & HOST_CMD_HOST_ID_SEQ_MASK)) {
+ dev_err(mors->dev,
+ "Late response for timed out req 0x%04x:%04x have 0x%04x:%04x 0x%04x",
+ resp_message_id, resp_host_id, message_id, host_id,
+ mors->cmd_seq);
+ is_late_response = true;
+ goto exit;
+ }
+ if ((host_id & HOST_CMD_HOST_ID_RETRY_MASK) !=
+ (resp_host_id & HOST_CMD_HOST_ID_RETRY_MASK))
+ dev_dbg(mors->dev,
+ "Command retry mismatch 0x%04x:%04x 0x%04x:%04x",
+ message_id, host_id, resp_message_id, resp_host_id);
+
+ resp_cb = (struct host_cmd_resp_cb *)IEEE80211_SKB_CB(cmd_skb)
+ ->driver_data;
+ length = resp_cb->length;
+ dest_resp = resp_cb->dest_resp;
+ if (length >= sizeof(struct host_cmd_resp) && dest_resp) {
+ ret = 0;
+ length = min_t(int, length,
+ le16_to_cpu(src_resp->hdr.len) +
+ sizeof(struct host_cmd_header));
+ memcpy(dest_resp, src_resp, length);
+ } else {
+ ret = le32_to_cpu(src_resp->status);
+ }
+
+ resp_cb->ret = ret;
+
+exit:
+ if (cmd_skb && !is_late_response) {
+ /* Complete if not already timed out */
+ if (mors->cmd_comp)
+ complete(mors->cmd_comp);
+ }
+
+ mutex_unlock(&mors->cmd_lock);
+exit_free:
+ dev_kfree_skb(skb);
+ return 0;
+}
+
+int mm81x_cmd_sta_state(struct mm81x *mors, struct mm81x_vif *mors_vif, u16 aid,
+ struct ieee80211_sta *sta,
+ enum ieee80211_sta_state state)
+{
+ struct host_cmd_req_set_sta_state req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_STA_STATE,
+ mors_vif->id),
+ .aid = cpu_to_le16(aid),
+ .state = cpu_to_le16(state),
+ .uapsd_queues = sta->uapsd_queues,
+ };
+
+ memcpy(req.sta_addr, sta->addr, sizeof(req.sta_addr));
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_add_if(struct mm81x *mors, u16 *vif_id, const u8 *addr,
+ enum nl80211_iftype type)
+{
+ int ret;
+ struct host_cmd_req_add_interface req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_ADD_INTERFACE, 0),
+ };
+ struct host_cmd_resp_add_interface resp;
+
+ switch (type) {
+ case NL80211_IFTYPE_STATION:
+ req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_STA);
+ break;
+ case NL80211_IFTYPE_AP:
+ req.interface_type = cpu_to_le32(HOST_CMD_INTERFACE_TYPE_AP);
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ memcpy(req.addr.octet, addr, sizeof(req.addr.octet));
+
+ ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+ (struct host_cmd_req *)&req, sizeof(resp), 0);
+ if (!ret)
+ *vif_id = le16_to_cpu(resp.hdr.vif_id);
+
+ return ret;
+}
+
+int mm81x_cmd_get_capabilities(struct mm81x *mors, u16 vif_id,
+ struct mm81x_fw_caps *capabilities)
+{
+ int ret;
+ int i;
+ struct host_cmd_req_get_capabilities req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_CAPABILITIES, vif_id),
+ };
+ struct host_cmd_resp_get_capabilities rsp;
+
+ ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&rsp,
+ (struct host_cmd_req *)&req, sizeof(rsp), 0);
+ if (ret)
+ return ret;
+
+ capabilities->ampdu_mss = rsp.capabilities.ampdu_mss;
+ capabilities->mm81x_mmss_offset = rsp.morse_mmss_offset;
+ capabilities->beamformee_sts_capability =
+ rsp.capabilities.beamformee_sts_capability;
+ capabilities->maximum_ampdu_length_exponent =
+ rsp.capabilities.maximum_ampdu_length_exponent;
+ capabilities->number_sounding_dimensions =
+ rsp.capabilities.number_sounding_dimensions;
+ for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++)
+ capabilities->flags[i] = le32_to_cpu(rsp.capabilities.flags[i]);
+
+ return ret;
+}
+
+int mm81x_cmd_get_max_txpower(struct mm81x *mors, s32 *out_power_mbm)
+{
+ int ret;
+ struct host_cmd_req_get_max_txpower req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_MAX_TXPOWER, 0),
+ };
+ struct host_cmd_resp_get_max_txpower resp;
+
+ ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+ (struct host_cmd_req *)&req, sizeof(resp), 0);
+ if (!ret)
+ *out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
+
+ return ret;
+}
+
+int mm81x_cmd_hw_scan(struct mm81x *mors, struct mm81x_hw_scan_params *params,
+ bool store)
+{
+ int ret;
+ struct host_cmd_req_hw_scan *req;
+ size_t cmd_size;
+ u8 *buf;
+ u32 flags = 0;
+
+ cmd_size = mm81x_hw_scan_h_get_cmd_size(params);
+ cmd_size = ROUND_BYTES_TO_WORD(cmd_size);
+
+ req = kzalloc(cmd_size, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ buf = req->variable;
+
+ if (store)
+ flags = HOST_CMD_HW_SCAN_FLAGS_STORE;
+ else if (params->operation == MM81X_HW_SCAN_OP_START)
+ flags |= HOST_CMD_HW_SCAN_FLAGS_START;
+ else if (params->operation == MM81X_HW_SCAN_OP_STOP)
+ flags |= HOST_CMD_HW_SCAN_FLAGS_ABORT;
+
+ flags |= HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES;
+
+ if (params->operation == MM81X_HW_SCAN_OP_START) {
+ req->dwell_time_ms = cpu_to_le32(params->dwell_time_ms);
+ buf = mm81x_hw_scan_h_insert_tlvs(params, buf);
+ }
+
+ req->flags = cpu_to_le32(flags);
+ req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_HW_SCAN, 0);
+ req->hdr.len = cpu_to_le16((u16)((buf - (u8 *)req) - sizeof(req->hdr)));
+ ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);
+ kfree(req);
+
+ return ret;
+}
+
+int mm81x_cmd_set_txpower(struct mm81x *mors, s32 *out_power_mbm,
+ int txpower_mbm)
+{
+ int ret;
+ struct host_cmd_req_set_txpower req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_TXPOWER, 0),
+ .power_qdbm = cpu_to_le32(MBM_TO_QDBM(txpower_mbm)),
+ };
+ struct host_cmd_resp_set_txpower resp;
+
+ ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+ (struct host_cmd_req *)&req, sizeof(resp), 0);
+ if (!ret)
+ *out_power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
+
+ return ret;
+}
+
+int mm81x_cmd_set_channel(struct mm81x *mors, u32 op_chan_freq_hz,
+ u8 pri_1mhz_chan_idx, u8 op_bw_mhz, u8 pri_bw_mhz,
+ s32 *power_mbm)
+{
+ int ret;
+ struct host_cmd_req_set_channel req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_CHANNEL, 0),
+ .op_chan_freq_hz = cpu_to_le32(op_chan_freq_hz),
+ .op_bw_mhz = op_bw_mhz,
+ .pri_bw_mhz = pri_bw_mhz,
+ .pri_1mhz_chan_idx = pri_1mhz_chan_idx,
+ .dot11_mode = HOST_CMD_DOT11_PROTO_MODE_AH,
+ };
+ struct host_cmd_resp_set_channel resp;
+
+ ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+ (struct host_cmd_req *)&req, sizeof(resp), 0);
+ if (!ret)
+ *power_mbm = QDBM_TO_MBM(le32_to_cpu(resp.power_qdbm));
+
+ return ret;
+}
+
+int mm81x_cmd_disable_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+ u16 aid, struct ieee80211_key_conf *key)
+{
+ struct host_cmd_req_disable_key req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_DISABLE_KEY, mors_vif->id),
+ .aid = cpu_to_le32(aid),
+ .key_idx = key->hw_key_idx,
+ .key_type =
+ cpu_to_le32((key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+ HOST_CMD_TEMPORAL_KEY_TYPE_PTK :
+ HOST_CMD_TEMPORAL_KEY_TYPE_GTK),
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_install_key(struct mm81x *mors, struct mm81x_vif *mors_vif,
+ u16 aid, struct ieee80211_key_conf *key,
+ enum host_cmd_key_cipher cipher,
+ enum host_cmd_aes_key_len length)
+{
+ int ret;
+ struct host_cmd_req_install_key req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_INSTALL_KEY, mors_vif->id),
+ .pn = cpu_to_le64(atomic64_read(&key->tx_pn)),
+ .aid = cpu_to_le32(aid),
+ .cipher = cipher,
+ .key_length = length,
+ .key_idx = key->keyidx,
+ .key_type = (key->flags & IEEE80211_KEY_FLAG_PAIRWISE) ?
+ HOST_CMD_TEMPORAL_KEY_TYPE_PTK :
+ HOST_CMD_TEMPORAL_KEY_TYPE_GTK,
+ };
+ struct host_cmd_resp_install_key resp;
+
+ if (key->keylen > sizeof(req.key))
+ return -EINVAL;
+
+ memcpy(req.key, key->key, key->keylen);
+
+ ret = mm81x_cmd_tx(mors, (struct host_cmd_resp *)&resp,
+ (struct host_cmd_req *)&req, sizeof(resp), 0);
+ if (!ret) {
+ key->hw_key_idx = resp.key_idx;
+ dev_dbg(mors->dev, "Installed key @ hw index: %d",
+ resp.key_idx);
+ }
+
+ return ret;
+}
+
+int mm81x_cmd_cfg_multicast_filter(struct mm81x *mors,
+ struct mm81x_vif *mors_vif)
+{
+ struct host_cmd_req_mcast_filter *req;
+ struct mcast_filter *filter = mors->mcast_filter;
+ u16 filter_list_len = sizeof(filter->addr_list[0]) * filter->count;
+ u16 alloc_len = filter_list_len + sizeof(*req);
+ int ret = 0;
+
+ req = kzalloc(alloc_len, GFP_KERNEL);
+ if (!req)
+ return -ENOMEM;
+
+ req->hdr = INIT_CMD_HDR((*req), HOST_CMD_ID_MCAST_FILTER, mors_vif->id);
+ req->hdr.len = cpu_to_le16(alloc_len - sizeof(req->hdr));
+ req->count = filter->count;
+ memcpy(req->hw_addr, filter->addr_list, filter_list_len);
+
+ ret = mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)req, 0, 0);
+ kfree(req);
+ return ret;
+}
+
+int mm81x_cmd_cfg_bss(struct mm81x *mors, u16 vif_id, u16 beacon_int,
+ u16 dtim_period, u32 cssid)
+{
+ struct host_cmd_req_bss_config req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_CONFIG, vif_id),
+ .beacon_interval_tu = cpu_to_le16(beacon_int),
+ .cssid = cpu_to_le32(cssid),
+ .dtim_period = cpu_to_le16(dtim_period),
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_config_beacon_timer(struct mm81x *mors, void *mm81x_vif,
+ bool enabled)
+{
+ struct mm81x_vif *vif = mm81x_vif;
+ struct host_cmd_req_bss_beacon_config req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_BSS_BEACON_CONFIG,
+ vif->id),
+ .enable = enabled,
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_set_ps(struct mm81x *mors, bool enabled)
+{
+ struct host_cmd_req_config_ps req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_CONFIG_PS, 0),
+ .enabled = (u8)enabled,
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0,
+ HOST_CMD_POWERSAVE_TIMEOUT_MS);
+}
+
+int mm81x_cmd_cfg_qos(struct mm81x *mors, struct mm81x_queue_params *params)
+{
+ struct host_cmd_req_set_qos_params req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_SET_QOS_PARAMS, 0),
+ .uapsd = params->uapsd,
+ .queue_idx = params->aci,
+ .aifs_slot_count = params->aifs,
+ .contention_window_min = cpu_to_le16(params->cw_min),
+ .contention_window_max = cpu_to_le16(params->cw_max),
+ .max_txop_usec = cpu_to_le32(params->txop),
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_rm_if(struct mm81x *mors, u16 vif_id)
+{
+ struct host_cmd_req_remove_interface req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_REMOVE_INTERFACE, vif_id),
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_set_frag_threshold(struct mm81x *mors, u32 frag_threshold)
+{
+ struct host_cmd_req_get_set_generic_param req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_SET_GENERIC_PARAM, 0),
+ .param_id = cpu_to_le32(HOST_CMD_PARAM_ID_FRAGMENT_THRESHOLD),
+ .action = cpu_to_le32(HOST_CMD_PARAM_ACTION_SET),
+ .value = cpu_to_le32(frag_threshold),
+ };
+
+ return mm81x_cmd_tx(mors, NULL, (struct host_cmd_req *)&req, 0, 0);
+}
+
+int mm81x_cmd_get_disabled_channels(
+ struct mm81x *mors, struct host_cmd_resp_get_disabled_channels *resp,
+ uint resp_len)
+{
+ struct host_cmd_req req = {
+ .hdr = INIT_CMD_HDR(req, HOST_CMD_ID_GET_DISABLED_CHANNELS, 0),
+ };
+
+ return mm81x_cmd_tx(mors, (struct host_cmd_resp *)resp, &req, resp_len,
+ 0);
+}
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 03/33] wifi: mm81x: add bus.h
From: Lachlan Hodges @ 2026-06-26 6:28 UTC (permalink / raw)
To: johannes, Lachlan Hodges, Dan Callaghan, Arien Judge
Cc: ayman.grais, linux-wireless, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
(Patches split per file for review, will be a single commit alongside
SDIO ids once review is complete. See cover letter for more
information)
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
drivers/net/wireless/morsemicro/mm81x/bus.h | 99 +++++++++++++++++++++
1 file changed, 99 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/mm81x/bus.h
diff --git a/drivers/net/wireless/morsemicro/mm81x/bus.h b/drivers/net/wireless/morsemicro/mm81x/bus.h
new file mode 100644
index 000000000000..d2ccabc037fb
--- /dev/null
+++ b/drivers/net/wireless/morsemicro/mm81x/bus.h
@@ -0,0 +1,99 @@
+/* SPDX-License-Identifier: GPL-2.0-only */
+/*
+ * Copyright (c) 2017-2026 Morse Micro
+ */
+
+#ifndef _MM81X_BUS_H_
+#define _MM81X_BUS_H_
+
+#include <linux/skbuff.h>
+#include "core.h"
+
+enum mm81x_bus_type {
+ MM81X_BUS_TYPE_USB,
+ MM81X_BUS_TYPE_SDIO,
+};
+
+struct mm81x_bus_ops {
+ int (*dm_read)(struct mm81x *mors, u32 addr, u8 *data, int len);
+ int (*dm_write)(struct mm81x *mors, u32 addr, const u8 *data, int len);
+ int (*reg32_read)(struct mm81x *mors, u32 addr, u32 *data);
+ int (*reg32_write)(struct mm81x *mors, u32 addr, u32 data);
+ int (*digital_reset)(struct mm81x *mors);
+ void (*set_bus_enable)(struct mm81x *mors, bool enable);
+ void (*config_burst_mode)(struct mm81x *mors, bool enable_burst);
+ void (*claim)(struct mm81x *mors);
+ void (*set_irq)(struct mm81x *mors, bool enable);
+ void (*release)(struct mm81x *mors);
+ unsigned int bulk_alignment;
+};
+
+/*
+ * Default TX alignment for buses which don't care. mac80211 will give us
+ * SKBs aligned to the 2 byte boundary, so 2 is effectively a noop.
+ */
+#define MM81X_BUS_DEFAULT_BULK_ALIGNMENT (2)
+
+/* mm81x_dm_read - len must be rounded up to the nearest 4-byte boundary */
+static inline int mm81x_dm_read(struct mm81x *mors, u32 addr, u8 *data, int len)
+{
+ return mors->bus_ops->dm_read(mors, addr, data, len);
+}
+
+static inline int mm81x_dm_write(struct mm81x *mors, u32 addr, const u8 *data,
+ int len)
+{
+ return mors->bus_ops->dm_write(mors, addr, data, len);
+}
+
+static inline int mm81x_reg32_read(struct mm81x *mors, u32 addr, u32 *data)
+{
+ return mors->bus_ops->reg32_read(mors, addr, data);
+}
+
+static inline int mm81x_reg32_write(struct mm81x *mors, u32 addr, u32 data)
+{
+ return mors->bus_ops->reg32_write(mors, addr, data);
+}
+
+static inline int mm81x_bus_digital_reset(struct mm81x *mors)
+{
+ if (mors->bus_ops->digital_reset)
+ return mors->bus_ops->digital_reset(mors);
+
+ return 0;
+}
+
+static inline void mm81x_set_bus_enable(struct mm81x *mors, bool enable)
+{
+ mors->bus_ops->set_bus_enable(mors, enable);
+}
+
+static inline void mm81x_bus_config_burst_mode(struct mm81x *mors,
+ bool enable_burst)
+{
+ if (mors->bus_ops->config_burst_mode)
+ mors->bus_ops->config_burst_mode(mors, enable_burst);
+}
+
+static inline void mm81x_claim_bus(struct mm81x *mors)
+{
+ mors->bus_ops->claim(mors);
+}
+
+static inline void mm81x_bus_set_irq(struct mm81x *mors, bool enable)
+{
+ mors->bus_ops->set_irq(mors, enable);
+}
+
+static inline void mm81x_release_bus(struct mm81x *mors)
+{
+ mors->bus_ops->release(mors);
+}
+
+static inline unsigned int mm81x_bus_get_alignment(struct mm81x *mors)
+{
+ return mors->bus_ops->bulk_alignment;
+}
+
+#endif /* !_MM81X_BUS_H_ */
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 02/33] wifi: ieee80211: introduce generic KHZ_TO_HZ helper
From: Lachlan Hodges @ 2026-06-26 6:28 UTC (permalink / raw)
To: johannes
Cc: arien.judge, dan.callaghan, ayman.grais, linux-wireless,
Lachlan Hodges, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
Useful for S1G drivers due to the increased required granularity,
but may be useful for others so include it as a generic helper.
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
include/linux/ieee80211.h | 1 +
1 file changed, 1 insertion(+)
diff --git a/include/linux/ieee80211.h b/include/linux/ieee80211.h
index d40484451e9a..084ad45aa2d8 100644
--- a/include/linux/ieee80211.h
+++ b/include/linux/ieee80211.h
@@ -2616,6 +2616,7 @@ static inline int ieee80211_get_tdls_action(struct sk_buff *skb)
/* convert frequencies */
#define MHZ_TO_KHZ(freq) ((freq) * 1000)
#define KHZ_TO_MHZ(freq) ((freq) / 1000)
+#define KHZ_TO_HZ(x) ((x) * 1000)
#define PR_KHZ(f) KHZ_TO_MHZ(f), f % 1000
#define KHZ_F "%d.%03d"
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 01/33] wifi: cfg80211: introduce helper to get S1G primary width
From: Lachlan Hodges @ 2026-06-26 6:28 UTC (permalink / raw)
To: johannes
Cc: arien.judge, dan.callaghan, ayman.grais, linux-wireless,
Lachlan Hodges, linux-kernel
In-Reply-To: <20260626063014.1275235-1-lachlan.hodges@morsemicro.com>
This is needed for drivers and will be needed for mac80211/cfg80211
in the future so introduce a generic accessor to retrieve the
chandefs S1G primary channel width.
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
---
include/net/cfg80211.h | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/include/net/cfg80211.h b/include/net/cfg80211.h
index 8188ad200de5..a5d54d70d72b 100644
--- a/include/net/cfg80211.h
+++ b/include/net/cfg80211.h
@@ -1236,6 +1236,26 @@ ieee80211_chandef_max_power(struct cfg80211_chan_def *chandef)
return chandef->chan->max_power;
}
+/**
+ * cfg80211_chandef_s1g_pri_width - return S1G primary width in MHz
+ *
+ * An S1G interface may have a primary channel width of either 1
+ * or 2MHz depending on whether chandef::s1g_primary_2mhz is set.
+ *
+ * Note: There is _always_ a 1MHz primary subchannel, regardless
+ * of the primary width. So chandef::chan always points to this
+ * 1MHz primary channel.
+ *
+ * @chandef: the chandef to use
+ *
+ * Returns: width in MHz of the S1G primary channel in use
+ */
+static inline int
+cfg80211_chandef_s1g_pri_width(struct cfg80211_chan_def *chandef)
+{
+ return chandef->s1g_primary_2mhz ? 2 : 1;
+}
+
/**
* cfg80211_any_usable_channels - check for usable channels
* @wiphy: the wiphy to check for
--
2.43.0
^ permalink raw reply related
* [PATCH wireless-next v3 00/33] wifi: mm81x: add mm81x driver
From: Lachlan Hodges @ 2026-06-26 6:28 UTC (permalink / raw)
To: johannes
Cc: arien.judge, dan.callaghan, ayman.grais, linux-wireless,
Lachlan Hodges, Andrew Pope, Bassem Dawood, Chetan Mistry,
James Herbert, Sahand Maleki, Simon Wadsworth
This series adds the first Wi-Fi HaLow driver to support the Morse
Micro mm81x chip family via USB and SDIO.
S1G support in the kernel is only new, and as a result this driver has
been scoped to be simple and only support station and AP interface.
The Wi-Fi specific features only cover the minimum required for basic
use such as powersave, aggregation, rate control and so on. The driver
will be extended into the future as S1G support for operations such as
ACS, channel switching and so on are added into the wireless stack.
The driver contains two checkpatch CHECKs for a static rate array
and a line length of 81 using the same ignore list as the wireless
checkpatch NIPA bot with the exception of OPEN_ENDED_LINE which has
been excluded.
The driver has been build tested on a long list of architectures and
compilers via Intels LKP.
The driver currently supports IEEE80211-2024 US channels only, with
AU 2020 also available. In order for this to be expanded additional
non-trivial kernel work is required which will begin once the
driver is upstream.
Changes since v2:
- In INIT_CMD_HDR don't init members to 0
- Remove the if (!cmd_q) and subsequent comment as this will never be
false with YAPS
- Clamp firmware retcode if outside errno range to prevent erroneous
ERR_PTR usage when passing ret code up the stack
- Remove some LE force wrappers
- convert beacon work from tasklet (deprecated) to work item running
on system_bh_wq
- mm81x_yaps_hw_status_registers -> mm81x_yaps_status_regs
- Introduce mm81x_yaps_hw_status_regs which is the fw representation
of the status registers for DMA target (i.e in little endian)
- Remove macro typedefs over default Elf types
- Introduce mm81x_elf32_ehdr and friends as implementation-specific
types to parse fw and bcf
- Use read_poll_timeout for waiting for host table pointer rather then
open coding
- Use put_unaligned_le16 for reading the mac address from OTP
- Use tabs for the CHANS1G macro, also enclose chan_flags in braces to
prevent checkpatch error
- remove some basic sta member variable wrappers and just call directly
- Remove sta_rc_update entirely, this cannot be reached in S1G
- Free mcast_filter to fix memory leak
- Rename mac2leuint32 to mac2le32 also add a comment
- use block_len and byte_len respectively for unaligned reads in sdio
- Propagate error in sdio_reg32_read
- Fix leaks and cleanups in usb probe
- Fix off by one in pkt->tc_queue check
- Remove some rc.{c/h} code that is unused
- Removed use_1mhz_probes scan param since we unconditionally set it
to true, as a result unconditionally set HOST_CMD_HW_SCAN_FLAGS_1MHZ_PROBES
- Implement flush with drop == false
- Move some macros / helper functions into stack that are generic
v1: https://lore.kernel.org/linux-wireless/20260227041108.66508-1-lachlan.hodges@morsemicro.com/
v2: https://lore.kernel.org/linux-wireless/20260430045615.334669-1-lachlan.hodges@morsemicro.com/
The driver has had many authors who are listed below in
alphabetical order:
Co-developed-by: Andrew Pope <andrew.pope@morsemicro.com>
Signed-off-by: Andrew Pope <andrew.pope@morsemicro.com>
Co-developed-by: Arien Judge <arien.judge@morsemicro.com>
Signed-off-by: Arien Judge <arien.judge@morsemicro.com>
Co-developed-by: Ayman Grais <ayman.grais@morsemicro.com>
Signed-off-by: Ayman Grais <ayman.grais@morsemicro.com>
Co-developed-by: Bassem Dawood <bassem@morsemicro.com>
Signed-off-by: Bassem Dawood <bassem@morsemicro.com>
Co-developed-by: Chetan Mistry <chetan.mistry@morsemicro.com>
Signed-off-by: Chetan Mistry <chetan.mistry@morsemicro.com>
Co-developed-by: Dan Callaghan <dan.callaghan@morsemicro.com>
Signed-off-by: Dan Callaghan <dan.callaghan@morsemicro.com>
Co-developed-by: James Herbert <james.herbert@morsemicro.com>
Signed-off-by: James Herbert <james.herbert@morsemicro.com>
Co-developed-by: Sahand Maleki <sahand.maleki@morsemicro.com>
Signed-off-by: Sahand Maleki <sahand.maleki@morsemicro.com>
Co-developed-by: Simon Wadsworth <simon@morsemicro.com>
Signed-off-by: Simon Wadsworth <simon@morsemicro.com>
Signed-off-by: Lachlan Hodges <lachlan.hodges@morsemicro.com>
Lachlan Hodges (33):
wifi: cfg80211: introduce helper to get S1G primary width
wifi: ieee80211: introduce generic KHZ_TO_HZ helper
wifi: mm81x: add bus.h
wifi: mm81x: add command.c
wifi: mm81x: add command_defs.h
wifi: mm81x: add command.h
wifi: mm81x: add core.c
wifi: mm81x: add core.h
wifi: mm81x: add fw.c
wifi: mm81x: add fw.h
wifi: mm81x: add hif.h
wifi: mm81x: add hw.c
wifi: mm81x: add hw.h
wifi: mm81x: add mac.c
wifi: mm81x: add mac.h
wifi: mm81x: add mmrc.c
wifi: mm81x: add mmrc.h
wifi: mm81x: add ps.c
wifi: mm81x: add ps.h
wifi: mm81x: add rate_code.h
wifi: mm81x: add rc.c
wifi: mm81x: add rc.h
mmc: sdio: add Morse Micro vendor ids
wifi: mm81x: add sdio.c
wifi: mm81x: add skbq.c
wifi: mm81x: add skbq.h
wifi: mm81x: add usb.c
wifi: mm81x: add yaps.c
wifi: mm81x: add yaps.h
wifi: mm81x: add yaps_hw.c
wifi: mm81x: add yaps_hw.h
wifi: mm81x: add Kconfig and Makefile
wifi: mm81x: add MAINTAINERS entry
MAINTAINERS | 8 +
drivers/net/wireless/Kconfig | 1 +
drivers/net/wireless/Makefile | 1 +
drivers/net/wireless/morsemicro/Kconfig | 15 +
drivers/net/wireless/morsemicro/Makefile | 2 +
drivers/net/wireless/morsemicro/mm81x/Kconfig | 24 +
.../net/wireless/morsemicro/mm81x/Makefile | 21 +
drivers/net/wireless/morsemicro/mm81x/bus.h | 99 +
.../net/wireless/morsemicro/mm81x/command.c | 563 ++++
.../net/wireless/morsemicro/mm81x/command.h | 85 +
.../wireless/morsemicro/mm81x/command_defs.h | 1658 +++++++++++
drivers/net/wireless/morsemicro/mm81x/core.c | 142 +
drivers/net/wireless/morsemicro/mm81x/core.h | 464 ++++
drivers/net/wireless/morsemicro/mm81x/fw.c | 732 +++++
drivers/net/wireless/morsemicro/mm81x/fw.h | 141 +
drivers/net/wireless/morsemicro/mm81x/hif.h | 117 +
drivers/net/wireless/morsemicro/mm81x/hw.c | 367 +++
drivers/net/wireless/morsemicro/mm81x/hw.h | 176 ++
drivers/net/wireless/morsemicro/mm81x/mac.c | 2443 +++++++++++++++++
drivers/net/wireless/morsemicro/mm81x/mac.h | 63 +
drivers/net/wireless/morsemicro/mm81x/mmrc.c | 1354 +++++++++
drivers/net/wireless/morsemicro/mm81x/mmrc.h | 193 ++
drivers/net/wireless/morsemicro/mm81x/ps.c | 120 +
drivers/net/wireless/morsemicro/mm81x/ps.h | 22 +
.../net/wireless/morsemicro/mm81x/rate_code.h | 177 ++
drivers/net/wireless/morsemicro/mm81x/rc.c | 494 ++++
drivers/net/wireless/morsemicro/mm81x/rc.h | 51 +
drivers/net/wireless/morsemicro/mm81x/sdio.c | 613 +++++
drivers/net/wireless/morsemicro/mm81x/skbq.c | 1065 +++++++
drivers/net/wireless/morsemicro/mm81x/skbq.h | 218 ++
drivers/net/wireless/morsemicro/mm81x/usb.c | 943 +++++++
drivers/net/wireless/morsemicro/mm81x/yaps.c | 704 +++++
drivers/net/wireless/morsemicro/mm81x/yaps.h | 77 +
.../net/wireless/morsemicro/mm81x/yaps_hw.c | 702 +++++
.../net/wireless/morsemicro/mm81x/yaps_hw.h | 52 +
include/linux/ieee80211.h | 1 +
include/linux/mmc/sdio_ids.h | 3 +
include/net/cfg80211.h | 20 +
38 files changed, 13931 insertions(+)
create mode 100644 drivers/net/wireless/morsemicro/Kconfig
create mode 100644 drivers/net/wireless/morsemicro/Makefile
create mode 100644 drivers/net/wireless/morsemicro/mm81x/Kconfig
create mode 100644 drivers/net/wireless/morsemicro/mm81x/Makefile
create mode 100644 drivers/net/wireless/morsemicro/mm81x/bus.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/command.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/command.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/command_defs.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/core.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/core.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/fw.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/fw.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/hif.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/hw.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/hw.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/mac.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/mac.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/mmrc.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/mmrc.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/ps.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/ps.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/rate_code.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/rc.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/rc.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/sdio.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/skbq.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/skbq.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/usb.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps.h
create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
create mode 100644 drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
--
2.43.0
^ permalink raw reply
* [PATCH v11] PCI: Add device-specific reset for Qualcomm devices
From: Jose Ignacio Tornos Martinez @ 2026-06-26 5:50 UTC (permalink / raw)
To: bhelgaas, alex, mani
Cc: jjohnson, linux-pci, linux-wireless, ath11k, ath12k, mhi,
linux-kernel, Jose Ignacio Tornos Martinez
Some Qualcomm PCIe devices (WCN6855/WCN7850 WiFi cards, SDX62/SDX65 modems)
lack working reset methods for VFIO passthrough scenarios. These devices
have no FLR capability, advertise NoSoftRst+ (blocking PM reset), and have
broken bus reset.
The problem manifests in VFIO passthrough scenarios:
- WCN6855 (17cb:1103) and WCN7850 (17cb:1107) WiFi devices:
Normal VM operation works fine, including clean shutdown/reboot.
However, when the VM terminates uncleanly (crash, force-off), VFIO
attempts to reset the device before it can be assigned to another VM.
Without a working reset method, the device remains in an undefined state,
preventing reuse.
- SDX62/SDX65 (17cb:0308) 5G modems: Never successfully initialize even
on first VM assignment without proper reset capability.
Add device-specific reset methods using BAR-space hardware reset registers
that exist in these devices:
- WCN6855/WCN7850 WiFi devices use SoC global reset via BAR0 (sequence from
ath11k/ath12k driver: ath11k_pci_soc_global_reset(), ath11k_pci_sw_reset(),
ath11k_mhi_set_mhictrl_reset()):
- Write/clear reset bit at offset 0x3008
- Wait for PCIe link recovery (up to 5 seconds)
- Clear MHI controller SYSERR status at offset 0x38
- SDX62/SDX65 modem devices use MHI SoC reset via BAR0 (sequence from MHI
driver: mhi_soc_reset(), mhi_pci_reset_prepare()):
- Write reset request to offset 0xb0
- Wait 2 seconds for reset completion
These are true hardware reset mechanisms (not power management or firmware
error recovery), providing proper device reset for VFIO scenarios.
Testing was performed on desktop platforms with M.2 WiFi and modem cards
using M.2-to-PCIe adapters, including extensive force-reset cycling to
verify stability.
Signed-off-by: Jose Ignacio Tornos Martinez <jtornosm@redhat.com>
---
v11: Address Manivannan Sadhasivam feedback:
- Remove unused QUALCOMM_WIFI_MHISTATUS define
- Use PCI_ERROR_RESPONSE instead of 0xffffffff
- Sort device IDs in ascending order (0x0308, 0x1103, 0x1107)
v10: https://lore.kernel.org/all/20260623183115.1585273-1-jtornosm@redhat.com/
drivers/pci/quirks.c | 117 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 117 insertions(+)
diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c
index 431c021d7414..0de606366200 100644
--- a/drivers/pci/quirks.c
+++ b/drivers/pci/quirks.c
@@ -4240,6 +4240,120 @@ static int reset_hinic_vf_dev(struct pci_dev *pdev, bool probe)
return 0;
}
+#define QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET 0x3008
+#define QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET_V BIT(0)
+#define QUALCOMM_WIFI_MHICTRL 0x38
+#define QUALCOMM_WIFI_MHICTRL_RESET_MASK 0x2
+
+/*
+ * Qualcomm WiFi device-specific reset using SoC global reset via BAR0
+ * registers.
+ */
+static int reset_qualcomm_wifi(struct pci_dev *pdev, bool probe)
+{
+ bool link_recovered = false;
+ unsigned long timeout;
+ void __iomem *bar;
+ u32 val;
+ u16 cmd;
+
+ if (probe)
+ return 0;
+
+ if (pdev->current_state != PCI_D0)
+ return -EINVAL;
+
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ pci_write_config_word(pdev, PCI_COMMAND, cmd | PCI_COMMAND_MEMORY);
+
+ bar = pci_iomap(pdev, 0, 0);
+ if (!bar) {
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+ return -ENODEV;
+ }
+
+ val = ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET);
+ val |= QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET_V;
+ iowrite32(val, bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET);
+ ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET);
+
+ msleep(10);
+
+ val &= ~QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET_V;
+ iowrite32(val, bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET);
+ ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET);
+
+ msleep(10);
+
+ timeout = jiffies + msecs_to_jiffies(5000);
+ while (time_before(jiffies, timeout)) {
+ val = ioread32(bar + QUALCOMM_WIFI_PCIE_SOC_GLOBAL_RESET);
+ if (val != PCI_ERROR_RESPONSE) {
+ link_recovered = true;
+ break;
+ }
+ msleep(20);
+ }
+
+ if (!link_recovered) {
+ pci_err(pdev, "PCIe link failed to recover after reset\n");
+ goto out_restore;
+ }
+
+ /* After SOC_GLOBAL_RESET, MHISTATUS may still have SYSERR bit set
+ * and thus need to set MHICTRL_RESET to clear SYSERR.
+ */
+ iowrite32(QUALCOMM_WIFI_MHICTRL_RESET_MASK, bar + QUALCOMM_WIFI_MHICTRL);
+ ioread32(bar + QUALCOMM_WIFI_MHICTRL);
+
+ msleep(10);
+
+out_restore:
+ pci_iounmap(pdev, bar);
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+
+ return link_recovered ? 0 : -ETIMEDOUT;
+}
+
+#define MHI_SOC_RESET_REQ_OFFSET 0xb0
+#define MHI_SOC_RESET_REQ BIT(0)
+
+/*
+ * Qualcomm modem device-specific reset using MHI SoC reset via BAR0
+ * register.
+ */
+static int reset_qualcomm_modem(struct pci_dev *pdev, bool probe)
+{
+ void __iomem *bar;
+ u16 cmd;
+
+ if (probe)
+ return 0;
+
+ if (pdev->current_state != PCI_D0)
+ return -EINVAL;
+
+ pci_read_config_word(pdev, PCI_COMMAND, &cmd);
+ pci_write_config_word(pdev, PCI_COMMAND, cmd | PCI_COMMAND_MEMORY);
+
+ bar = pci_iomap(pdev, 0, 0);
+ if (!bar) {
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+ return -ENODEV;
+ }
+
+ iowrite32(MHI_SOC_RESET_REQ, bar + MHI_SOC_RESET_REQ_OFFSET);
+ ioread32(bar + MHI_SOC_RESET_REQ_OFFSET);
+
+ /* Be sure device reset has been executed */
+ msleep(2000);
+
+ pci_iounmap(pdev, bar);
+ pci_write_config_word(pdev, PCI_COMMAND, cmd);
+
+ return 0;
+}
+
static const struct pci_dev_reset_methods pci_dev_reset_methods[] = {
{ PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_82599_SFP_VF,
reset_intel_82599_sfp_virtfn },
@@ -4255,6 +4369,9 @@ static const struct pci_dev_reset_methods pci_dev_reset_methods[] = {
reset_chelsio_generic_dev },
{ PCI_VENDOR_ID_HUAWEI, PCI_DEVICE_ID_HINIC_VF,
reset_hinic_vf_dev },
+ { PCI_VENDOR_ID_QCOM, 0x0308, reset_qualcomm_modem }, /* SDX62/SDX65 modems */
+ { PCI_VENDOR_ID_QCOM, 0x1103, reset_qualcomm_wifi }, /* WCN6855 WiFi */
+ { PCI_VENDOR_ID_QCOM, 0x1107, reset_qualcomm_wifi }, /* WCN7850 WiFi */
{ 0 }
};
--
2.54.0
^ permalink raw reply related
* Re: [PATCH ath-next v3] wifi: ath12k: avoid setting 320MHz support on non 6GHz band
From: Rameshkumar Sundaram @ 2026-06-26 4:59 UTC (permalink / raw)
To: Nicolas Escande, ath12k; +Cc: linux-wireless
In-Reply-To: <20260623151613.72113-1-nico.escande@gmail.com>
On 6/23/2026 8:46 PM, Nicolas Escande wrote:
> On a split phy qcn9274 (2.4GHz + 5GHz low), "iw phy" reports 320MHz
> related features on the 5GHz band while it should not:
>
> Wiphy phy1
> [...]
> Band 2:
> [...]
> EHT Iftypes: managed
> [...]
> EHT PHY Capabilities: (0xe2ffdbe018778000):
> 320MHz in 6GHz Supported
> [...]
> Beamformee SS (320MHz): 7
> [...]
> Number Of Sounding Dimensions (320MHz): 3
> [...]
> EHT MCS/NSS: (0x22222222222222222200000000):
>
> This is also reflected in the beacons sent by a mesh interface started on
> that band. They erroneously advertise 320MHz support too.
>
> This should not happen as IEEE Std 802.11-2024, subclause 9.4.2.323.3 says
> we should not set the 320MHz related fields when not operating on a 6GHz
> band. For example it says about Bit 0 "Support For 320 MHz In 6 GHz"
>
> "Reserved if the EHT Capabilities element is indicating capabilities for
> the 2.4 GHz or 5 GHz bands."
>
> Fix this by clearing the related bits when converting from WMI eht phy
> capabilities to mac80211 phy capabilities, for bands other than 6GHz.
>
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.3.1-00218-QCAHKSWPL_SILICONZ-1
>
> Signed-off-by: Nicolas Escande <nico.escande@gmail.com>
Reviewed-by: Rameshkumar Sundaram <rameshkumar.sundaram@oss.qualcomm.com>
^ permalink raw reply
* [PATCHv2 ath-next] wifi: ath9k: return ath_buf to pool on A-MPDU subframe retry
From: Rosen Penev @ 2026-06-26 4:10 UTC (permalink / raw)
To: linux-wireless; +Cc: Toke Høiland-Jørgensen, open list
When an A-MPDU subframe needs retransmission, its ath_buf descriptor was
moved to a local bf_head list that went out of scope without returning
the buffer to the free pool (sc->tx.txbuf). This progressively depletes
the 512-entry TX buffer pool under normal retransmission conditions,
eventually stalling all TX.
Unmap the DMA mapping (a new one will be created on retry), clear the
buffer references including fi->bf to prevent reuse of the freed
descriptor on retry, and return it to the pool via ath_tx_return_buffer.
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
v2: set fi-bf to NULL.
drivers/net/wireless/ath/ath9k/xmit.c | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/drivers/net/wireless/ath/ath9k/xmit.c b/drivers/net/wireless/ath/ath9k/xmit.c
index 3334f570be50..31187753a819 100644
--- a/drivers/net/wireless/ath/ath9k/xmit.c
+++ b/drivers/net/wireless/ath/ath9k/xmit.c
@@ -665,6 +665,16 @@ static void ath_tx_complete_aggr(struct ath_softc *sc, struct ath_txq *txq,
* queue to retain ordering
*/
__skb_queue_tail(&bf_pending, skb);
+
+ if (!list_empty(&bf_head)) {
+ dma_unmap_single(sc->dev, bf->bf_buf_addr,
+ skb->len, DMA_TO_DEVICE);
+ bf->bf_buf_addr = 0;
+ bf->bf_mpdu = NULL;
+ fi->bf = NULL;
+ list_del(&bf->list);
+ ath_tx_return_buffer(sc, bf);
+ }
}
bf = bf_next;
--
2.54.0
^ permalink raw reply related
* [PATCHv3 ath-next] wifi: ath9k: eeprom: drop static from local pdadc and vpdTable arrays
From: Rosen Penev @ 2026-06-26 4:06 UTC (permalink / raw)
To: linux-wireless
Cc: Toke Høiland-Jørgensen, Nathan Chancellor,
Nick Desaulniers, Bill Wendling, Justin Stitt, open list,
open list:CLANG/LLVM BUILD SUPPORT:Keyword:b(?i:clang|llvm)b
Remove the static qualifier from mutable local arrays in three EEPROM
power-calibration functions. These arrays are written to during normal
operation, so static storage is both unnecessary and misleading: it
implies sharing across calls when no such sharing is intended, and it
makes the code subtly non-reentrant. The sibling function in
eeprom_9287.c already uses an automatic (stack-local) pdadcValues,
confirming this is the correct pattern.
This keeps ~1 KB of data off the static data section at the cost of
stack usage, consistent with the rest of the driver's coding style.
As a safety measure, also add bounds validation for the EEPROM-derived
loop limits and indices that drive these arrays. Without these guards,
a malformed EEPROM calibration dataset can cause stack buffer overflows
(vpdTable rows are 64 bytes but the fill loop runs up to 128 iterations),
out-of-bounds reads when the VPD table has fewer than 2 entries, a
negative-index fallback when numXpdGains == 0, and unbounded shifts in
the pdadc adjustment functions. All of these are reachable through
on-device EEPROM data and were latent as BSS corruptions before the
stack move.
Also alias vpdTableI onto vpdTableL to shrink stack frame
vpdTableL, vpdTableR, and vpdTableI are never live simultaneously.
vpdTableL and vpdTableR are consumed during the frequency-interpolation
step that writes vpdTableI; after the if/else they are never read
again. Reuse vpdTableL for the interpolated result (what was
vpdTableI), reducing the stack frame by one 256-byte array.
The read-via-write in the else branch is safe: ath9k_hw_interpolate()
receives vpdTableL[i][j] by value as a function argument before the
return value is written back to vpdTableL[i][j].
Stack frame size change (x86_64, clang):
before: 0x440 (1088 B)
after: 0x330 (816 B)
Assisted-by: opencode:big-pickle
Signed-off-by: Rosen Penev <rosenp@gmail.com>
---
v3: clean up based on review
v2: add bounds checks
drivers/net/wireless/ath/ath9k/eeprom.c | 111 ++++++++++++-------
drivers/net/wireless/ath/ath9k/eeprom_4k.c | 2 +-
drivers/net/wireless/ath/ath9k/eeprom_9287.c | 33 ++++--
drivers/net/wireless/ath/ath9k/eeprom_def.c | 10 +-
4 files changed, 109 insertions(+), 47 deletions(-)
diff --git a/drivers/net/wireless/ath/ath9k/eeprom.c b/drivers/net/wireless/ath/ath9k/eeprom.c
index df58dc02e104..05f0a0d70a50 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom.c
@@ -241,11 +241,24 @@ void ath9k_hw_fill_vpd_table(u8 pwrMin, u8 pwrMax, u8 *pPwrList,
u8 *pVpdList, u16 numIntercepts,
u8 *pRetVpdList)
{
- u16 i, k;
+ u16 i, k, maxIndex;
+ u16 range;
u8 currPwr = pwrMin;
u16 idxL = 0, idxR = 0;
- for (i = 0; i <= (pwrMax - pwrMin) / 2; i++) {
+ if (pwrMax < pwrMin) {
+ pr_warn_ratelimited("ath9k: VPD table pwrMax (%u) < pwrMin (%u)\n", pwrMax, pwrMin);
+ memset(pRetVpdList, 0, AR5416_MAX_PWR_RANGE_IN_HALF_DB);
+ return;
+ }
+
+ range = (pwrMax - pwrMin) / 2;
+ maxIndex = min_t(u16, range, AR5416_MAX_PWR_RANGE_IN_HALF_DB - 1);
+ if (range >= AR5416_MAX_PWR_RANGE_IN_HALF_DB)
+ pr_warn_ratelimited("ath9k: VPD table range %u exceeds maximum, clamped to %u\n",
+ range, maxIndex);
+
+ for (i = 0; i <= maxIndex; i++) {
ath9k_hw_get_lower_upper_index(currPwr, pPwrList,
numIntercepts, &(idxL),
&(idxR));
@@ -460,12 +473,8 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
int i, j, k;
int16_t ss;
u16 idxL = 0, idxR = 0, numPiers;
- static u8 vpdTableL[AR5416_NUM_PD_GAINS]
- [AR5416_MAX_PWR_RANGE_IN_HALF_DB];
- static u8 vpdTableR[AR5416_NUM_PD_GAINS]
- [AR5416_MAX_PWR_RANGE_IN_HALF_DB];
- static u8 vpdTableI[AR5416_NUM_PD_GAINS]
- [AR5416_MAX_PWR_RANGE_IN_HALF_DB];
+ u8 vpdTableL[AR5416_NUM_PD_GAINS][AR5416_MAX_PWR_RANGE_IN_HALF_DB];
+ u8 vpdTableR[AR5416_NUM_PD_GAINS][AR5416_MAX_PWR_RANGE_IN_HALF_DB];
u8 *pVpdL, *pVpdR, *pPwrL, *pPwrR;
u8 minPwrT4[AR5416_NUM_PD_GAINS];
@@ -473,6 +482,8 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
int16_t vpdStep;
int16_t tmpVal;
u16 sizeCurrVpdTable, maxIndex, tgtIndex;
+ u16 vpdRange, vpdFillMax;
+ u16 vpdFillMaxArr[AR5416_NUM_PD_GAINS];
bool match;
int16_t minDelta = 0;
struct chan_centers centers;
@@ -506,30 +517,27 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
minPwrT4[i] = data_9287[idxL].pwrPdg[i][0];
maxPwrT4[i] = data_9287[idxL].pwrPdg[i][intercepts - 1];
ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i],
- data_9287[idxL].pwrPdg[i],
- data_9287[idxL].vpdPdg[i],
- intercepts,
- vpdTableI[i]);
+ data_9287[idxL].pwrPdg[i],
+ data_9287[idxL].vpdPdg[i], intercepts,
+ vpdTableL[i]);
}
} else if (eeprom_4k) {
for (i = 0; i < numXpdGains; i++) {
minPwrT4[i] = data_4k[idxL].pwrPdg[i][0];
maxPwrT4[i] = data_4k[idxL].pwrPdg[i][intercepts - 1];
ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i],
- data_4k[idxL].pwrPdg[i],
- data_4k[idxL].vpdPdg[i],
- intercepts,
- vpdTableI[i]);
+ data_4k[idxL].pwrPdg[i],
+ data_4k[idxL].vpdPdg[i], intercepts,
+ vpdTableL[i]);
}
} else {
for (i = 0; i < numXpdGains; i++) {
minPwrT4[i] = data_def[idxL].pwrPdg[i][0];
maxPwrT4[i] = data_def[idxL].pwrPdg[i][intercepts - 1];
ath9k_hw_fill_vpd_table(minPwrT4[i], maxPwrT4[i],
- data_def[idxL].pwrPdg[i],
- data_def[idxL].vpdPdg[i],
- intercepts,
- vpdTableI[i]);
+ data_def[idxL].pwrPdg[i],
+ data_def[idxL].vpdPdg[i], intercepts,
+ vpdTableL[i]);
}
}
} else {
@@ -567,19 +575,37 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
intercepts,
vpdTableR[i]);
- for (j = 0; j <= (maxPwrT4[i] - minPwrT4[i]) / 2; j++) {
- vpdTableI[i][j] =
- (u8)(ath9k_hw_interpolate((u16)
- FREQ2FBIN(centers.
- synth_center,
- IS_CHAN_2GHZ
- (chan)),
- bChans[idxL], bChans[idxR],
- vpdTableL[i][j], vpdTableR[i][j]));
+ vpdRange = (maxPwrT4[i] >= minPwrT4[i]) ? (maxPwrT4[i] - minPwrT4[i]) / 2 :
+ 0;
+ vpdFillMax = min_t(u16, vpdRange, AR5416_MAX_PWR_RANGE_IN_HALF_DB - 1);
+ /*
+ * vpdTableL doubles as the interpolated output in-place.
+ * ath9k_hw_interpolate() receives vpdTableL[i][j] by
+ * value (left-pier data) before the return overwrites
+ * vpdTableL[i][j], and each column j is read only once
+ * per iteration - safe as long as vpdTableR remains a
+ * separate buffer.
+ */
+ for (j = 0; j <= vpdFillMax; j++) {
+ vpdTableL[i][j] = (u8)(ath9k_hw_interpolate(
+ (u16)FREQ2FBIN(centers.synth_center, IS_CHAN_2GHZ(chan)),
+ bChans[idxL], bChans[idxR], vpdTableL[i][j],
+ vpdTableR[i][j]));
}
}
}
+ /*
+ * Compute safe VPD table index limit for each gain row.
+ * This mirrors the vpdFillMax computation in the else-branch
+ * interpolation above - both clamp the same derived value.
+ */
+ for (i = 0; i < numXpdGains; i++) {
+ vpdFillMaxArr[i] = min_t(
+ u16, (maxPwrT4[i] >= minPwrT4[i]) ? (maxPwrT4[i] - minPwrT4[i]) / 2 : 0,
+ AR5416_MAX_PWR_RANGE_IN_HALF_DB - 1);
+ }
+
k = 0;
for (i = 0; i < numXpdGains; i++) {
@@ -605,33 +631,39 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
(minPwrT4[i] / 2)) -
tPdGainOverlap + 1 + minDelta);
}
- vpdStep = (int16_t)(vpdTableI[i][1] - vpdTableI[i][0]);
+ sizeCurrVpdTable = vpdFillMaxArr[i] + 1;
+
+ if (sizeCurrVpdTable >= 2)
+ vpdStep = (int16_t)(vpdTableL[i][1] - vpdTableL[i][0]);
+ else
+ vpdStep = 1; /* no entries to diff; avoids zero-step extrapolation */
vpdStep = (int16_t)((vpdStep < 1) ? 1 : vpdStep);
while ((ss < 0) && (k < (AR5416_NUM_PDADC_VALUES - 1))) {
- tmpVal = (int16_t)(vpdTableI[i][0] + ss * vpdStep);
+ tmpVal = (int16_t)(vpdTableL[i][0] + ss * vpdStep);
pPDADCValues[k++] = (u8)((tmpVal < 0) ? 0 : tmpVal);
ss++;
}
-
- sizeCurrVpdTable = (u8) ((maxPwrT4[i] - minPwrT4[i]) / 2 + 1);
tgtIndex = (u8)(pPdGainBoundaries[i] + tPdGainOverlap -
(minPwrT4[i] / 2));
maxIndex = (tgtIndex < sizeCurrVpdTable) ?
tgtIndex : sizeCurrVpdTable;
while ((ss < maxIndex) && (k < (AR5416_NUM_PDADC_VALUES - 1))) {
- pPDADCValues[k++] = vpdTableI[i][ss++];
+ pPDADCValues[k++] = vpdTableL[i][ss++];
}
- vpdStep = (int16_t)(vpdTableI[i][sizeCurrVpdTable - 1] -
- vpdTableI[i][sizeCurrVpdTable - 2]);
+ if (sizeCurrVpdTable >= 2)
+ vpdStep = (int16_t)(vpdTableL[i][sizeCurrVpdTable - 1] -
+ vpdTableL[i][sizeCurrVpdTable - 2]);
+ else
+ vpdStep = 1; /* no entries to diff; avoids zero-step extrapolation */
vpdStep = (int16_t)((vpdStep < 1) ? 1 : vpdStep);
if (tgtIndex >= maxIndex) {
while ((ss <= tgtIndex) &&
(k < (AR5416_NUM_PDADC_VALUES - 1))) {
- tmpVal = (int16_t)((vpdTableI[i][sizeCurrVpdTable - 1] +
+ tmpVal = (int16_t)((vpdTableL[i][sizeCurrVpdTable - 1] +
(ss - maxIndex + 1) * vpdStep));
pPDADCValues[k++] = (u8)((tmpVal > 255) ?
255 : tmpVal);
@@ -650,6 +682,11 @@ void ath9k_hw_get_gain_boundaries_pdadcs(struct ath_hw *ah,
i++;
}
+ /* Ensure the table contains at least one valid element. */
+ if (k == 0) {
+ WARN_ONCE(1, "ath9k: no PDADC values produced for gain boundaries\n");
+ pPDADCValues[k++] = 0;
+ }
while (k < AR5416_NUM_PDADC_VALUES) {
pPDADCValues[k] = pPDADCValues[k - 1];
k++;
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_4k.c b/drivers/net/wireless/ath/ath9k/eeprom_4k.c
index 3e16cfe059f3..eec7efdc21c3 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_4k.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_4k.c
@@ -288,7 +288,7 @@ static void ath9k_hw_set_4k_power_cal_table(struct ath_hw *ah,
struct cal_data_per_freq_4k *pRawDataset;
u8 *pCalBChans = NULL;
u16 pdGainOverlap_t2;
- static u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
+ u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK];
u16 numPiers, i, j;
u16 numXpdGain, xpdMask;
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_9287.c b/drivers/net/wireless/ath/ath9k/eeprom_9287.c
index c139ac49ccf6..043ee31f0d14 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_9287.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_9287.c
@@ -366,6 +366,15 @@ static void ath9k_hw_set_ar9287_power_cal_table(struct ath_hw *ah,
int16_t diff = 0;
struct ar9287_eeprom *pEepData = &ah->eeprom.map9287;
+ /*
+ * pdadcValues must be zeroed here: under EEP_OL_PWRCTRL the
+ * ath9k_hw_get_gain_boundaries_pdadcs() init path is skipped and
+ * only the unconditional diff-offset shift below runs, which would
+ * otherwise operate on indeterminate stack data. eeprom_def.c
+ * does not need this because both branches of its OLC/!OLC fork
+ * fully populate the array.
+ */
+ memset(pdadcValues, 0, sizeof(pdadcValues));
xpdMask = pEepData->modalHeader.xpdGain;
if (ath9k_hw_ar9287_get_eeprom_rev(ah) >= AR9287_EEP_MINOR_VER_2)
@@ -463,13 +472,23 @@ static void ath9k_hw_set_ar9287_power_cal_table(struct ath_hw *ah,
(int32_t)AR9287_PWR_TABLE_OFFSET_DB);
diff *= 2;
- for (j = 0; j < ((u16)AR5416_NUM_PDADC_VALUES-diff); j++)
- pdadcValues[j] = pdadcValues[j+diff];
-
- for (j = (u16)(AR5416_NUM_PDADC_VALUES-diff);
- j < AR5416_NUM_PDADC_VALUES; j++)
- pdadcValues[j] =
- pdadcValues[AR5416_NUM_PDADC_VALUES-diff];
+ /* diff is safe: the bounds check above ensures
+ * it is in [0, AR5416_NUM_PDADC_VALUES), so the
+ * subtraction AR5416_NUM_PDADC_VALUES - diff
+ * cannot underflow.
+ */
+ if (diff >= 0 && diff < AR5416_NUM_PDADC_VALUES) {
+ for (j = 0; j < ((u16)AR5416_NUM_PDADC_VALUES - diff); j++)
+ pdadcValues[j] = pdadcValues[j + diff];
+
+ for (j = (u16)(AR5416_NUM_PDADC_VALUES - diff);
+ j < AR5416_NUM_PDADC_VALUES; j++)
+ pdadcValues[j] =
+ pdadcValues[AR5416_NUM_PDADC_VALUES - diff];
+ } else {
+ ath_warn(ath9k_hw_common(ah),
+ "ignoring invalid PDADC offset %d\n", diff);
+ }
}
if (!ath9k_hw_ar9287_get_eeprom(ah, EEP_OL_PWRCTRL)) {
diff --git a/drivers/net/wireless/ath/ath9k/eeprom_def.c b/drivers/net/wireless/ath/ath9k/eeprom_def.c
index 5ba467cb7425..3ef927f422dc 100644
--- a/drivers/net/wireless/ath/ath9k/eeprom_def.c
+++ b/drivers/net/wireless/ath/ath9k/eeprom_def.c
@@ -744,8 +744,14 @@ static void ath9k_adjust_pdadc_values(struct ath_hw *ah,
*/
if (AR_SREV_9280_20_OR_LATER(ah)) {
if (AR5416_PWR_TABLE_OFFSET_DB != pwr_table_offset) {
+ if (diff < 0 || diff >= AR5416_NUM_PDADC_VALUES) {
+ ath_warn(ath9k_hw_common(ah), "ignoring invalid PDADC offset %d\n",
+ diff);
+ return;
+ }
+
/* shift the table to start at the new offset */
- for (k = 0; k < (u16)NUM_PDADC(diff); k++ ) {
+ for (k = 0; k < (u16)NUM_PDADC(diff); k++) {
pdadcValues[k] = pdadcValues[k + diff];
}
@@ -769,7 +775,7 @@ static void ath9k_hw_set_def_power_cal_table(struct ath_hw *ah,
struct cal_data_per_freq *pRawDataset;
u8 *pCalBChans = NULL;
u16 pdGainOverlap_t2;
- static u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
+ u8 pdadcValues[AR5416_NUM_PDADC_VALUES];
u16 gainBoundaries[AR5416_PD_GAINS_IN_MASK];
u16 numPiers, i, j;
int16_t diff = 0;
--
2.54.0
^ permalink raw reply related
* Re: [PATCH ath-next v2] wifi: ath12k: advertise ieee_link_id in vdev start MLO params
From: Baochen Qiang @ 2026-06-26 1:43 UTC (permalink / raw)
To: Manish Dharanenthiran, ath12k
Cc: linux-wireless, Hari Naraayana Desikan Kannan, Karthik M
In-Reply-To: <20260623-ieee_link_id-v2-1-8a89d71baf58@oss.qualcomm.com>
On 6/23/2026 1:46 PM, Manish Dharanenthiran wrote:
> Firmware builds the AP MLD partner profile from the hw_link_id passed in
> the vdev start parameters. However, hw_link_id is not always the same as
> the logical per-MLD ieee_link_id, since ieee_link_id is assigned per MLD
> and not per pdev.
>
> This matters in mixed MLO and SLO setups. For example:
>
> MLD 1 - 5 GHz + 6 GHz (2-link MLO): ieee_link_id 0 and 1
> MLD 2 - 6 GHz only (1-link SLO): ieee_link_id 0
> MLD 3 - 5 GHz only (1-link SLO): ieee_link_id 0
>
> The same physical 6 GHz radio can use ieee_link_id 1 for one
> MLD and ieee_link_id 0 for another. Pass the correct ieee_link_id to
> firmware so it can build accurate per-STA profile elements.
>
> Add ieee_link_id to wmi_vdev_start_mlo_params for the self link and to
> wmi_partner_link_info for each partner link. Populate these fields in
> ath12k_mac_mlo_get_vdev_args() from the corresponding vdev link_id
> before encoding the WMI command.
>
> Introduce two new flags in ML params to indicate to firmware when
> the new fields are valid:
>
> ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18) for the self link
> ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19) for partner links
>
> Firmware parses ieee_link_id only when the matching flag is set.
>
> Also fix the debug message by using correct format specifiers and host-endian
> values instead of __le32 values.
>
> Tested-on: QCN9274 hw2.0 PCI WLAN.WBE.1.6-01243-QCAHKSWPL_SILICONZ-1
>
> Co-developed-by: Hari Naraayana Desikan Kannan <hari.kannan@oss.qualcomm.com>
> Signed-off-by: Hari Naraayana Desikan Kannan <hari.kannan@oss.qualcomm.com>
> Co-developed-by: Karthik M <karthik.m@oss.qualcomm.com>
> Signed-off-by: Karthik M <karthik.m@oss.qualcomm.com>
> Signed-off-by: Manish Dharanenthiran <manish.dharanenthiran@oss.qualcomm.com>
Reviewed-by: Baochen Qiang <baochen.qiang@oss.qualcomm.com>
^ permalink raw reply
* [PATCH v4] wifi: ath6kl: fix OOB access from firmware ADDBA window size
From: Tristan Madani @ 2026-06-26 0:04 UTC (permalink / raw)
To: linux-wireless, linux-kernel
Cc: Vasanthakumar Thiagarajan, Jeff Johnson, Johannes Berg,
Tristan Madani
aggr_recv_addba_req_evt() logs a debug message when the firmware-supplied
win_sz is outside [AGGR_WIN_SZ_MIN, AGGR_WIN_SZ_MAX] but does not
return. The out-of-range win_sz is then used in TID_WINDOW_SZ() to
compute a kzalloc size and stored in rxtid->hold_q_sz, leading to
zero-size or overflowed allocations and subsequent out-of-bounds access.
Clean up any previously active aggregation session for the TID first,
then return early when win_sz is out of the valid range, instead of
proceeding with a broken allocation size.
Fixes: bdcd81707973 ("Add ath6kl cleaned up driver")
Cc: stable@vger.kernel.org
Suggested-by: Vasanthakumar Thiagarajan <vasanthakumar.thiagarajan@oss.qualcomm.com>
Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
---
Changes in v4:
- Move aggregation session cleanup before the window size check so
that a previously active session is always torn down, even when the
firmware sends an ADDBA event with an out-of-range window size
(Vasanthakumar Thiagarajan).
Changes in v3:
- Regenerated from wireless-next with proper git format-patch to
produce valid index hashes (v2 had post-processed index lines).
Changes in v2:
- No code changes from v1.
drivers/net/wireless/ath/ath6kl/txrx.c | 10 ++++++----
1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/net/wireless/ath/ath6kl/txrx.c b/drivers/net/wireless/ath/ath6kl/txrx.c
index 80e66ac..a39c815 100644
--- a/drivers/net/wireless/ath/ath6kl/txrx.c
+++ b/drivers/net/wireless/ath/ath6kl/txrx.c
@@ -1722,13 +1722,15 @@ void aggr_recv_addba_req_evt(struct ath6kl_vif *vif, u8 tid_mux, u16 seq_no,
rxtid = &aggr_conn->rx_tid[tid];
- if (win_sz < AGGR_WIN_SZ_MIN || win_sz > AGGR_WIN_SZ_MAX)
- ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: win_sz %d, tid %d\n",
- __func__, win_sz, tid);
-
if (rxtid->aggr)
aggr_delete_tid_state(aggr_conn, tid);
+ if (win_sz < AGGR_WIN_SZ_MIN || win_sz > AGGR_WIN_SZ_MAX) {
+ ath6kl_dbg(ATH6KL_DBG_WLAN_RX, "%s: win_sz %d, tid %d\n",
+ __func__, win_sz, tid);
+ return;
+ }
+
rxtid->seq_next = seq_no;
hold_q_size = TID_WINDOW_SZ(win_sz) * sizeof(struct skb_hold_q);
rxtid->hold_q = kzalloc(hold_q_size, GFP_KERNEL);
--
2.47.3
^ permalink raw reply related
* Re: [PATCH] wifi: carl9170: clamp command response copy to the read buffer size
From: Tristan Madani @ 2026-06-25 23:46 UTC (permalink / raw)
To: Christian Lamparter
Cc: Doruk Tan Ozturk, Johannes Berg, Jeff Johnson, linux-wireless,
linux-kernel, stable, tristan
Hi Christian, Doruk,
No worries at all, these things happen. Both patches address the same
underlying issue from different angles -- mine returns early after
carl9170_restart() to skip the invalid response entirely, Doruk's
clamps the memcpy to ar->readlen.
Either approach works. If it helps, I'm happy to respin my 3-patch
series (the other two patches fix the TX status off-by-two and
rx_stream failover overflow in the same driver).
Let me know if you'd prefer a fresh resend or if you'd rather pick
from what's already on the list.
Thanks,
Tristan
^ permalink raw reply
* [PATCH wireless v4 3/3] wifi: ath6kl: fix OOB read from firmware num_msg in TX complete handler
From: Tristan Madani @ 2026-06-25 23:29 UTC (permalink / raw)
To: Jeff Johnson, Kalle Valo
Cc: Johannes Berg, linux-wireless, linux-kernel, tristan
From: Tristan Madani <tristan@talencesecurity.com>
The firmware-controlled num_msg field (u8, 0-255) drives the loop in
ath6kl_wmi_tx_complete_event_rx() without validation against the buffer
length. This allows out-of-bounds reads of up to 1020 bytes past the
WMI event buffer when the firmware sends an inflated num_msg.
Add a check that the buffer is large enough to hold the fixed struct
and the num_msg variable-length entries.
Fixes: bdcd81707973 ("Add ath6kl cleaned up driver")
Signed-off-by: Tristan Madani <tristan@talencesecurity.com>
---
Changes in v4:
- Split combined bounds check into two sequential checks to avoid
overreading evt->num_msg in the error log when the buffer is too
small for the fixed struct (Jeff Johnson).
Changes in v3:
- Regenerated from wireless-next with proper git format-patch to
produce valid index hashes (v2 had post-processed index lines).
Changes in v2:
- No code changes from v1.
drivers/net/wireless/ath/ath6kl/wmi.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/drivers/net/wireless/ath/ath6kl/wmi.c b/drivers/net/wireless/ath/ath6kl/wmi.c
index 3787b9f..a572952 100644
--- a/drivers/net/wireless/ath/ath6kl/wmi.c
+++ b/drivers/net/wireless/ath/ath6kl/wmi.c
@@ -484,6 +484,18 @@ static int ath6kl_wmi_tx_complete_event_rx(u8 *datap, int len)
evt = (struct wmi_tx_complete_event *) datap;
+ if (len < sizeof(*evt)) {
+ ath6kl_dbg(ATH6KL_DBG_WMI, "tx complete: invalid len %d\n",
+ len);
+ return -EINVAL;
+ }
+
+ if (len < sizeof(*evt) + evt->num_msg * sizeof(struct tx_complete_msg_v1)) {
+ ath6kl_dbg(ATH6KL_DBG_WMI, "tx complete: invalid len %d for %u msgs\n",
+ len, evt->num_msg);
+ return -EINVAL;
+ }
+
ath6kl_dbg(ATH6KL_DBG_WMI, "comp: %d %d %d\n",
evt->num_msg, evt->msg_len, evt->msg_type);
--
2.47.3
^ permalink raw reply related
* Re: [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: George Moussalem @ 2026-06-25 14:24 UTC (permalink / raw)
To: Philipp Zabel, Jens Axboe, Ulf Hansson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Johannes Berg, Jeff Johnson,
Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
Balakrishna Godavarthi, Rocky Liao, Saravana Kannan, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Bjorn Andersson,
Konrad Dybcio, Mathieu Poirier
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc
In-Reply-To: <439f76c3fcafdfb91cca426fcae17ef776776eab.camel@pengutronix.de>
Thanks, that was quick!
On 6/25/26 18:18, Philipp Zabel wrote:
> On Do, 2026-06-25 at 18:10 +0400, George Moussalem via B4 Relay wrote:
>> From: George Moussalem <george.moussalem@outlook.com>
>>
>> Add support to bring up the M0 core of the bluetooth subsystem found in
>> the IPQ5018 SoC.
>>
>> The signed firmware loaded is authenticated by TrustZone. If successful,
>> the M0 core boots the firmware and the peripheral is taken out of reset
>> using a Secure Channel Manager call to TrustZone.
>>
>> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
>> ---
>> drivers/remoteproc/Kconfig | 12 ++
>> drivers/remoteproc/Makefile | 1 +
>> drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
>> 3 files changed, 274 insertions(+)
>>
> [...]
>> diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
>> new file mode 100644
>> index 000000000000..7168e270e4d4
>> --- /dev/null
>> +++ b/drivers/remoteproc/qcom_m0_btss_pil.c
>> @@ -0,0 +1,261 @@
>> +// SPDX-License-Identifier: GPL-2.0-only
>> +/*
>> + * Copyright (c) 2026 The Linux Foundation. All rights reserved.
>> + */
>> +
>> +#include <linux/clk.h>
>> +#include <linux/delay.h>
>> +#include <linux/elf.h>
>> +#include <linux/firmware/qcom/qcom_scm.h>
>> +#include <linux/io.h>
>> +#include <linux/kernel.h>
>> +#include <linux/of_reserved_mem.h>
>> +#include <linux/platform_device.h>
>> +#include <linux/reset.h>
>> +#include <linux/soc/qcom/mdt_loader.h>
>> +
>> +#include "qcom_common.h"
>> +
>> +#define BTSS_PAS_ID 0xc
>> +
>> +struct m0_btss {
>> + struct device *dev;
>> + phys_addr_t mem_phys;
>> + phys_addr_t mem_reloc;
>> + void __iomem *mem_region;
>> + size_t mem_size;
>> + struct reset_control *btss_reset;
>
> Why is this stored here? It doesn't seem to be used.
will remove it and use devm_reset_control_get_exclusive_deasserted as
suggested below.
>
> [...]
>> +static int m0_btss_pil_probe(struct platform_device *pdev)
>> +{
>> + // struct reset_control *btss_reset;
>
> It looks like this shouldn't be commented out.
>
>> + struct device *dev = &pdev->dev;
>> + const char *fw_name = NULL;
>> + struct m0_btss *desc;
>> + struct clk *lpo_clk;
>> + struct rproc *rproc;
>> + int ret;
>> +
>> + ret = of_property_read_string(dev->of_node, "firmware-name",
>> + &fw_name);
>> + if (ret < 0)
>> + return ret;
>> +
>> + rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
>> + fw_name, sizeof(*desc));
>> + if (!rproc) {
>> + dev_err(dev, "failed to allocate rproc\n");
>> + return -ENOMEM;
>> + }
>> +
>> + desc = rproc->priv;
>> + desc->dev = dev;
>> +
>> + ret = m0_btss_alloc_memory_region(desc);
>> + if (ret)
>> + return ret;
>> +
>> + lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
>> + if (IS_ERR(lpo_clk))
>> + return dev_err_probe(dev, PTR_ERR(lpo_clk),
>> + "Failed to get lpo clock\n");
>> +
>> + desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
>
> Please use devm_reset_control_get_exclusive() directly.
>
>> + if (IS_ERR_OR_NULL(desc->btss_reset))
>> + return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
>> + "unable to acquire btss_reset\n");
>> +
>> + ret = reset_control_deassert(desc->btss_reset);
>> + if (ret)
>> + return dev_err_probe(rproc->dev.parent, ret,
>> + "Failed to deassert reset\n");
>
> Shouldn't this be asserted on remove? Otherwise after an unbind/bind
> cycle probe will enable the clock with reset already deasserted.
> That may or may not be problematic.
>
> Consider using devm_reset_control_get_exclusive_deasserted().
>
>
> regards
> Philipp
Regards,
George
^ permalink raw reply
* Re: [PATCH 2/6] remoteproc: qcom: Add M0 BTSS secure PIL driver
From: Philipp Zabel @ 2026-06-25 14:18 UTC (permalink / raw)
To: george.moussalem, Jens Axboe, Ulf Hansson, Rob Herring,
Krzysztof Kozlowski, Conor Dooley, Johannes Berg, Jeff Johnson,
Bartosz Golaszewski, Marcel Holtmann, Luiz Augusto von Dentz,
Balakrishna Godavarthi, Rocky Liao, Saravana Kannan, Andrew Lunn,
Heiner Kallweit, Russell King, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, Bjorn Andersson,
Konrad Dybcio, Mathieu Poirier
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc
In-Reply-To: <20260625-ipq5018-bluetooth-v1-2-d999be0e04f7@outlook.com>
On Do, 2026-06-25 at 18:10 +0400, George Moussalem via B4 Relay wrote:
> From: George Moussalem <george.moussalem@outlook.com>
>
> Add support to bring up the M0 core of the bluetooth subsystem found in
> the IPQ5018 SoC.
>
> The signed firmware loaded is authenticated by TrustZone. If successful,
> the M0 core boots the firmware and the peripheral is taken out of reset
> using a Secure Channel Manager call to TrustZone.
>
> Signed-off-by: George Moussalem <george.moussalem@outlook.com>
> ---
> drivers/remoteproc/Kconfig | 12 ++
> drivers/remoteproc/Makefile | 1 +
> drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++++++++++++++++++++++++++++++
> 3 files changed, 274 insertions(+)
>
[...]
> diff --git a/drivers/remoteproc/qcom_m0_btss_pil.c b/drivers/remoteproc/qcom_m0_btss_pil.c
> new file mode 100644
> index 000000000000..7168e270e4d4
> --- /dev/null
> +++ b/drivers/remoteproc/qcom_m0_btss_pil.c
> @@ -0,0 +1,261 @@
> +// SPDX-License-Identifier: GPL-2.0-only
> +/*
> + * Copyright (c) 2026 The Linux Foundation. All rights reserved.
> + */
> +
> +#include <linux/clk.h>
> +#include <linux/delay.h>
> +#include <linux/elf.h>
> +#include <linux/firmware/qcom/qcom_scm.h>
> +#include <linux/io.h>
> +#include <linux/kernel.h>
> +#include <linux/of_reserved_mem.h>
> +#include <linux/platform_device.h>
> +#include <linux/reset.h>
> +#include <linux/soc/qcom/mdt_loader.h>
> +
> +#include "qcom_common.h"
> +
> +#define BTSS_PAS_ID 0xc
> +
> +struct m0_btss {
> + struct device *dev;
> + phys_addr_t mem_phys;
> + phys_addr_t mem_reloc;
> + void __iomem *mem_region;
> + size_t mem_size;
> + struct reset_control *btss_reset;
Why is this stored here? It doesn't seem to be used.
[...]
> +static int m0_btss_pil_probe(struct platform_device *pdev)
> +{
> + // struct reset_control *btss_reset;
It looks like this shouldn't be commented out.
> + struct device *dev = &pdev->dev;
> + const char *fw_name = NULL;
> + struct m0_btss *desc;
> + struct clk *lpo_clk;
> + struct rproc *rproc;
> + int ret;
> +
> + ret = of_property_read_string(dev->of_node, "firmware-name",
> + &fw_name);
> + if (ret < 0)
> + return ret;
> +
> + rproc = devm_rproc_alloc(dev, "m0btss", &m0_btss_ops,
> + fw_name, sizeof(*desc));
> + if (!rproc) {
> + dev_err(dev, "failed to allocate rproc\n");
> + return -ENOMEM;
> + }
> +
> + desc = rproc->priv;
> + desc->dev = dev;
> +
> + ret = m0_btss_alloc_memory_region(desc);
> + if (ret)
> + return ret;
> +
> + lpo_clk = devm_clk_get_enabled(dev, "btss_lpo_clk");
> + if (IS_ERR(lpo_clk))
> + return dev_err_probe(dev, PTR_ERR(lpo_clk),
> + "Failed to get lpo clock\n");
> +
> + desc->btss_reset = devm_reset_control_get(dev, "btss_reset");
Please use devm_reset_control_get_exclusive() directly.
> + if (IS_ERR_OR_NULL(desc->btss_reset))
> + return dev_err_probe(dev, PTR_ERR(desc->btss_reset),
> + "unable to acquire btss_reset\n");
> +
> + ret = reset_control_deassert(desc->btss_reset);
> + if (ret)
> + return dev_err_probe(rproc->dev.parent, ret,
> + "Failed to deassert reset\n");
Shouldn't this be asserted on remove? Otherwise after an unbind/bind
cycle probe will enable the clock with reset already deasserted.
That may or may not be problematic.
Consider using devm_reset_control_get_exclusive_deasserted().
regards
Philipp
^ permalink raw reply
* Re: [PATCH] wifi: cfg80211: replace BOOL_TO_STR macro with str_true_false()
From: serhat @ 2026-06-25 14:16 UTC (permalink / raw)
To: Johannes Berg; +Cc: linux-wireless, linux-kernel
In-Reply-To: <d1db2391fe528c4e79b1afd9c80e49f44b948beb.camel@sipsolutions.net>
Hi Johannes,
You are right. Replacing the macro breaks libtraceevent parsing in
trace-cmd, since it cannot evaluate the kernel function.
Please drop this patch. Thanks for catching this.
Best regards,
Serhat
Johannes Berg <johannes@sipsolutions.net>, 25 Haz 2026 Per, 14:22
tarihinde şunu yazdı:
>
> On Wed, 2026-06-24 at 23:49 +0300, Serhat Kumral wrote:
> > Remove the local BOOL_TO_STR macro and replace all its usages with
> > the kernel's str_true_false() helper from <linux/string_choices.h>.
> >
> > No functional change intended.
> >
>
> I believe this breaks trace-cmd reporting. Please check and resend
> indicating that you have.
>
> johannes
^ permalink raw reply
* [PATCH 5/6] Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add driver support for the Qualcomm IPQ5018 bluetooth chip.
The firmware runs on the M0 co-processor.
The host and the M0 core use a shared memory carveout for transport
using ring buffers. This driver implements the transport layer between
the HCI core and the Bluetooth subsystem running on the M0 core.
Notifications of host and M0 core events are triggered by an IPC
register BIT and an interrupt line respectfully.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
drivers/bluetooth/Kconfig | 11 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btqcomipc.c | 939 ++++++++++++++++++++++++++++++++++++++++++
3 files changed, 951 insertions(+)
diff --git a/drivers/bluetooth/Kconfig b/drivers/bluetooth/Kconfig
index 4e8c24d757e9..6b8bed6a6ffd 100644
--- a/drivers/bluetooth/Kconfig
+++ b/drivers/bluetooth/Kconfig
@@ -413,6 +413,17 @@ config BT_MTKUART
Say Y here to compile support for MediaTek Bluetooth UART devices
into the kernel or say M to compile it as module (btmtkuart).
+config BT_QCOMIPC
+ tristate "Qualcomm IPQ5018 IPC based HCI support"
+ select BT_QCA
+ help
+ Qualcomm IPQ5018 IPC based HCI driver.
+ This driver is used to bridge HCI data onto shared memory between
+ the host and the M0 BTSS core.
+
+ Say Y here to compile support for HCI over Qualcomm IPC into the
+ kernel or say M to compile as a module.
+
config BT_QCOMSMD
tristate "Qualcomm SMD based HCI support"
depends on RPMSG || (COMPILE_TEST && RPMSG=n)
diff --git a/drivers/bluetooth/Makefile b/drivers/bluetooth/Makefile
index e6b1c1180d1d..05f19047bed0 100644
--- a/drivers/bluetooth/Makefile
+++ b/drivers/bluetooth/Makefile
@@ -20,6 +20,7 @@ obj-$(CONFIG_BT_MRVL) += btmrvl.o
obj-$(CONFIG_BT_MRVL_SDIO) += btmrvl_sdio.o
obj-$(CONFIG_BT_MTKSDIO) += btmtksdio.o
obj-$(CONFIG_BT_MTKUART) += btmtkuart.o
+obj-$(CONFIG_BT_QCOMIPC) += btqcomipc.o
obj-$(CONFIG_BT_QCOMSMD) += btqcomsmd.o
obj-$(CONFIG_BT_BCM) += btbcm.o
obj-$(CONFIG_BT_RTL) += btrtl.o
diff --git a/drivers/bluetooth/btqcomipc.c b/drivers/bluetooth/btqcomipc.c
new file mode 100644
index 000000000000..662a75b6c4a9
--- /dev/null
+++ b/drivers/bluetooth/btqcomipc.c
@@ -0,0 +1,939 @@
+// SPDX-License-Identifier: GPL-2.0-only
+/*
+ * Copyright (c) 2020 The Linux Foundation. All rights reserved.
+ */
+
+#include <linux/bits.h>
+#include <linux/delay.h>
+#include <linux/device.h>
+#include <linux/firmware/qcom/qcom_scm.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/mfd/syscon.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_irq.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/platform_device.h>
+#include <linux/regmap.h>
+#include <linux/remoteproc.h>
+#include <linux/skbuff.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/workqueue.h>
+
+#include <net/bluetooth/bluetooth.h>
+#include <net/bluetooth/hci_core.h>
+
+#include "btqca.h"
+
+/** Message header format.
+ *
+ * ----------------------------------------------------------------
+ * BitPos | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 - 0 |
+ * ---------------------------------------------------------------
+ * Field | long_msg |ACK | RFU | pkt_type | cmd |
+ * ----------------------------------------------------------------
+ *
+ * - long_msg : If set, indicates that the payload is larger than the
+ * IPC_MSG_PLD_SZ. The payload instead contains a pointer to the
+ * long message buffer in the shared BTSS memory space.
+ *
+ * - ACK : Set if sending ACK if required by sending acknowledegement
+ * to sender i.e. send an ack IPC interrupt if set.
+ *
+ * - RFU : Reserved for future use.
+ *
+ * - pkt_type : IPC Packet Type
+ *
+ * - cmd : Contains unique command ID
+ */
+
+#define IPC_MSG_HDR_SZ 4
+#define IPC_MSG_PLD_SZ 40
+#define IPC_TOTAL_MSG_SZ (IPC_MSG_HDR_SZ + IPC_MSG_PLD_SZ)
+
+/* Message Header */
+#define IPC_HDR_LONG_MSG BIT(15)
+#define IPC_HDR_REQ_ACK BIT(14)
+#define IPC_HDR_PKT_TYPE_MASK GENMASK(9, 8)
+#define IPC_HDR_PKT_TYPE_CUST 0
+#define IPC_HDR_PKT_TYPE_HCI 1
+#define IPC_HDR_PKT_TYPE_AUDIO 2
+#define IPC_HDR_PKT_TYPE_RFU 3
+#define IPC_HDR_CMD_MASK GENMASK(7, 0)
+
+#define IPC_CMD_STOP 1
+#define IPC_CMD_SWITCH_TO_UART 2
+#define IPC_CMD_PREPARE_DUMP 3
+#define IPC_CMD_COLLECT_DUMP 4
+#define IPC_CMD_START 5
+
+#define IPC_TX_QSIZE 32
+
+#define TO_APPS_ADDR(a) (desc->mem_region + (int)(uintptr_t)a)
+#define TO_BT_ADDR(a) (a - desc->mem_region)
+#define IPC_LBUF_SZ(w, x, y, z) (((TO_BT_ADDR((void *)w) + w->x) - w->y) / w->z)
+
+#define GET_NO_OF_BLOCKS(a, b) ((a + b - 1) / b)
+
+#define GET_RX_INDEX_FROM_BUF(x, y) ((x - desc->rx_ctxt->lring_buf) / y)
+
+#define GET_TX_INDEX_FROM_BUF(x, y) ((x - desc->tx_ctxt->lring_buf) / y)
+
+#define IS_RX_MEM_NON_CONTIGIOUS(buf, len, sz) \
+ ((buf + len) > (desc->rx_ctxt->lring_buf + \
+ (sz * desc->rx_ctxt->lmsg_buf_cnt)))
+
+#define POWER_CONTROL_DELAY_MS 50
+
+#define BTSS_PAS_ID 0xc
+
+struct long_msg_info {
+ __le16 smsg_free_cnt;
+ __le16 lmsg_free_cnt;
+ u8 ridx;
+ u8 widx;
+} __packed;
+
+struct ipc_aux_ptr {
+ __le32 len;
+ __le32 buf;
+} __packed;
+
+struct ring_buffer {
+ __le16 msg_hdr;
+ __le16 len;
+ union {
+ u8 smsg_data[IPC_MSG_PLD_SZ];
+ __le32 lmsg_data;
+ } payload;
+} __packed;
+
+struct ring_buffer_info {
+ __le32 rbuf;
+ u8 ring_buf_cnt;
+ u8 ridx;
+ u8 widx;
+ u8 tidx;
+ __le32 next;
+} __packed;
+
+struct context_info {
+ __le16 total_size;
+ u8 lmsg_buf_cnt;
+ u8 smsg_buf_cnt;
+ struct ring_buffer_info sring_buf_info;
+ __le32 sring_buf;
+ __le32 lring_buf;
+ __le32 reserved;
+} __packed;
+
+struct qcom_btss {
+ struct device *dev;
+ struct rproc *rproc;
+ struct hci_dev *hdev;
+
+ struct regmap *regmap;
+ u32 offset;
+ u32 bit;
+ int irq;
+
+ void *mem_region;
+ phys_addr_t mem_phys;
+ phys_addr_t mem_reloc;
+ size_t mem_size;
+
+ struct sk_buff_head tx_q;
+ struct workqueue_struct *wq;
+ struct work_struct work;
+ wait_queue_head_t wait_q;
+ spinlock_t lock;
+
+ struct context_info *tx_ctxt;
+ struct context_info *rx_ctxt;
+ struct long_msg_info lmsg_ctxt;
+
+ bool running;
+};
+
+static void btqcomipc_update_stats(struct hci_dev *hdev, struct sk_buff *skb);
+
+static void *btss_alloc_lmsg(struct qcom_btss *desc, u32 len,
+ struct ipc_aux_ptr *aux_ptr, bool *is_lbuf_full)
+{
+ struct device *dev = desc->dev;
+ u8 idx, blks, blks_consumed;
+ void *ret_ptr;
+ u32 lsz;
+
+ if (desc->tx_ctxt->lring_buf == 0) {
+ dev_err(dev, "no long message buffer initialized\n");
+ return ERR_PTR(-ENODEV);
+ }
+
+ lsz = IPC_LBUF_SZ(desc->tx_ctxt, total_size, lring_buf, lmsg_buf_cnt);
+ blks = GET_NO_OF_BLOCKS(len, lsz);
+
+ if (!desc->lmsg_ctxt.lmsg_free_cnt ||
+ (blks > desc->lmsg_ctxt.lmsg_free_cnt))
+ return ERR_PTR(-EAGAIN);
+
+ idx = desc->lmsg_ctxt.widx;
+
+ if ((desc->lmsg_ctxt.widx + blks) > desc->tx_ctxt->lmsg_buf_cnt) {
+ blks_consumed = desc->tx_ctxt->lmsg_buf_cnt - idx;
+ aux_ptr->len = len - (blks_consumed * lsz);
+ aux_ptr->buf = desc->tx_ctxt->lring_buf;
+ }
+
+ desc->lmsg_ctxt.widx = (desc->lmsg_ctxt.widx + blks) %
+ desc->tx_ctxt->lmsg_buf_cnt;
+
+ desc->lmsg_ctxt.lmsg_free_cnt -= blks;
+
+ if (desc->lmsg_ctxt.lmsg_free_cnt <=
+ ((desc->tx_ctxt->lmsg_buf_cnt * 20) / 100))
+ *is_lbuf_full = true;
+
+ ret_ptr = TO_APPS_ADDR(desc->tx_ctxt->lring_buf) + (idx * lsz);
+
+ return ret_ptr;
+}
+
+static struct ring_buffer_info *btss_get_tx_rbuf(struct qcom_btss *desc,
+ bool *is_sbuf_full)
+{
+ u8 idx;
+ struct ring_buffer_info *rinfo;
+
+ for (rinfo = &(desc->tx_ctxt->sring_buf_info); rinfo != NULL;
+ rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+ idx = (rinfo->widx + 1) % (desc->tx_ctxt->smsg_buf_cnt);
+
+ if (idx != rinfo->tidx) {
+ desc->lmsg_ctxt.smsg_free_cnt--;
+
+ if (desc->lmsg_ctxt.smsg_free_cnt <=
+ ((desc->tx_ctxt->smsg_buf_cnt * 20) / 100))
+ *is_sbuf_full = true;
+
+ return rinfo;
+ }
+ }
+
+ return ERR_PTR(-EAGAIN);
+}
+
+static int btss_send(struct qcom_btss *desc, u16 msg_hdr,
+ struct sk_buff *skb)
+{
+ struct hci_dev *hdev = desc->hdev;
+ struct ring_buffer_info *rinfo;
+ struct ipc_aux_ptr aux_ptr;
+ struct ring_buffer *rbuf;
+ bool is_lbuf_full = false;
+ bool is_sbuf_full = false;
+ u16 hdr = msg_hdr;
+ void *ptr_buf;
+ u32 len;
+
+ /* Account for HCI packet type as it's not included in the skb payload */
+ len = skb->len + 1;
+ memset(&aux_ptr, 0, sizeof(struct ipc_aux_ptr));
+
+ if (len > IPC_MSG_PLD_SZ) {
+ hdr |= IPC_HDR_LONG_MSG;
+
+ ptr_buf = btss_alloc_lmsg(desc, len,
+ &aux_ptr, &is_lbuf_full);
+ if (IS_ERR(ptr_buf)) {
+ bt_dev_err(hdev, "long msg buf full");
+ hdev->stat.err_tx++;
+ return PTR_ERR(ptr_buf);
+ }
+ }
+
+ rinfo = btss_get_tx_rbuf(desc, &is_sbuf_full);
+ if (IS_ERR(rinfo)) {
+ bt_dev_err(hdev, "short msg buf full");
+ hdev->stat.err_tx++;
+ return PTR_ERR(rinfo);
+ }
+
+ rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(rinfo->rbuf)))[rinfo->widx];
+
+ if (len > IPC_MSG_PLD_SZ)
+ rbuf->payload.lmsg_data = cpu_to_le32(TO_BT_ADDR(ptr_buf));
+ else
+ ptr_buf = rbuf->payload.smsg_data;
+
+ /* if it's a short message, the aux len and buf are NULL */
+ memcpy_toio(ptr_buf, &hci_skb_pkt_type(skb), 1);
+ memcpy_toio((u8 *)ptr_buf + 1, skb->data, skb->len - aux_ptr.len);
+ if (aux_ptr.buf) {
+ memcpy_toio(TO_APPS_ADDR(aux_ptr.buf),
+ (skb->data + (skb->len - aux_ptr.len)), aux_ptr.len);
+ }
+
+ if (is_sbuf_full || is_lbuf_full)
+ hdr |= IPC_HDR_REQ_ACK;
+
+ rbuf->msg_hdr = cpu_to_le16(hdr);
+ rbuf->len = cpu_to_le16(len);
+
+ rinfo->widx = (rinfo->widx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+
+ regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+ return 0;
+}
+
+static void btss_process_tx_queue(struct qcom_btss *desc)
+{
+ struct sk_buff *skb;
+ u16 hdr;
+ int ret;
+
+ while ((skb = skb_dequeue(&desc->tx_q))) {
+ hdr = FIELD_PREP(IPC_HDR_PKT_TYPE_MASK, IPC_HDR_PKT_TYPE_HCI);
+
+ ret = btss_send(desc, hdr, skb);
+ if (ret) {
+ bt_dev_err(desc->hdev, "Failed to send message");
+ skb_queue_head(&desc->tx_q, skb);
+ break;
+ }
+
+ btqcomipc_update_stats(desc->hdev, skb);
+ kfree_skb(skb);
+ }
+}
+
+static void btss_free_lmsg(struct qcom_btss *desc, u32 lmsg, u16 len)
+{
+ u8 idx;
+ u8 blks;
+ u32 lsz = IPC_LBUF_SZ(desc->tx_ctxt, total_size, lring_buf,
+ lmsg_buf_cnt);
+
+ idx = GET_TX_INDEX_FROM_BUF(lmsg, lsz);
+
+ if (idx != desc->lmsg_ctxt.ridx)
+ return;
+
+ blks = GET_NO_OF_BLOCKS(len, lsz);
+
+ desc->lmsg_ctxt.ridx = (desc->lmsg_ctxt.ridx + blks) %
+ desc->tx_ctxt->lmsg_buf_cnt;
+
+ desc->lmsg_ctxt.lmsg_free_cnt += blks;
+}
+
+static int btss_send_ctrl(struct qcom_btss *desc, u16 msg_hdr)
+{
+ struct ring_buffer_info *rinfo;
+ struct ring_buffer *rbuf;
+
+ rinfo = btss_get_tx_rbuf(desc, NULL);
+ if (IS_ERR(rinfo))
+ return PTR_ERR(rinfo);
+
+ rbuf = &((struct ring_buffer *)TO_APPS_ADDR(rinfo->rbuf))[rinfo->widx];
+ rbuf->msg_hdr = cpu_to_le16(msg_hdr);
+ rbuf->len = 0;
+
+ rinfo->widx = (rinfo->widx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+
+ regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+ return 0;
+}
+
+static void btss_recv_cust_frame(struct qcom_btss *desc, u8 cmd)
+{
+ u16 msg_hdr = 0;
+ int ret;
+
+ msg_hdr |= cmd;
+
+ switch (cmd) {
+ case IPC_CMD_STOP:
+ bt_dev_info(desc->hdev, "BTSS stopped, gracefully stopping APSS IPC");
+ break;
+ case IPC_CMD_START:
+ desc->tx_ctxt = (struct context_info *)((void *)desc->rx_ctxt +
+ le16_to_cpu(desc->rx_ctxt->total_size));
+ desc->lmsg_ctxt.widx = 0;
+ desc->lmsg_ctxt.ridx = 0;
+ desc->lmsg_ctxt.smsg_free_cnt = desc->tx_ctxt->smsg_buf_cnt;
+ desc->lmsg_ctxt.lmsg_free_cnt = desc->tx_ctxt->lmsg_buf_cnt;
+ WRITE_ONCE(desc->running, true);
+ wake_up(&desc->wait_q);
+
+ bt_dev_info(desc->hdev, "BTSS started, initializing APSS BT IPC");
+ return;
+ default:
+ bt_dev_err(desc->hdev, "Unsupported CMD ID: %u", cmd);
+ return;
+ }
+
+ if (unlikely(!READ_ONCE(desc->running))) {
+ bt_dev_err(desc->hdev, "BTSS not initialized, no message sent");
+ return;
+ }
+
+ WRITE_ONCE(desc->running, false);
+
+ ret = btss_send_ctrl(desc, msg_hdr);
+ if (ret)
+ bt_dev_err(desc->hdev, "Failed to send control message");
+}
+
+static inline int btss_recv_hci_frame(struct qcom_btss *desc, const u8 *data, size_t len)
+{
+ unsigned char pkt_type;
+ struct sk_buff *skb;
+ size_t pkt_len;
+
+ if (len < 1)
+ return -EPROTO;
+
+ pkt_type = data[0];
+
+ switch (pkt_type) {
+ case HCI_EVENT_PKT:
+ {
+ if (len < HCI_EVENT_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_event_hdr *hdr = (struct hci_event_hdr *)(data + 1);
+
+ pkt_len = HCI_EVENT_HDR_SIZE + hdr->plen;
+ break;
+ }
+ case HCI_COMMAND_PKT: {
+ if (len < HCI_COMMAND_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_command_hdr *hdr = (struct hci_command_hdr *)(data + 1);
+
+ pkt_len = HCI_COMMAND_HDR_SIZE + le16_to_cpu(hdr->plen);
+ break;
+ }
+ case HCI_ACLDATA_PKT:
+ {
+ if (len < HCI_ACL_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_acl_hdr *hdr = (struct hci_acl_hdr *)(data + 1);
+
+ pkt_len = HCI_ACL_HDR_SIZE + le16_to_cpu(hdr->dlen);
+ break;
+ }
+ case HCI_SCODATA_PKT:
+ {
+ if (len < HCI_SCO_HDR_SIZE)
+ return -EILSEQ;
+ struct hci_sco_hdr *hdr = (struct hci_sco_hdr *)(data + 1);
+
+ pkt_len = HCI_SCO_HDR_SIZE + hdr->dlen;
+ break;
+ }
+ default:
+ return -EPROTO;
+ }
+
+ if (pkt_len > len)
+ return -EINVAL;
+
+ skb = bt_skb_alloc(pkt_len, GFP_ATOMIC);
+ if (!skb) {
+ desc->hdev->stat.err_rx++;
+ return -ENOMEM;
+ }
+
+ skb->dev = (void *)desc->hdev;
+ hci_skb_pkt_type(skb) = pkt_type;
+ skb_put_data(skb, data + 1, pkt_len);
+
+ hci_recv_frame(desc->hdev, skb);
+ desc->hdev->stat.byte_rx += pkt_len;
+
+ return 0;
+}
+
+static inline int btss_process_rx(struct qcom_btss *desc,
+ struct ring_buffer_info *rinfo,
+ bool *ack, u8 *rx_count)
+{
+ u8 ridx, lbuf_idx, blks_consumed, pkt_type, cmd;
+ struct ipc_aux_ptr aux_ptr;
+ struct ring_buffer *rbuf;
+ uint8_t *rxbuf = NULL;
+ unsigned char *buf;
+ u32 lsz;
+ int ret;
+
+ ridx = rinfo->ridx;
+
+ while (ridx != rinfo->widx) {
+ memset(&aux_ptr, 0, sizeof(struct ipc_aux_ptr));
+
+ rbuf = &((struct ring_buffer *)(TO_APPS_ADDR(rinfo->rbuf)))[ridx];
+
+ if (rbuf->msg_hdr & IPC_HDR_LONG_MSG) {
+ rxbuf = TO_APPS_ADDR(rbuf->payload.lmsg_data);
+ lsz = IPC_LBUF_SZ(desc->rx_ctxt, total_size, lring_buf,
+ lmsg_buf_cnt);
+
+ if (IS_RX_MEM_NON_CONTIGIOUS(rbuf->payload.lmsg_data,
+ rbuf->len, lsz)) {
+ lbuf_idx = GET_RX_INDEX_FROM_BUF(
+ rbuf->payload.lmsg_data, lsz);
+
+ blks_consumed = desc->rx_ctxt->lmsg_buf_cnt -
+ lbuf_idx;
+ aux_ptr.len = rbuf->len - (blks_consumed * lsz);
+ aux_ptr.buf = desc->rx_ctxt->lring_buf;
+ }
+ } else {
+ rxbuf = rbuf->payload.smsg_data;
+ }
+
+ *ack = (rbuf->msg_hdr & IPC_HDR_REQ_ACK);
+
+ pkt_type = FIELD_GET(IPC_HDR_PKT_TYPE_MASK, rbuf->msg_hdr);
+
+ switch (pkt_type) {
+ case IPC_HDR_PKT_TYPE_HCI:
+ buf = kmalloc(rbuf->len, GFP_ATOMIC);
+ if (!buf) {
+ rinfo->ridx = ridx;
+ return -ENOMEM;
+ }
+
+ memcpy_fromio(buf, rxbuf, rbuf->len - aux_ptr.len);
+
+ if (aux_ptr.buf)
+ memcpy_fromio(buf + (rbuf->len - aux_ptr.len),
+ TO_APPS_ADDR(aux_ptr.buf), aux_ptr.len);
+
+ ret = btss_recv_hci_frame(desc, buf, rbuf->len);
+ if (ret)
+ bt_dev_err(desc->hdev, "Failed to process HCI frame: %d", ret);
+ kfree(buf);
+ break;
+ case IPC_HDR_PKT_TYPE_CUST:
+ cmd = FIELD_GET(IPC_HDR_CMD_MASK, rbuf->msg_hdr);
+ btss_recv_cust_frame(desc, cmd);
+ break;
+ default:
+ break;
+ }
+
+ ridx = (ridx + 1) % rinfo->ring_buf_cnt;
+
+ if (rx_count)
+ (*rx_count)++;
+
+ rinfo->ridx = ridx;
+ }
+
+ return 0;
+}
+
+static void btss_process_ack(struct qcom_btss *desc)
+{
+ struct ring_buffer_info *rinfo;
+ struct ring_buffer *rbuf;
+ u8 tidx;
+
+ for (rinfo = &desc->tx_ctxt->sring_buf_info; rinfo != NULL;
+ rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+ tidx = rinfo->tidx;
+ rbuf = (struct ring_buffer *)TO_APPS_ADDR(rinfo->rbuf);
+
+ while (tidx != rinfo->ridx) {
+ if (rbuf[tidx].msg_hdr & IPC_HDR_LONG_MSG) {
+ btss_free_lmsg(desc,
+ rbuf[tidx].payload.lmsg_data,
+ rbuf[tidx].len);
+ }
+
+ tidx = (tidx + 1) % desc->tx_ctxt->smsg_buf_cnt;
+ desc->lmsg_ctxt.smsg_free_cnt++;
+ }
+
+ rinfo->tidx = tidx;
+
+ btss_process_tx_queue(desc);
+ }
+}
+
+static void btss_worker(struct work_struct *work)
+{
+ struct qcom_btss *desc = container_of(work, struct qcom_btss, work);
+ struct ring_buffer_info *rinfo;
+ bool ack = false;
+ u32 offset;
+ int ret;
+
+ if (desc->rproc->state != RPROC_RUNNING)
+ return;
+
+ spin_lock(&desc->lock);
+
+ if (unlikely(!READ_ONCE(desc->running))) {
+ // FW sets offset of RX context info at start of memory region upon boot
+ offset = readl(desc->mem_region);
+ dev_dbg(desc->dev, "offset after M0 boot: 0x%08x\n", offset);
+ desc->rx_ctxt = (struct context_info *)(desc->mem_region + offset);
+ } else {
+ btss_process_ack(desc);
+ }
+
+ for (rinfo = &(desc->rx_ctxt->sring_buf_info);
+ rinfo != NULL;
+ rinfo = (struct ring_buffer_info *)(uintptr_t)(rinfo->next)) {
+ ret = btss_process_rx(desc, rinfo, &ack,
+ &desc->rx_ctxt->smsg_buf_cnt);
+ if (ret) {
+ bt_dev_err(desc->hdev, "Failed to process peer msgs: %d", ret);
+ goto spin_unlock;
+ }
+ }
+
+ if (ack)
+ regmap_set_bits(desc->regmap, desc->offset, BIT(desc->bit));
+
+spin_unlock:
+ spin_unlock(&desc->lock);
+}
+
+static irqreturn_t btss_irq_handler(int irq, void *data)
+{
+ struct qcom_btss *desc = data;
+
+ queue_work(desc->wq, &desc->work);
+
+ return IRQ_HANDLED;
+}
+
+static int btss_init(struct qcom_btss *desc)
+{
+ struct device *dev = desc->dev;
+ int ret;
+
+ init_waitqueue_head(&desc->wait_q);
+ spin_lock_init(&desc->lock);
+ skb_queue_head_init(&desc->tx_q);
+
+ desc->wq = create_singlethread_workqueue("btss_wq");
+ if (!desc->wq) {
+ dev_err(dev, "Failed to initialize workqueue\n");
+ return -EAGAIN;
+ }
+
+ INIT_WORK(&desc->work, btss_worker);
+
+ ret = devm_request_threaded_irq(dev, desc->irq, NULL, btss_irq_handler,
+ IRQF_TRIGGER_RISING | IRQF_ONESHOT,
+ "btss_irq", desc);
+
+ if (ret)
+ dev_err(dev, "error registering irq[%d] ret = %d\n",
+ desc->irq, ret);
+
+ return ret;
+}
+
+static void btqcomipc_update_stats(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ u8 pkt_type = hci_skb_pkt_type(skb);
+
+ hdev->stat.byte_tx += skb->len;
+ switch (pkt_type) {
+ case HCI_COMMAND_PKT:
+ hdev->stat.cmd_tx++;
+ break;
+ case HCI_ACLDATA_PKT:
+ hdev->stat.acl_tx++;
+ break;
+ case HCI_SCODATA_PKT:
+ hdev->stat.sco_tx++;
+ break;
+ default:
+ break;
+ }
+}
+
+static int btqcomipc_open(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ int ret;
+
+ ret = btss_init(desc);
+ if (ret) {
+ bt_dev_err(hdev, "Failed initializing BTSS: %d", ret);
+ return ret;
+ }
+
+ /* wait 2 seconds for filesystem to become available */
+ msleep(2000);
+
+ /* Boot M0 firmware */
+ ret = rproc_boot(desc->rproc);
+ if (ret) {
+ bt_dev_err(hdev, "Failed to boot M0 processor: %d", ret);
+ return ret;
+ }
+
+ msleep(POWER_CONTROL_DELAY_MS);
+ ret = wait_event_timeout(desc->wait_q, READ_ONCE(desc->running),
+ msecs_to_jiffies(1000));
+
+ if (!ret) {
+ bt_dev_err(hdev, "Timeout waiting for BTSS start");
+ return -ETIMEDOUT;
+ }
+
+ return 0;
+}
+
+static int btqcomipc_close(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ rproc_shutdown(desc->rproc);
+ msleep(POWER_CONTROL_DELAY_MS);
+
+ return 0;
+}
+
+static int btqcomipc_shutdown(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ WRITE_ONCE(desc->running, false);
+ if (desc->wq != NULL) {
+ disable_irq(desc->irq);
+ flush_workqueue(desc->wq);
+ devm_free_irq(desc->dev, desc->irq, desc);
+ skb_queue_purge(&desc->tx_q);
+ destroy_workqueue(desc->wq);
+ desc->wq = NULL;
+ }
+
+ return 0;
+}
+
+static int btqcomipc_setup(struct hci_dev *hdev)
+{
+ struct qca_btsoc_version ver;
+ int ret;
+
+ /*
+ * Enable controller to do both LE scan and BR/EDR inquiry
+ * simultaneously.
+ */
+ hci_set_quirk(hdev, HCI_QUIRK_SIMULTANEOUS_DISCOVERY);
+
+ /*
+ * Enable NON_PERSISTENT_SETUP QUIRK to ensure to execute
+ * setup for every hci up.
+ */
+ hci_set_quirk(hdev, HCI_QUIRK_NON_PERSISTENT_SETUP);
+ ret = qca_read_soc_version(hdev, &ver, QCA_IPQ5018);
+ if (ret)
+ return -EINVAL;
+
+ ret = qca_uart_setup(hdev, 0, QCA_IPQ5018, ver, NULL);
+ if (ret) {
+ bt_dev_err(hdev, "Failed to setup UART: %d\n", ret);
+ return ret;
+ }
+
+ bt_dev_info(hdev, "QCA Build Info: %s", hdev->fw_info);
+
+ /* Obtain and set BD address from NVMEM cell */
+ hci_set_quirk(hdev, HCI_QUIRK_USE_BDADDR_NVMEM);
+ hci_set_quirk(hdev, HCI_QUIRK_BDADDR_NVMEM_BE);
+
+ return 0;
+}
+
+static int btqcomipc_send(struct hci_dev *hdev, struct sk_buff *skb)
+{
+ u16 hdr = FIELD_PREP(IPC_HDR_PKT_TYPE_MASK, IPC_HDR_PKT_TYPE_HCI);
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+ int ret;
+
+ if (unlikely(!READ_ONCE(desc->running))) {
+ bt_dev_err(hdev, "BTSS not initialized, failed to send message");
+ ret = -ENODEV;
+ goto free_skb;
+ }
+
+ ret = btss_send(desc, hdr, skb);
+ if (ret) {
+ if (ret == -EAGAIN) {
+ if (skb_queue_len(&desc->tx_q) >= IPC_TX_QSIZE) {
+ bt_dev_err(hdev, "TX queue full, dropping message");
+ hdev->stat.err_tx++;
+ ret = -ENOBUFS;
+ } else {
+ skb_queue_tail(&desc->tx_q, skb);
+ return 0;
+ }
+ } else {
+ bt_dev_err(hdev, "Failed to send message: %d", ret);
+ hdev->stat.err_tx++;
+ }
+ }
+
+ btqcomipc_update_stats(desc->hdev, skb);
+
+free_skb:
+ kfree_skb(skb);
+
+ return ret;
+}
+
+static int btqcomipc_flush(struct hci_dev *hdev)
+{
+ struct qcom_btss *desc = hci_get_drvdata(hdev);
+
+ skb_queue_purge(&desc->tx_q);
+ return 0;
+}
+
+static int btqcomipc_alloc_memory_region(struct qcom_btss *desc)
+{
+ struct device *dev = desc->dev;
+ struct resource res;
+ int ret;
+
+ /* lookup reserved-memory region of the remoteproc node */
+ ret = of_reserved_mem_region_to_resource(desc->rproc->dev.parent->of_node, 0, &res);
+ if (ret) {
+ dev_err(dev, "unable to acquire memory-region resource\n");
+ return ret;
+ }
+
+ desc->mem_phys = res.start;
+ desc->mem_reloc = res.start;
+ desc->mem_size = resource_size(&res);
+ desc->mem_region = devm_ioremap(dev, desc->mem_phys, desc->mem_size);
+ if (!desc->mem_region) {
+ dev_err(dev, "unable to map memory region: %pR\n", &res);
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+static void btqcomipc_rproc_put(void *data)
+{
+ struct rproc *rproc = data;
+
+ rproc_put(rproc);
+}
+
+static int btqcomipc_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct qcom_btss *desc;
+ phandle rproc_phandle;
+ struct hci_dev *hdev;
+ unsigned int args[2];
+ int ret;
+
+ desc = devm_kzalloc(dev, sizeof(*desc), GFP_KERNEL);
+ if (!desc)
+ return -ENOMEM;
+
+ desc->dev = dev;
+
+ if (of_property_read_u32(dev->of_node, "qcom,rproc", &rproc_phandle))
+ return dev_err_probe(dev, -ENOENT, "Failed to get remoteproc handle\n");
+
+ desc->rproc = rproc_get_by_phandle(rproc_phandle);
+ if (!desc->rproc)
+ return dev_err_probe(dev, -EPROBE_DEFER, "Failed to get remoteproc\n");
+
+ devm_add_action_or_reset(dev, btqcomipc_rproc_put, desc->rproc);
+
+ ret = btqcomipc_alloc_memory_region(desc);
+ if (ret)
+ return ret;
+
+ desc->regmap = syscon_regmap_lookup_by_phandle_args(dev->of_node, "qcom,ipc", 2, args);
+ if (IS_ERR(desc->regmap))
+ return PTR_ERR(desc->regmap);
+
+ desc->offset = args[0];
+ desc->bit = args[1];
+
+ desc->irq = platform_get_irq(pdev, 0);
+ if (desc->irq < 0)
+ return dev_err_probe(dev, desc->irq, "Failed to acquire IRQ\n");
+
+ hdev = hci_alloc_dev();
+ if (!hdev)
+ return -ENOMEM;
+
+ hci_set_drvdata(hdev, desc);
+ desc->hdev = hdev;
+ SET_HCIDEV_DEV(hdev, &pdev->dev);
+ hdev->bus = HCI_IPC;
+
+ hdev->open = btqcomipc_open;
+ hdev->close = btqcomipc_close;
+ hdev->shutdown = btqcomipc_shutdown;
+ hdev->setup = btqcomipc_setup;
+ hdev->send = btqcomipc_send;
+ hdev->flush = btqcomipc_flush;
+ hdev->set_bdaddr = qca_set_bdaddr;
+
+ ret = hci_register_dev(hdev);
+ if (ret < 0) {
+ hci_free_dev(hdev);
+ return dev_err_probe(dev, -EBUSY, "Failed to register hdev\n");
+ }
+
+ platform_set_drvdata(pdev, desc);
+
+ return 0;
+}
+
+static void btqcomipc_remove(struct platform_device *pdev)
+{
+ struct qcom_btss *desc = platform_get_drvdata(pdev);
+
+ if (!desc || !desc->hdev)
+ return;
+
+ hci_unregister_dev(desc->hdev);
+ hci_free_dev(desc->hdev);
+}
+
+static const struct of_device_id btqcomipc_of_match[] = {
+ { .compatible = "qcom,ipq5018-bt" },
+ { /* sentinel */},
+};
+MODULE_DEVICE_TABLE(of, btqcomipc_of_match);
+
+static struct platform_driver btqcomipc_driver = {
+ .probe = btqcomipc_probe,
+ .remove = btqcomipc_remove,
+ .driver = {
+ .name = "btqcomipc",
+ .of_match_table = btqcomipc_of_match,
+ },
+};
+
+module_platform_driver(btqcomipc_driver);
+
+MODULE_DESCRIPTION("Qualcomm Bluetooth IPC Driver");
+MODULE_LICENSE("GPL");
--
2.53.0
^ permalink raw reply related
* [PATCH 6/6] arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add nodes for the M0 remoteproc, reserved memory carveout, and Bluetooth
to bring up the M0 core and enable the Bluetooth Subsystem.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
arch/arm64/boot/dts/qcom/ipq5018.dtsi | 34 +++++++++++++++++++++++++++++++++-
1 file changed, 33 insertions(+), 1 deletion(-)
diff --git a/arch/arm64/boot/dts/qcom/ipq5018.dtsi b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
index 6f8004a22a1f..4fdf20c87b0a 100644
--- a/arch/arm64/boot/dts/qcom/ipq5018.dtsi
+++ b/arch/arm64/boot/dts/qcom/ipq5018.dtsi
@@ -17,6 +17,17 @@ / {
#address-cells = <2>;
#size-cells = <2>;
+ bluetooth: bluetooth {
+ compatible = "qcom,ipq5018-bt";
+
+ qcom,ipc = <&apcs_glb 8 23>;
+ interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+ qcom,rproc = <&m0_btss>;
+
+ status = "disabled";
+ };
+
clocks {
gephy_rx_clk: gephy-rx-clk {
compatible = "fixed-clock";
@@ -131,11 +142,31 @@ psci {
method = "smc";
};
+ m0_btss: remoteproc {
+ compatible = "qcom,ipq5018-btss-pil";
+
+ firmware-name = "qca/bt_fw_patch.mbn";
+
+ clocks = <&gcc GCC_BTSS_LPO_CLK>;
+ clock-names = "btss_lpo_clk";
+ resets = <&gcc GCC_BTSS_BCR>;
+ reset-names = "btss_reset";
+
+ memory-region = <&btss_region>;
+
+ status = "disabled";
+ };
+
reserved-memory {
#address-cells = <2>;
#size-cells = <2>;
ranges;
+ btss_region: bluetooth@7000000 {
+ reg = <0x0 0x07000000 0x0 0x58000>;
+ no-map;
+ };
+
bootloader@4a800000 {
reg = <0x0 0x4a800000 0x0 0x200000>;
no-map;
@@ -647,7 +678,8 @@ watchdog: watchdog@b017000 {
apcs_glb: mailbox@b111000 {
compatible = "qcom,ipq5018-apcs-apps-global",
- "qcom,ipq6018-apcs-apps-global";
+ "qcom,ipq6018-apcs-apps-global",
+ "syscon";
reg = <0x0b111000 0x1000>;
#clock-cells = <1>;
clocks = <&a53pll>, <&xo_board_clk>, <&gcc GPLL0>;
--
2.53.0
^ permalink raw reply related
* [PATCH 4/6] dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Document the Qualcomm IPQ5018 Bluetooth controller.
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
.../bindings/net/bluetooth/qcom,ipq5018-bt.yaml | 63 ++++++++++++++++++++++
1 file changed, 63 insertions(+)
diff --git a/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
new file mode 100644
index 000000000000..afd33f851858
--- /dev/null
+++ b/Documentation/devicetree/bindings/net/bluetooth/qcom,ipq5018-bt.yaml
@@ -0,0 +1,63 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/net/bluetooth/qcom,ipq5018-bt.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Qualcomm IPQ5018 Bluetooth
+
+maintainers:
+ - George Moussalem <george.moussalem@outlook.com>
+
+properties:
+ compatible:
+ enum:
+ - qcom,ipq5018-bt
+
+ interrupts:
+ items:
+ - description:
+ Interrupt line from the M0 Bluetooth Subsystem to the host processor
+ to notify it of events such as re
+
+ qcom,ipc:
+ $ref: /schemas/types.yaml#/definitions/phandle-array
+ items:
+ - items:
+ - description: phandle to a syscon node representing the APCS registers
+ - description: u32 representing offset to the register within the syscon
+ - description: u32 representing the ipc bit within the register
+ description: |
+ These entries specify the outgoing IPC bit used for signaling the remote
+ M0 BTSS core of a host event or for sending an ACK if the remote processor
+ expects it.
+
+ qcom,rproc:
+ $ref: /schemas/types.yaml#/definitions/phandle
+ description:
+ Phandle to the remote processor node representing the M0 BTSS core.
+
+required:
+ - compatible
+ - interrupts
+ - qcom,ipc
+ - qcom,rproc
+
+allOf:
+ - $ref: bluetooth-controller.yaml#
+ - $ref: qcom,bluetooth-common.yaml
+
+unevaluatedProperties: false
+
+examples:
+ - |
+ #include <dt-bindings/interrupt-controller/arm-gic.h>
+
+ bluetooth: bluetooth {
+ compatible = "qcom,ipq5018-bt";
+
+ qcom,ipc = <&apcs_glb 8 23>;
+ interrupts = <GIC_SPI 162 IRQ_TYPE_EDGE_RISING>;
+
+ qcom,rproc = <&m0_btss>;
+ };
--
2.53.0
^ permalink raw reply related
* [PATCH 3/6] Bluetooth: btqca: Add IPQ5018 support
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
In-Reply-To: <20260625-ipq5018-bluetooth-v1-0-d999be0e04f7@outlook.com>
From: George Moussalem <george.moussalem@outlook.com>
Add the IPQ5018 SoC type and support for loading its firmware.
The firmware tested has been taken from GPL sources of various router
boards. Firmware files needed are:
- qca/bt_fw_patch.mbn
- qca/mpnv10.bin
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
drivers/bluetooth/btqca.c | 16 ++++++++++++++++
drivers/bluetooth/btqca.h | 3 +++
2 files changed, 19 insertions(+)
diff --git a/drivers/bluetooth/btqca.c b/drivers/bluetooth/btqca.c
index 04ebe290bc78..e136e91976cf 100644
--- a/drivers/bluetooth/btqca.c
+++ b/drivers/bluetooth/btqca.c
@@ -380,6 +380,9 @@ static int qca_tlv_check_data(struct hci_dev *hdev,
break;
case TLV_TYPE_NVM:
+ if (soc_type == QCA_IPQ5018)
+ break;
+
if (fw_size < sizeof(struct tlv_type_hdr))
return -EINVAL;
@@ -794,6 +797,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
else
rom_ver = ((soc_ver & 0x00000f00) >> 0x04) | (soc_ver & 0x0000000f);
+ if (soc_type == QCA_IPQ5018)
+ goto download_nvm;
+
if (soc_type == QCA_WCN6750)
qca_send_patch_config_cmd(hdev);
@@ -881,6 +887,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
if (soc_type == QCA_QCA2066 || soc_type == QCA_WCN7850)
qca_read_fw_board_id(hdev, &boardid);
+download_nvm:
/* Download NVM configuration */
config.type = TLV_TYPE_NVM;
if (firmware_name) {
@@ -939,6 +946,10 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
qca_get_nvm_name_by_board(config.fwname, sizeof(config.fwname),
"hmtnv", soc_type, ver, rom_ver, boardid);
break;
+ case QCA_IPQ5018:
+ snprintf(config.fwname, sizeof(config.fwname),
+ "qca/mpnv%02x.bin", rom_ver);
+ break;
default:
snprintf(config.fwname, sizeof(config.fwname),
"qca/nvm_%08x.bin", soc_ver);
@@ -958,6 +969,9 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
return err;
}
+ if (soc_type == QCA_IPQ5018)
+ msleep(NVM_READY_DELAY_MS);
+
switch (soc_type) {
case QCA_QCA2066:
case QCA_QCA6390:
@@ -965,6 +979,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
+ case QCA_IPQ5018:
err = qca_disable_soc_logging(hdev);
if (err < 0)
return err;
@@ -1001,6 +1016,7 @@ int qca_uart_setup(struct hci_dev *hdev, uint8_t baudrate,
case QCA_WCN6750:
case QCA_WCN6855:
case QCA_WCN7850:
+ case QCA_IPQ5018:
/* get fw build info */
err = qca_read_fw_build_info(hdev);
if (err < 0)
diff --git a/drivers/bluetooth/btqca.h b/drivers/bluetooth/btqca.h
index 8f3c1b1c77b3..343cd62d1137 100644
--- a/drivers/bluetooth/btqca.h
+++ b/drivers/bluetooth/btqca.h
@@ -54,6 +54,8 @@
#define QCA_HSP_GF_SOC_ID 0x1200
#define QCA_HSP_GF_SOC_MASK 0x0000ff00
+#define NVM_READY_DELAY_MS 1500
+
enum qca_baudrate {
QCA_BAUDRATE_115200 = 0,
QCA_BAUDRATE_57600,
@@ -158,6 +160,7 @@ enum qca_btsoc_type {
QCA_WCN6750,
QCA_WCN6855,
QCA_WCN7850,
+ QCA_IPQ5018,
};
#if IS_ENABLED(CONFIG_BT_QCA)
--
2.53.0
^ permalink raw reply related
* [PATCH 0/6] Add support for IPQ5018 Bluetooth
From: George Moussalem via B4 Relay @ 2026-06-25 14:10 UTC (permalink / raw)
To: Jens Axboe, Ulf Hansson, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Johannes Berg, Jeff Johnson, Bartosz Golaszewski,
Marcel Holtmann, Luiz Augusto von Dentz, Balakrishna Godavarthi,
Rocky Liao, Saravana Kannan, Andrew Lunn, Heiner Kallweit,
Russell King, David S. Miller, Eric Dumazet, Jakub Kicinski,
Paolo Abeni, Simon Horman, Bjorn Andersson, Konrad Dybcio,
Mathieu Poirier, Philipp Zabel
Cc: linux-block, linux-kernel, linux-mmc, devicetree, linux-wireless,
ath10k, linux-arm-msm, linux-bluetooth, netdev, linux-remoteproc,
George Moussalem
Hello,
This patch series introduces Bluetooth support for IPQ5018.
Bluetooth firmware on the IPQ5018 platform runs on the M0 co-processor.
The firmware is loaded by the host into a dedicated reserved memory
carveout and authenticated by TrustZone. A Secure Channel Manager (SCM)
call safely brings the peripheral core out of reset.
A shared memory ring buffer topology handles runtime data frame
transport between the host APSS and the M0 core. An outgoing APCS IPC
bit and an incoming GIC interrupt handle host/guest signaling.
This series has been tested and verified on various IPQ5018 router
boards utilizing firmware extracted from GPL distributions, using both
mdt and mbn file formats.
[ 14.781511] Bluetooth: hci0: QCA Product ID :0x00000016
[ 14.781583] Bluetooth: hci0: QCA SOC Version :0x20180100
[ 14.785926] Bluetooth: hci0: QCA ROM Version :0x00000100
[ 14.791546] Bluetooth: hci0: QCA Patch Version:0x00003ded
[ 14.796698] Bluetooth: hci0: QCA controller version 0x01000100
[ 14.802217] Bluetooth: hci0: QCA Downloading qca/mpnv10.bin
[ 16.393850] Bluetooth: hci0: QCA Build Info: BTFW.MAPLE.1.0.0-00102-MPL_ROM_PATCHZ-1
Best regards,
George Moussalem
Signed-off-by: George Moussalem <george.moussalem@outlook.com>
---
George Moussalem (6):
dt-bindings: remoteproc: document M0 Bluetooth Subsystem secure PIL
remoteproc: qcom: Add M0 BTSS secure PIL driver
Bluetooth: btqca: Add IPQ5018 support
dt-bindings: net: bluetooth: Document Qualcomm IPQ5018 Bluetooth controller
Bluetooth: Introduce Qualcomm IPQ5018 IPC based HCI driver
arm64: dts: qcom: ipq5018: add nodes required for Bluetooth support
.../bindings/net/bluetooth/qcom,ipq5018-bt.yaml | 63 ++
.../bindings/remoteproc/qcom,m0-btss-pil.yaml | 72 ++
arch/arm64/boot/dts/qcom/ipq5018.dtsi | 34 +-
drivers/bluetooth/Kconfig | 11 +
drivers/bluetooth/Makefile | 1 +
drivers/bluetooth/btqca.c | 16 +
drivers/bluetooth/btqca.h | 3 +
drivers/bluetooth/btqcomipc.c | 939 +++++++++++++++++++++
drivers/remoteproc/Kconfig | 12 +
drivers/remoteproc/Makefile | 1 +
drivers/remoteproc/qcom_m0_btss_pil.c | 261 ++++++
11 files changed, 1412 insertions(+), 1 deletion(-)
---
base-commit: 4d1ab324fcb7d20df5a071edb0304461846fdc12
change-id: 20260625-ipq5018-bluetooth-06ff66c9d753
prerequisite-message-id: <20260612-block-as-nvmem-v5-0-95e0b30fff90@oss.qualcomm.com>
prerequisite-patch-id: 6ce8686c1683f468d86b4502f5ec9d19c392a382
prerequisite-patch-id: e362f7fcbacff716b7ef720e6780786a7d88c013
prerequisite-patch-id: 9168930e40551e842c8171d5433a6f39ad4b78a4
prerequisite-patch-id: 64fecfbd1e085d7d2ab0ae23295ca34ec8e14c5e
prerequisite-patch-id: 566804aaa690ee9aa285d0fd75fd16d94fbadebf
prerequisite-patch-id: dc18bec338f54b3051f4523f9d1d3c0566a20ccd
prerequisite-patch-id: b6b3eb46429936ab49423d295433daf47981db0f
prerequisite-patch-id: 75caa99e3bbcdf41b6462b9f5f703bea1d4a65fa
prerequisite-patch-id: 35e9968f482f78ca233eb0306d9c5fdbff093175
Best regards,
--
George Moussalem <george.moussalem@outlook.com>
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox