Mostly driver changes this time:

- new driver mm81x for an S1G device
  - new driver nxpwifi for NXP devices
    (mostly forked off from mwifiex)
  - ath12k: much kernel infrastructure integration work
  - brcmfmac: DPP support, some Cypress part update
  - nl80211: per-link statistics support
 -----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCgAdFiEEpeA8sTs3M8SN2hR410qiO8sPaAAFAmpl5OQACgkQ10qiO8sP
 aAC64g//azU0JD8z/CLmEfPiG4sHQL7WvtkKKkkdxt5kle9wcjRh/8DDhGgF8Qb1
 Knq90Ees6wJAhZkGVKQW4cf3yWXSzp7sdg/M3bQQeelCJo4Di0xiKDS8Rjxplyh0
 q/ukqck7qvzYsMvnOvmnsJcpZ6kIvICC1Xi3/KMok9h3gtUHdJqhc1tEZz1Xt7Kl
 5qtEQWaMTcrQjf/RtGaesD3ai/asxjUBCOqIdlPM0FktQuDZ7nyZOYlT1pQi3ReN
 4tODKGYNG8EkNi4p7Pfo4miZsfx13ZwSs/qGaSeFSddUWcttqLf1oe7O5BvPOjoV
 nN33NIurdiBGky5sV8krjPar3C2OCoqdi8FASg3BJZYgtd3B950267ShsVn6Mp0B
 yTbo90JqX86PIGDkzaMosNkc7OFSfHmh78tuSeiM0FV30TbnjQEasLZdx/kZ9CP2
 01A8zurfL8Q81tZNiywzIAnD4PhIzi8zvG/nqJGWukizAGiZhUEwbfgXvd8vEOns
 njoprYVBETT8awkV05vgiJhGxDf88kpD58s/8g36XNys06gHzXM46/IspNVZQu+9
 qOxdHRL14B+ANdJirRgeJIZj6Wll7pNx+aqPndI6h11ezqfzR8mDYrjB0Kl38z8u
 BOeZ7FE51AtwjLMnMofgZCZoAkJqA/y8n1WrGX7lUtS2uqDIBVw=
 =eCJ1
 -----END PGP SIGNATURE-----

Merge tag 'wireless-2026-07-26' of https://git.kernel.org/pub/scm/linux/kernel/git/wireless/wireless-next

Johannes Berg says:

====================
wireless-next-2026-07-26

Mostly driver changes this time:
 - new driver mm81x for an S1G device
 - new driver nxpwifi for NXP devices
   (mostly forked off from mwifiex)
 - ath12k: much kernel infrastructure integration work
 - brcmfmac: DPP support, some Cypress part update
 - nl80211: per-link statistics support
====================

Link: https://patch.msgid.link/20260726105205.942922-60-johannes@sipsolutions.net
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Jakub Kicinski 2026-07-27 16:01:11 -07:00
commit edc84a9396
172 changed files with 53108 additions and 1403 deletions

View File

@ -18221,6 +18221,14 @@ F: drivers/regulator/mpq7920.c
F: drivers/regulator/mpq7920.h
F: include/linux/mfd/mp2629.h
MORSE MICRO MM81X WIRELESS DRIVER
M: Lachlan Hodges <lachlan.hodges@morsemicro.com>
M: Dan Callaghan <dan.callaghan@morsemicro.com>
R: Arien Judge <arien.judge@morsemicro.com>
L: linux-wireless@vger.kernel.org
S: Supported
F: drivers/net/wireless/morsemicro/
MOST(R) TECHNOLOGY DRIVER
M: Parthiban Veerasooran <parthiban.veerasooran@microchip.com>
M: Christian Gromm <christian.gromm@microchip.com>
@ -19556,6 +19564,13 @@ S: Maintained
F: Documentation/devicetree/bindings/ptp/nxp,ptp-netc.yaml
F: drivers/ptp/ptp_netc.c
NXP NXPWIFI WIRELESS DRIVER
M: Jeff Chen <jeff.chen_1@nxp.com>
R: Francesco Dolcini <francesco@dolcini.it>
L: linux-wireless@vger.kernel.org
S: Maintained
F: drivers/net/wireless/nxp/
NXP PF5300/PF5301/PF5302 PMIC REGULATOR DEVICE DRIVER
M: Woodrow Douglass <wdouglass@carnegierobotics.com>
S: Maintained
@ -22330,6 +22345,15 @@ F: Documentation/devicetree/bindings/media/*qcom*
F: drivers/media/platform/qcom
F: include/dt-bindings/media/*qcom*
QUALCOMM PAS TZ SERVICE
M: Sumit Garg <sumit.garg@oss.qualcomm.com>
L: linux-arm-msm@vger.kernel.org
S: Maintained
F: drivers/firmware/qcom/qcom_pas.c
F: drivers/firmware/qcom/qcom_pas.h
F: drivers/firmware/qcom/qcom_pas_tee.c
F: include/linux/firmware/qcom/qcom_pas.h
QUALCOMM SMB CHARGER DRIVER
M: Casey Connolly <casey.connolly@linaro.org>
L: linux-arm-msm@vger.kernel.org

View File

@ -6,9 +6,29 @@
menu "Qualcomm firmware drivers"
config QCOM_PAS
tristate "Qualcomm generic PAS interface driver"
help
Enable the generic Peripheral Authentication Service (PAS) provided
by the firmware. It acts as the common layer with different TZ
backends plugged in whether it's an SCM implementation or a proper
TEE bus based PAS service implementation.
config QCOM_PAS_TEE
tristate "Qualcomm PAS TEE interface driver"
select QCOM_PAS
depends on TEE
depends on !CPU_BIG_ENDIAN
default m if ARCH_QCOM
help
Enable the generic Peripheral Authentication Service (PAS) provided
by the firmware TEE implementation as the backend.
config QCOM_SCM
tristate "Qualcomm PAS SCM interface driver"
select QCOM_PAS
select QCOM_TZMEM
tristate
default y if ARCH_QCOM
config QCOM_TZMEM
tristate

View File

@ -8,3 +8,5 @@ qcom-scm-objs += qcom_scm.o qcom_scm-smc.o qcom_scm-legacy.o
obj-$(CONFIG_QCOM_TZMEM) += qcom_tzmem.o
obj-$(CONFIG_QCOM_QSEECOM) += qcom_qseecom.o
obj-$(CONFIG_QCOM_QSEECOM_UEFISECAPP) += qcom_qseecom_uefisecapp.o
obj-$(CONFIG_QCOM_PAS) += qcom_pas.o
obj-$(CONFIG_QCOM_PAS_TEE) += qcom_pas_tee.o

View File

@ -0,0 +1,298 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2010,2015,2019 The Linux Foundation. All rights reserved.
* Copyright (C) 2015 Linaro Ltd.
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/device/devres.h>
#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include "qcom_pas.h"
static struct qcom_pas_ops *ops_ptr;
/**
* devm_qcom_pas_context_alloc() - Allocate peripheral authentication service
* context for a given peripheral
*
* PAS context is device-resource managed, so the caller does not need
* to worry about freeing the context memory.
*
* @dev: PAS firmware device
* @pas_id: peripheral authentication service id
* @mem_phys: Subsystem reserve memory start address
* @mem_size: Subsystem reserve memory size
*
* Return: The new PAS context, or ERR_PTR() on failure.
*/
struct qcom_pas_context *devm_qcom_pas_context_alloc(struct device *dev,
u32 pas_id,
phys_addr_t mem_phys,
size_t mem_size)
{
struct qcom_pas_context *ctx;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
return ERR_PTR(-ENOMEM);
ctx->dev = dev;
ctx->pas_id = pas_id;
ctx->mem_phys = mem_phys;
ctx->mem_size = mem_size;
return ctx;
}
EXPORT_SYMBOL_GPL(devm_qcom_pas_context_alloc);
/**
* qcom_pas_init_image() - Initialize peripheral authentication service state
* machine for a given peripheral, using the metadata
* @pas_id: peripheral authentication service id
* @metadata: pointer to memory containing ELF header, program header table
* and optional blob of data used for authenticating the metadata
* and the rest of the firmware
* @size: size of the metadata
* @ctx: optional pas context
*
* Return: 0 on success.
*
* Upon successful return, the PAS metadata context (@ctx) will be used to
* track the metadata allocation, this needs to be released by invoking
* qcom_pas_metadata_release() by the caller.
*/
int qcom_pas_init_image(u32 pas_id, const void *metadata, size_t size,
struct qcom_pas_context *ctx)
{
if (!ops_ptr)
return -ENODEV;
return ops_ptr->init_image(ops_ptr->dev, pas_id, metadata, size, ctx);
}
EXPORT_SYMBOL_GPL(qcom_pas_init_image);
/**
* qcom_pas_metadata_release() - release metadata context
* @ctx: pas context
*/
void qcom_pas_metadata_release(struct qcom_pas_context *ctx)
{
if (!ops_ptr || !ctx || !ctx->ptr)
return;
ops_ptr->metadata_release(ops_ptr->dev, ctx);
}
EXPORT_SYMBOL_GPL(qcom_pas_metadata_release);
/**
* qcom_pas_mem_setup() - Prepare the memory related to a given peripheral
* for firmware loading
* @pas_id: peripheral authentication service id
* @addr: start address of memory area to prepare
* @size: size of the memory area to prepare
*
* Return: 0 on success.
*/
int qcom_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
{
if (!ops_ptr)
return -ENODEV;
return ops_ptr->mem_setup(ops_ptr->dev, pas_id, addr, size);
}
EXPORT_SYMBOL_GPL(qcom_pas_mem_setup);
/**
* qcom_pas_get_rsc_table() - Retrieve the resource table in passed output buffer
* for a given peripheral.
*
* Qualcomm remote processor may rely on both static and dynamic resources for
* its functionality. Static resources typically refer to memory-mapped
* addresses required by the subsystem and are often embedded within the
* firmware binary and dynamic resources, such as shared memory in DDR etc.,
* are determined at runtime during the boot process.
*
* On Qualcomm Technologies devices, it's possible that static resources are
* not embedded in the firmware binary and instead are provided by TrustZone.
* However, dynamic resources are always expected to come from TrustZone. This
* indicates that for Qualcomm devices, all resources (static and dynamic) will
* be provided by TrustZone PAS service.
*
* If the remote processor firmware binary does contain static resources, they
* should be passed in input_rt. These will be forwarded to TrustZone for
* authentication. TrustZone will then append the dynamic resources and return
* the complete resource table in output_rt_tzm.
*
* If the remote processor firmware binary does not include a resource table,
* the caller of this function should set input_rt as NULL and input_rt_size
* as zero respectively.
*
* More about documentation on resource table data structures can be found in
* include/linux/remoteproc.h
*
* @ctx: PAS context
* @input_rt: resource table buffer which is present in firmware binary
* @input_rt_size: size of the resource table present in firmware binary
* @output_rt_size: TrustZone expects caller should pass worst case size for
* the output_rt_tzm.
*
* Return:
* On success, returns a pointer to the allocated buffer containing the final
* resource table and output_rt_size will have actual resource table size from
* TrustZone. The caller is responsible for freeing the buffer. On failure,
* returns ERR_PTR(-errno).
*/
struct resource_table *qcom_pas_get_rsc_table(struct qcom_pas_context *ctx,
void *input_rt,
size_t input_rt_size,
size_t *output_rt_size)
{
if (!ops_ptr)
return ERR_PTR(-ENODEV);
if (!ctx)
return ERR_PTR(-EINVAL);
return ops_ptr->get_rsc_table(ops_ptr->dev, ctx, input_rt,
input_rt_size, output_rt_size);
}
EXPORT_SYMBOL_GPL(qcom_pas_get_rsc_table);
/**
* qcom_pas_auth_and_reset() - Authenticate the given peripheral firmware
* and reset the remote processor
* @pas_id: peripheral authentication service id
*
* Return: 0 on success.
*/
int qcom_pas_auth_and_reset(u32 pas_id)
{
if (!ops_ptr)
return -ENODEV;
return ops_ptr->auth_and_reset(ops_ptr->dev, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_pas_auth_and_reset);
/**
* qcom_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the
* remote processor
*
* @ctx: Context saved during call to devm_qcom_pas_context_alloc()
*
* This function performs the necessary steps to prepare a PAS subsystem,
* authenticate it using the provided metadata, and initiate a reset sequence.
*
* It should be used when Linux is in control setting up the IOMMU hardware
* for remote subsystem during secure firmware loading processes. The
* preparation step sets up a shmbridge over the firmware memory before
* TrustZone accesses the firmware memory region for authentication. The
* authentication step verifies the integrity and authenticity of the firmware
* or configuration using secure metadata. Finally, the reset step ensures the
* subsystem starts in a clean and sane state.
*
* Return: 0 on success, negative errno on failure.
*/
int qcom_pas_prepare_and_auth_reset(struct qcom_pas_context *ctx)
{
if (!ops_ptr)
return -ENODEV;
if (!ctx)
return -EINVAL;
return ops_ptr->prepare_and_auth_reset(ops_ptr->dev, ctx);
}
EXPORT_SYMBOL_GPL(qcom_pas_prepare_and_auth_reset);
/**
* qcom_pas_set_remote_state() - Set the remote processor state
* @state: peripheral state
* @pas_id: peripheral authentication service id
*
* Return: 0 on success.
*/
int qcom_pas_set_remote_state(u32 state, u32 pas_id)
{
if (!ops_ptr)
return -ENODEV;
return ops_ptr->set_remote_state(ops_ptr->dev, state, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_pas_set_remote_state);
/**
* qcom_pas_shutdown() - Shut down the remote processor
* @pas_id: peripheral authentication service id
*
* Return: 0 on success.
*/
int qcom_pas_shutdown(u32 pas_id)
{
if (!ops_ptr)
return -ENODEV;
return ops_ptr->shutdown(ops_ptr->dev, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_pas_shutdown);
/**
* qcom_pas_supported() - Check if the peripheral authentication service is
* supported for the given peripheral
* @pas_id: peripheral authentication service id
*
* Return: true if PAS is supported for this peripheral, otherwise false.
*/
bool qcom_pas_supported(u32 pas_id)
{
if (!ops_ptr)
return false;
return ops_ptr->supported(ops_ptr->dev, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_pas_supported);
/**
* qcom_pas_is_available() - Check if the peripheral authentication service is
* available. Note that it is mandatory for any PAS
* client to invoke this API. If it returns true then
* only any other PAS API can be invoked.
*
* Return: true if PAS is available, otherwise false.
*/
bool qcom_pas_is_available(void)
{
/*
* The barrier for ops_ptr is intended to synchronize the data stores
* for the ops data structure when client drivers are in parallel
* checking for PAS service availability.
*
* Once the PAS backend becomes available, it is allowed for multiple
* threads to enter TZ for parallel bringup of co-processors during
* boot.
*/
return !!smp_load_acquire(&ops_ptr);
}
EXPORT_SYMBOL_GPL(qcom_pas_is_available);
void qcom_pas_ops_register(struct qcom_pas_ops *ops)
{
if (!qcom_pas_is_available())
/* Paired with smp_load_acquire() in qcom_pas_is_available() */
smp_store_release(&ops_ptr, ops);
else
pr_err("qcom_pas: ops already registered by %s\n",
ops_ptr->drv_name);
}
EXPORT_SYMBOL_GPL(qcom_pas_ops_register);
void qcom_pas_ops_unregister(void)
{
/* Paired with smp_load_acquire() in qcom_pas_is_available() */
smp_store_release(&ops_ptr, NULL);
}
EXPORT_SYMBOL_GPL(qcom_pas_ops_unregister);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Qualcomm generic TZ PAS driver");

View File

@ -0,0 +1,50 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#ifndef __QCOM_PAS_INT_H
#define __QCOM_PAS_INT_H
struct device;
/**
* struct qcom_pas_ops - Qcom Peripheral Authentication Service (PAS) ops
* @drv_name: PAS driver name.
* @dev: PAS device pointer.
* @supported: Peripheral supported callback.
* @init_image: Peripheral image initialization callback.
* @mem_setup: Peripheral memory setup callback.
* @get_rsc_table: Peripheral get resource table callback.
* @prepare_and_auth_reset: Peripheral prepare firmware authentication and
* reset callback.
* @auth_and_reset: Peripheral firmware authentication and reset
* callback.
* @set_remote_state: Peripheral set remote state callback.
* @shutdown: Peripheral shutdown callback.
* @metadata_release: Image metadata release callback.
*/
struct qcom_pas_ops {
const char *drv_name;
struct device *dev;
bool (*supported)(struct device *dev, u32 pas_id);
int (*init_image)(struct device *dev, u32 pas_id, const void *metadata,
size_t size, struct qcom_pas_context *ctx);
int (*mem_setup)(struct device *dev, u32 pas_id, phys_addr_t addr,
phys_addr_t size);
void *(*get_rsc_table)(struct device *dev, struct qcom_pas_context *ctx,
void *input_rt, size_t input_rt_size,
size_t *output_rt_size);
int (*prepare_and_auth_reset)(struct device *dev,
struct qcom_pas_context *ctx);
int (*auth_and_reset)(struct device *dev, u32 pas_id);
int (*set_remote_state)(struct device *dev, u32 state, u32 pas_id);
int (*shutdown)(struct device *dev, u32 pas_id);
void (*metadata_release)(struct device *dev,
struct qcom_pas_context *ctx);
};
void qcom_pas_ops_register(struct qcom_pas_ops *ops);
void qcom_pas_ops_unregister(void);
#endif /* __QCOM_PAS_INT_H */

View File

@ -0,0 +1,479 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
*/
#include <linux/delay.h>
#include <linux/of.h>
#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/tee_drv.h>
#include <linux/uuid.h>
#include "qcom_pas.h"
/*
* Peripheral Authentication Service (PAS) supported.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
*/
#define TA_QCOM_PAS_IS_SUPPORTED 1
/*
* PAS capabilities.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
* [out] params[1].value.a: PAS capability flags
*/
#define TA_QCOM_PAS_CAPABILITIES 2
/*
* PAS image initialization.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
* [in] params[1].memref: Loadable firmware metadata
*/
#define TA_QCOM_PAS_INIT_IMAGE 3
/*
* PAS memory setup.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
* [in] params[0].value.b: Relocatable firmware size
* [in] params[1].value.a: 32bit LSB relocatable firmware memory address
* [in] params[1].value.b: 32bit MSB relocatable firmware memory address
*/
#define TA_QCOM_PAS_MEM_SETUP 4
/*
* PAS get resource table.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
* [inout] params[1].memref: Resource table config
*/
#define TA_QCOM_PAS_GET_RESOURCE_TABLE 5
/*
* PAS image authentication and co-processor reset.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
* [in] params[0].value.b: Firmware size
* [in] params[1].value.a: 32bit LSB firmware memory address
* [in] params[1].value.b: 32bit MSB firmware memory address
* [in] params[2].memref: Optional fw memory space shared/lent
*/
#define TA_QCOM_PAS_AUTH_AND_RESET 6
/*
* PAS co-processor set suspend/resume state.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
* [in] params[0].value.b: Co-processor state identifier
*/
#define TA_QCOM_PAS_SET_REMOTE_STATE 7
/*
* PAS co-processor shutdown.
*
* [in] params[0].value.a: Unique 32bit remote processor identifier
*/
#define TA_QCOM_PAS_SHUTDOWN 8
#define TEE_NUM_PARAMS 4
/**
* struct qcom_pas_tee_private - PAS service private data
* @dev: PAS service device.
* @ctx: TEE context handler.
* @session_id: PAS TA session identifier.
*/
struct qcom_pas_tee_private {
struct device *dev;
struct tee_context *ctx;
u32 session_id;
};
static bool qcom_pas_tee_supported(struct device *dev, u32 pas_id)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_IS_SUPPORTED,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = pas_id
}
};
int ret;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS not supported, pas_id: %d, ret: %d, err: 0x%x\n",
pas_id, ret, inv_arg.ret);
return false;
}
return true;
}
static int qcom_pas_tee_init_image(struct device *dev, u32 pas_id,
const void *metadata, size_t size,
struct qcom_pas_context *ctx)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_INIT_IMAGE,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = pas_id
},
[1] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
}
};
struct tee_shm *mdata_shm;
u8 *mdata_buf = NULL;
int ret;
mdata_shm = tee_shm_alloc_kernel_buf(data->ctx, size);
if (IS_ERR(mdata_shm)) {
dev_err(dev, "mdata_shm allocation failed\n");
return PTR_ERR(mdata_shm);
}
mdata_buf = tee_shm_get_va(mdata_shm, 0);
if (IS_ERR(mdata_buf)) {
dev_err(dev, "mdata_buf get VA failed\n");
tee_shm_free(mdata_shm);
return PTR_ERR(mdata_buf);
}
memcpy(mdata_buf, metadata, size);
param[1].u.memref.shm = mdata_shm;
param[1].u.memref.size = size;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS init image failed, pas_id: %d, ret: %d, err: 0x%x\n",
pas_id, ret, inv_arg.ret);
tee_shm_free(mdata_shm);
return ret ?: -EINVAL;
}
if (ctx)
ctx->ptr = (void *)mdata_shm;
else
tee_shm_free(mdata_shm);
return ret;
}
static int qcom_pas_tee_mem_setup(struct device *dev, u32 pas_id,
phys_addr_t addr, phys_addr_t size)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_MEM_SETUP,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = pas_id,
.u.value.b = size,
},
[1] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = lower_32_bits(addr),
.u.value.b = upper_32_bits(addr),
}
};
int ret;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS mem setup failed, pas_id: %d, ret: %d, err: 0x%x\n",
pas_id, ret, inv_arg.ret);
return ret ?: -EINVAL;
}
return ret;
}
DEFINE_FREE(shm_free, struct tee_shm *, tee_shm_free(_T))
static void *qcom_pas_tee_get_rsc_table(struct device *dev,
struct qcom_pas_context *ctx,
void *input_rt, size_t input_rt_size,
size_t *output_rt_size)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_GET_RESOURCE_TABLE,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = ctx->pas_id,
},
[1] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INOUT,
.u.memref.size = input_rt_size,
}
};
void *rt_buf = NULL;
int ret;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n",
ctx->pas_id, ret, inv_arg.ret);
return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL);
}
if (param[1].u.memref.size >= input_rt_size) {
struct tee_shm *rt_shm __free(shm_free) =
tee_shm_alloc_kernel_buf(data->ctx,
param[1].u.memref.size);
void *rt_shm_va;
if (IS_ERR_OR_NULL(rt_shm)) {
dev_err(dev, "rt_shm allocation failed\n");
rt_shm = NULL;
return ERR_PTR(-ENOMEM);
}
rt_shm_va = tee_shm_get_va(rt_shm, 0);
if (IS_ERR(rt_shm_va)) {
dev_err(dev, "rt_shm get VA failed\n");
return ERR_CAST(rt_shm_va);
}
memcpy(rt_shm_va, input_rt, input_rt_size);
param[1].u.memref.shm = rt_shm;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS get RT failed, pas_id: %d, ret: %d, err: 0x%x\n",
ctx->pas_id, ret, inv_arg.ret);
return ret ? ERR_PTR(ret) : ERR_PTR(-EINVAL);
}
if (param[1].u.memref.size) {
*output_rt_size = param[1].u.memref.size;
rt_buf = kmemdup(rt_shm_va, *output_rt_size, GFP_KERNEL);
if (!rt_buf)
return ERR_PTR(-ENOMEM);
}
} else {
*output_rt_size = 0;
}
return rt_buf;
}
static int __qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id,
phys_addr_t mem_phys, size_t mem_size)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_AUTH_AND_RESET,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = pas_id,
.u.value.b = mem_size,
},
[1] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = lower_32_bits(mem_phys),
.u.value.b = upper_32_bits(mem_phys),
},
/* Reserved for fw memory space to be shared or lent */
[2] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_MEMREF_INPUT,
}
};
int ret;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS auth reset failed, pas_id: %d, ret: %d, err: 0x%x\n",
pas_id, ret, inv_arg.ret);
return ret ?: -EINVAL;
}
return ret;
}
static int qcom_pas_tee_auth_and_reset(struct device *dev, u32 pas_id)
{
return __qcom_pas_tee_auth_and_reset(dev, pas_id, 0, 0);
}
static int qcom_pas_tee_prepare_and_auth_reset(struct device *dev,
struct qcom_pas_context *ctx)
{
return __qcom_pas_tee_auth_and_reset(dev, ctx->pas_id, ctx->mem_phys,
ctx->mem_size);
}
static int qcom_pas_tee_set_remote_state(struct device *dev, u32 state,
u32 pas_id)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_SET_REMOTE_STATE,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = pas_id,
.u.value.b = state,
}
};
int ret;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS set remote state failed, pas_id: %d, ret: %d, err: 0x%x\n",
pas_id, ret, inv_arg.ret);
return ret ?: -EINVAL;
}
return ret;
}
static int qcom_pas_tee_shutdown(struct device *dev, u32 pas_id)
{
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
struct tee_ioctl_invoke_arg inv_arg = {
.func = TA_QCOM_PAS_SHUTDOWN,
.session = data->session_id,
.num_params = TEE_NUM_PARAMS
};
struct tee_param param[4] = {
[0] = {
.attr = TEE_IOCTL_PARAM_ATTR_TYPE_VALUE_INPUT,
.u.value.a = pas_id
}
};
int ret;
ret = tee_client_invoke_func(data->ctx, &inv_arg, param);
if (ret < 0 || inv_arg.ret != 0) {
dev_err(dev, "PAS shutdown failed, pas_id: %d, ret: %d, err: 0x%x\n",
pas_id, ret, inv_arg.ret);
return ret ?: -EINVAL;
}
return ret;
}
static void qcom_pas_tee_metadata_release(struct device *dev,
struct qcom_pas_context *ctx)
{
struct tee_shm *mdata_shm = ctx->ptr;
tee_shm_free(mdata_shm);
ctx->ptr = NULL;
}
static struct qcom_pas_ops qcom_pas_ops_tee = {
.drv_name = "qcom-pas-tee",
.supported = qcom_pas_tee_supported,
.init_image = qcom_pas_tee_init_image,
.mem_setup = qcom_pas_tee_mem_setup,
.get_rsc_table = qcom_pas_tee_get_rsc_table,
.auth_and_reset = qcom_pas_tee_auth_and_reset,
.prepare_and_auth_reset = qcom_pas_tee_prepare_and_auth_reset,
.set_remote_state = qcom_pas_tee_set_remote_state,
.shutdown = qcom_pas_tee_shutdown,
.metadata_release = qcom_pas_tee_metadata_release,
};
static int optee_ctx_match(struct tee_ioctl_version_data *ver, const void *data)
{
return ver->impl_id == TEE_IMPL_ID_OPTEE;
}
static int qcom_pas_tee_probe(struct tee_client_device *pas_dev)
{
struct device *dev = &pas_dev->dev;
struct qcom_pas_tee_private *data;
struct tee_ioctl_open_session_arg sess_arg = {
.clnt_login = TEE_IOCTL_LOGIN_REE_KERNEL
};
int ret;
data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->ctx = tee_client_open_context(NULL, optee_ctx_match, NULL, NULL);
if (IS_ERR(data->ctx))
return -ENODEV;
export_uuid(sess_arg.uuid, &pas_dev->id.uuid);
ret = tee_client_open_session(data->ctx, &sess_arg, NULL);
if (ret < 0 || sess_arg.ret != 0) {
dev_err(dev, "tee_client_open_session failed, ret: %d, err: 0x%x\n",
ret, sess_arg.ret);
tee_client_close_context(data->ctx);
return ret ?: -EINVAL;
}
data->session_id = sess_arg.session;
dev_set_drvdata(dev, data);
qcom_pas_ops_tee.dev = dev;
qcom_pas_ops_register(&qcom_pas_ops_tee);
return ret;
}
static void qcom_pas_tee_remove(struct tee_client_device *pas_dev)
{
struct device *dev = &pas_dev->dev;
struct qcom_pas_tee_private *data = dev_get_drvdata(dev);
qcom_pas_ops_unregister();
tee_client_close_session(data->ctx, data->session_id);
tee_client_close_context(data->ctx);
}
static const struct tee_client_device_id qcom_pas_tee_id_table[] = {
{UUID_INIT(0xcff7d191, 0x7ca0, 0x4784,
0xaf, 0x13, 0x48, 0x22, 0x3b, 0x9a, 0x4f, 0xbe)},
{}
};
MODULE_DEVICE_TABLE(tee, qcom_pas_tee_id_table);
static struct tee_client_driver optee_pas_tee_driver = {
.probe = qcom_pas_tee_probe,
.remove = qcom_pas_tee_remove,
.id_table = qcom_pas_tee_id_table,
.driver = {
.name = "qcom-pas-tee",
},
};
module_tee_client_driver(optee_pas_tee_driver);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Qualcomm PAS TEE driver");

View File

@ -13,6 +13,7 @@
#include <linux/dma-mapping.h>
#include <linux/err.h>
#include <linux/export.h>
#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/firmware/qcom/qcom_tzmem.h>
#include <linux/init.h>
@ -33,6 +34,7 @@
#include <dt-bindings/interrupt-controller/arm-gic.h>
#include "qcom_pas.h"
#include "qcom_scm.h"
#include "qcom_tzmem.h"
@ -479,25 +481,6 @@ void qcom_scm_cpu_power_down(u32 flags)
}
EXPORT_SYMBOL_GPL(qcom_scm_cpu_power_down);
int qcom_scm_set_remote_state(u32 state, u32 id)
{
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_BOOT,
.cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE,
.arginfo = QCOM_SCM_ARGS(2),
.args[0] = state,
.args[1] = id,
.owner = ARM_SMCCC_OWNER_SIP,
};
struct qcom_scm_res res;
int ret;
ret = qcom_scm_call(__scm->dev, &desc, &res);
return ret ? : res.result[0];
}
EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state);
static int qcom_scm_disable_sdi(void)
{
int ret;
@ -570,26 +553,12 @@ static void qcom_scm_set_download_mode(u32 dload_mode)
dev_err(__scm->dev, "failed to set download mode: %d\n", ret);
}
/**
* devm_qcom_scm_pas_context_alloc() - Allocate peripheral authentication service
* context for a given peripheral
*
* PAS context is device-resource managed, so the caller does not need
* to worry about freeing the context memory.
*
* @dev: PAS firmware device
* @pas_id: peripheral authentication service id
* @mem_phys: Subsystem reserve memory start address
* @mem_size: Subsystem reserve memory size
*
* Returns: The new PAS context, or ERR_PTR() on failure.
*/
struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
u32 pas_id,
phys_addr_t mem_phys,
size_t mem_size)
{
struct qcom_scm_pas_context *ctx;
struct qcom_pas_context *ctx;
ctx = devm_kzalloc(dev, sizeof(*ctx), GFP_KERNEL);
if (!ctx)
@ -600,11 +569,12 @@ struct qcom_scm_pas_context *devm_qcom_scm_pas_context_alloc(struct device *dev,
ctx->mem_phys = mem_phys;
ctx->mem_size = mem_size;
return ctx;
return (struct qcom_scm_pas_context *)ctx;
}
EXPORT_SYMBOL_GPL(devm_qcom_scm_pas_context_alloc);
static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
static int __qcom_scm_pas_init_image(struct device *dev, u32 pas_id,
dma_addr_t mdata_phys,
struct qcom_scm_res *res)
{
struct qcom_scm_desc desc = {
@ -626,7 +596,7 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
desc.args[1] = mdata_phys;
ret = qcom_scm_call(__scm->dev, &desc, res);
ret = qcom_scm_call(dev, &desc, res);
qcom_scm_bw_disable();
disable_clk:
@ -635,7 +605,8 @@ static int __qcom_scm_pas_init_image(u32 pas_id, dma_addr_t mdata_phys,
return ret;
}
static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
static int qcom_scm_pas_prep_and_init_image(struct device *dev,
struct qcom_pas_context *ctx,
const void *metadata, size_t size)
{
struct qcom_scm_res res;
@ -650,7 +621,7 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
memcpy(mdata_buf, metadata, size);
mdata_phys = qcom_tzmem_to_phys(mdata_buf);
ret = __qcom_scm_pas_init_image(ctx->pas_id, mdata_phys, &res);
ret = __qcom_scm_pas_init_image(dev, ctx->pas_id, mdata_phys, &res);
if (ret < 0)
qcom_tzmem_free(mdata_buf);
else
@ -659,25 +630,9 @@ static int qcom_scm_pas_prep_and_init_image(struct qcom_scm_pas_context *ctx,
return ret ? : res.result[0];
}
/**
* qcom_scm_pas_init_image() - Initialize peripheral authentication service
* state machine for a given peripheral, using the
* metadata
* @pas_id: peripheral authentication service id
* @metadata: pointer to memory containing ELF header, program header table
* and optional blob of data used for authenticating the metadata
* and the rest of the firmware
* @size: size of the metadata
* @ctx: optional pas context
*
* Return: 0 on success.
*
* Upon successful return, the PAS metadata context (@ctx) will be used to
* track the metadata allocation, this needs to be released by invoking
* qcom_scm_pas_metadata_release() by the caller.
*/
int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
struct qcom_scm_pas_context *ctx)
static int __qcom_scm_pas_init_image2(struct device *dev, u32 pas_id,
const void *metadata, size_t size,
struct qcom_pas_context *ctx)
{
struct qcom_scm_res res;
dma_addr_t mdata_phys;
@ -685,7 +640,7 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
int ret;
if (ctx && ctx->use_tzmem)
return qcom_scm_pas_prep_and_init_image(ctx, metadata, size);
return qcom_scm_pas_prep_and_init_image(dev, ctx, metadata, size);
/*
* During the scm call memory protection will be enabled for the meta
@ -699,16 +654,15 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
* If we pass a buffer that is already part of an SHM Bridge to this
* call, it will fail.
*/
mdata_buf = dma_alloc_coherent(__scm->dev, size, &mdata_phys,
GFP_KERNEL);
mdata_buf = dma_alloc_coherent(dev, size, &mdata_phys, GFP_KERNEL);
if (!mdata_buf)
return -ENOMEM;
memcpy(mdata_buf, metadata, size);
ret = __qcom_scm_pas_init_image(pas_id, mdata_phys, &res);
ret = __qcom_scm_pas_init_image(dev, pas_id, mdata_phys, &res);
if (ret < 0 || !ctx) {
dma_free_coherent(__scm->dev, size, mdata_buf, mdata_phys);
dma_free_coherent(dev, size, mdata_buf, mdata_phys);
} else if (ctx) {
ctx->ptr = mdata_buf;
ctx->phys = mdata_phys;
@ -717,36 +671,35 @@ int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
return ret ? : res.result[0];
}
int qcom_scm_pas_init_image(u32 pas_id, const void *metadata, size_t size,
struct qcom_scm_pas_context *ctx)
{
return __qcom_scm_pas_init_image2(__scm->dev, pas_id, metadata, size,
(struct qcom_pas_context *)ctx);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_init_image);
/**
* qcom_scm_pas_metadata_release() - release metadata context
* @ctx: pas context
*/
void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx)
static void __qcom_scm_pas_metadata_release(struct device *dev,
struct qcom_pas_context *ctx)
{
if (!ctx->ptr)
return;
if (ctx->use_tzmem)
qcom_tzmem_free(ctx->ptr);
else
dma_free_coherent(__scm->dev, ctx->size, ctx->ptr, ctx->phys);
dma_free_coherent(dev, ctx->size, ctx->ptr, ctx->phys);
ctx->ptr = NULL;
}
void qcom_scm_pas_metadata_release(struct qcom_scm_pas_context *ctx)
{
__qcom_scm_pas_metadata_release(__scm->dev,
(struct qcom_pas_context *)ctx);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_metadata_release);
/**
* qcom_scm_pas_mem_setup() - Prepare the memory related to a given peripheral
* for firmware loading
* @pas_id: peripheral authentication service id
* @addr: start address of memory area to prepare
* @size: size of the memory area to prepare
*
* Returns 0 on success.
*/
int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
static int __qcom_scm_pas_mem_setup(struct device *dev, u32 pas_id,
phys_addr_t addr, phys_addr_t size)
{
int ret;
struct qcom_scm_desc desc = {
@ -768,7 +721,7 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
if (ret)
goto disable_clk;
ret = qcom_scm_call(__scm->dev, &desc, &res);
ret = qcom_scm_call(dev, &desc, &res);
qcom_scm_bw_disable();
disable_clk:
@ -776,9 +729,15 @@ int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
return ret ? : res.result[0];
}
int qcom_scm_pas_mem_setup(u32 pas_id, phys_addr_t addr, phys_addr_t size)
{
return __qcom_scm_pas_mem_setup(__scm->dev, pas_id, addr, size);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_mem_setup);
static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
static void *__qcom_scm_pas_get_rsc_table(struct device *dev, u32 pas_id,
void *input_rt_tzm,
size_t input_rt_size,
size_t *output_rt_size)
{
@ -813,7 +772,7 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
* with output_rt_tzm buffer with res.result[2] size however, It should not
* be of unresonable size.
*/
ret = qcom_scm_call(__scm->dev, &desc, &res);
ret = qcom_scm_call(dev, &desc, &res);
if (!ret && res.result[2] > SZ_1G) {
ret = -E2BIG;
goto free_output_rt;
@ -830,51 +789,11 @@ static void *__qcom_scm_pas_get_rsc_table(u32 pas_id, void *input_rt_tzm,
return ret ? ERR_PTR(ret) : output_rt_tzm;
}
/**
* qcom_scm_pas_get_rsc_table() - Retrieve the resource table in passed output buffer
* for a given peripheral.
*
* Qualcomm remote processor may rely on both static and dynamic resources for
* its functionality. Static resources typically refer to memory-mapped addresses
* required by the subsystem and are often embedded within the firmware binary
* and dynamic resources, such as shared memory in DDR etc., are determined at
* runtime during the boot process.
*
* On Qualcomm Technologies devices, it's possible that static resources are not
* embedded in the firmware binary and instead are provided by TrustZone However,
* dynamic resources are always expected to come from TrustZone. This indicates
* that for Qualcomm devices, all resources (static and dynamic) will be provided
* by TrustZone via the SMC call.
*
* If the remote processor firmware binary does contain static resources, they
* should be passed in input_rt. These will be forwarded to TrustZone for
* authentication. TrustZone will then append the dynamic resources and return
* the complete resource table in output_rt_tzm.
*
* If the remote processor firmware binary does not include a resource table,
* the caller of this function should set input_rt as NULL and input_rt_size
* as zero respectively.
*
* More about documentation on resource table data structures can be found in
* include/linux/remoteproc.h
*
* @ctx: PAS context
* @pas_id: peripheral authentication service id
* @input_rt: resource table buffer which is present in firmware binary
* @input_rt_size: size of the resource table present in firmware binary
* @output_rt_size: TrustZone expects caller should pass worst case size for
* the output_rt_tzm.
*
* Return:
* On success, returns a pointer to the allocated buffer containing the final
* resource table and output_rt_size will have actual resource table size from
* TrustZone. The caller is responsible for freeing the buffer. On failure,
* returns ERR_PTR(-errno).
*/
struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx,
void *input_rt,
size_t input_rt_size,
size_t *output_rt_size)
static void *__qcom_scm_pas_get_rsc_table2(struct device *dev,
struct qcom_pas_context *ctx,
void *input_rt,
size_t input_rt_size,
size_t *output_rt_size)
{
struct resource_table empty_rsc = {};
size_t size = SZ_16K;
@ -909,11 +828,12 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
memcpy(input_rt_tzm, input_rt, input_rt_size);
output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id, input_rt_tzm,
output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id,
input_rt_tzm,
input_rt_size, &size);
if (PTR_ERR(output_rt_tzm) == -EOVERFLOW)
/* Try again with the size requested by the TZ */
output_rt_tzm = __qcom_scm_pas_get_rsc_table(ctx->pas_id,
output_rt_tzm = __qcom_scm_pas_get_rsc_table(dev, ctx->pas_id,
input_rt_tzm,
input_rt_size,
&size);
@ -943,16 +863,20 @@ struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *c
return ret ? ERR_PTR(ret) : tbl_ptr;
}
struct resource_table *qcom_scm_pas_get_rsc_table(struct qcom_scm_pas_context *ctx,
void *input_rt,
size_t input_rt_size,
size_t *output_rt_size)
{
return __qcom_scm_pas_get_rsc_table2(__scm->dev,
(struct qcom_pas_context *)ctx,
input_rt, input_rt_size,
output_rt_size);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_get_rsc_table);
/**
* qcom_scm_pas_auth_and_reset() - Authenticate the given peripheral firmware
* and reset the remote processor
* @pas_id: peripheral authentication service id
*
* Return 0 on success.
*/
int qcom_scm_pas_auth_and_reset(u32 pas_id)
static int __qcom_scm_pas_auth_and_reset(struct device *dev, u32 pas_id)
{
int ret;
struct qcom_scm_desc desc = {
@ -972,7 +896,7 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id)
if (ret)
goto disable_clk;
ret = qcom_scm_call(__scm->dev, &desc, &res);
ret = qcom_scm_call(dev, &desc, &res);
qcom_scm_bw_disable();
disable_clk:
@ -980,28 +904,15 @@ int qcom_scm_pas_auth_and_reset(u32 pas_id)
return ret ? : res.result[0];
}
int qcom_scm_pas_auth_and_reset(u32 pas_id)
{
return __qcom_scm_pas_auth_and_reset(__scm->dev, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_auth_and_reset);
/**
* qcom_scm_pas_prepare_and_auth_reset() - Prepare, authenticate, and reset the
* remote processor
*
* @ctx: Context saved during call to qcom_scm_pas_context_init()
*
* This function performs the necessary steps to prepare a PAS subsystem,
* authenticate it using the provided metadata, and initiate a reset sequence.
*
* It should be used when Linux is in control setting up the IOMMU hardware
* for remote subsystem during secure firmware loading processes. The preparation
* step sets up a shmbridge over the firmware memory before TrustZone accesses the
* firmware memory region for authentication. The authentication step verifies
* the integrity and authenticity of the firmware or configuration using secure
* metadata. Finally, the reset step ensures the subsystem starts in a clean and
* sane state.
*
* Return: 0 on success, negative errno on failure.
*/
int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
static int __qcom_scm_pas_prepare_and_auth_reset(struct device *dev,
struct qcom_pas_context *ctx)
{
u64 handle;
int ret;
@ -1012,7 +923,7 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
* memory region and then invokes a call to TrustZone to authenticate.
*/
if (!ctx->use_tzmem)
return qcom_scm_pas_auth_and_reset(ctx->pas_id);
return __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id);
/*
* When Linux runs @ EL2 Linux must create the shmbridge itself and then
@ -1022,20 +933,45 @@ int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
if (ret)
return ret;
ret = qcom_scm_pas_auth_and_reset(ctx->pas_id);
ret = __qcom_scm_pas_auth_and_reset(dev, ctx->pas_id);
qcom_tzmem_shm_bridge_delete(handle);
return ret;
}
int qcom_scm_pas_prepare_and_auth_reset(struct qcom_scm_pas_context *ctx)
{
return __qcom_scm_pas_prepare_and_auth_reset(__scm->dev,
(struct qcom_pas_context *)ctx);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_prepare_and_auth_reset);
/**
* qcom_scm_pas_shutdown() - Shut down the remote processor
* @pas_id: peripheral authentication service id
*
* Returns 0 on success.
*/
int qcom_scm_pas_shutdown(u32 pas_id)
static int __qcom_scm_pas_set_remote_state(struct device *dev, u32 state,
u32 pas_id)
{
struct qcom_scm_desc desc = {
.svc = QCOM_SCM_SVC_BOOT,
.cmd = QCOM_SCM_BOOT_SET_REMOTE_STATE,
.arginfo = QCOM_SCM_ARGS(2),
.args[0] = state,
.args[1] = pas_id,
.owner = ARM_SMCCC_OWNER_SIP,
};
struct qcom_scm_res res;
int ret;
ret = qcom_scm_call(dev, &desc, &res);
return ret ? : res.result[0];
}
int qcom_scm_set_remote_state(u32 state, u32 id)
{
return __qcom_scm_pas_set_remote_state(__scm->dev, state, id);
}
EXPORT_SYMBOL_GPL(qcom_scm_set_remote_state);
static int __qcom_scm_pas_shutdown(struct device *dev, u32 pas_id)
{
int ret;
struct qcom_scm_desc desc = {
@ -1055,7 +991,7 @@ int qcom_scm_pas_shutdown(u32 pas_id)
if (ret)
goto disable_clk;
ret = qcom_scm_call(__scm->dev, &desc, &res);
ret = qcom_scm_call(dev, &desc, &res);
qcom_scm_bw_disable();
disable_clk:
@ -1063,16 +999,14 @@ int qcom_scm_pas_shutdown(u32 pas_id)
return ret ? : res.result[0];
}
int qcom_scm_pas_shutdown(u32 pas_id)
{
return __qcom_scm_pas_shutdown(__scm->dev, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_shutdown);
/**
* qcom_scm_pas_supported() - Check if the peripheral authentication service is
* available for the given peripherial
* @pas_id: peripheral authentication service id
*
* Returns true if PAS is supported for this peripheral, otherwise false.
*/
bool qcom_scm_pas_supported(u32 pas_id)
static bool __qcom_scm_pas_supported(struct device *dev, u32 pas_id)
{
int ret;
struct qcom_scm_desc desc = {
@ -1084,16 +1018,49 @@ bool qcom_scm_pas_supported(u32 pas_id)
};
struct qcom_scm_res res;
if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
if (!__qcom_scm_is_call_available(dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PIL_PAS_IS_SUPPORTED))
return false;
ret = qcom_scm_call(__scm->dev, &desc, &res);
ret = qcom_scm_call(dev, &desc, &res);
return ret ? false : !!res.result[0];
}
bool qcom_scm_pas_supported(u32 pas_id)
{
return __qcom_scm_pas_supported(__scm->dev, pas_id);
}
EXPORT_SYMBOL_GPL(qcom_scm_pas_supported);
static struct qcom_pas_ops qcom_pas_ops_scm = {
.drv_name = "qcom_scm",
.supported = __qcom_scm_pas_supported,
.init_image = __qcom_scm_pas_init_image2,
.mem_setup = __qcom_scm_pas_mem_setup,
.get_rsc_table = __qcom_scm_pas_get_rsc_table2,
.auth_and_reset = __qcom_scm_pas_auth_and_reset,
.prepare_and_auth_reset = __qcom_scm_pas_prepare_and_auth_reset,
.set_remote_state = __qcom_scm_pas_set_remote_state,
.shutdown = __qcom_scm_pas_shutdown,
.metadata_release = __qcom_scm_pas_metadata_release,
};
/**
* qcom_scm_is_pas_available() - Check if the peripheral authentication service
* is available via SCM or not
*
* Returns true if PAS is available, otherwise false.
*/
static bool qcom_scm_is_pas_available(void)
{
if (!__qcom_scm_is_call_available(__scm->dev, QCOM_SCM_SVC_PIL,
QCOM_SCM_PIL_PAS_AUTH_AND_RESET))
return false;
return true;
}
static int __qcom_scm_pas_mss_reset(struct device *dev, bool reset)
{
struct qcom_scm_desc desc = {
@ -2837,6 +2804,11 @@ static int qcom_scm_probe(struct platform_device *pdev)
__get_convention();
if (qcom_scm_is_pas_available()) {
qcom_pas_ops_scm.dev = scm->dev;
qcom_pas_ops_register(&qcom_pas_ops_scm);
}
/*
* If "download mode" is requested, from this point on warmboot
* will cause the boot stages to enter download mode, unless
@ -2876,6 +2848,7 @@ static void qcom_scm_shutdown(struct platform_device *pdev)
{
/* Clean shutdown, disable download mode to allow normal restart */
qcom_scm_set_download_mode(QCOM_DLOAD_NODUMP);
qcom_pas_ops_unregister();
}
static const struct of_device_id qcom_scm_dt_match[] = {

View File

@ -208,6 +208,9 @@ static const struct mmc_fixup __maybe_unused sdio_fixup_methods[] = {
SDIO_FIXUP(SDIO_VENDOR_ID_MARVELL, SDIO_DEVICE_ID_MARVELL_8887_F0,
add_limit_rate_quirk, 150000000),
SDIO_FIXUP(SDIO_VENDOR_ID_NXP, SDIO_DEVICE_ID_NXP_IW61X_BASE,
add_quirk, MMC_QUIRK_BLKSZ_FOR_BYTE_MODE),
END_FIXUP
};

View File

@ -27,6 +27,8 @@ source "drivers/net/wireless/intersil/Kconfig"
source "drivers/net/wireless/marvell/Kconfig"
source "drivers/net/wireless/mediatek/Kconfig"
source "drivers/net/wireless/microchip/Kconfig"
source "drivers/net/wireless/morsemicro/Kconfig"
source "drivers/net/wireless/nxp/Kconfig"
source "drivers/net/wireless/purelifi/Kconfig"
source "drivers/net/wireless/ralink/Kconfig"
source "drivers/net/wireless/realtek/Kconfig"

View File

@ -12,6 +12,8 @@ obj-$(CONFIG_WLAN_VENDOR_INTERSIL) += intersil/
obj-$(CONFIG_WLAN_VENDOR_MARVELL) += marvell/
obj-$(CONFIG_WLAN_VENDOR_MEDIATEK) += mediatek/
obj-$(CONFIG_WLAN_VENDOR_MICROCHIP) += microchip/
obj-$(CONFIG_WLAN_VENDOR_MORSEMICRO) += morsemicro/
obj-$(CONFIG_WLAN_VENDOR_NXP) += nxp/
obj-$(CONFIG_WLAN_VENDOR_PURELIFI) += purelifi/
obj-$(CONFIG_WLAN_VENDOR_QUANTENNA) += quantenna/
obj-$(CONFIG_WLAN_VENDOR_RALINK) += ralink/

View File

@ -87,24 +87,24 @@ static int ath10k_ahb_clock_init(struct ath10k *ar)
dev = &ar_ahb->pdev->dev;
ar_ahb->cmd_clk = devm_clk_get(dev, "wifi_wcss_cmd");
if (IS_ERR_OR_NULL(ar_ahb->cmd_clk)) {
if (IS_ERR(ar_ahb->cmd_clk)) {
ath10k_err(ar, "failed to get cmd clk: %ld\n",
PTR_ERR(ar_ahb->cmd_clk));
return ar_ahb->cmd_clk ? PTR_ERR(ar_ahb->cmd_clk) : -ENODEV;
return PTR_ERR(ar_ahb->cmd_clk);
}
ar_ahb->ref_clk = devm_clk_get(dev, "wifi_wcss_ref");
if (IS_ERR_OR_NULL(ar_ahb->ref_clk)) {
if (IS_ERR(ar_ahb->ref_clk)) {
ath10k_err(ar, "failed to get ref clk: %ld\n",
PTR_ERR(ar_ahb->ref_clk));
return ar_ahb->ref_clk ? PTR_ERR(ar_ahb->ref_clk) : -ENODEV;
return PTR_ERR(ar_ahb->ref_clk);
}
ar_ahb->rtc_clk = devm_clk_get(dev, "wifi_wcss_rtc");
if (IS_ERR_OR_NULL(ar_ahb->rtc_clk)) {
if (IS_ERR(ar_ahb->rtc_clk)) {
ath10k_err(ar, "failed to get rtc clk: %ld\n",
PTR_ERR(ar_ahb->rtc_clk));
return ar_ahb->rtc_clk ? PTR_ERR(ar_ahb->rtc_clk) : -ENODEV;
return PTR_ERR(ar_ahb->rtc_clk);
}
return 0;

View File

@ -2345,10 +2345,8 @@ static int ath10k_htt_rx_handle_amsdu(struct ath10k_htt *htt)
if (ret < 0) {
ath10k_warn(ar, "rx ring became corrupted: %d\n", ret);
__skb_queue_purge(&amsdu);
/* FIXME: It's probably a good idea to reboot the
* device instead of leaving it inoperable.
*/
htt->rx_confused = true;
ath10k_core_start_recovery(ar);
return ret;
}
@ -3313,6 +3311,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb)
if (ret < 0) {
ath10k_warn(ar, "failed to pop paddr list: %d\n", ret);
htt->rx_confused = true;
ath10k_core_start_recovery(ar);
return -EIO;
}
@ -3346,6 +3345,7 @@ static int ath10k_htt_rx_in_ord_ind(struct ath10k *ar, struct sk_buff *skb)
ath10k_warn(ar, "failed to extract amsdu: %d\n", ret);
htt->rx_confused = true;
__skb_queue_purge(&list);
ath10k_core_start_recovery(ar);
return -EIO;
}
}

View File

@ -1049,9 +1049,11 @@ static const struct __ath11k_core_usecase_firmware_table {
const char *compatible;
const char *firmware_name;
} ath11k_core_usecase_firmware_table[] = {
{ ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"},
{ ATH11K_HW_WCN6855_HW21, "qcom,lemans-evk", "nfa765"},
{ ATH11K_HW_WCN6855_HW21, "qcom,monaco-evk", "nfa765"},
{ ATH11K_HW_WCN6855_HW21, "qcom,hamoa-iot-evk", "nfa765"},
{ ATH11K_HW_WCN6855_HW21, "qcom,purwa-iot-evk", "nfa765"},
{ ATH11K_HW_WCN6855_HW21, "qcom,qcs6490-rb3gen2", "nfa765"},
{ /* Sentinel */ }
};

View File

@ -2334,10 +2334,10 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc,
case RX_MSDU_START_PKT_TYPE_11N:
rx_status->encoding = RX_ENC_HT;
if (rate_mcs > ATH11K_HT_MCS_MAX) {
ath11k_warn(ar->ab,
"Received with invalid mcs in HT mode %d\n",
rate_mcs);
break;
ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX,
"Received HT frame with out-of-range mcs %d, capping to %d\n",
rate_mcs, ATH11K_HT_MCS_MAX);
rate_mcs = ATH11K_HT_MCS_MAX;
}
rx_status->rate_idx = rate_mcs + (8 * (nss - 1));
if (sgi)
@ -2346,13 +2346,13 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc,
break;
case RX_MSDU_START_PKT_TYPE_11AC:
rx_status->encoding = RX_ENC_VHT;
rx_status->rate_idx = rate_mcs;
if (rate_mcs > ATH11K_VHT_MCS_MAX) {
ath11k_warn(ar->ab,
"Received with invalid mcs in VHT mode %d\n",
rate_mcs);
break;
ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX,
"Received VHT frame with out-of-range mcs %d, capping to %d\n",
rate_mcs, ATH11K_VHT_MCS_MAX);
rate_mcs = ATH11K_VHT_MCS_MAX;
}
rx_status->rate_idx = rate_mcs;
rx_status->nss = nss;
if (sgi)
rx_status->enc_flags |= RX_ENC_FLAG_SHORT_GI;
@ -2362,14 +2362,14 @@ static void ath11k_dp_rx_h_rate(struct ath11k *ar, struct hal_rx_desc *rx_desc,
rx_status->enc_flags |= RX_ENC_FLAG_LDPC;
break;
case RX_MSDU_START_PKT_TYPE_11AX:
rx_status->rate_idx = rate_mcs;
if (rate_mcs > ATH11K_HE_MCS_MAX) {
ath11k_warn(ar->ab,
"Received with invalid mcs in HE mode %d\n",
rate_mcs);
break;
}
rx_status->encoding = RX_ENC_HE;
if (rate_mcs > ATH11K_HE_MCS_MAX) {
ath11k_dbg(ar->ab, ATH11K_DBG_DP_RX,
"Received HE frame with out-of-range mcs %d, capping to %d\n",
rate_mcs, ATH11K_HE_MCS_MAX);
rate_mcs = ATH11K_HE_MCS_MAX;
}
rx_status->rate_idx = rate_mcs;
rx_status->nss = nss;
rx_status->he_gi = ath11k_mac_he_gi_to_nl80211_he_gi(sgi);
rx_status->bw = ath11k_mac_bw_to_mac80211_bw(bw);

View File

@ -2423,8 +2423,8 @@ int ath11k_wmi_send_scan_start_cmd(struct ath11k *ar,
for (i = 0; i < params->num_hint_bssid; ++i) {
hint_bssid->freq_flags =
params->hint_bssid[i].freq_flags;
ether_addr_copy(&params->hint_bssid[i].bssid.addr[0],
&hint_bssid->bssid.addr[0]);
ether_addr_copy(&hint_bssid->bssid.addr[0],
&params->hint_bssid[i].bssid.addr[0]);
hint_bssid++;
}
}
@ -4858,6 +4858,12 @@ static int ath11k_wmi_tlv_ext_hal_reg_caps(struct ath11k_base *soc,
return ret;
}
if (reg_cap.phy_id >= ARRAY_SIZE(soc->hal_reg_cap)) {
ath11k_warn(soc, "invalid reg cap phy_id %u\n",
reg_cap.phy_id);
return -EINVAL;
}
memcpy(&soc->hal_reg_cap[reg_cap.phy_id],
&reg_cap, sizeof(reg_cap));
}
@ -8895,13 +8901,15 @@ static void ath11k_wmi_tlv_op_rx(struct ath11k_base *ab, struct sk_buff *skb)
struct wmi_cmd_hdr *cmd_hdr;
enum wmi_tlv_event_id id;
if (skb->len < sizeof(*cmd_hdr))
goto out;
cmd_hdr = (struct wmi_cmd_hdr *)skb->data;
id = FIELD_GET(WMI_CMD_HDR_CMD_ID, (cmd_hdr->cmd_id));
trace_ath11k_wmi_event(ab, id, skb->data, skb->len);
if (skb_pull(skb, sizeof(struct wmi_cmd_hdr)) == NULL)
goto out;
skb_pull(skb, sizeof(*cmd_hdr));
switch (id) {
/* Process all the WMI events here */

View File

@ -18,7 +18,7 @@ config ATH12K_AHB
bool "Qualcomm ath12k AHB support"
depends on ATH12K && REMOTEPROC
select QCOM_MDT_LOADER
select QCOM_SCM
select QCOM_PAS
help
Enable support for Ath12k AHB bus chipsets, example IPQ5332.

View File

@ -5,19 +5,19 @@
*/
#include <linux/dma-mapping.h>
#include <linux/firmware/qcom/qcom_scm.h>
#include <linux/firmware/qcom/qcom_pas.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/platform_device.h>
#include <linux/remoteproc.h>
#include <linux/soc/qcom/mdt_loader.h>
#include <linux/soc/qcom/smem_state.h>
#include <linux/of_reserved_mem.h>
#include "ahb.h"
#include "debug.h"
#include "hif.h"
#define ATH12K_IRQ_CE0_OFFSET 4
#define ATH12K_MAX_UPDS 1
#define ATH12K_UPD_IRQ_WRD_LEN 18
static struct ath12k_ahb_driver *ath12k_ahb_family_drivers[ATH12K_DEVICE_FAMILY_MAX];
@ -338,24 +338,25 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab)
char fw2_name[ATH12K_USERPD_FW_NAME_LEN];
struct device *dev = ab->dev;
const struct firmware *fw, *fw2;
struct reserved_mem *rmem = NULL;
unsigned long time_left;
phys_addr_t mem_phys;
struct resource res;
void *mem_region;
size_t mem_size;
u32 pasid;
int ret;
rmem = ath12k_core_get_reserved_mem(ab, 0);
if (!rmem)
return -ENODEV;
ret = of_reserved_mem_region_to_resource_byname(dev->of_node, "q6-region",
&res);
if (ret)
return ret;
mem_phys = rmem->base;
mem_size = rmem->size;
mem_phys = res.start;
mem_size = resource_size(&res);
mem_region = devm_memremap(dev, mem_phys, mem_size, MEMREMAP_WC);
if (IS_ERR(mem_region)) {
ath12k_err(ab, "unable to map memory region: %pa+%pa\n",
&rmem->base, &rmem->size);
ath12k_err(ab, "unable to map memory region: %pa+%zx\n",
&res.start, mem_size);
return PTR_ERR(mem_region);
}
@ -420,7 +421,7 @@ static int ath12k_ahb_power_up(struct ath12k_base *ab)
if (ab_ahb->scm_auth_enabled) {
/* Authenticate FW image using peripheral ID */
ret = qcom_scm_pas_auth_and_reset(pasid);
ret = qcom_pas_auth_and_reset(pasid);
if (ret) {
ath12k_err(ab, "failed to boot the remote processor %d\n", ret);
goto err_fw2;
@ -485,10 +486,10 @@ static void ath12k_ahb_power_down(struct ath12k_base *ab, bool is_suspend)
pasid = (u32_encode_bits(ab_ahb->userpd_id, ATH12K_USERPD_ID_MASK)) |
ATH12K_AHB_UPD_SWID;
/* Release the firmware */
ret = qcom_scm_pas_shutdown(pasid);
ret = qcom_pas_shutdown(pasid);
if (ret)
ath12k_err(ab, "scm pas shutdown failed for userPD%d\n",
ab_ahb->userpd_id);
ath12k_err(ab, "PAS shutdown failed for userPD%d: %d\n",
ab_ahb->userpd_id, ret);
}
}

View File

@ -27,7 +27,7 @@
#define ATH12K_USERPD_SPAWN_TIMEOUT (5 * HZ)
#define ATH12K_USERPD_READY_TIMEOUT (10 * HZ)
#define ATH12K_USERPD_STOP_TIMEOUT (5 * HZ)
#define ATH12K_USERPD_ID_MASK GENMASK(9, 8)
#define ATH12K_USERPD_ID_MASK GENMASK(10, 8)
#define ATH12K_USERPD_FW_NAME_LEN 35
enum ath12k_ahb_smp2p_msg_id {

View File

@ -49,7 +49,7 @@ ath12k_mem_profile_based_param ath12k_mem_profile_based_param[] = {
.dp_params = {
.tx_comp_ring_size = 32768,
.rxdma_monitor_buf_ring_size = 4096,
.rxdma_monitor_dst_ring_size = 8092,
.rxdma_monitor_dst_ring_size = 8192,
.num_pool_tx_desc = 32768,
.rx_desc_count = 12288,
},
@ -637,31 +637,6 @@ u32 ath12k_core_get_max_peers_per_radio(struct ath12k_base *ab)
}
EXPORT_SYMBOL(ath12k_core_get_max_peers_per_radio);
struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab,
int index)
{
struct device *dev = ab->dev;
struct reserved_mem *rmem;
struct device_node *node;
node = of_parse_phandle(dev->of_node, "memory-region", index);
if (!node) {
ath12k_dbg(ab, ATH12K_DBG_BOOT,
"failed to parse memory-region for index %d\n", index);
return NULL;
}
rmem = of_reserved_mem_lookup(node);
of_node_put(node);
if (!rmem) {
ath12k_dbg(ab, ATH12K_DBG_BOOT,
"unable to get memory-region for index %d\n", index);
return NULL;
}
return rmem;
}
static inline
void ath12k_core_to_group_ref_get(struct ath12k_base *ab)
{
@ -708,8 +683,10 @@ static void ath12k_core_stop(struct ath12k_base *ab)
ath12k_core_to_group_ref_put(ab);
if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags))
if (!test_bit(ATH12K_FLAG_CRASH_FLUSH, &ab->dev_flags)) {
ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab));
ath12k_qmi_firmware_stop(ab);
}
ath12k_acpi_stop(ab);
@ -1371,6 +1348,7 @@ int ath12k_core_qmi_firmware_ready(struct ath12k_base *ab)
goto exit;
err_deinit:
ath12k_dp_reoq_lut_addr_reset(ath12k_ab_to_dp(ab));
ath12k_dp_cmn_device_deinit(ath12k_ab_to_dp(ab));
mutex_unlock(&ab->core_lock);
mutex_unlock(&ag->mutex);
@ -1524,7 +1502,7 @@ static void ath12k_core_pre_reconfigure_recovery(struct ath12k_base *ab)
complete_all(&ar->scan.completed);
complete(&ar->scan.on_channel);
complete(&ar->peer_assoc_done);
complete(&ar->peer_delete_done);
ath12k_peer_delete_wait_flush(ar);
complete(&ar->install_key_done);
complete(&ar->vdev_setup_done);
complete(&ar->vdev_delete_done);

View File

@ -665,7 +665,8 @@ struct ath12k {
/* protects the radio specific data like debug stats, ppdu_stats_info stats,
* vdev_stop_status info, scan data, ath12k_sta info, ath12k_link_vif info,
* channel context data, survey info, test mode data, regd_channel_update_queue.
* channel context data, test mode data, regd_channel_update_queue,
* peer_delete_waits.
*/
spinlock_t data_lock;
@ -687,7 +688,7 @@ struct ath12k {
u8 radio_idx;
struct completion peer_assoc_done;
struct completion peer_delete_done;
struct list_head peer_delete_waits;
int install_key_status;
struct completion install_key_done;
@ -721,7 +722,6 @@ struct ath12k {
* avoid reporting garbage data.
*/
bool ch_info_can_report_survey;
struct survey_info survey[ATH12K_NUM_CHANS];
struct completion bss_survey_done;
struct work_struct regd_update_work;
@ -791,6 +791,11 @@ struct ath12k_hw {
*/
struct mutex hw_mutex;
enum ath12k_hw_state state;
/* protects survey[] shared across radios of this hw. */
spinlock_t survey_lock;
struct survey_info survey[ATH12K_NUM_CHANS];
bool regd_updated;
bool use_6ghz_regd;
@ -1294,8 +1299,6 @@ void ath12k_fw_stats_init(struct ath12k *ar);
void ath12k_fw_stats_bcn_free(struct list_head *head);
void ath12k_fw_stats_free(struct ath12k_fw_stats *stats);
void ath12k_fw_stats_reset(struct ath12k *ar);
struct reserved_mem *ath12k_core_get_reserved_mem(struct ath12k_base *ab,
int index);
enum ath12k_qmi_mem_mode ath12k_core_get_memory_mode(struct ath12k_base *ab);
static inline const char *ath12k_scan_state_str(enum ath12k_scan_state state)

View File

@ -1031,6 +1031,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file,
struct ath12k_device_dp_stats *device_stats = &dp->device_stats;
int len = 0, i, j, ret;
struct ath12k *ar;
u32 center_freq;
const int size = 4096;
static const char *rxdma_err[HAL_REO_ENTR_RING_RXDMA_ECODE_MAX] = {
[HAL_REO_ENTR_RING_RXDMA_ECODE_OVERFLOW_ERR] = "Overflow",
@ -1082,6 +1083,9 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file,
if (!buf)
return -ENOMEM;
len += scnprintf(buf + len, size - len,
"DEVICE DP STATS (timestamp: %lldms):\n\n",
ktime_to_ms(ktime_get()));
len += scnprintf(buf + len, size - len, "DEVICE RX STATS:\n\n");
len += scnprintf(buf + len, size - len, "err ring pkts: %u\n",
device_stats->err_ring_pkts);
@ -1161,6 +1165,12 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file,
for (i = 0; i < ab->num_radios; i++) {
ar = ath12k_mac_get_ar_by_pdev_id(ab, DP_SW2HW_MACID(i));
if (ar) {
spin_lock_bh(&ar->data_lock);
center_freq = ar->rx_channel ? ar->rx_channel->center_freq : 0;
spin_unlock_bh(&ar->data_lock);
len += scnprintf(buf + len, size - len,
"\nradio%d center_freq: %u\n",
i, center_freq);
len += scnprintf(buf + len, size - len,
"\nradio%d tx_pending: %u\n", i,
atomic_read(&ar->dp.num_tx_pending));
@ -1173,7 +1183,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file,
for (i = 0; i < DP_REO_DST_RING_MAX; i++) {
len += scnprintf(buf + len, size - len, "Ring%d:", i + 1);
for (j = 0; j < ATH12K_MAX_DEVICES; j++) {
for (j = 0; j < ab->ag->num_devices; j++) {
len += scnprintf(buf + len, size - len,
"\t%d:%u", j,
device_stats->reo_rx[i][j]);
@ -1190,7 +1200,7 @@ static ssize_t ath12k_debugfs_dump_device_dp_stats(struct file *file,
for (i = 0; i < HAL_WBM_REL_SRC_MODULE_MAX; i++) {
len += scnprintf(buf + len, size - len, "%s:", wbm_rel_src[i]);
for (j = 0; j < ATH12K_MAX_DEVICES; j++) {
for (j = 0; j < ab->ag->num_devices; j++) {
len += scnprintf(buf + len,
size - len,
"\t%d:%u", j,

View File

@ -1097,7 +1097,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab)
return;
if (dp->reoq_lut.vaddr_unaligned) {
ath12k_hal_write_reoq_lut_addr(ab, 0);
dma_free_coherent(ab->dev, dp->reoq_lut.size,
dp->reoq_lut.vaddr_unaligned,
dp->reoq_lut.paddr_unaligned);
@ -1105,7 +1104,6 @@ static void ath12k_dp_reoq_lut_cleanup(struct ath12k_base *ab)
}
if (dp->ml_reoq_lut.vaddr_unaligned) {
ath12k_hal_write_ml_reoq_lut_addr(ab, 0);
dma_free_coherent(ab->dev, dp->ml_reoq_lut.size,
dp->ml_reoq_lut.vaddr_unaligned,
dp->ml_reoq_lut.paddr_unaligned);
@ -1568,6 +1566,7 @@ static int ath12k_dp_setup(struct ath12k_base *ab)
ath12k_dp_rx_free(ab);
fail_cmn_reoq_cleanup:
ath12k_dp_reoq_lut_addr_reset(dp);
ath12k_dp_reoq_lut_cleanup(ab);
fail_cmn_srng_cleanup:
@ -1627,3 +1626,14 @@ void ath12k_dp_cmn_hw_group_assign(struct ath12k_dp *dp,
dp->device_id = ab->device_id;
dp_hw_grp->dp[dp->device_id] = dp;
}
void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp)
{
struct ath12k_base *ab = dp->ab;
if (dp->reoq_lut.vaddr_unaligned)
ath12k_hal_write_reoq_lut_addr(ab, 0);
if (dp->ml_reoq_lut.vaddr_unaligned)
ath12k_hal_write_ml_reoq_lut_addr(ab, 0);
}

View File

@ -205,7 +205,7 @@ struct ath12k_pdev_dp {
#define DP_REO_CMD_RING_SIZE 256
#define DP_REO_STATUS_RING_SIZE 2048
#define DP_RXDMA_BUF_RING_SIZE 4096
#define DP_RX_MAC_BUF_RING_SIZE 2048
#define DP_RX_MAC_BUF_RING_SIZE 4096
#define DP_RXDMA_REFILL_RING_SIZE 2048
#define DP_RXDMA_ERR_DST_RING_SIZE 1024
#define DP_RXDMA_MON_STATUS_RING_SIZE 1024
@ -538,7 +538,7 @@ struct ath12k_dp {
/* Lock for protection of peers and rhead_peer_addr */
spinlock_t dp_lock;
struct ath12k_dp_arch_ops *ops;
const struct ath12k_dp_arch_ops *ops;
/* Linked list of struct ath12k_dp_link_peer */
struct list_head peers;
@ -701,4 +701,5 @@ struct ath12k_rx_desc_info *ath12k_dp_get_rx_desc(struct ath12k_dp *dp,
u32 cookie);
struct ath12k_tx_desc_info *ath12k_dp_get_tx_desc(struct ath12k_dp *dp,
u32 desc_id);
void ath12k_dp_reoq_lut_addr_reset(struct ath12k_dp *dp);
#endif

View File

@ -493,12 +493,8 @@ EXPORT_SYMBOL(ath12k_dp_mon_update_radiotap);
void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
struct napi_struct *napi,
struct sk_buff *msdu,
const struct hal_rx_mon_ppdu_info *ppduinfo,
struct ieee80211_rx_status *status,
u8 decap)
struct ieee80211_rx_status *status)
{
struct ath12k_dp *dp = dp_pdev->dp;
struct ath12k_base *ab = dp->ab;
static const struct ieee80211_radiotap_he known = {
.data1 = cpu_to_le16(IEEE80211_RADIOTAP_HE_DATA1_DATA_MCS_KNOWN |
IEEE80211_RADIOTAP_HE_DATA1_BW_RU_ALLOC_KNOWN),
@ -506,14 +502,6 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
};
struct ieee80211_rx_status *rx_status;
struct ieee80211_radiotap_he *he = NULL;
struct ieee80211_sta *pubsta = NULL;
struct ath12k_dp_link_peer *peer;
struct ath12k_skb_rxcb *rxcb = ATH12K_SKB_RXCB(msdu);
struct hal_rx_desc_data rx_info;
bool is_mcbc = rxcb->is_mcbc;
bool is_eapol_tkip = rxcb->is_eapol;
struct hal_rx_desc *rx_desc = (struct hal_rx_desc *)msdu->data;
u8 addr[ETH_ALEN] = {};
status->link_valid = 0;
@ -524,64 +512,10 @@ void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
status->flag |= RX_FLAG_RADIOTAP_HE;
}
ath12k_dp_extract_rx_desc_data(dp->hal, &rx_info, rx_desc, rx_desc);
rcu_read_lock();
spin_lock_bh(&dp->dp_lock);
peer = ath12k_dp_rx_h_find_link_peer(dp_pdev, msdu, &rx_info);
if (peer && peer->sta) {
pubsta = peer->sta;
memcpy(addr, peer->addr, ETH_ALEN);
if (pubsta->valid_links) {
status->link_valid = 1;
status->link_id = peer->link_id;
}
}
spin_unlock_bh(&dp->dp_lock);
rcu_read_unlock();
ath12k_dbg(ab, ATH12K_DBG_DATA,
"rx skb %p len %u peer %pM %u %s %s%s%s%s%s%s%s%s %srate_idx %u vht_nss %u freq %u band %u flag 0x%x fcs-err %i mic-err %i amsdu-more %i\n",
msdu,
msdu->len,
addr,
rxcb->tid,
(is_mcbc) ? "mcast" : "ucast",
(status->encoding == RX_ENC_LEGACY) ? "legacy" : "",
(status->encoding == RX_ENC_HT) ? "ht" : "",
(status->encoding == RX_ENC_VHT) ? "vht" : "",
(status->encoding == RX_ENC_HE) ? "he" : "",
(status->bw == RATE_INFO_BW_40) ? "40" : "",
(status->bw == RATE_INFO_BW_80) ? "80" : "",
(status->bw == RATE_INFO_BW_160) ? "160" : "",
(status->bw == RATE_INFO_BW_320) ? "320" : "",
status->enc_flags & RX_ENC_FLAG_SHORT_GI ? "sgi " : "",
status->rate_idx,
status->nss,
status->freq,
status->band, status->flag,
!!(status->flag & RX_FLAG_FAILED_FCS_CRC),
!!(status->flag & RX_FLAG_MMIC_ERROR),
!!(status->flag & RX_FLAG_AMSDU_MORE));
ath12k_dbg_dump(ab, ATH12K_DBG_DP_RX, NULL, "dp rx msdu: ",
msdu->data, msdu->len);
rx_status = IEEE80211_SKB_RXCB(msdu);
*rx_status = *status;
/* TODO: trace rx packet */
/* PN for multicast packets are not validate in HW,
* so skip 802.3 rx path
* Also, fast_rx expects the STA to be authorized, hence
* eapol packets are sent in slow path.
*/
if (decap == DP_RX_DECAP_TYPE_ETHERNET2_DIX && !is_eapol_tkip &&
!(is_mcbc && rx_status->flag & RX_FLAG_DECRYPTED))
rx_status->flag |= RX_FLAG_8023;
ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), pubsta, msdu, napi);
ieee80211_rx_napi(ath12k_pdev_dp_to_hw(dp_pdev), NULL, msdu, napi);
}
EXPORT_SYMBOL(ath12k_dp_mon_rx_deliver_msdu);

View File

@ -112,9 +112,7 @@ void ath12k_dp_mon_update_radiotap(struct ath12k_pdev_dp *dp_pdev,
void ath12k_dp_mon_rx_deliver_msdu(struct ath12k_pdev_dp *dp_pdev,
struct napi_struct *napi,
struct sk_buff *msdu,
const struct hal_rx_mon_ppdu_info *ppduinfo,
struct ieee80211_rx_status *status,
u8 decap);
struct ieee80211_rx_status *status);
struct sk_buff *
ath12k_dp_mon_rx_merg_msdus(struct ath12k_pdev_dp *dp_pdev,
struct dp_mon_mpdu *mon_mpdu,

View File

@ -828,8 +828,8 @@ void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len)
{
struct hal_tlv_64_hdr *tlv64 = tlv;
tlv64->tl = le64_encode_bits(tag, HAL_TLV_HDR_TAG) |
le64_encode_bits(len, HAL_TLV_HDR_LEN);
tlv64->tl = le64_encode_bits(tag, HAL_TLV_64_HDR_TAG) |
le64_encode_bits(len, HAL_TLV_64_HDR_LEN);
return tlv64->value;
}
@ -846,26 +846,44 @@ void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len)
}
EXPORT_SYMBOL(ath12k_hal_encode_tlv32_hdr);
u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc)
void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid)
{
struct hal_tlv_64_hdr *tlv64 = tlv;
u16 tag;
tag = le64_get_bits(tlv64->tl, HAL_SRNG_TLV_HDR_TAG);
*desc = tlv64->value;
if (tag)
*tag = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_TAG);
if (len)
*len = le64_get_bits(tlv64->tl, HAL_TLV_64_HDR_LEN);
if (usrid)
*usrid = le64_get_bits(tlv64->tl, HAL_TLV_64_USR_ID);
return tag;
return tlv64->value;
}
EXPORT_SYMBOL(ath12k_hal_decode_tlv64_hdr);
u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc)
void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid)
{
struct hal_tlv_hdr *tlv32 = tlv;
u16 tag;
tag = le32_get_bits(tlv32->tl, HAL_SRNG_TLV_HDR_TAG);
*desc = tlv32->value;
if (tag)
*tag = le32_get_bits(tlv32->tl, HAL_TLV_HDR_TAG);
if (len)
*len = le32_get_bits(tlv32->tl, HAL_TLV_HDR_LEN);
if (usrid)
*usrid = le32_get_bits(tlv32->tl, HAL_TLV_USR_ID);
return tag;
return tlv32->value;
}
EXPORT_SYMBOL(ath12k_hal_decode_tlv32_hdr);
u32 ath12k_hal_get_tlv64_hdr_align(void)
{
return HAL_TLV_64_ALIGN;
}
EXPORT_SYMBOL(ath12k_hal_get_tlv64_hdr_align);
u32 ath12k_hal_get_tlv32_hdr_align(void)
{
return HAL_TLV_ALIGN;
}
EXPORT_SYMBOL(ath12k_hal_get_tlv32_hdr_align);

View File

@ -1024,7 +1024,7 @@ enum hal_wbm_rel_desc_type {
/* Interrupt mitigation - timer threshold in us */
#define HAL_SRNG_INT_TIMER_THRESHOLD_TX 1000
#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 500
#define HAL_SRNG_INT_TIMER_THRESHOLD_RX 200
#define HAL_SRNG_INT_TIMER_THRESHOLD_OTHER 256
enum hal_srng_mac_type {
@ -1441,10 +1441,12 @@ struct hal_ops {
u8 *rbm, u32 *msdu_cnt);
void *(*reo_cmd_enc_tlv_hdr)(void *tlv, u64 tag, u64 len);
u16 (*reo_status_dec_tlv_hdr)(void *tlv, void **desc);
void *(*mon_rx_status_dec_tlv_hdr)(void *tlv, u16 *tag, u16 *len, u16 *usrid);
u32 (*get_tlv_hdr_align)(void);
};
#define HAL_TLV_HDR_TAG GENMASK(9, 1)
#define HAL_TLV_HDR_LEN GENMASK(25, 10)
#define HAL_TLV_HDR_LEN GENMASK(21, 10)
#define HAL_TLV_USR_ID GENMASK(31, 26)
#define HAL_TLV_ALIGN 4
@ -1464,9 +1466,6 @@ struct hal_tlv_64_hdr {
u8 value[];
} __packed;
#define HAL_SRNG_TLV_HDR_TAG GENMASK(9, 1)
#define HAL_SRNG_TLV_HDR_LEN GENMASK(25, 10)
dma_addr_t ath12k_hal_srng_get_tp_addr(struct ath12k_base *ab,
struct hal_srng *srng);
dma_addr_t ath12k_hal_srng_get_hp_addr(struct ath12k_base *ab,
@ -1556,6 +1555,8 @@ void ath12k_hal_rx_reo_ent_buf_paddr_get(struct ath12k_hal *hal, void *rx_desc,
u8 *rbm, u32 *msdu_cnt);
void *ath12k_hal_encode_tlv64_hdr(void *tlv, u64 tag, u64 len);
void *ath12k_hal_encode_tlv32_hdr(void *tlv, u64 tag, u64 len);
u16 ath12k_hal_decode_tlv64_hdr(void *tlv, void **desc);
u16 ath12k_hal_decode_tlv32_hdr(void *tlv, void **desc);
void *ath12k_hal_decode_tlv64_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid);
void *ath12k_hal_decode_tlv32_hdr(void *tlv, u16 *tag, u16 *len, u16 *usrid);
u32 ath12k_hal_get_tlv64_hdr_align(void);
u32 ath12k_hal_get_tlv32_hdr_align(void);
#endif

View File

@ -196,6 +196,7 @@ struct ath12k_hw_params {
bool supports_shadow_regs:1;
bool supports_aspm:1;
bool current_cc_support:1;
bool supports_cong_ctrl_max_msdus:1;
u32 num_tcl_banks;
u32 max_tx_ring;

View File

@ -3989,6 +3989,17 @@ static void ath12k_bss_assoc(struct ath12k *ar,
ath12k_warn(ar->ab, "failed to set vdev %i OBSS PD parameters: %d\n",
arvif->vdev_id, ret);
if (ar->ab->hw_params->supports_sta_ps &&
ahvif->vdev_type == WMI_VDEV_TYPE_STA &&
ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE) {
ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id,
WMI_VDEV_PARAM_DTIM_POLICY,
WMI_DTIM_POLICY_STICK);
if (ret)
ath12k_warn(ar->ab, "failed to set vdev %d stick DTIM policy: %d\n",
arvif->vdev_id, ret);
}
if (test_bit(WMI_TLV_SERVICE_11D_OFFLOAD, ar->ab->wmi_ab.svc_map) &&
ahvif->vdev_type == WMI_VDEV_TYPE_STA &&
ahvif->vdev_subtype == WMI_VDEV_SUBTYPE_NONE)
@ -9726,6 +9737,19 @@ static int ath12k_mac_start(struct ath12k *ar)
goto err;
}
if (ab->hw_params->supports_cong_ctrl_max_msdus) {
ret = ath12k_wmi_pdev_set_param(ar,
WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS,
ATH12K_NUM_POOL_TX_DESC(ab),
pdev->pdev_id);
if (ret) {
ath12k_err(ab,
"failed to set congestion control MAX MSDUS: %d\n",
ret);
goto err;
}
}
__ath12k_set_antenna(ar, ar->cfg_tx_chainmask, ar->cfg_rx_chainmask);
/* TODO: Do we need to enable ANI? */
@ -10121,16 +10145,16 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif)
if (vif->type != NL80211_IFTYPE_STATION &&
vif->type != NL80211_IFTYPE_AP)
vif->offload_flags &= ~(IEEE80211_OFFLOAD_ENCAP_ENABLED |
IEEE80211_OFFLOAD_DECAP_ENABLED);
IEEE80211_OFFLOAD_DECAP_ENABLED |
IEEE80211_OFFLOAD_ENCAP_MCAST |
IEEE80211_OFFLOAD_ENCAP_4ADDR);
if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED) {
if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED)
ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_ETHERNET;
vif->offload_flags |= IEEE80211_OFFLOAD_ENCAP_4ADDR;
} else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags)) {
else if (test_bit(ATH12K_FLAG_RAW_MODE, &ab->dev_flags))
ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_RAW;
} else {
else
ahvif->dp_vif.tx_encap_type = ATH12K_HW_TXRX_NATIVE_WIFI;
}
ret = ath12k_wmi_vdev_set_param_cmd(ar, arvif->vdev_id,
param_id, ahvif->dp_vif.tx_encap_type);
@ -10140,6 +10164,10 @@ static void ath12k_mac_update_vif_offload(struct ath12k_link_vif *arvif)
vif->offload_flags &= ~IEEE80211_OFFLOAD_ENCAP_ENABLED;
}
if (vif->offload_flags & IEEE80211_OFFLOAD_ENCAP_ENABLED)
vif->offload_flags |= (IEEE80211_OFFLOAD_ENCAP_MCAST |
IEEE80211_OFFLOAD_ENCAP_4ADDR);
param_id = WMI_VDEV_PARAM_RX_DECAP_TYPE;
if (vif->offload_flags & IEEE80211_OFFLOAD_DECAP_ENABLED)
param_value = ATH12K_HW_TXRX_ETHERNET;
@ -10568,22 +10596,8 @@ int ath12k_mac_vdev_create(struct ath12k *ar, struct ath12k_link_vif *arvif)
err_peer_del:
if (ahvif->vdev_type == WMI_VDEV_TYPE_AP) {
reinit_completion(&ar->peer_delete_done);
ret = ath12k_wmi_send_peer_delete_cmd(ar, arvif->bssid,
arvif->vdev_id);
if (ret) {
ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n",
arvif->vdev_id, arvif->bssid);
goto err_dp_peer_del;
}
ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id,
arvif->bssid);
if (ret)
goto err_dp_peer_del;
ar->num_peers--;
/* ignore return value: propagate the original error */
ath12k_peer_delete(ar, arvif->vdev_id, arvif->bssid);
}
err_dp_peer_del:
@ -11257,6 +11271,8 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif,
ml_arg->assoc_link = arvif->is_sta_assoc_link;
ml_arg->ieee_link_id = arvif->link_id;
partner_info = ml_arg->partner_info;
links = ahvif->links_map;
@ -11280,6 +11296,7 @@ ath12k_mac_mlo_get_vdev_args(struct ath12k_link_vif *arvif,
partner_info->vdev_id = arvif_p->vdev_id;
partner_info->hw_link_id = arvif_p->ar->pdev->hw_link_id;
partner_info->ieee_link_id = arvif_p->link_id;
ether_addr_copy(partner_info->addr, link_conf->addr);
ml_arg->num_partner_links++;
partner_info++;
@ -13585,52 +13602,54 @@ ath12k_mac_update_bss_chan_survey(struct ath12k *ar,
int ath12k_mac_op_get_survey(struct ieee80211_hw *hw, int idx,
struct survey_info *survey)
{
struct ath12k_hw *ah = hw->priv;
struct ath12k *ar;
struct ieee80211_supported_band *sband;
struct survey_info *ar_survey;
struct survey_info *ah_survey;
int sband_idx = idx;
lockdep_assert_wiphy(hw->wiphy);
if (idx >= ATH12K_NUM_CHANS)
if (sband_idx >= ATH12K_NUM_CHANS)
return -ENOENT;
sband = hw->wiphy->bands[NL80211_BAND_2GHZ];
if (sband && idx >= sband->n_channels) {
idx -= sband->n_channels;
if (sband && sband_idx >= sband->n_channels) {
sband_idx -= sband->n_channels;
sband = NULL;
}
if (!sband)
sband = hw->wiphy->bands[NL80211_BAND_5GHZ];
if (sband && idx >= sband->n_channels) {
idx -= sband->n_channels;
if (sband && sband_idx >= sband->n_channels) {
sband_idx -= sband->n_channels;
sband = NULL;
}
if (!sband)
sband = hw->wiphy->bands[NL80211_BAND_6GHZ];
if (!sband || idx >= sband->n_channels)
if (!sband || sband_idx >= sband->n_channels)
return -ENOENT;
ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[idx]);
ar = ath12k_mac_get_ar_by_chan(hw, &sband->channels[sband_idx]);
if (!ar) {
if (sband->channels[idx].flags & IEEE80211_CHAN_DISABLED) {
if (sband->channels[sband_idx].flags & IEEE80211_CHAN_DISABLED) {
memset(survey, 0, sizeof(*survey));
return 0;
}
return -ENOENT;
}
ar_survey = &ar->survey[idx];
ah_survey = &ah->survey[idx];
ath12k_mac_update_bss_chan_survey(ar, &sband->channels[idx]);
ath12k_mac_update_bss_chan_survey(ar, &sband->channels[sband_idx]);
spin_lock_bh(&ar->data_lock);
memcpy(survey, ar_survey, sizeof(*survey));
spin_unlock_bh(&ar->data_lock);
scoped_guard(spinlock_bh, &ah->survey_lock) {
memcpy(survey, ah_survey, sizeof(*survey));
}
survey->channel = &sband->channels[idx];
survey->channel = &sband->channels[sband_idx];
if (ar->rx_channel == survey->channel)
survey->filled |= SURVEY_INFO_IN_USE;
@ -14875,12 +14894,6 @@ static int ath12k_mac_hw_register(struct ath12k_hw *ah)
wiphy->features |= NL80211_FEATURE_TX_POWER_INSERTION;
/* MLO is not yet supported so disable Wireless Extensions for now
* to make sure ath12k users don't use it. This flag can be removed
* once WIPHY_FLAG_SUPPORTS_MLO is enabled.
*/
wiphy->flags |= WIPHY_FLAG_DISABLE_WEXT;
/* Copy over MLO related capabilities received from
* WMI_SERVICE_READY_EXT2_EVENT if single_chip_mlo_supp is set.
*/
@ -15058,11 +15071,11 @@ static void ath12k_mac_setup(struct ath12k *ar)
spin_lock_init(&ar->dp.ppdu_list_lock);
INIT_LIST_HEAD(&ar->arvifs);
INIT_LIST_HEAD(&ar->dp.ppdu_stats_info);
INIT_LIST_HEAD(&ar->peer_delete_waits);
init_completion(&ar->vdev_setup_done);
init_completion(&ar->vdev_delete_done);
init_completion(&ar->peer_assoc_done);
init_completion(&ar->peer_delete_done);
init_completion(&ar->install_key_done);
init_completion(&ar->bss_survey_done);
init_completion(&ar->scan.started);
@ -15311,6 +15324,7 @@ static struct ath12k_hw *ath12k_mac_hw_allocate(struct ath12k_hw_group *ag,
mutex_init(&ah->hw_mutex);
spin_lock_init(&ah->survey_lock);
spin_lock_init(&ah->dp_hw.peer_lock);
INIT_LIST_HEAD(&ah->dp_hw.dp_peers_list);

View File

@ -5,6 +5,7 @@
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/msi.h>
#include <linux/pci.h>
#include <linux/time.h>
@ -541,6 +542,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab)
int i, j, n, ret, num_vectors = 0;
u32 user_base_data = 0, base_vector = 0, base_idx;
struct ath12k_ext_irq_grp *irq_grp;
bool threaded_napi = false;
int irq;
base_idx = ATH12K_PCI_IRQ_CE0_OFFSET + CE_COUNT_MAX;
ret = ath12k_pci_get_user_msi_assignment(ab, "DP",
@ -550,6 +553,10 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab)
if (ret < 0)
return ret;
irq = ath12k_pci_get_msi_irq(ab->dev, base_vector);
if (irq >= 0)
threaded_napi = !irq_can_set_affinity(irq);
for (i = 0; i < ATH12K_EXT_IRQ_GRP_NUM_MAX; i++) {
irq_grp = &ab->ext_irq_grp[i];
u32 num_irq = 0;
@ -564,6 +571,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab)
netif_napi_add(irq_grp->napi_ndev, &irq_grp->napi,
ath12k_pci_ext_grp_napi_poll);
if (threaded_napi)
netif_threaded_enable(irq_grp->napi_ndev);
if (ab->hw_params->ring_mask->tx[i] ||
ab->hw_params->ring_mask->rx[i] ||
@ -582,7 +591,8 @@ static int ath12k_pci_ext_irq_config(struct ath12k_base *ab)
for (j = 0; j < irq_grp->num_irq; j++) {
int irq_idx = irq_grp->irqs[j];
int vector = (i % num_vectors) + base_vector;
int irq = ath12k_pci_get_msi_irq(ab->dev, vector);
irq = ath12k_pci_get_msi_irq(ab->dev, vector);
ab->irq_num[irq_idx] = irq;

View File

@ -9,6 +9,55 @@
#include "debug.h"
#include "debugfs.h"
static void ath12k_peer_delete_wait_register(struct ath12k *ar,
struct ath12k_peer_delete_wait *wait,
u32 vdev_id, const u8 *addr)
{
wait->vdev_id = vdev_id;
ether_addr_copy(wait->addr, addr);
init_completion(&wait->done);
spin_lock_bh(&ar->data_lock);
list_add(&wait->list, &ar->peer_delete_waits);
spin_unlock_bh(&ar->data_lock);
}
static void ath12k_peer_delete_wait_unregister(struct ath12k *ar,
struct ath12k_peer_delete_wait *wait)
{
spin_lock_bh(&ar->data_lock);
list_del(&wait->list);
spin_unlock_bh(&ar->data_lock);
}
void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr)
{
struct ath12k_peer_delete_wait *wait;
guard(spinlock_bh)(&ar->data_lock);
list_for_each_entry(wait, &ar->peer_delete_waits, list) {
if (wait->vdev_id == vdev_id &&
ether_addr_equal(wait->addr, addr)) {
complete(&wait->done);
return;
}
}
ath12k_warn(ar->ab, "failed to find link peer with vdev id %u addr %pM\n",
vdev_id, addr);
}
void ath12k_peer_delete_wait_flush(struct ath12k *ar)
{
struct ath12k_peer_delete_wait *wait;
spin_lock_bh(&ar->data_lock);
list_for_each_entry(wait, &ar->peer_delete_waits, list)
complete(&wait->done);
spin_unlock_bh(&ar->data_lock);
}
static int ath12k_wait_for_dp_link_peer_common(struct ath12k_base *ab, int vdev_id,
const u8 *addr, bool expect_mapped)
{
@ -62,20 +111,19 @@ static int ath12k_wait_for_peer_deleted(struct ath12k *ar, int vdev_id, const u8
return ath12k_wait_for_dp_link_peer_common(ar->ab, vdev_id, addr, false);
}
int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id,
const u8 *addr)
int ath12k_wait_for_peer_delete_done(struct ath12k *ar,
struct ath12k_peer_delete_wait *wait)
{
int ret;
unsigned long time_left;
int ret;
ret = ath12k_wait_for_peer_deleted(ar, vdev_id, addr);
ret = ath12k_wait_for_peer_deleted(ar, wait->vdev_id, wait->addr);
if (ret) {
ath12k_warn(ar->ab, "failed wait for peer deleted");
ath12k_warn(ar->ab, "failed wait for peer deleted\n");
return ret;
}
time_left = wait_for_completion_timeout(&ar->peer_delete_done,
3 * HZ);
time_left = wait_for_completion_timeout(&wait->done, 3 * HZ);
if (time_left == 0) {
ath12k_warn(ar->ab, "Timeout in receiving peer delete response\n");
return -ETIMEDOUT;
@ -91,8 +139,6 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add
lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
reinit_completion(&ar->peer_delete_done);
ret = ath12k_wmi_send_peer_delete_cmd(ar, addr, vdev_id);
if (ret) {
ath12k_warn(ab,
@ -106,6 +152,7 @@ static int ath12k_peer_delete_send(struct ath12k *ar, u32 vdev_id, const u8 *add
int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr)
{
struct ath12k_peer_delete_wait wait;
int ret;
lockdep_assert_wiphy(ath12k_ar_to_hw(ar)->wiphy);
@ -114,17 +161,25 @@ int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr)
&(ath12k_ar_to_ah(ar)->dp_hw), vdev_id,
addr, ar->hw_link_id);
/*
* Register the stack waiter before sending so the resp_event for
* this peer cannot arrive while no waiter is queued.
*/
ath12k_peer_delete_wait_register(ar, &wait, vdev_id, addr);
ret = ath12k_peer_delete_send(ar, vdev_id, addr);
if (ret)
return ret;
goto out;
ret = ath12k_wait_for_peer_delete_done(ar, vdev_id, addr);
ret = ath12k_wait_for_peer_delete_done(ar, &wait);
if (ret)
return ret;
goto out;
ar->num_peers--;
return 0;
out:
ath12k_peer_delete_wait_unregister(ar, &wait);
return ret;
}
static int ath12k_wait_for_peer_created(struct ath12k *ar, int vdev_id, const u8 *addr)
@ -184,22 +239,26 @@ int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif,
peer = ath12k_dp_link_peer_find_by_vdev_and_addr(dp, arg->vdev_id,
arg->peer_addr);
if (!peer) {
struct ath12k_peer_delete_wait wait;
spin_unlock_bh(&dp->dp_lock);
ath12k_warn(ar->ab, "failed to find peer %pM on vdev %i after creation\n",
arg->peer_addr, arg->vdev_id);
reinit_completion(&ar->peer_delete_done);
ath12k_peer_delete_wait_register(ar, &wait, arg->vdev_id,
arg->peer_addr);
ret = ath12k_wmi_send_peer_delete_cmd(ar, arg->peer_addr,
arg->vdev_id);
if (ret) {
ath12k_warn(ar->ab, "failed to delete peer vdev_id %d addr %pM\n",
arg->vdev_id, arg->peer_addr);
ath12k_peer_delete_wait_unregister(ar, &wait);
return ret;
}
ret = ath12k_wait_for_peer_delete_done(ar, arg->vdev_id,
arg->peer_addr);
ret = ath12k_wait_for_peer_delete_done(ar, &wait);
ath12k_peer_delete_wait_unregister(ar, &wait);
if (ret)
return ret;
@ -283,13 +342,14 @@ u16 ath12k_peer_ml_alloc(struct ath12k_hw *ah)
int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta)
{
DECLARE_BITMAP(registered, IEEE80211_MLD_MAX_NUM_LINKS);
struct ieee80211_sta *sta = ath12k_ahsta_to_sta(ahsta);
struct ath12k_hw *ah = ahvif->ah;
struct ath12k_link_vif *arvif;
struct ath12k_link_sta *arsta;
int ret, err_ret = 0;
unsigned long links;
struct ath12k *ar;
int ret, err_ret = 0;
u8 link_id;
lockdep_assert_wiphy(ah->hw->wiphy);
@ -297,8 +357,19 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
if (!sta->mlo)
return -EINVAL;
/* FW expects delete of all link peers at once before waiting for reception
* of peer unmap or delete responses
struct ath12k_peer_delete_wait *waits __free(kfree) =
kzalloc_objs(*waits, IEEE80211_MLD_MAX_NUM_LINKS);
if (!waits)
return -ENOMEM;
bitmap_zero(registered, IEEE80211_MLD_MAX_NUM_LINKS);
/*
* Firmware expects delete of all link peers at once before waiting
* for reception of peer unmap or delete responses. Phase 1 registers
* a per-link stack waiter and sends WMI peer delete for every
* link; the resp_event handler matches each response to its
* (vdev_id, addr) waiter on ar->peer_delete_waits.
*/
links = ahsta->links_map;
for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
@ -318,29 +389,36 @@ int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_st
arvif->vdev_id, arsta->addr,
ar->hw_link_id);
ath12k_peer_delete_wait_register(ar, &waits[link_id],
arvif->vdev_id, arsta->addr);
ret = ath12k_peer_delete_send(ar, arvif->vdev_id, arsta->addr);
if (ret) {
ath12k_warn(ar->ab,
"failed to delete peer vdev_id %d addr %pM ret %d\n",
arvif->vdev_id, arsta->addr, ret);
err_ret = ret;
ath12k_peer_delete_wait_unregister(ar, &waits[link_id]);
continue;
}
set_bit(link_id, registered);
}
/* Ensure all link peers are deleted and unmapped */
/*
* Phase 2: wait for unmap + delete_resp on each registered link
* and tear down the waiter.
*/
links = ahsta->links_map;
for_each_set_bit(link_id, &links, IEEE80211_MLD_MAX_NUM_LINKS) {
if (!test_bit(link_id, registered))
continue;
arvif = wiphy_dereference(ah->hw->wiphy, ahvif->link[link_id]);
arsta = wiphy_dereference(ah->hw->wiphy, ahsta->link[link_id]);
if (!arvif || !arsta)
continue;
ar = arvif->ar;
if (!ar)
continue;
ret = ath12k_wait_for_peer_delete_done(ar, arvif->vdev_id, arsta->addr);
ret = ath12k_wait_for_peer_delete_done(ar, &waits[link_id]);
ath12k_peer_delete_wait_unregister(ar, &waits[link_id]);
if (ret) {
err_ret = ret;
continue;

View File

@ -9,13 +9,23 @@
#include "dp_peer.h"
struct ath12k_peer_delete_wait {
struct list_head list;
u32 vdev_id;
u8 addr[ETH_ALEN];
struct completion done;
};
void ath12k_peer_delete_resp_signal(struct ath12k *ar, u32 vdev_id, const u8 *addr);
void ath12k_peer_delete_wait_flush(struct ath12k *ar);
void ath12k_peer_cleanup(struct ath12k *ar, u32 vdev_id);
int ath12k_peer_delete(struct ath12k *ar, u32 vdev_id, u8 *addr);
int ath12k_peer_create(struct ath12k *ar, struct ath12k_link_vif *arvif,
struct ieee80211_sta *sta,
struct ath12k_wmi_peer_create_arg *arg);
int ath12k_wait_for_peer_delete_done(struct ath12k *ar, u32 vdev_id,
const u8 *addr);
int ath12k_wait_for_peer_delete_done(struct ath12k *ar,
struct ath12k_peer_delete_wait *wait);
int ath12k_peer_mlo_link_peers_delete(struct ath12k_vif *ahvif, struct ath12k_sta *ahsta);
struct ath12k_ml_peer *ath12k_peer_ml_find(struct ath12k_hw *ah,
const u8 *addr);

View File

@ -13,6 +13,7 @@
#include <linux/firmware.h>
#include <linux/of_address.h>
#include <linux/ioport.h>
#include <linux/of_reserved_mem.h>
#define SLEEP_CLOCK_SELECT_INTERNAL_BIT 0x02
#define HOST_CSTATE_BIT 0x04
@ -21,45 +22,45 @@
static const struct qmi_elem_info wlfw_host_mlo_chip_info_s_v01_ei[] = {
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
chip_id),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
num_local_links),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01,
.elem_size = sizeof(u8),
.array_type = STATIC_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01,
.elem_size = sizeof(u8),
.array_type = STATIC_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
hw_link_id),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01,
.elem_size = sizeof(u8),
.array_type = STATIC_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = QMI_WLFW_MAX_NUM_MLO_LINKS_PER_CHIP_V01,
.elem_size = sizeof(u8),
.array_type = STATIC_ARRAY,
.tlv_type = 0,
.offset = offsetof(struct wlfw_host_mlo_chip_info_s_v01,
valid_mlo_link_id),
},
{
.data_type = QMI_EOTI,
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
@ -506,6 +507,24 @@ static const struct qmi_elem_info qmi_wlanfw_host_cap_req_msg_v01_ei[] = {
.offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01,
feature_list),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x33,
.offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01,
dynamic_mem_support_valid),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x33,
.offset = offsetof(struct qmi_wlanfw_host_cap_req_msg_v01,
dynamic_mem_support),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
@ -585,23 +604,41 @@ static const struct qmi_elem_info qmi_wlanfw_phy_cap_resp_msg_v01_ei[] = {
board_id),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01,
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01,
single_chip_mlo_support_valid),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01,
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01,
single_chip_mlo_support),
},
{
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x17,
.offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01,
dynamic_ddr_support_valid),
},
{
.data_type = QMI_UNSIGNED_1_BYTE,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.tlv_type = 0x17,
.offset = offsetof(struct qmi_wlanfw_phy_cap_resp_msg_v01,
dynamic_ddr_support),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
@ -1625,42 +1662,45 @@ static const struct qmi_elem_info qmi_wlanfw_m3_info_resp_msg_v01_ei[] = {
static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_req_msg_v01_ei[] = {
{
.data_type = QMI_UNSIGNED_8_BYTE,
.elem_len = 1,
.elem_size = sizeof(u64),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, addr),
.data_type = QMI_UNSIGNED_8_BYTE,
.elem_len = 1,
.elem_size = sizeof(u64),
.array_type = NO_ARRAY,
.tlv_type = 0x01,
.offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01,
addr),
},
{
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(u32),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01, size),
.data_type = QMI_UNSIGNED_4_BYTE,
.elem_len = 1,
.elem_size = sizeof(u32),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct qmi_wlanfw_aux_uc_info_req_msg_v01,
size),
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
static const struct qmi_elem_info qmi_wlanfw_aux_uc_info_resp_msg_v01_ei[] = {
{
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01, resp),
.ei_array = qmi_response_type_v01_ei,
.data_type = QMI_STRUCT,
.elem_len = 1,
.elem_size = sizeof(struct qmi_response_type_v01),
.array_type = NO_ARRAY,
.tlv_type = 0x02,
.offset = offsetof(struct qmi_wlanfw_aux_uc_info_resp_msg_v01,
resp),
.ei_array = qmi_response_type_v01_ei,
},
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
@ -1772,7 +1812,8 @@ static const struct qmi_elem_info qmi_wlanfw_shadow_reg_cfg_s_v01_ei[] = {
},
{
.data_type = QMI_EOTI,
.array_type = QMI_COMMON_TLV_TYPE,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
@ -1925,7 +1966,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = {
.data_type = QMI_OPT_FLAG,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.array_type = NO_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01,
shadow_reg_valid),
@ -1934,7 +1975,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = {
.data_type = QMI_DATA_LEN,
.elem_len = 1,
.elem_size = sizeof(u8),
.array_type = NO_ARRAY,
.array_type = NO_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01,
shadow_reg_len),
@ -1943,7 +1984,7 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_req_msg_v01_ei[] = {
.data_type = QMI_STRUCT,
.elem_len = QMI_WLANFW_MAX_NUM_SHADOW_REG_V01,
.elem_size = sizeof(struct qmi_wlanfw_shadow_reg_cfg_s_v01),
.array_type = VAR_LEN_ARRAY,
.array_type = VAR_LEN_ARRAY,
.tlv_type = 0x13,
.offset = offsetof(struct qmi_wlanfw_wlan_cfg_req_msg_v01,
shadow_reg),
@ -2003,15 +2044,17 @@ static const struct qmi_elem_info qmi_wlanfw_wlan_cfg_resp_msg_v01_ei[] = {
static const struct qmi_elem_info qmi_wlanfw_mem_ready_ind_msg_v01_ei[] = {
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
static const struct qmi_elem_info qmi_wlanfw_fw_ready_ind_msg_v01_ei[] = {
{
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.data_type = QMI_EOTI,
.array_type = NO_ARRAY,
.tlv_type = QMI_COMMON_TLV_TYPE,
},
};
@ -2094,14 +2137,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab,
if (!ag->mlo_capable) {
ath12k_dbg(ab, ATH12K_DBG_QMI,
"MLO is disabled hence skip QMI MLO cap");
"MLO is disabled hence skip QMI MLO cap\n");
return 0;
}
if (!ab->qmi.num_radios || ab->qmi.num_radios == U8_MAX) {
ag->mlo_capable = false;
ath12k_dbg(ab, ATH12K_DBG_QMI,
"skip QMI MLO cap due to invalid num_radio %d\n",
"skip QMI MLO cap due to invalid num_radio %u\n",
ab->qmi.num_radios);
return 0;
}
@ -2125,7 +2168,7 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab,
req->mlo_num_chips_valid = 1;
req->mlo_num_chips = ag->num_devices;
ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %d group_id %d num_devices %d",
ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo capability advertisement device_id %u group_id %u num_devices %u\n",
req->mlo_chip_id, req->mlo_group_id, req->mlo_num_chips);
mutex_lock(&ag->mutex);
@ -2146,14 +2189,14 @@ static int ath12k_host_cap_parse_mlo(struct ath12k_base *ab,
info->chip_id = partner_ab->device_id;
info->num_local_links = partner_ab->qmi.num_radios;
ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %d num_link %d\n",
ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo device id %u num_link %u\n",
info->chip_id, info->num_local_links);
for (j = 0; j < info->num_local_links; j++) {
info->hw_link_id[j] = partner_ab->wsi_info.hw_link_id_base + j;
info->valid_mlo_link_id[j] = 1;
ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %d\n",
ath12k_dbg(ab, ATH12K_DBG_QMI, "mlo hw_link_id %u\n",
info->hw_link_id[j]);
hw_link_id++;
@ -2248,6 +2291,11 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab)
if (ret < 0)
goto out;
if (ab->qmi.dynamic_ddr_support) {
req.dynamic_mem_support_valid = 1;
req.dynamic_mem_support = 1;
}
ret = qmi_txn_init(&ab->qmi.handle, &txn,
qmi_wlanfw_host_cap_resp_msg_v01_ei, &resp);
if (ret < 0)
@ -2268,7 +2316,7 @@ int ath12k_qmi_host_cap_send(struct ath12k_base *ab)
goto out;
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "Host capability request failed, result: %d, err: %d\n",
ath12k_warn(ab, "Host capability request failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -2319,11 +2367,15 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab)
ab->qmi.num_radios = resp.num_phy;
if (resp.dynamic_ddr_support_valid)
ab->qmi.dynamic_ddr_support = resp.dynamic_ddr_support;
ath12k_dbg(ab, ATH12K_DBG_QMI,
"phy capability resp valid %d single_chip_mlo_support %d valid %d num_phy %d valid %d board_id %d\n",
"phy capability resp valid %u single_chip_mlo_support %u valid %u num_phy %u valid %u board_id %u dynamic_ddr_valid %u dynamic_ddr_support %u\n",
resp.single_chip_mlo_support_valid, resp.single_chip_mlo_support,
resp.num_phy_valid, resp.num_phy,
resp.board_id_valid, resp.board_id);
resp.board_id_valid, resp.board_id, resp.dynamic_ddr_support_valid,
resp.dynamic_ddr_support);
return;
@ -2332,7 +2384,7 @@ static void ath12k_qmi_phy_cap_send(struct ath12k_base *ab)
ab->qmi.num_radios = ab->hw_params->def_num_link;
ath12k_dbg(ab, ATH12K_DBG_QMI,
"no valid response from PHY capability, choose default num_phy %d\n",
"no valid response from PHY capability, choose default num_phy %u\n",
ab->qmi.num_radios);
}
@ -2393,7 +2445,7 @@ static int ath12k_qmi_fw_ind_register_send(struct ath12k_base *ab)
}
if (resp->resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "FW Ind register request failed, result: %d, err: %d\n",
ath12k_warn(ab, "FW Ind register request failed, result: %u, err: %u\n",
resp->resp.result, resp->resp.error);
ret = -EINVAL;
goto out;
@ -2428,7 +2480,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab)
if (!test_bit(ATH12K_FLAG_FIXED_MEM_REGION, &ab->dev_flags) &&
ab->qmi.target_mem_delayed) {
delayed = true;
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %d\n",
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi delays mem_request %u\n",
ab->qmi.mem_seg_count);
} else {
delayed = false;
@ -2474,7 +2526,7 @@ int ath12k_qmi_respond_fw_mem_request(struct ath12k_base *ab)
if (delayed && resp.resp.error == 0)
goto out;
ath12k_warn(ab, "Respond mem req failed, result: %d, err: %d\n",
ath12k_warn(ab, "Respond mem req failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -2606,13 +2658,13 @@ static int ath12k_qmi_alloc_chunk(struct ath12k_base *ab,
if (chunk->size > ATH12K_QMI_MAX_CHUNK_SIZE) {
ab->qmi.target_mem_delayed = true;
ath12k_warn(ab,
"qmi dma allocation failed (%d B type %u), will try later with small size\n",
"qmi dma allocation failed (%u B type %u), will try later with small size\n",
chunk->size,
chunk->type);
ath12k_qmi_free_target_mem_chunk(ab);
return -EAGAIN;
}
ath12k_warn(ab, "memory allocation failure for %u size: %d\n",
ath12k_warn(ab, "memory allocation failure for %u size: %u\n",
chunk->type, chunk->size);
return -ENOMEM;
}
@ -2659,7 +2711,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab)
mlo_size += chunk->size;
if (ag->mlo_mem.mlo_mem_size &&
mlo_size > ag->mlo_mem.mlo_mem_size) {
ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d",
ath12k_err(ab, "QMI MLO memory allocation failure, requested size %d is more than allocated size %d\n",
mlo_size, ag->mlo_mem.mlo_mem_size);
ret = -EINVAL;
goto err;
@ -2668,7 +2720,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab)
mlo_chunk = &ag->mlo_mem.chunk[mlo_idx];
if (mlo_chunk->paddr) {
if (chunk->size != mlo_chunk->size) {
ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %d is more than allocated size %d",
ath12k_err(ab, "QMI MLO chunk memory allocation failure for index %d, requested size %u is more than allocated size %u\n",
mlo_idx, chunk->size, mlo_chunk->size);
ret = -EINVAL;
goto err;
@ -2699,7 +2751,7 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab)
if (!ag->mlo_mem.mlo_mem_size) {
ag->mlo_mem.mlo_mem_size = mlo_size;
} else if (ag->mlo_mem.mlo_mem_size != mlo_size) {
ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d",
ath12k_err(ab, "QMI MLO memory size error, expected size is %d but requested size is %d\n",
ag->mlo_mem.mlo_mem_size, mlo_size);
ret = -EINVAL;
goto err;
@ -2725,121 +2777,96 @@ static int ath12k_qmi_alloc_target_mem_chunk(struct ath12k_base *ab)
return ret;
}
static const char *ath12k_qmi_get_mem_reg_name(int mem_type)
{
switch (mem_type) {
case HOST_DDR_REGION_TYPE:
case BDF_MEM_REGION_TYPE:
return "q6-region";
case M3_DUMP_REGION_TYPE:
return "m3-dump";
case CALDB_MEM_REGION_TYPE:
return "q6-caldb";
case MLO_GLOBAL_MEM_REGION_TYPE:
return "mlo-global-mem";
default:
return NULL;
}
}
static int ath12k_qmi_assign_target_mem_chunk(struct ath12k_base *ab)
{
struct reserved_mem *rmem;
size_t avail_rmem_size;
struct device_node *np = ab->dev->of_node;
size_t avail_rmem_size, offset = 0;
struct target_mem_chunk *chunk;
struct resource res;
const char *rname;
int i, idx, ret;
for (i = 0, idx = 0; i < ab->qmi.mem_seg_count; i++) {
switch (ab->qmi.target_mem[i].type) {
case HOST_DDR_REGION_TYPE:
rmem = ath12k_core_get_reserved_mem(ab, 0);
if (!rmem) {
ret = -ENODEV;
goto out;
}
avail_rmem_size = rmem->size;
if (avail_rmem_size < ab->qmi.target_mem[i].size) {
ath12k_dbg(ab, ATH12K_DBG_QMI,
"failed to assign mem type %u req size %u avail size %zu\n",
ab->qmi.target_mem[i].type,
ab->qmi.target_mem[i].size,
avail_rmem_size);
ret = -EINVAL;
goto out;
}
ab->qmi.target_mem[idx].paddr = rmem->base;
ab->qmi.target_mem[idx].v.ioaddr =
ioremap(ab->qmi.target_mem[idx].paddr,
ab->qmi.target_mem[i].size);
if (!ab->qmi.target_mem[idx].v.ioaddr) {
ret = -EIO;
goto out;
}
ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
idx++;
break;
case BDF_MEM_REGION_TYPE:
rmem = ath12k_core_get_reserved_mem(ab, 0);
if (!rmem) {
ret = -ENODEV;
goto out;
}
avail_rmem_size = rmem->size - ab->hw_params->bdf_addr_offset;
if (avail_rmem_size < ab->qmi.target_mem[i].size) {
ath12k_dbg(ab, ATH12K_DBG_QMI,
"failed to assign mem type %u req size %u avail size %zu\n",
ab->qmi.target_mem[i].type,
ab->qmi.target_mem[i].size,
avail_rmem_size);
ret = -EINVAL;
goto out;
}
ab->qmi.target_mem[idx].paddr =
rmem->base + ab->hw_params->bdf_addr_offset;
ab->qmi.target_mem[idx].v.ioaddr =
ioremap(ab->qmi.target_mem[idx].paddr,
ab->qmi.target_mem[i].size);
if (!ab->qmi.target_mem[idx].v.ioaddr) {
ret = -EIO;
goto out;
}
ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
idx++;
break;
case CALDB_MEM_REGION_TYPE:
/* Cold boot calibration is not enabled in Ath12k. Hence,
chunk = &ab->qmi.target_mem[i];
if (chunk->type == CALDB_MEM_REGION_TYPE) {
/*
* Cold boot calibration is not enabled in Ath12k. Hence,
* assign paddr = 0.
* Once cold boot calibration is enabled add support to
* assign reserved memory from DT.
*/
ab->qmi.target_mem[idx].paddr = 0;
ab->qmi.target_mem[idx].v.ioaddr = NULL;
ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
ab->qmi.target_mem[idx].size = chunk->size;
ab->qmi.target_mem[idx].type = chunk->type;
idx++;
break;
case M3_DUMP_REGION_TYPE:
rmem = ath12k_core_get_reserved_mem(ab, 1);
if (!rmem) {
ret = -EINVAL;
goto out;
}
continue;
}
avail_rmem_size = rmem->size;
if (avail_rmem_size < ab->qmi.target_mem[i].size) {
ath12k_dbg(ab, ATH12K_DBG_QMI,
"failed to assign mem type %u req size %u avail size %zu\n",
ab->qmi.target_mem[i].type,
ab->qmi.target_mem[i].size,
rname = ath12k_qmi_get_mem_reg_name(chunk->type);
if (!rname) {
ath12k_warn(ab, "qmi ignore invalid mem req type %u\n",
chunk->type);
continue;
}
ret = of_reserved_mem_region_to_resource_byname(np, rname, &res);
if (ret)
goto out;
avail_rmem_size = resource_size(&res);
if (chunk->type == BDF_MEM_REGION_TYPE ||
chunk->type == HOST_DDR_REGION_TYPE) {
if (ab->hw_params->bdf_addr_offset > avail_rmem_size ||
offset > avail_rmem_size - ab->hw_params->bdf_addr_offset) {
ath12k_err(ab, "qmi mem offset overflow: bdf_offset=%u offset=%zu size=%zu\n",
ab->hw_params->bdf_addr_offset, offset,
avail_rmem_size);
ret = -EINVAL;
goto out;
}
ab->qmi.target_mem[idx].paddr = rmem->base;
ab->qmi.target_mem[idx].v.ioaddr =
ioremap(ab->qmi.target_mem[idx].paddr,
ab->qmi.target_mem[i].size);
if (!ab->qmi.target_mem[idx].v.ioaddr) {
ret = -EIO;
goto out;
}
ab->qmi.target_mem[idx].size = ab->qmi.target_mem[i].size;
ab->qmi.target_mem[idx].type = ab->qmi.target_mem[i].type;
idx++;
break;
default:
ath12k_warn(ab, "qmi ignore invalid mem req type %u\n",
ab->qmi.target_mem[i].type);
break;
avail_rmem_size -= ab->hw_params->bdf_addr_offset + offset;
res.start += ab->hw_params->bdf_addr_offset + offset;
offset += chunk->size;
}
if (avail_rmem_size < chunk->size) {
ath12k_dbg(ab, ATH12K_DBG_QMI,
"failed to assign mem type %u req size %u avail size %zu\n",
chunk->type, chunk->size, avail_rmem_size);
ret = -EINVAL;
goto out;
}
ab->qmi.target_mem[idx].paddr = res.start;
ab->qmi.target_mem[idx].v.ioaddr = ioremap(ab->qmi.target_mem[idx].paddr,
chunk->size);
if (!ab->qmi.target_mem[idx].v.ioaddr) {
ret = -EIO;
goto out;
}
ab->qmi.target_mem[idx].size = chunk->size;
ab->qmi.target_mem[idx].type = chunk->type;
idx++;
}
ab->qmi.mem_seg_count = idx;
@ -2884,7 +2911,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab)
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "qmi targetcap req failed, result: %d, err: %d\n",
ath12k_warn(ab, "qmi targetcap req failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -2936,7 +2963,7 @@ int ath12k_qmi_request_target_cap(struct ath12k_base *ab)
ab->qmi.target.chip_id, ab->qmi.target.chip_family,
ab->qmi.target.board_id, ab->qmi.target.soc_id);
ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s",
ath12k_info(ab, "fw_version 0x%x fw_build_timestamp %s fw_build_id %s\n",
ab->qmi.target.fw_version,
ab->qmi.target.fw_build_timestamp,
ab->qmi.target.fw_build_id);
@ -3006,7 +3033,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab,
if (ret < 0)
goto out;
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %d\n",
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi bdf download req fixed addr type %u\n",
type);
ret = qmi_send_request(&ab->qmi.handle, NULL, &txn,
@ -3023,7 +3050,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab,
goto out;
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "qmi BDF download failed, result: %d, err: %d\n",
ath12k_warn(ab, "qmi BDF download failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -3036,7 +3063,7 @@ static int ath12k_qmi_load_file_target_mem(struct ath12k_base *ab,
temp += req->data_len;
req->seg_id++;
ath12k_dbg(ab, ATH12K_DBG_QMI,
"qmi bdf download request remaining %i\n",
"qmi bdf download request remaining %u\n",
remaining);
}
}
@ -3127,7 +3154,7 @@ int ath12k_qmi_load_bdf_qmi(struct ath12k_base *ab,
release_firmware(fw_entry);
return ret;
default:
ath12k_warn(ab, "unknown file type for load %d", type);
ath12k_warn(ab, "unknown file type for load %d\n", type);
goto out;
}
@ -3239,7 +3266,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab)
if (ab->hw_params->fw.m3_loader == ath12k_m3_fw_loader_driver) {
ret = ath12k_qmi_m3_load(ab);
if (ret) {
ath12k_err(ab, "failed to load m3 firmware: %d", ret);
ath12k_err(ab, "failed to load m3 firmware: %d\n", ret);
return ret;
}
req.addr = m3_mem->paddr;
@ -3269,7 +3296,7 @@ int ath12k_qmi_wlanfw_m3_info_send(struct ath12k_base *ab)
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "qmi M3 info request failed, result: %d, err: %d\n",
ath12k_warn(ab, "qmi M3 info request failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -3364,7 +3391,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab)
ret = ath12k_qmi_aux_uc_load(ab);
if (ret) {
ath12k_err(ab, "failed to load aux_uc firmware: %d", ret);
ath12k_err(ab, "failed to load aux_uc firmware: %d\n", ret);
return ret;
}
@ -3394,7 +3421,7 @@ int ath12k_qmi_wlanfw_aux_uc_info_send(struct ath12k_base *ab)
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "qmi AUX_UC info request failed, result: %d, err: %d\n",
ath12k_warn(ab, "qmi AUX_UC info request failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -3426,7 +3453,7 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab,
qmi_wlanfw_wlan_mode_req_msg_v01_ei, &req);
if (ret < 0) {
qmi_txn_cancel(&txn);
ath12k_warn(ab, "qmi failed to send mode request, mode: %d, err = %d\n",
ath12k_warn(ab, "qmi failed to send mode request, mode: %u, err = %d\n",
mode, ret);
goto out;
}
@ -3437,13 +3464,13 @@ static int ath12k_qmi_wlanfw_mode_send(struct ath12k_base *ab,
ath12k_warn(ab, "WLFW service is dis-connected\n");
return 0;
}
ath12k_warn(ab, "qmi failed set mode request, mode: %d, err = %d\n",
ath12k_warn(ab, "qmi failed set mode request, mode: %u, err = %d\n",
mode, ret);
goto out;
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "Mode request failed, mode: %d, result: %d err: %d\n",
ath12k_warn(ab, "Mode request failed, mode: %u, result: %u err: %u\n",
mode, resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -3536,7 +3563,7 @@ static int ath12k_qmi_wlanfw_wlan_cfg_send(struct ath12k_base *ab)
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "qmi wlan config request failed, result: %d, err: %d\n",
ath12k_warn(ab, "qmi wlan config request failed, result: %u, err: %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -3580,7 +3607,7 @@ static int ath12k_qmi_wlanfw_wlan_ini_send(struct ath12k_base *ab)
}
if (resp.resp.result != QMI_RESULT_SUCCESS_V01) {
ath12k_warn(ab, "QMI wlan ini response failure: %d %d\n",
ath12k_warn(ab, "QMI wlan ini response failure: %u %u\n",
resp.resp.result, resp.resp.error);
ret = -EINVAL;
goto out;
@ -3663,7 +3690,7 @@ void ath12k_qmi_trigger_host_cap(struct ath12k_base *ab)
spin_unlock(&qmi->event_lock);
ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %d\n",
ath12k_dbg(ab, ATH12K_DBG_QMI, "trigger host cap for device id %u\n",
ab->device_id);
ath12k_qmi_driver_event_post(qmi, ATH12K_QMI_EVENT_HOST_CAP, NULL);
@ -3833,7 +3860,7 @@ static void ath12k_qmi_msg_mem_request_cb(struct qmi_handle *qmi_hdl,
for (i = 0; i < qmi->mem_seg_count ; i++) {
ab->qmi.target_mem[i].type = msg->mem_seg[i].type;
ab->qmi.target_mem[i].size = msg->mem_seg[i].size;
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %d\n",
ath12k_dbg(ab, ATH12K_DBG_QMI, "qmi mem seg type %d size %u\n",
msg->mem_seg[i].type, msg->mem_seg[i].size);
}
@ -3954,7 +3981,7 @@ static int ath12k_qmi_event_host_cap(struct ath12k_qmi *qmi)
ret = ath12k_qmi_host_cap_send(ab);
if (ret < 0) {
ath12k_warn(ab, "failed to send qmi host cap for device id %d: %d\n",
ath12k_warn(ab, "failed to send qmi host cap for device id %u: %d\n",
ab->device_id, ret);
return ret;
}
@ -4024,7 +4051,7 @@ static void ath12k_qmi_driver_event_work(struct work_struct *work)
set_bit(ATH12K_FLAG_QMI_FAIL, &ab->dev_flags);
break;
default:
ath12k_warn(ab, "invalid event type: %d", event->type);
ath12k_warn(ab, "invalid event type: %d\n", event->type);
break;
}

View File

@ -13,7 +13,6 @@
#define ATH12K_HOST_VERSION_STRING "WIN"
#define ATH12K_QMI_WLANFW_TIMEOUT_MS 10000
#define ATH12K_QMI_MAX_BDF_FILE_NAME_SIZE 64
#define ATH12K_QMI_CALDB_ADDRESS 0x4BA00000
#define ATH12K_QMI_WLANFW_MAX_BUILD_ID_LEN_V01 128
#define ATH12K_QMI_WLFW_SERVICE_VERS_V01 0x01
#define ATH12K_QMI_WLFW_SERVICE_INS_ID_V01 0x02
@ -24,9 +23,7 @@
#define ATH12K_QMI_WLANFW_MAX_TIMESTAMP_LEN_V01 32
#define ATH12K_QMI_RESP_LEN_MAX 8192
#define ATH12K_QMI_WLANFW_MAX_NUM_MEM_SEG_V01 52
#define ATH12K_QMI_CALDB_SIZE 0x480000
#define ATH12K_QMI_BDF_EXT_STR_LENGTH 0x20
#define ATH12K_QMI_FW_MEM_REQ_SEGMENT_CNT 3
#define ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01 4
#define ATH12K_QMI_DEVMEM_CMEM_INDEX 0
@ -156,12 +153,11 @@ struct ath12k_qmi {
struct m3_mem_region aux_uc_mem;
unsigned int service_ins_id;
struct dev_mem_info dev_mem[ATH12K_QMI_WLFW_MAX_DEV_MEM_NUM_V01];
u8 dynamic_ddr_support;
};
#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 261
#define QMI_WLANFW_HOST_CAP_REQ_MSG_V01_MAX_LEN 265
#define QMI_WLANFW_HOST_CAP_REQ_V01 0x0034
#define QMI_WLANFW_HOST_CAP_RESP_MSG_V01_MAX_LEN 7
#define QMI_WLFW_HOST_CAP_RESP_V01 0x0034
#define QMI_WLFW_MAX_NUM_GPIO_V01 32
#define QMI_WLANFW_MAX_PLATFORM_NAME_LEN_V01 64
#define QMI_WLANFW_MAX_HOST_DDR_RANGE_SIZE_V01 3
@ -258,7 +254,8 @@ struct qmi_wlanfw_host_cap_req_msg_v01 {
struct wlfw_host_mlo_chip_info_s_v01 mlo_chip_info[QMI_WLFW_MAX_NUM_MLO_CHIPS_V01];
u8 feature_list_valid;
u64 feature_list;
u8 dynamic_mem_support_valid;
u8 dynamic_mem_support;
};
struct qmi_wlanfw_host_cap_resp_msg_v01 {
@ -267,8 +264,6 @@ struct qmi_wlanfw_host_cap_resp_msg_v01 {
#define QMI_WLANFW_PHY_CAP_REQ_MSG_V01_MAX_LEN 0
#define QMI_WLANFW_PHY_CAP_REQ_V01 0x0057
#define QMI_WLANFW_PHY_CAP_RESP_MSG_V01_MAX_LEN 18
#define QMI_WLANFW_PHY_CAP_RESP_V01 0x0057
struct qmi_wlanfw_phy_cap_req_msg_v01 {
};
@ -281,12 +276,12 @@ struct qmi_wlanfw_phy_cap_resp_msg_v01 {
u32 board_id;
u8 single_chip_mlo_support_valid;
u8 single_chip_mlo_support;
u8 dynamic_ddr_support_valid;
u8 dynamic_ddr_support;
};
#define QMI_WLANFW_IND_REGISTER_REQ_MSG_V01_MAX_LEN 54
#define QMI_WLANFW_IND_REGISTER_REQ_V01 0x0020
#define QMI_WLANFW_IND_REGISTER_RESP_MSG_V01_MAX_LEN 18
#define QMI_WLANFW_IND_REGISTER_RESP_V01 0x0020
#define QMI_WLANFW_CLIENT_ID 0x4b4e454c
struct qmi_wlanfw_ind_register_req_msg_v01 {
@ -322,12 +317,8 @@ struct qmi_wlanfw_ind_register_resp_msg_v01 {
u64 fw_status;
};
#define QMI_WLANFW_REQUEST_MEM_IND_MSG_V01_MAX_LEN 1824
#define QMI_WLANFW_RESPOND_MEM_REQ_MSG_V01_MAX_LEN 888
#define QMI_WLANFW_RESPOND_MEM_RESP_MSG_V01_MAX_LEN 7
#define QMI_WLANFW_REQUEST_MEM_IND_V01 0x0035
#define QMI_WLANFW_RESPOND_MEM_REQ_V01 0x0036
#define QMI_WLANFW_RESPOND_MEM_RESP_V01 0x0036
#define QMI_WLANFW_MAX_NUM_MEM_CFG_V01 2
#define QMI_WLANFW_MAX_STR_LEN_V01 16
@ -385,9 +376,7 @@ struct qmi_wlanfw_fw_ready_ind_msg_v01 {
};
#define QMI_WLANFW_CAP_REQ_MSG_V01_MAX_LEN 0
#define QMI_WLANFW_CAP_RESP_MSG_V01_MAX_LEN 207
#define QMI_WLANFW_CAP_REQ_V01 0x0024
#define QMI_WLANFW_CAP_RESP_V01 0x0024
enum qmi_wlanfw_pipedir_enum_v01 {
QMI_WLFW_PIPEDIR_NONE_V01 = 0,
@ -500,8 +489,6 @@ struct qmi_wlanfw_cap_req_msg_v01 {
};
#define QMI_WLANFW_BDF_DOWNLOAD_REQ_MSG_V01_MAX_LEN 6182
#define QMI_WLANFW_BDF_DOWNLOAD_RESP_MSG_V01_MAX_LEN 7
#define QMI_WLANFW_BDF_DOWNLOAD_RESP_V01 0x0025
#define QMI_WLANFW_BDF_DOWNLOAD_REQ_V01 0x0025
/* TODO: Need to check with MCL and FW team that data can be pointer and
* can be last element in structure
@ -529,8 +516,6 @@ struct qmi_wlanfw_bdf_download_resp_msg_v01 {
};
#define QMI_WLANFW_M3_INFO_REQ_MSG_V01_MAX_MSG_LEN 18
#define QMI_WLANFW_M3_INFO_RESP_MSG_V01_MAX_MSG_LEN 7
#define QMI_WLANFW_M3_INFO_RESP_V01 0x003C
#define QMI_WLANFW_M3_INFO_REQ_V01 0x003C
struct qmi_wlanfw_m3_info_req_msg_v01 {
@ -543,7 +528,6 @@ struct qmi_wlanfw_m3_info_resp_msg_v01 {
};
#define QMI_WLANFW_AUX_UC_INFO_REQ_MSG_V01_MAX_MSG_LEN 18
#define QMI_WLANFW_AUX_UC_INFO_RESP_MSG_V01_MAX_MSG_LEN 7
#define QMI_WLANFW_AUX_UC_INFO_REQ_V01 0x005A
struct qmi_wlanfw_aux_uc_info_req_msg_v01 {
@ -556,13 +540,9 @@ struct qmi_wlanfw_aux_uc_info_resp_msg_v01 {
};
#define QMI_WLANFW_WLAN_MODE_REQ_MSG_V01_MAX_LEN 11
#define QMI_WLANFW_WLAN_MODE_RESP_MSG_V01_MAX_LEN 7
#define QMI_WLANFW_WLAN_CFG_REQ_MSG_V01_MAX_LEN 803
#define QMI_WLANFW_WLAN_CFG_RESP_MSG_V01_MAX_LEN 7
#define QMI_WLANFW_WLAN_MODE_REQ_V01 0x0022
#define QMI_WLANFW_WLAN_MODE_RESP_V01 0x0022
#define QMI_WLANFW_WLAN_CFG_REQ_V01 0x0023
#define QMI_WLANFW_WLAN_CFG_RESP_V01 0x0023
#define QMI_WLANFW_MAX_STR_LEN_V01 16
#define QMI_WLANFW_MAX_NUM_CE_V01 12
#define QMI_WLANFW_MAX_NUM_SVC_V01 24
@ -605,9 +585,7 @@ struct qmi_wlanfw_wlan_cfg_resp_msg_v01 {
};
#define ATH12K_QMI_WLANFW_WLAN_INI_REQ_V01 0x002F
#define ATH12K_QMI_WLANFW_WLAN_INI_RESP_V01 0x002F
#define QMI_WLANFW_WLAN_INI_REQ_MSG_V01_MAX_LEN 7
#define QMI_WLANFW_WLAN_INI_RESP_MSG_V01_MAX_LEN 7
struct qmi_wlanfw_wlan_ini_req_msg_v01 {
/* Must be set to true if enable_fwlog is being passed */

View File

@ -139,7 +139,7 @@ static int ath12k_wifi7_dp_service_srng(struct ath12k_dp *dp,
return tot_work_done;
}
static struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = {
static const struct ath12k_dp_arch_ops ath12k_wifi7_dp_arch_ops = {
.service_srng = ath12k_wifi7_dp_service_srng,
.tx_get_vdev_bank_config = ath12k_wifi7_dp_tx_get_vdev_bank_config,
.reo_cmd_send = ath12k_wifi7_dp_reo_cmd_send,

View File

@ -1565,16 +1565,17 @@ ath12k_wifi7_dp_mon_parse_status_msdu_end(struct ath12k_mon_data *pmon,
static enum hal_rx_mon_status
ath12k_wifi7_dp_mon_rx_parse_status_tlv(struct ath12k_pdev_dp *dp_pdev,
struct ath12k_mon_data *pmon,
const struct hal_tlv_64_hdr *tlv)
const void *tlv)
{
struct hal_rx_mon_ppdu_info *ppdu_info = &pmon->mon_ppdu_info;
const void *tlv_data = tlv->value;
u32 info[7], userid;
u16 tlv_tag, tlv_len;
struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev);
struct ath12k_hal *hal = &ar->ab->hal;
u16 tlv_tag, tlv_len, userid;
void *tlv_data;
u32 info[7];
tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG);
tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN);
userid = le64_get_bits(tlv->tl, HAL_TLV_64_USR_ID);
tlv_data = hal->ops->mon_rx_status_dec_tlv_hdr((void *)tlv, &tlv_tag,
&tlv_len, &userid);
if (ppdu_info->tlv_aggr.in_progress && ppdu_info->tlv_aggr.tlv_tag != tlv_tag) {
ath12k_wifi7_dp_mon_parse_eht_sig_hdr(ppdu_info,
@ -2480,7 +2481,6 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev,
{
struct sk_buff *mon_skb, *skb_next, *header;
struct ieee80211_rx_status *rxs = &dp_pdev->rx_status;
u8 decap = DP_RX_DECAP_TYPE_RAW;
mon_skb = ath12k_dp_mon_rx_merg_msdus(dp_pdev, mon_mpdu, ppduinfo, rxs);
if (!mon_skb)
@ -2507,12 +2507,8 @@ ath12k_wifi7_dp_mon_rx_deliver(struct ath12k_pdev_dp *dp_pdev,
}
rxs->flag |= RX_FLAG_ONLY_MONITOR;
if (!(rxs->flag & RX_FLAG_ONLY_MONITOR))
decap = mon_mpdu->decap_format;
ath12k_dp_mon_update_radiotap(dp_pdev, ppduinfo, mon_skb, rxs);
ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, ppduinfo,
rxs, decap);
ath12k_dp_mon_rx_deliver_msdu(dp_pdev, napi, mon_skb, rxs);
mon_skb = skb_next;
} while (mon_skb);
rxs->flag = 0;
@ -2930,11 +2926,12 @@ static enum dp_mon_status_buf_state
ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng,
struct dp_rxdma_mon_ring *rx_ring)
{
struct ath12k_hal *hal = &ab->hal;
struct ath12k_skb_rxcb *rxcb;
struct hal_tlv_64_hdr *tlv;
struct sk_buff *skb;
void *status_desc;
dma_addr_t paddr;
u16 tlv_tag;
u32 cookie;
int buf_id;
u8 rbm;
@ -2959,8 +2956,8 @@ ath12k_wifi7_dp_rx_mon_buf_done(struct ath12k_base *ab, struct hal_srng *srng,
skb->len + skb_tailroom(skb),
DMA_FROM_DEVICE);
tlv = (struct hal_tlv_64_hdr *)skb->data;
if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) != HAL_RX_STATUS_BUFFER_DONE)
hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag, NULL, NULL);
if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE)
return DP_MON_STATUS_NO_DMA;
return DP_MON_STATUS_REPLINISH;
@ -2972,41 +2969,40 @@ ath12k_wifi7_dp_mon_parse_rx_dest(struct ath12k_pdev_dp *dp_pdev,
struct sk_buff *skb)
{
struct ath12k *ar = ath12k_pdev_dp_to_ar(dp_pdev);
struct hal_tlv_64_hdr *tlv;
struct ath12k_hal *hal = &ar->ab->hal;
u8 *tlv_value, *tlv = skb->data;
struct ath12k_skb_rxcb *rxcb;
enum hal_rx_mon_status hal_status;
u16 tlv_tag, tlv_len;
u8 *ptr = skb->data;
u32 tlv_hdr_len;
tlv_hdr_len = hal->ops->get_tlv_hdr_align();
do {
tlv = (struct hal_tlv_64_hdr *)ptr;
tlv_tag = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_TAG);
tlv_value = hal->ops->mon_rx_status_dec_tlv_hdr(tlv, &tlv_tag,
&tlv_len, NULL);
/* The actual length of PPDU_END is the combined length of many PHY
* TLVs that follow. Skip the TLV header and
* rx_rxpcu_classification_overview that follows the header to get to
* next TLV.
*/
if (tlv_tag == HAL_RX_PPDU_END)
tlv_len = sizeof(struct hal_rx_rxpcu_classification_overview);
else
tlv_len = le64_get_bits(tlv->tl, HAL_TLV_64_HDR_LEN);
hal_status = ath12k_wifi7_dp_mon_rx_parse_status_tlv(dp_pdev, pmon,
tlv);
if (ar->monitor_started && ar->ab->hw_params->rxdma1_enable &&
ath12k_wifi7_dp_mon_parse_rx_dest_tlv(dp_pdev, pmon, hal_status,
tlv->value))
tlv_value))
return HAL_RX_MON_STATUS_PPDU_DONE;
ptr += sizeof(*tlv) + tlv_len;
ptr = PTR_ALIGN(ptr, HAL_TLV_64_ALIGN);
tlv = PTR_ALIGN(tlv + tlv_len + tlv_hdr_len, tlv_hdr_len);
if ((ptr - skb->data) > skb->len)
if (unlikely(tlv - skb->data > skb->len ||
skb->len - (tlv - skb->data) < tlv_hdr_len))
break;
} while ((hal_status == HAL_RX_MON_STATUS_PPDU_NOT_DONE) ||
(hal_status == HAL_RX_MON_STATUS_BUF_ADDR) ||
(hal_status == HAL_RX_MON_STATUS_MPDU_START) ||
@ -3056,15 +3052,16 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id,
int buf_id, srng_id, num_buffs_reaped = 0;
enum dp_mon_status_buf_state reap_status;
struct dp_rxdma_mon_ring *rx_ring;
struct ath12k_hal *hal = &ab->hal;
struct ath12k_mon_data *pmon;
struct ath12k_skb_rxcb *rxcb;
struct hal_tlv_64_hdr *tlv;
void *rx_mon_status_desc;
struct hal_srng *srng;
struct ath12k_dp *dp;
struct sk_buff *skb;
struct ath12k *ar;
dma_addr_t paddr;
u16 tlv_tag;
u32 cookie;
u8 rbm;
@ -3109,14 +3106,13 @@ ath12k_wifi7_dp_rx_reap_mon_status_ring(struct ath12k_base *ab, int mac_id,
skb->len + skb_tailroom(skb),
DMA_FROM_DEVICE);
tlv = (struct hal_tlv_64_hdr *)skb->data;
if (le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG) !=
HAL_RX_STATUS_BUFFER_DONE) {
hal->ops->mon_rx_status_dec_tlv_hdr(skb->data, &tlv_tag,
NULL, NULL);
if (tlv_tag != HAL_RX_STATUS_BUFFER_DONE) {
pmon->buf_state = DP_MON_STATUS_NO_DMA;
ath12k_warn(ab,
"mon status DONE not set %llx, buf_id %d\n",
le64_get_bits(tlv->tl, HAL_TLV_HDR_TAG),
buf_id);
"mon status DONE not set %x, buf_id %d\n",
tlv_tag, buf_id);
/* RxDMA status done bit might not be set even
* though tp is moved by HW.
*/

View File

@ -455,7 +455,7 @@ static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcc2072(void *tlv, void **desc)
struct hal_reo_get_queue_stats_status_qcc2072 *status_tlv;
u16 tag;
tag = ath12k_hal_decode_tlv32_hdr(tlv, (void **)&status_tlv);
status_tlv = ath12k_hal_decode_tlv32_hdr(tlv, &tag, NULL, NULL);
/*
* actual desc of REO status entry starts after tlv32_padding,
* see hal_reo_get_queue_stats_status_qcc2072
@ -506,6 +506,8 @@ const struct hal_ops hal_qcc2072_ops = {
.rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get,
.reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv32_hdr,
.reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcc2072,
.mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv32_hdr,
.get_tlv_hdr_align = ath12k_hal_get_tlv32_hdr_align,
};
u32 ath12k_hal_rx_desc_get_mpdu_start_offset_qcc2072(void)

View File

@ -950,6 +950,15 @@ void ath12k_hal_extract_rx_desc_data_qcn9274(struct hal_rx_desc_data *rx_desc_da
rx_desc_data->err_bitmap = ath12k_hal_rx_h_mpdu_err_qcn9274(rx_desc);
}
static u16 ath12k_hal_reo_status_dec_tlv_hdr_qcn9274(void *tlv, void **desc)
{
u16 tag;
*desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL);
return tag;
}
const struct ath12k_hw_hal_params ath12k_hw_hal_params_qcn9274 = {
.rx_buf_rbm = HAL_RX_BUF_RBM_SW3_BM,
.wbm2sw_cc_enable = HAL_WBM_SW_COOKIE_CONV_CFG_WBM2SW0_EN |
@ -1138,5 +1147,7 @@ const struct hal_ops hal_qcn9274_ops = {
.rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get,
.rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get,
.reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr,
.reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr,
.reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_qcn9274,
.mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr,
.get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align,
};

View File

@ -140,6 +140,38 @@ struct rx_mpdu_start_qcn9274 {
__le32 res1;
} __packed;
struct rx_mpdu_start_qcc2072 {
__le32 info0;
__le32 info2;
__le32 reo_queue_desc_lo;
__le32 info1;
__le32 pn[4];
__le32 info4;
__le32 peer_meta_data;
__le16 ast_index;
__le16 sw_peer_id;
__le16 info3;
__le16 phy_ppdu_id;
__le32 info5;
__le32 info6;
__le16 frame_ctrl;
__le16 duration;
u8 addr1[ETH_ALEN];
u8 addr2[ETH_ALEN];
u8 addr3[ETH_ALEN];
__le16 seq_ctrl;
u8 addr4[ETH_ALEN];
__le16 qos_ctrl;
__le32 ht_ctrl;
__le32 info7;
__le32 res0;
__le32 res1;
__le32 res2;
__le32 info8;
__le32 res3;
__le32 res4;
} __packed;
#define QCN9274_MPDU_START_SELECT_MPDU_START_TAG BIT(0)
#define QCN9274_MPDU_START_SELECT_INFO0_REO_QUEUE_DESC_LO BIT(1)
#define QCN9274_MPDU_START_SELECT_INFO1_PN_31_0 BIT(2)
@ -1492,7 +1524,7 @@ struct hal_rx_desc_qcc2072 {
struct rx_msdu_end_qcn9274 msdu_end;
u8 rx_padding0[RX_BE_PADDING0_BYTES];
__le32 mpdu_start_tag;
struct rx_mpdu_start_qcn9274 mpdu_start;
struct rx_mpdu_start_qcc2072 mpdu_start;
struct rx_pkt_hdr_tlv_qcc2072 pkt_hdr_tlv;
u8 msdu_payload[];
};

View File

@ -756,6 +756,15 @@ int ath12k_hal_srng_create_config_wcn7850(struct ath12k_hal *hal)
return 0;
}
static u16 ath12k_hal_reo_status_dec_tlv_hdr_wcn7850(void *tlv, void **desc)
{
u16 tag;
*desc = ath12k_hal_decode_tlv64_hdr(tlv, &tag, NULL, NULL);
return tag;
}
const struct ath12k_hal_tcl_to_wbm_rbm_map
ath12k_hal_tcl_to_wbm_rbm_map_wcn7850[DP_TCL_NUM_RING_MAX] = {
{
@ -821,5 +830,7 @@ const struct hal_ops hal_wcn7850_ops = {
.rx_msdu_list_get = ath12k_wifi7_hal_rx_msdu_list_get,
.rx_reo_ent_buf_paddr_get = ath12k_wifi7_hal_rx_reo_ent_buf_paddr_get,
.reo_cmd_enc_tlv_hdr = ath12k_hal_encode_tlv64_hdr,
.reo_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr,
.reo_status_dec_tlv_hdr = ath12k_hal_reo_status_dec_tlv_hdr_wcn7850,
.mon_rx_status_dec_tlv_hdr = ath12k_hal_decode_tlv64_hdr,
.get_tlv_hdr_align = ath12k_hal_get_tlv64_hdr_align,
};

View File

@ -393,6 +393,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
BIT(NL80211_IFTYPE_MESH_POINT) |
BIT(NL80211_IFTYPE_AP_VLAN),
.supports_monitor = false,
.supports_cong_ctrl_max_msdus = true,
.idle_ps = false,
.download_calib = true,
@ -483,6 +484,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO),
.supports_monitor = true,
.supports_cong_ctrl_max_msdus = false,
.idle_ps = true,
.download_calib = false,
@ -571,6 +573,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
BIT(NL80211_IFTYPE_MESH_POINT) |
BIT(NL80211_IFTYPE_AP_VLAN),
.supports_monitor = true,
.supports_cong_ctrl_max_msdus = true,
.idle_ps = false,
.download_calib = true,
@ -657,6 +660,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
BIT(NL80211_IFTYPE_AP) |
BIT(NL80211_IFTYPE_MESH_POINT),
.supports_monitor = true,
.supports_cong_ctrl_max_msdus = true,
.idle_ps = false,
.download_calib = true,
@ -692,7 +696,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
.ce_ie_addr = &ath12k_wifi7_ce_ie_addr_ipq5332,
.ce_remap = &ath12k_wifi7_ce_remap_ipq5332,
.bdf_addr_offset = 0xC00000,
.bdf_addr_offset = 0x1A00000,
.dp_primary_link_only = true,
.client = {
@ -741,6 +745,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
BIT(NL80211_IFTYPE_P2P_CLIENT) |
BIT(NL80211_IFTYPE_P2P_GO),
.supports_monitor = true,
.supports_cong_ctrl_max_msdus = false,
.idle_ps = true,
.download_calib = false,
@ -829,6 +834,7 @@ static const struct ath12k_hw_params ath12k_wifi7_hw_params[] = {
BIT(NL80211_IFTYPE_AP) |
BIT(NL80211_IFTYPE_MESH_POINT),
.supports_monitor = true,
.supports_cong_ctrl_max_msdus = true,
.idle_ps = false,
.download_calib = true,
@ -906,6 +912,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
struct ethhdr *eth;
bool is_prb_rsp;
u16 mcbc_gsn;
u8 cb_flags;
u8 link_id;
int ret;
struct ath12k_dp *tmp_dp;
@ -999,8 +1006,13 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
ieee80211_has_protected(hdr->frame_control))
is_dvlan = true;
/*
* Add a sta pointer check to differentiate multicast encapsulation
* offload packets, as the ATH12K_SKB_HW_80211_ENCAP flag is also set
* for such packets.
*/
if (!vif->valid_links || !is_mcast || is_dvlan ||
(skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) ||
((skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP) && sta) ||
test_bit(ATH12K_FLAG_RAW_MODE, &ar->ab->dev_flags)) {
ret = ath12k_wifi7_dp_tx(dp_pdev, arvif, arsta, skb, false, 0, is_mcast);
if (unlikely(ret)) {
@ -1012,6 +1024,7 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
mcbc_gsn = atomic_inc_return(&ahvif->dp_vif.mcbc_gsn) & 0xfff;
links_map = ahvif->links_map;
cb_flags = skb_cb->flags;
for_each_set_bit(link_id, &links_map,
IEEE80211_MLD_MAX_NUM_LINKS) {
tmp_arvif = rcu_dereference(ahvif->link[link_id]);
@ -1019,21 +1032,49 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
continue;
tmp_ar = tmp_arvif->ar;
tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_ar->ab->dp,
tmp_dp = ath12k_ab_to_dp(tmp_ar->ab);
tmp_dp_pdev = ath12k_dp_to_pdev_dp(tmp_dp,
tmp_ar->pdev_idx);
if (!tmp_dp_pdev)
continue;
msdu_copied = skb_copy(skb, GFP_ATOMIC);
if (!msdu_copied) {
ath12k_err(ar->ab,
"skb copy failure link_id 0x%X vdevid 0x%X\n",
link_id, tmp_arvif->vdev_id);
continue;
}
ath12k_mlo_mcast_update_tx_link_address(vif, link_id,
msdu_copied,
info_flags);
if (cb_flags & ATH12K_SKB_HW_80211_ENCAP) {
/*
* skb->data may be modified for the
* iova_mask devices. It is better to
* use skb_copy() for such devices to
* avoid any potential skb corruption
* related issues.
*/
if (tmp_dp->hw_params->iova_mask) {
msdu_copied = skb_copy(skb, GFP_ATOMIC);
} else {
/*
* ath12k_wifi7_dp_tx() should
* treat cloned HW-encap Ethernet
* multicast frames as read-only.
*/
msdu_copied = skb_clone(skb, GFP_ATOMIC);
}
if (!msdu_copied) {
ath12k_err(ar->ab,
"skb copy/clone failure link_id 0x%X vdevid 0x%X\n",
link_id, tmp_arvif->vdev_id);
continue;
}
} else {
msdu_copied = skb_copy(skb, GFP_ATOMIC);
if (!msdu_copied) {
ath12k_err(ar->ab,
"skb copy failure link_id 0x%X vdevid 0x%X\n",
link_id, tmp_arvif->vdev_id);
continue;
}
ath12k_mlo_mcast_update_tx_link_address(vif, link_id,
msdu_copied,
info_flags);
}
skb_cb = ATH12K_SKB_CB(msdu_copied);
skb_cb->link_id = link_id;
@ -1049,7 +1090,6 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
if (unlikely(!ahvif->dp_vif.key_cipher))
goto skip_peer_find;
tmp_dp = ath12k_ab_to_dp(tmp_ar->ab);
spin_lock_bh(&tmp_dp->dp_lock);
peer = ath12k_dp_link_peer_find_by_addr(tmp_dp,
tmp_arvif->bssid);
@ -1068,11 +1108,16 @@ static void ath12k_wifi7_mac_op_tx(struct ieee80211_hw *hw,
skb_cb->cipher = key->cipher;
skb_cb->flags |= ATH12K_SKB_CIPHER_SET;
if (skb_cb->flags & ATH12K_SKB_HW_80211_ENCAP)
goto skip_fctl_protected_check;
hdr = (struct ieee80211_hdr *)msdu_copied->data;
if (!ieee80211_has_protected(hdr->frame_control))
hdr->frame_control |=
cpu_to_le16(IEEE80211_FCTL_PROTECTED);
}
skip_fctl_protected_check:
spin_unlock_bh(&tmp_dp->dp_lock);
skip_peer_find:

View File

@ -1228,10 +1228,16 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg,
le32_encode_bits(arg->ml.mcast_link,
ATH12K_WMI_FLAG_MLO_MCAST_VDEV) |
le32_encode_bits(arg->ml.link_add,
ATH12K_WMI_FLAG_MLO_LINK_ADD);
ATH12K_WMI_FLAG_MLO_LINK_ADD) |
le32_encode_bits(arg->ml.assoc_link,
ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE) |
cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID);
ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %d start ml flags 0x%x\n",
arg->vdev_id, ml_params->flags);
ml_params->ieee_link_id = cpu_to_le32(arg->ml.ieee_link_id);
ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "vdev %u start link_id %u ml flags 0x%x\n",
arg->vdev_id, arg->ml.ieee_link_id,
le32_to_cpu(ml_params->flags));
ptr += sizeof(*ml_params);
@ -1244,19 +1250,23 @@ int ath12k_wmi_vdev_start(struct ath12k *ar, struct wmi_vdev_start_req_arg *arg,
partner_info = ptr;
for (i = 0; i < arg->ml.num_partner_links; i++) {
struct wmi_ml_partner_info *pinfo = &arg->ml.partner_info[i];
partner_info->tlv_header =
ath12k_wmi_tlv_cmd_hdr(WMI_TAG_MLO_PARTNER_LINK_PARAMS,
sizeof(*partner_info));
partner_info->vdev_id =
cpu_to_le32(arg->ml.partner_info[i].vdev_id);
partner_info->hw_link_id =
cpu_to_le32(arg->ml.partner_info[i].hw_link_id);
partner_info->vdev_id = cpu_to_le32(pinfo->vdev_id);
partner_info->hw_link_id = cpu_to_le32(pinfo->hw_link_id);
ether_addr_copy(partner_info->vdev_addr.addr,
arg->ml.partner_info[i].addr);
pinfo->addr);
partner_info->flags =
cpu_to_le32(ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER);
partner_info->ieee_link_id = cpu_to_le32(pinfo->ieee_link_id);
ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %d hw_link_id %d macaddr%pM\n",
partner_info->vdev_id, partner_info->hw_link_id,
partner_info->vdev_addr.addr);
ath12k_dbg(ar->ab, ATH12K_DBG_WMI, "partner vdev %u hw_link_id %u macaddr %pM link_id %u ml flags 0x%x\n",
pinfo->vdev_id, pinfo->hw_link_id,
pinfo->addr, pinfo->ieee_link_id,
le32_to_cpu(partner_info->flags));
partner_info++;
}
@ -2629,9 +2639,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar,
struct wmi_tlv *tlv;
void *ptr;
int i, ret, len;
u32 *tmp_ptr, extraie_len_with_pad = 0;
struct ath12k_wmi_hint_short_ssid_arg *s_ssid = NULL;
struct ath12k_wmi_hint_bssid_arg *hint_bssid = NULL;
__le32 *tmp_ptr;
u32 extraie_len_with_pad = 0;
struct ath12k_wmi_hint_short_ssid_params *s_ssid = NULL;
struct ath12k_wmi_hint_bssid_params *hint_bssid = NULL;
len = sizeof(*cmd);
@ -2714,9 +2725,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar,
tlv = ptr;
tlv->header = ath12k_wmi_tlv_hdr(WMI_TAG_ARRAY_UINT32, len);
ptr += TLV_HDR_SIZE;
tmp_ptr = (u32 *)ptr;
tmp_ptr = (__le32 *)ptr;
memcpy(tmp_ptr, arg->chan_list, arg->num_chan * 4);
for (i = 0; i < arg->num_chan; i++)
tmp_ptr[i] = cpu_to_le32(arg->chan_list[i]);
ptr += len;
@ -2772,8 +2784,10 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar,
ptr += TLV_HDR_SIZE;
s_ssid = ptr;
for (i = 0; i < arg->num_hint_s_ssid; ++i) {
s_ssid->freq_flags = arg->hint_s_ssid[i].freq_flags;
s_ssid->short_ssid = arg->hint_s_ssid[i].short_ssid;
s_ssid->freq_flags =
cpu_to_le32(arg->hint_s_ssid[i].freq_flags);
s_ssid->short_ssid =
cpu_to_le32(arg->hint_s_ssid[i].short_ssid);
s_ssid++;
}
ptr += len;
@ -2787,9 +2801,9 @@ int ath12k_wmi_send_scan_start_cmd(struct ath12k *ar,
hint_bssid = ptr;
for (i = 0; i < arg->num_hint_bssid; ++i) {
hint_bssid->freq_flags =
arg->hint_bssid[i].freq_flags;
ether_addr_copy(&arg->hint_bssid[i].bssid.addr[0],
&hint_bssid->bssid.addr[0]);
cpu_to_le32(arg->hint_bssid[i].freq_flags);
ether_addr_copy(&hint_bssid->bssid.addr[0],
&arg->hint_bssid[i].bssid.addr[0]);
hint_bssid++;
}
}
@ -5154,6 +5168,7 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
__le32 cap_info_internal)
{
struct ath12k_band_cap *cap_band = &pdev->cap.band[band];
u8 *phy_cap = (u8 *)&cap_band->eht_cap_phy_info[0];
u32 support_320mhz;
u8 i;
@ -5167,8 +5182,22 @@ static void ath12k_wmi_eht_caps_parse(struct ath12k_pdev *pdev, u32 band,
for (i = 0; i < WMI_MAX_EHTCAP_PHY_SIZE; i++)
cap_band->eht_cap_phy_info[i] = le32_to_cpu(cap_phy_info[i]);
if (band == NL80211_BAND_6GHZ)
if (band == NL80211_BAND_6GHZ) {
cap_band->eht_cap_phy_info[0] |= support_320mhz;
} else {
/*
* Firmware may report 6 GHz/320 MHz specific capabilities for
* non-6 GHz bands, so explicitly clear them.
*/
phy_cap[0] &= ~IEEE80211_EHT_PHY_CAP0_320MHZ_IN_6GHZ;
phy_cap[1] &= ~IEEE80211_EHT_PHY_CAP1_BEAMFORMEE_SS_320MHZ_MASK;
phy_cap[2] &= ~IEEE80211_EHT_PHY_CAP2_SOUNDING_DIM_320MHZ_MASK;
phy_cap[3] &= ~IEEE80211_EHT_PHY_CAP3_SOUNDING_DIM_320MHZ_MASK;
phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_MCS15_SUPP_320MHZ;
phy_cap[6] &= ~IEEE80211_EHT_PHY_CAP6_EHT_DUP_6GHZ_SUPP;
phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_NON_OFDMA_UL_MU_MIMO_320MHZ;
phy_cap[7] &= ~IEEE80211_EHT_PHY_CAP7_MU_BEAMFORMER_320MHZ;
}
cap_band->eht_mcs_20_only = le32_to_cpu(supp_mcs[0]);
cap_band->eht_mcs_80 = le32_to_cpu(supp_mcs[1]);
@ -6713,16 +6742,12 @@ static int ath12k_pull_roam_ev(struct ath12k_base *ab, struct sk_buff *skb,
return 0;
}
static int freq_to_idx(struct ath12k *ar, int freq)
static int freq_to_idx(struct ieee80211_hw *hw, int freq)
{
struct ieee80211_supported_band *sband;
struct ieee80211_hw *hw = ath12k_ar_to_hw(ar);
int band, ch, idx = 0;
for (band = NL80211_BAND_2GHZ; band < NUM_NL80211_BANDS; band++) {
if (!ar->mac.sbands[band].channels)
continue;
sband = hw->wiphy->bands[band];
if (!sband)
continue;
@ -7072,25 +7097,29 @@ static void ath12k_peer_delete_resp_event(struct ath12k_base *ab, struct sk_buff
{
struct wmi_peer_delete_resp_event peer_del_resp;
struct ath12k *ar;
u32 vdev_id;
if (ath12k_pull_peer_del_resp_ev(ab, skb, &peer_del_resp) != 0) {
ath12k_warn(ab, "failed to extract peer delete resp");
ath12k_warn(ab, "failed to extract peer delete resp\n");
return;
}
vdev_id = le32_to_cpu(peer_del_resp.vdev_id);
rcu_read_lock();
ar = ath12k_mac_get_ar_by_vdev_id(ab, le32_to_cpu(peer_del_resp.vdev_id));
ar = ath12k_mac_get_ar_by_vdev_id(ab, vdev_id);
if (!ar) {
ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d",
peer_del_resp.vdev_id);
ath12k_warn(ab, "invalid vdev id in peer delete resp ev %d\n",
vdev_id);
rcu_read_unlock();
return;
}
complete(&ar->peer_delete_done);
ath12k_peer_delete_resp_signal(ar, vdev_id,
peer_del_resp.peer_macaddr.addr);
rcu_read_unlock();
ath12k_dbg(ab, ATH12K_DBG_WMI, "peer delete resp for vdev id %d addr %pM\n",
peer_del_resp.vdev_id, peer_del_resp.peer_macaddr.addr);
vdev_id, peer_del_resp.peer_macaddr.addr);
}
static void ath12k_vdev_delete_resp_event(struct ath12k_base *ab,
@ -7629,6 +7658,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb)
{
struct wmi_chan_info_event ch_info_ev = {};
struct ath12k *ar;
struct ath12k_hw *ah;
struct survey_info *survey;
int idx;
/* HW channel counters frequency value in hertz */
@ -7660,6 +7690,7 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb)
return;
}
spin_lock_bh(&ar->data_lock);
ah = ath12k_ar_to_ah(ar);
switch (ar->scan.state) {
case ATH12K_SCAN_IDLE:
@ -7671,8 +7702,8 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb)
break;
}
idx = freq_to_idx(ar, le32_to_cpu(ch_info_ev.freq));
if (idx >= ARRAY_SIZE(ar->survey)) {
idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(ch_info_ev.freq));
if (idx >= ARRAY_SIZE(ah->survey)) {
ath12k_warn(ab, "chan info: invalid frequency %d (idx %d out of bounds)\n",
ch_info_ev.freq, idx);
goto exit;
@ -7685,14 +7716,20 @@ static void ath12k_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb)
cc_freq_hz = (le32_to_cpu(ch_info_ev.mac_clk_mhz) * 1000);
if (ch_info_ev.cmd_flags == WMI_CHAN_INFO_START_RESP) {
survey = &ar->survey[idx];
memset(survey, 0, sizeof(*survey));
survey->noise = le32_to_cpu(ch_info_ev.noise_floor);
survey->filled = SURVEY_INFO_NOISE_DBM | SURVEY_INFO_TIME |
SURVEY_INFO_TIME_BUSY;
survey->time = div_u64(le32_to_cpu(ch_info_ev.cycle_count), cc_freq_hz);
survey->time_busy = div_u64(le32_to_cpu(ch_info_ev.rx_clear_count),
cc_freq_hz);
scoped_guard(spinlock_bh, &ah->survey_lock) {
survey = &ah->survey[idx];
memset(survey, 0, sizeof(*survey));
survey->noise = le32_to_cpu(ch_info_ev.noise_floor);
survey->time =
div_u64(le32_to_cpu(ch_info_ev.cycle_count),
cc_freq_hz);
survey->time_busy =
div_u64(le32_to_cpu(ch_info_ev.rx_clear_count),
cc_freq_hz);
survey->filled = SURVEY_INFO_NOISE_DBM |
SURVEY_INFO_TIME |
SURVEY_INFO_TIME_BUSY;
}
}
exit:
spin_unlock_bh(&ar->data_lock);
@ -7705,6 +7742,7 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb)
struct wmi_pdev_bss_chan_info_event bss_ch_info_ev = {};
struct survey_info *survey;
struct ath12k *ar;
struct ath12k_hw *ah;
u32 cc_freq_hz = ab->cc_freq_hz;
u64 busy, total, tx, rx, rx_bss;
int idx;
@ -7745,28 +7783,31 @@ ath12k_pdev_bss_chan_info_event(struct ath12k_base *ab, struct sk_buff *skb)
return;
}
spin_lock_bh(&ar->data_lock);
idx = freq_to_idx(ar, le32_to_cpu(bss_ch_info_ev.freq));
if (idx >= ARRAY_SIZE(ar->survey)) {
ah = ath12k_ar_to_ah(ar);
idx = freq_to_idx(ath12k_ar_to_hw(ar), le32_to_cpu(bss_ch_info_ev.freq));
if (idx >= ARRAY_SIZE(ah->survey)) {
ath12k_warn(ab, "bss chan info: invalid frequency %d (idx %d out of bounds)\n",
bss_ch_info_ev.freq, idx);
goto exit;
}
survey = &ar->survey[idx];
scoped_guard(spinlock_bh, &ah->survey_lock) {
survey = &ah->survey[idx];
survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor);
survey->time = div_u64(total, cc_freq_hz);
survey->time_busy = div_u64(busy, cc_freq_hz);
survey->time_rx = div_u64(rx_bss, cc_freq_hz);
survey->time_tx = div_u64(tx, cc_freq_hz);
survey->filled |= (SURVEY_INFO_NOISE_DBM |
SURVEY_INFO_TIME |
SURVEY_INFO_TIME_BUSY |
SURVEY_INFO_TIME_RX |
SURVEY_INFO_TIME_TX);
}
survey->noise = le32_to_cpu(bss_ch_info_ev.noise_floor);
survey->time = div_u64(total, cc_freq_hz);
survey->time_busy = div_u64(busy, cc_freq_hz);
survey->time_rx = div_u64(rx_bss, cc_freq_hz);
survey->time_tx = div_u64(tx, cc_freq_hz);
survey->filled |= (SURVEY_INFO_NOISE_DBM |
SURVEY_INFO_TIME |
SURVEY_INFO_TIME_BUSY |
SURVEY_INFO_TIME_RX |
SURVEY_INFO_TIME_TX);
exit:
spin_unlock_bh(&ar->data_lock);
complete(&ar->bss_survey_done);
rcu_read_unlock();
@ -10257,12 +10298,12 @@ static void ath12k_wmi_op_rx(struct ath12k_base *ab, struct sk_buff *skb)
struct wmi_cmd_hdr *cmd_hdr;
enum wmi_tlv_event_id id;
cmd_hdr = (struct wmi_cmd_hdr *)skb->data;
id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID);
if (!skb_pull(skb, sizeof(struct wmi_cmd_hdr)))
cmd_hdr = skb_pull_data(skb, sizeof(*cmd_hdr));
if (!cmd_hdr)
goto out;
id = le32_get_bits(cmd_hdr->cmd_id, WMI_CMD_HDR_CMD_ID);
switch (id) {
/* Process all the WMI events here */
case WMI_SERVICE_READY_EVENTID:

View File

@ -1083,6 +1083,7 @@ enum wmi_tlv_pdev_param {
WMI_PDEV_PARAM_RADIO_CHAN_STATS_ENABLE,
WMI_PDEV_PARAM_RADIO_DIAGNOSIS_ENABLE,
WMI_PDEV_PARAM_MESH_MCAST_ENABLE,
WMI_PDEV_PARAM_SET_CONG_CTRL_MAX_MSDUS = 0xa6,
WMI_PDEV_PARAM_SET_CMD_OBSS_PD_THRESHOLD = 0xbc,
WMI_PDEV_PARAM_SET_CMD_OBSS_PD_PER_AC = 0xbe,
WMI_PDEV_PARAM_ENABLE_SR_PROHIBIT = 0xc6,
@ -2330,6 +2331,13 @@ enum wmi_slot_time {
WMI_VDEV_SLOT_TIME_SHORT = 2,
};
enum wmi_dtim_policy {
WMI_DTIM_POLICY_IGNORE = 1,
WMI_DTIM_POLICY_NORMAL = 2,
WMI_DTIM_POLICY_STICK = 3,
WMI_DTIM_POLICY_AUTO = 4,
};
enum wmi_preamble {
WMI_VDEV_PREAMBLE_LONG = 1,
WMI_VDEV_PREAMBLE_SHORT = 2,
@ -2954,10 +2962,14 @@ struct wmi_vdev_create_mlo_params {
#define ATH12K_WMI_FLAG_MLO_EMLSR_SUPPORT BIT(6)
#define ATH12K_WMI_FLAG_MLO_FORCED_INACTIVE BIT(7)
#define ATH12K_WMI_FLAG_MLO_LINK_ADD BIT(8)
#define ATH12K_WMI_FLAG_MLO_START_AS_ACTIVE BIT(17)
#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID BIT(18)
#define ATH12K_WMI_FLAG_MLO_IEEE_LINK_IDX_VALID_PARTNER BIT(19)
struct wmi_vdev_start_mlo_params {
__le32 tlv_header;
__le32 flags;
__le32 ieee_link_id;
} __packed;
struct wmi_partner_link_info {
@ -2965,6 +2977,8 @@ struct wmi_partner_link_info {
__le32 vdev_id;
__le32 hw_link_id;
struct ath12k_wmi_mac_addr_params vdev_addr;
__le32 flags;
__le32 ieee_link_id;
} __packed;
struct wmi_vdev_delete_cmd {
@ -3120,6 +3134,7 @@ struct wmi_ml_partner_info {
bool primary_umac;
bool logical_link_idx_valid;
u32 logical_link_idx;
u32 ieee_link_id;
};
struct wmi_ml_arg {
@ -3127,6 +3142,7 @@ struct wmi_ml_arg {
bool assoc_link;
bool mcast_link;
bool link_add;
u32 ieee_link_id;
u8 num_partner_links;
struct wmi_ml_partner_info partner_info[ATH12K_WMI_MLO_MAX_LINKS];
};
@ -3549,6 +3565,16 @@ struct ath12k_wmi_hint_bssid_arg {
struct ath12k_wmi_mac_addr_params bssid;
};
struct ath12k_wmi_hint_short_ssid_params {
__le32 freq_flags;
__le32 short_ssid;
};
struct ath12k_wmi_hint_bssid_params {
__le32 freq_flags;
struct ath12k_wmi_mac_addr_params bssid;
};
struct ath12k_wmi_scan_req_arg {
u32 scan_id;
u32 scan_req_id;

View File

@ -3437,7 +3437,7 @@ ath6kl_mgmt_stypes[NUM_NL80211_IFTYPES] = {
},
};
static struct cfg80211_ops ath6kl_cfg80211_ops = {
static const struct cfg80211_ops ath6kl_cfg80211_ops = {
.add_virtual_intf = ath6kl_cfg80211_add_iface,
.del_virtual_intf = ath6kl_cfg80211_del_iface,
.change_virtual_intf = ath6kl_cfg80211_change_iface,

View File

@ -1296,6 +1296,9 @@ static int ath6kl_wmi_scan_complete_rx(struct wmi *wmi, u8 *datap, int len,
{
struct wmi_scan_complete_event *ev;
if (len < sizeof(*ev))
return -EINVAL;
ev = (struct wmi_scan_complete_event *) datap;
ath6kl_scan_complete_evt(vif, a_sle32_to_cpu(ev->status));
@ -3372,7 +3375,12 @@ static int ath6kl_wmi_get_pmkid_list_event_rx(struct wmi *wmi, u8 *datap,
static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len,
struct ath6kl_vif *vif)
{
struct wmi_addba_req_event *cmd = (struct wmi_addba_req_event *) datap;
struct wmi_addba_req_event *cmd;
if (len < sizeof(*cmd))
return -EINVAL;
cmd = (struct wmi_addba_req_event *)datap;
aggr_recv_addba_req_evt(vif, cmd->tid,
le16_to_cpu(cmd->st_seq_no), cmd->win_sz);
@ -3383,7 +3391,12 @@ static int ath6kl_wmi_addba_req_event_rx(struct wmi *wmi, u8 *datap, int len,
static int ath6kl_wmi_delba_req_event_rx(struct wmi *wmi, u8 *datap, int len,
struct ath6kl_vif *vif)
{
struct wmi_delba_event *cmd = (struct wmi_delba_event *) datap;
struct wmi_delba_event *cmd;
if (len < sizeof(*cmd))
return -EINVAL;
cmd = (struct wmi_delba_event *)datap;
aggr_recv_delba_req_evt(vif, cmd->tid);

View File

@ -381,6 +381,7 @@ struct ar9170 {
unsigned int tx_ack_failures;
unsigned int tx_fcs_errors;
unsigned int rx_dropped;
unsigned int rx_phy_errors;
/* EEPROM */
struct ar9170_eeprom eeprom;

View File

@ -52,7 +52,7 @@ int carl9170_write_reg(struct ar9170 *ar, const u32 reg, const u32 val)
(u8 *) buf, 0, NULL);
if (err) {
if (net_ratelimit()) {
wiphy_err(ar->hw->wiphy, "writing reg %#x "
wiphy_dbg(ar->hw->wiphy, "writing reg %#x "
"(val %#x) failed (%d)\n", reg, val, err);
}
}
@ -78,7 +78,7 @@ int carl9170_read_mreg(struct ar9170 *ar, const int nregs,
4 * nregs, (u8 *)res);
if (err) {
if (net_ratelimit()) {
wiphy_err(ar->hw->wiphy, "reading regs failed (%d)\n",
wiphy_dbg(ar->hw->wiphy, "reading regs failed (%d)\n",
err);
}
return err;

View File

@ -794,6 +794,7 @@ DEBUGFS_READONLY_FILE(tx_janitor_last_run, 64, "last run:%d ms ago",
DEBUGFS_READONLY_FILE(tx_dropped, 20, "%d", ar->tx_dropped);
DEBUGFS_READONLY_FILE(rx_dropped, 20, "%d", ar->rx_dropped);
DEBUGFS_READONLY_FILE(rx_phy_errors, 20, "%d", ar->rx_phy_errors);
DEBUGFS_READONLY_FILE(sniffer_enabled, 20, "%d", ar->sniffer_enabled);
DEBUGFS_READONLY_FILE(rx_software_decryption, 20, "%d",
@ -830,6 +831,7 @@ void carl9170_debugfs_register(struct ar9170 *ar)
DEBUGFS_ADD(tx_ampdu_list_len);
DEBUGFS_ADD(rx_dropped);
DEBUGFS_ADD(rx_phy_errors);
DEBUGFS_ADD(sniffer_enabled);
DEBUGFS_ADD(rx_software_decryption);

View File

@ -908,7 +908,13 @@ static int carl9170_op_config(struct ieee80211_hw *hw, int radio_idx, u32 change
}
if (changed & IEEE80211_CONF_CHANGE_SMPS) {
/* TODO */
/*
* We advertise SM_PS disabled (all chains active).
* mac80211 may still request mode changes, which we
* accept but only support OFF (both chains active).
* Static/dynamic SMPS would require firmware support
* for chain control that the AR9170 does not provide.
*/
err = 0;
}

View File

@ -456,7 +456,9 @@ static void carl9170_rx_phy_status(struct ar9170 *ar,
if (phy->rssi[i] & 0x80)
phy->rssi[i] = ((~phy->rssi[i] & 0x7f) + 1) & 0x7f;
/* TODO: we could do something with phy_errors */
if (phy->phy_err)
ar->rx_phy_errors++;
status->signal = ar->noise[0] + phy->rssi_combined;
}

View File

@ -2326,7 +2326,7 @@ static void wil_probe_client_handle(struct wil6210_priv *wil,
*/
bool alive = (sta->status == wil_sta_connected);
cfg80211_probe_status(ndev, sta->addr, req->cookie, alive,
cfg80211_probe_status(ndev, sta->addr, req->cookie, -1, alive,
0, false, GFP_KERNEL);
}
@ -2379,9 +2379,9 @@ void wil_probe_client_flush(struct wil6210_vif *vif)
mutex_unlock(&vif->probe_client_mutex);
}
static int wil_cfg80211_probe_client(struct wiphy *wiphy,
struct net_device *dev,
const u8 *peer, u64 *cookie)
static int wil_cfg80211_probe_peer(struct wiphy *wiphy,
struct net_device *dev,
const u8 *peer, u64 *cookie)
{
struct wil6210_priv *wil = wiphy_to_wil(wiphy);
struct wil6210_vif *vif = ndev_to_vif(dev);
@ -2660,7 +2660,7 @@ static const struct cfg80211_ops wil_cfg80211_ops = {
.add_station = wil_cfg80211_add_station,
.del_station = wil_cfg80211_del_station,
.change_station = wil_cfg80211_change_station,
.probe_client = wil_cfg80211_probe_client,
.probe_peer = wil_cfg80211_probe_peer,
.change_bss = wil_cfg80211_change_bss,
/* P2P device */
.start_p2p_device = wil_cfg80211_start_p2p_device,

View File

@ -495,7 +495,6 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf,
ssize_t ret;
char *buf;
const size_t bufsize = 1024 * 16; /* 16 kiB buffer */
const size_t buforder = get_order(bufsize);
int err = 0;
if (!count)
@ -518,15 +517,14 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf,
dfile = fops_to_dfs_file(dev, dfops);
if (!dfile->buffer) {
buf = (char *)__get_free_pages(GFP_KERNEL, buforder);
buf = kzalloc(bufsize, GFP_KERNEL);
if (!buf) {
err = -ENOMEM;
goto out_unlock;
}
memset(buf, 0, bufsize);
ret = dfops->read(dev, buf, bufsize);
if (ret <= 0) {
free_pages((unsigned long)buf, buforder);
kfree(buf);
err = ret;
goto out_unlock;
}
@ -538,7 +536,7 @@ static ssize_t b43_debugfs_read(struct file *file, char __user *userbuf,
dfile->buffer,
dfile->data_len);
if (*ppos >= dfile->data_len) {
free_pages((unsigned long)dfile->buffer, buforder);
kfree(dfile->buffer);
dfile->buffer = NULL;
dfile->data_len = 0;
}
@ -577,7 +575,7 @@ static ssize_t b43_debugfs_write(struct file *file,
goto out_unlock;
}
buf = (char *)get_zeroed_page(GFP_KERNEL);
buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf) {
err = -ENOMEM;
goto out_unlock;
@ -591,7 +589,7 @@ static ssize_t b43_debugfs_write(struct file *file,
goto out_freepage;
out_freepage:
free_page((unsigned long)buf);
kfree(buf);
out_unlock:
mutex_unlock(&dev->wl->mutex);

View File

@ -192,7 +192,6 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf,
ssize_t ret;
char *buf;
const size_t bufsize = 1024 * 16; /* 16 KiB buffer */
const size_t buforder = get_order(bufsize);
int err = 0;
if (!count)
@ -215,12 +214,11 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf,
dfile = fops_to_dfs_file(dev, dfops);
if (!dfile->buffer) {
buf = (char *)__get_free_pages(GFP_KERNEL, buforder);
buf = kzalloc(bufsize, GFP_KERNEL);
if (!buf) {
err = -ENOMEM;
goto out_unlock;
}
memset(buf, 0, bufsize);
if (dfops->take_irqlock) {
spin_lock_irq(&dev->wl->irq_lock);
ret = dfops->read(dev, buf, bufsize);
@ -228,7 +226,7 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf,
} else
ret = dfops->read(dev, buf, bufsize);
if (ret <= 0) {
free_pages((unsigned long)buf, buforder);
kfree(buf);
err = ret;
goto out_unlock;
}
@ -240,7 +238,7 @@ static ssize_t b43legacy_debugfs_read(struct file *file, char __user *userbuf,
dfile->buffer,
dfile->data_len);
if (*ppos >= dfile->data_len) {
free_pages((unsigned long)dfile->buffer, buforder);
kfree(dfile->buffer);
dfile->buffer = NULL;
dfile->data_len = 0;
}
@ -279,7 +277,7 @@ static ssize_t b43legacy_debugfs_write(struct file *file,
goto out_unlock;
}
buf = (char *)get_zeroed_page(GFP_KERNEL);
buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf) {
err = -ENOMEM;
goto out_unlock;
@ -298,7 +296,7 @@ static ssize_t b43legacy_debugfs_write(struct file *file,
goto out_freepage;
out_freepage:
free_page((unsigned long)buf);
kfree(buf);
out_unlock:
mutex_unlock(&dev->wl->mutex);

View File

@ -989,10 +989,10 @@ static const struct sdio_device_id brcmf_sdmmc_ids[] = {
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43364, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4335_4339, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4339, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43430, CYW),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43439, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4345, CYW),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_43455, CYW),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4354, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4356, WCC),
BRCMF_SDIO_DEVICE(SDIO_DEVICE_ID_BROADCOM_4359, WCC),

View File

@ -8,6 +8,7 @@
#include <linux/kernel.h>
#include <linux/etherdevice.h>
#include <linux/module.h>
#include <linux/unaligned.h>
#include <linux/vmalloc.h>
#include <net/cfg80211.h>
#include <net/netlink.h>
@ -2174,6 +2175,9 @@ brcmf_set_key_mgmt(struct net_device *ndev, struct cfg80211_connect_params *sme)
val = WPA2_AUTH_PSK | WPA2_AUTH_FT;
profile->is_ft = true;
break;
case WLAN_AKM_SUITE_WFA_DPP:
val = WFA_AUTH_DPP;
break;
default:
bphy_err(drvr, "invalid akm suite (%d)\n",
sme->crypto.akm_suites[0]);
@ -2483,43 +2487,50 @@ brcmf_cfg80211_connect(struct wiphy *wiphy, struct net_device *ndev,
goto done;
}
if (sme->crypto.psk &&
profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) {
if (WARN_ON(profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE)) {
err = -EINVAL;
goto done;
}
brcmf_dbg(INFO, "using PSK offload\n");
profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
}
if (brcmf_feat_is_enabled(ifp, BRCMF_FEAT_FWSUP)) {
u32 akm = sme->crypto.n_akm_suites ? sme->crypto.akm_suites[0] : 0;
bool is_sae_akm = akm == WLAN_AKM_SUITE_SAE ||
akm == WLAN_AKM_SUITE_FT_OVER_SAE;
if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) {
/* enable firmware supplicant for this interface */
err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1);
if (err < 0) {
bphy_err(drvr, "failed to enable fw supplicant\n");
goto done;
if (sme->crypto.psk && !is_sae_akm &&
profile->use_fwsup != BRCMF_PROFILE_FWSUP_SAE) {
if (WARN_ON(profile->use_fwsup !=
BRCMF_PROFILE_FWSUP_NONE)) {
err = -EINVAL;
goto done;
}
brcmf_dbg(INFO, "using PSK offload\n");
profile->use_fwsup = BRCMF_PROFILE_FWSUP_PSK;
}
}
if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK)
err = brcmf_set_pmk(ifp, sme->crypto.psk,
BRCMF_WSEC_MAX_PSK_LEN);
else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE) {
/* clean up user-space RSNE */
err = brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0);
if (err) {
bphy_err(drvr, "failed to clean up user-space RSNE\n");
goto done;
if (profile->use_fwsup != BRCMF_PROFILE_FWSUP_NONE) {
/* enable firmware supplicant for this interface */
err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 1);
if (err < 0) {
bphy_err(drvr, "failed to enable fw supplicant\n");
goto done;
}
} else {
err = brcmf_fil_iovar_int_set(ifp, "sup_wpa", 0);
}
err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto);
if (!err && sme->crypto.psk)
if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_PSK)
err = brcmf_set_pmk(ifp, sme->crypto.psk,
BRCMF_WSEC_MAX_PSK_LEN);
else if (profile->use_fwsup == BRCMF_PROFILE_FWSUP_SAE &&
sme->crypto.sae_pwd &&
brcmf_feat_is_enabled(ifp, BRCMF_FEAT_SAE)) {
/* clean up user-space RSNE */
if (brcmf_fil_iovar_data_set(ifp, "wpaie", NULL, 0)) {
bphy_err(drvr, "failed to clean up user-space RSNE\n");
goto done;
}
err = brcmf_fwvid_set_sae_password(ifp, &sme->crypto);
if (!err && sme->crypto.psk)
err = brcmf_set_pmk(ifp, sme->crypto.psk,
BRCMF_WSEC_MAX_PSK_LEN);
}
if (err)
goto done;
}
if (err)
goto done;
/* Join with specific BSSID and cached SSID
* If SSID is zero join based on BSSID only
*/
@ -4538,6 +4549,11 @@ static bool brcmf_valid_wpa_oui(u8 *oui, bool is_rsn_ie)
return (memcmp(oui, WPA_OUI, TLV_OUI_LEN) == 0);
}
static bool brcmf_valid_dpp_suite(u8 *oui)
{
return get_unaligned_be32(oui) == WLAN_AKM_SUITE_WFA_DPP;
}
static s32
brcmf_configure_wpaie(struct brcmf_if *ifp,
const struct brcmf_vs_tlv *wpa_ie,
@ -4651,42 +4667,47 @@ brcmf_configure_wpaie(struct brcmf_if *ifp,
goto exit;
}
for (i = 0; i < count; i++) {
if (!brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) {
if (brcmf_valid_dpp_suite(&data[offset])) {
wpa_auth |= WFA_AUTH_DPP;
offset += TLV_OUI_LEN;
} else if (brcmf_valid_wpa_oui(&data[offset], is_rsn_ie)) {
offset += TLV_OUI_LEN;
switch (data[offset]) {
case RSN_AKM_NONE:
brcmf_dbg(TRACE, "RSN_AKM_NONE\n");
wpa_auth |= WPA_AUTH_NONE;
break;
case RSN_AKM_UNSPECIFIED:
brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n");
is_rsn_ie ?
(wpa_auth |= WPA2_AUTH_UNSPECIFIED) :
(wpa_auth |= WPA_AUTH_UNSPECIFIED);
break;
case RSN_AKM_PSK:
brcmf_dbg(TRACE, "RSN_AKM_PSK\n");
is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) :
(wpa_auth |= WPA_AUTH_PSK);
break;
case RSN_AKM_SHA256_PSK:
brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n");
wpa_auth |= WPA2_AUTH_PSK_SHA256;
break;
case RSN_AKM_SHA256_1X:
brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n");
wpa_auth |= WPA2_AUTH_1X_SHA256;
break;
case RSN_AKM_SAE:
brcmf_dbg(TRACE, "RSN_AKM_SAE\n");
wpa_auth |= WPA3_AUTH_SAE_PSK;
break;
default:
bphy_err(drvr, "Invalid key mgmt info\n");
}
} else {
err = -EINVAL;
bphy_err(drvr, "invalid OUI\n");
goto exit;
}
offset += TLV_OUI_LEN;
switch (data[offset]) {
case RSN_AKM_NONE:
brcmf_dbg(TRACE, "RSN_AKM_NONE\n");
wpa_auth |= WPA_AUTH_NONE;
break;
case RSN_AKM_UNSPECIFIED:
brcmf_dbg(TRACE, "RSN_AKM_UNSPECIFIED\n");
is_rsn_ie ? (wpa_auth |= WPA2_AUTH_UNSPECIFIED) :
(wpa_auth |= WPA_AUTH_UNSPECIFIED);
break;
case RSN_AKM_PSK:
brcmf_dbg(TRACE, "RSN_AKM_PSK\n");
is_rsn_ie ? (wpa_auth |= WPA2_AUTH_PSK) :
(wpa_auth |= WPA_AUTH_PSK);
break;
case RSN_AKM_SHA256_PSK:
brcmf_dbg(TRACE, "RSN_AKM_MFP_PSK\n");
wpa_auth |= WPA2_AUTH_PSK_SHA256;
break;
case RSN_AKM_SHA256_1X:
brcmf_dbg(TRACE, "RSN_AKM_MFP_1X\n");
wpa_auth |= WPA2_AUTH_1X_SHA256;
break;
case RSN_AKM_SAE:
brcmf_dbg(TRACE, "RSN_AKM_SAE\n");
wpa_auth |= WPA3_AUTH_SAE_PSK;
break;
default:
bphy_err(drvr, "Invalid key mgmt info\n");
}
offset++;
}
@ -4706,10 +4727,12 @@ brcmf_configure_wpaie(struct brcmf_if *ifp,
*/
if (!(wpa_auth & (WPA2_AUTH_PSK_SHA256 |
WPA2_AUTH_1X_SHA256 |
WFA_AUTH_DPP |
WPA3_AUTH_SAE_PSK))) {
err = -EINVAL;
goto exit;
}
/* Firmware has requirement that WPA2_AUTH_PSK/
* WPA2_AUTH_UNSPECIFIED be set, if SHA256 OUI
* is to be included in the rsn ie.

View File

@ -6,6 +6,7 @@
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/rtnetlink.h>
#include <linux/unaligned.h>
#include <net/cfg80211.h>
#include <brcmu_wifi.h>
@ -44,9 +45,6 @@
#define BRCMF_SCB_TIMEOUT_VALUE 20
#define P2P_VER 9 /* P2P version: 9=WiFi P2P v1.0 */
#define P2P_PUB_AF_CATEGORY 0x04
#define P2P_PUB_AF_ACTION 0x09
#define P2P_AF_CATEGORY 0x7f
#define P2P_OUI "\x50\x6F\x9A" /* P2P OUI */
#define P2P_OUI_LEN 3 /* P2P OUI length */
@ -143,10 +141,10 @@ struct brcmf_p2p_scan_le {
/**
* struct brcmf_p2p_pub_act_frame - WiFi P2P Public Action Frame
*
* @category: P2P_PUB_AF_CATEGORY
* @action: P2P_PUB_AF_ACTION
* @category: WLAN_CATEGORY_PUBLIC
* @action: WLAN_PUB_ACTION_VENDOR_SPECIFIC
* @oui: P2P_OUI
* @oui_type: OUI type - P2P_VER
* @oui_type: OUI type - WLAN_OUI_TYPE_WFA_P2P
* @subtype: OUI subtype - P2P_TYPE_*
* @dialog_token: nonzero, identifies req/rsp transaction
* @elts: Variable length information elements.
@ -166,7 +164,7 @@ struct brcmf_p2p_pub_act_frame {
*
* @category: P2P_AF_CATEGORY
* @oui: OUI - P2P_OUI
* @type: OUI Type - P2P_VER
* @type: OUI Type - WLAN_OUI_TYPE_WFA_P2P
* @subtype: OUI Subtype - P2P_AF_*
* @dialog_token: nonzero, identifies req/resp tranaction
* @elts: Variable length information elements.
@ -228,10 +226,38 @@ static bool brcmf_p2p_is_pub_action(void *frame, u32 frame_len)
if (frame_len < sizeof(*pact_frm))
return false;
if (pact_frm->category == P2P_PUB_AF_CATEGORY &&
pact_frm->action == P2P_PUB_AF_ACTION &&
pact_frm->oui_type == P2P_VER &&
memcmp(pact_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0)
if (pact_frm->category == WLAN_CATEGORY_PUBLIC &&
pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC &&
pact_frm->oui_type == WLAN_OUI_TYPE_WFA_P2P &&
get_unaligned_be24(pact_frm->oui) == WLAN_OUI_WFA)
return true;
return false;
}
/**
* brcmf_p2p_is_dpp_pub_action() - true if dpp public type frame.
*
* @frame: action frame data.
* @frame_len: length of action frame data.
*
* Determine if action frame is dpp public action type
*/
static bool brcmf_p2p_is_dpp_pub_action(void *frame, u32 frame_len)
{
struct brcmf_p2p_pub_act_frame *pact_frm;
if (!frame)
return false;
pact_frm = (struct brcmf_p2p_pub_act_frame *)frame;
if (frame_len < sizeof(struct brcmf_p2p_pub_act_frame) - 1)
return false;
if (pact_frm->category == WLAN_CATEGORY_PUBLIC &&
pact_frm->action == WLAN_PUB_ACTION_VENDOR_SPECIFIC &&
pact_frm->oui_type == WLAN_OUI_TYPE_WFA_DPP &&
get_unaligned_be24(pact_frm->oui) == WLAN_OUI_WFA)
return true;
return false;
@ -257,7 +283,7 @@ static bool brcmf_p2p_is_p2p_action(void *frame, u32 frame_len)
return false;
if (act_frm->category == P2P_AF_CATEGORY &&
act_frm->type == P2P_VER &&
act_frm->type == WLAN_OUI_TYPE_WFA_P2P &&
memcmp(act_frm->oui, P2P_OUI, P2P_OUI_LEN) == 0)
return true;
@ -1782,7 +1808,9 @@ bool brcmf_p2p_send_action_frame(struct brcmf_if *ifp,
goto exit;
}
} else if (brcmf_p2p_is_p2p_action(action_frame->data,
action_frame_len)) {
action_frame_len) ||
brcmf_p2p_is_dpp_pub_action(action_frame->data,
action_frame_len)) {
/* do not configure anything. it will be */
/* sent with a default configuration */
} else {

View File

@ -233,6 +233,8 @@ static inline bool ac_bitmap_tst(u8 bitmap, int prec)
#define WPA3_AUTH_SAE_PSK 0x40000 /* SAE with 4-way handshake */
#define WFA_AUTH_DPP 0x200000 /* WFA DPP AUTH */
#define DOT11_DEFAULT_RTS_LEN 2347
#define DOT11_DEFAULT_FRAG_LEN 2346

View File

@ -10,7 +10,7 @@ config P54_COMMON
also need to be enabled in order to support any devices.
These devices require softmac firmware which can be found at
<http://wireless.wiki.kernel.org/en/users/Drivers/p54>
<https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html>
If you choose to build a module, it'll be called p54common.
@ -22,7 +22,7 @@ config P54_USB
This driver is for USB isl38xx based wireless cards.
These devices require softmac firmware which can be found at
<http://wireless.wiki.kernel.org/en/users/Drivers/p54>
<https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html>
If you choose to build a module, it'll be called p54usb.
@ -36,7 +36,7 @@ config P54_PCI
supported by the fullmac driver/firmware.
This driver requires softmac firmware which can be found at
<http://wireless.wiki.kernel.org/en/users/Drivers/p54>
<https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html>
If you choose to build a module, it'll be called p54pci.

View File

@ -131,9 +131,7 @@ int p54_parse_firmware(struct ieee80211_hw *dev, const struct firmware *fw)
if (priv->fw_var < 0x500)
wiphy_info(priv->hw->wiphy,
"you are using an obsolete firmware. "
"visit http://wireless.wiki.kernel.org/en/users/Drivers/p54 "
"and grab one for \"kernel >= 2.6.28\"!\n");
"you are using an obsolete firmware. visit https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54.html and grab one for \"kernel >= 2.6.28\"!\n");
if (priv->fw_var >= 0x300) {
/* Firmware supports QoS, use it! */

View File

@ -36,7 +36,7 @@ static struct usb_driver p54u_driver;
* Note:
*
* Always update our wiki's device list (located at:
* http://wireless.wiki.kernel.org/en/users/Drivers/p54/devices ),
* https://wireless.docs.kernel.org/en/latest/en/users/drivers/p54/devices.html ),
* whenever you add a new device.
*/

View File

@ -35,8 +35,7 @@ static ssize_t lbs_dev_info(struct file *file, char __user *userbuf,
{
struct lbs_private *priv = file->private_data;
size_t pos = 0;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
ssize_t res;
if (!buf)
return -ENOMEM;
@ -48,7 +47,7 @@ static ssize_t lbs_dev_info(struct file *file, char __user *userbuf,
res = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
free_page(addr);
kfree(buf);
return res;
}
@ -96,8 +95,7 @@ static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf,
ssize_t ret;
size_t pos = 0;
struct sleep_params sp;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
@ -113,7 +111,7 @@ static ssize_t lbs_sleepparams_read(struct file *file, char __user *userbuf,
ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
out_unlock:
free_page(addr);
kfree(buf);
return ret;
}
@ -165,8 +163,7 @@ static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf,
struct lbs_private *priv = file->private_data;
ssize_t ret;
size_t pos = 0;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
@ -174,7 +171,7 @@ static ssize_t lbs_host_sleep_read(struct file *file, char __user *userbuf,
ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
free_page(addr);
kfree(buf);
return ret;
}
@ -228,7 +225,7 @@ static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask,
u8 freq;
int events = 0;
buf = (char *)get_zeroed_page(GFP_KERNEL);
buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
@ -261,7 +258,7 @@ static ssize_t lbs_threshold_read(uint16_t tlv_type, uint16_t event_mask,
kfree(subscribed);
out_page:
free_page((unsigned long)buf);
kfree(buf);
return ret;
}
@ -436,8 +433,7 @@ static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf,
struct lbs_private *priv = file->private_data;
ssize_t pos = 0;
int ret;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
u32 val = 0;
if (!buf)
@ -450,7 +446,7 @@ static ssize_t lbs_rdmac_read(struct file *file, char __user *userbuf,
priv->mac_offset, val);
ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
}
free_page(addr);
kfree(buf);
return ret;
}
@ -506,8 +502,7 @@ static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf,
struct lbs_private *priv = file->private_data;
ssize_t pos = 0;
int ret;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
u32 val;
if (!buf)
@ -520,7 +515,7 @@ static ssize_t lbs_rdbbp_read(struct file *file, char __user *userbuf,
priv->bbp_offset, val);
ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
}
free_page(addr);
kfree(buf);
return ret;
}
@ -578,8 +573,7 @@ static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf,
struct lbs_private *priv = file->private_data;
ssize_t pos = 0;
int ret;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
u32 val;
if (!buf)
@ -592,7 +586,7 @@ static ssize_t lbs_rdrf_read(struct file *file, char __user *userbuf,
priv->rf_offset, val);
ret = simple_read_from_buffer(userbuf, count, ppos, buf, pos);
}
free_page(addr);
kfree(buf);
return ret;
}
@ -812,8 +806,7 @@ static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf,
char *p;
int i;
struct debug_data *d;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
@ -836,7 +829,7 @@ static ssize_t lbs_debugfs_read(struct file *file, char __user *userbuf,
res = simple_read_from_buffer(userbuf, count, ppos, p, pos);
free_page(addr);
kfree(buf);
return res;
}

View File

@ -4558,9 +4558,9 @@ mwifiex_cfg80211_disassociate(struct wiphy *wiphy,
}
static int
mwifiex_cfg80211_probe_client(struct wiphy *wiphy,
struct net_device *dev, const u8 *peer,
u64 *cookie)
mwifiex_cfg80211_probe_peer(struct wiphy *wiphy,
struct net_device *dev, const u8 *peer,
u64 *cookie)
{
/* hostapd looks for NL80211_CMD_PROBE_CLIENT support; otherwise,
* it requires monitor-mode support (which mwifiex doesn't support).
@ -4726,7 +4726,7 @@ int mwifiex_register_cfg80211(struct mwifiex_adapter *adapter)
ops->disassoc = mwifiex_cfg80211_disassociate;
ops->disconnect = NULL;
ops->connect = NULL;
ops->probe_client = mwifiex_cfg80211_probe_client;
ops->probe_peer = mwifiex_cfg80211_probe_peer;
}
wiphy->max_scan_ssids = MWIFIEX_MAX_SSID_LIST_LENGTH;
wiphy->max_scan_ie_len = MWIFIEX_MAX_VSIE_LEN;

View File

@ -6,6 +6,7 @@
*/
#include <linux/debugfs.h>
#include <linux/slab.h>
#include "main.h"
#include "11n.h"
@ -67,8 +68,8 @@ mwifiex_info_read(struct file *file, char __user *ubuf,
struct net_device *netdev = priv->netdev;
struct netdev_hw_addr *ha;
struct netdev_queue *txq;
unsigned long page = get_zeroed_page(GFP_KERNEL);
char *p = (char *) page, fmt[64];
char *page = kzalloc(PAGE_SIZE, GFP_KERNEL);
char *p = page, fmt[64];
struct mwifiex_bss_info info;
ssize_t ret;
int i = 0;
@ -133,11 +134,10 @@ mwifiex_info_read(struct file *file, char __user *ubuf,
}
p += sprintf(p, "\n");
ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page,
(unsigned long) p - page);
ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page);
free_and_exit:
free_page(page);
kfree(page);
return ret;
}
@ -168,8 +168,8 @@ mwifiex_getlog_read(struct file *file, char __user *ubuf,
{
struct mwifiex_private *priv =
(struct mwifiex_private *) file->private_data;
unsigned long page = get_zeroed_page(GFP_KERNEL);
char *p = (char *) page;
char *page = kzalloc(PAGE_SIZE, GFP_KERNEL);
char *p = page;
ssize_t ret;
struct mwifiex_ds_get_stats stats;
@ -220,11 +220,10 @@ mwifiex_getlog_read(struct file *file, char __user *ubuf,
stats.bcn_miss_cnt);
ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page,
(unsigned long) p - page);
ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page);
free_and_exit:
free_page(page);
kfree(page);
return ret;
}
@ -247,8 +246,8 @@ mwifiex_histogram_read(struct file *file, char __user *ubuf,
ssize_t ret;
struct mwifiex_histogram_data *phist_data;
int i, value;
unsigned long page = get_zeroed_page(GFP_KERNEL);
char *p = (char *)page;
char *page = kzalloc(PAGE_SIZE, GFP_KERNEL);
char *p = page;
if (!p)
return -ENOMEM;
@ -309,11 +308,10 @@ mwifiex_histogram_read(struct file *file, char __user *ubuf,
i, value);
}
ret = simple_read_from_buffer(ubuf, count, ppos, (char *)page,
(unsigned long)p - page);
ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page);
free_and_exit:
free_page(page);
kfree(page);
return ret;
}
@ -383,8 +381,8 @@ mwifiex_debug_read(struct file *file, char __user *ubuf,
{
struct mwifiex_private *priv =
(struct mwifiex_private *) file->private_data;
unsigned long page = get_zeroed_page(GFP_KERNEL);
char *p = (char *) page;
char *page = kzalloc(PAGE_SIZE, GFP_KERNEL);
char *p = page;
ssize_t ret;
if (!p)
@ -396,11 +394,10 @@ mwifiex_debug_read(struct file *file, char __user *ubuf,
p += mwifiex_debug_info_to_buffer(priv, p, &info);
ret = simple_read_from_buffer(ubuf, count, ppos, (char *) page,
(unsigned long) p - page);
ret = simple_read_from_buffer(ubuf, count, ppos, page, p - page);
free_and_exit:
free_page(page);
kfree(page);
return ret;
}
@ -457,8 +454,7 @@ mwifiex_regrdwr_read(struct file *file, char __user *ubuf,
{
struct mwifiex_private *priv =
(struct mwifiex_private *) file->private_data;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *) addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
int pos = 0, ret = 0;
u32 reg_value;
@ -497,7 +493,7 @@ mwifiex_regrdwr_read(struct file *file, char __user *ubuf,
ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
done:
free_page(addr);
kfree(buf);
return ret;
}
@ -511,8 +507,7 @@ mwifiex_debug_mask_read(struct file *file, char __user *ubuf,
{
struct mwifiex_private *priv =
(struct mwifiex_private *)file->private_data;
unsigned long page = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)page;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
size_t ret = 0;
int pos = 0;
@ -523,7 +518,7 @@ mwifiex_debug_mask_read(struct file *file, char __user *ubuf,
priv->adapter->debug_mask);
ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
free_page(page);
kfree(buf);
return ret;
}
@ -652,8 +647,7 @@ mwifiex_memrw_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct mwifiex_private *priv = (void *)file->private_data;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
int ret, pos = 0;
if (!buf)
@ -663,7 +657,7 @@ mwifiex_memrw_read(struct file *file, char __user *ubuf,
priv->mem_rw.value);
ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
free_page(addr);
kfree(buf);
return ret;
}
@ -719,8 +713,7 @@ mwifiex_rdeeprom_read(struct file *file, char __user *ubuf,
{
struct mwifiex_private *priv =
(struct mwifiex_private *) file->private_data;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *) addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
int pos, ret, i;
u8 value[MAX_EEPROM_DATA];
@ -749,7 +742,7 @@ mwifiex_rdeeprom_read(struct file *file, char __user *ubuf,
done:
ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
out_free:
free_page(addr);
kfree(buf);
return ret;
}
@ -820,8 +813,7 @@ mwifiex_hscfg_read(struct file *file, char __user *ubuf,
size_t count, loff_t *ppos)
{
struct mwifiex_private *priv = (void *)file->private_data;
unsigned long addr = get_zeroed_page(GFP_KERNEL);
char *buf = (char *)addr;
char *buf = kzalloc(PAGE_SIZE, GFP_KERNEL);
int pos, ret;
struct mwifiex_ds_hs_cfg hscfg;
@ -836,7 +828,7 @@ mwifiex_hscfg_read(struct file *file, char __user *ubuf,
ret = simple_read_from_buffer(ubuf, count, ppos, buf, pos);
free_page(addr);
kfree(buf);
return ret;
}

View File

@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0-only
config WLAN_VENDOR_MORSEMICRO
bool "Morse Micro devices"
default y
help
If you have a wireless card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all the
questions about these cards. If you say Y, you will be asked for
your specific card in the following questions.
if WLAN_VENDOR_MORSEMICRO
source "drivers/net/wireless/morsemicro/mm81x/Kconfig"
endif # WLAN_VENDOR_MORSEMICRO

View File

@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_MM81X) += mm81x/

View File

@ -0,0 +1,24 @@
# SPDX-License-Identifier: GPL-2.0
config MM81X
tristate "Morse Micro MM81x wireless devices"
depends on MAC80211
select FW_LOADER
select CRC7
help
This module adds support for wireless devices based
on Morse Micro MM81xx chipsets.
config MM81X_USB
tristate "Morse Micro MM81x USB support"
depends on MM81X && USB
help
This module adds support for the USB interface of
devices using the Morse Micro MM81x chipset.
config MM81X_SDIO
tristate "Morse Micro MM81x SDIO support"
depends on MM81X && MMC
help
This module adds support for the SDIO interface of
devices using the Morse Micro MM81x chipset.

View File

@ -0,0 +1,21 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_MM81X) += mm81x_core.o
mm81x_core-y += core.o
mm81x_core-y += mac.o
mm81x_core-y += hw.o
mm81x_core-y += fw.o
mm81x_core-y += command.o
mm81x_core-y += ps.o
mm81x_core-y += skbq.o
mm81x_core-y += yaps_hw.o
mm81x_core-y += yaps.o
mm81x_core-y += rc.o
mm81x_core-y += mmrc.o
obj-$(CONFIG_MM81X_USB) += mm81x_usb.o
mm81x_usb-y += usb.o
obj-$(CONFIG_MM81X_SDIO) += mm81x_sdio.o
mm81x_sdio-y += sdio.o

View File

@ -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_ */

View File

@ -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);
}

View File

@ -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_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
// 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 (CHIP_ID_MM8108):
mors->regs = &mm8108_regs;
mors->hif.ops = &mm81x_yaps_ops;
break;
default:
return -ENODEV;
}
return ret;
}
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, u32 fw_ver)
{
const char *fw_base;
switch (chip_id) {
case CHIP_ID_MM8108:
fw_base = MM8108_FW_BASE;
break;
default:
return NULL;
}
return kasprintf(GFP_KERNEL, MM81X_FW_DIR "/v%u/%s" MM81X_FW_EXT,
fw_ver, fw_base);
}
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");

View File

@ -0,0 +1,456 @@
/* 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
#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 fw_flags;
u32 fw_major;
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, u32 fw_ver);
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_ */

View File

@ -0,0 +1,752 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/kernel.h>
#include <linux/firmware.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/string_choices.h>
#include <net/mac80211.h>
#include <linux/elf.h>
#include <linux/crc32.h>
#include "fw.h"
#include "mac.h"
#include "bus.h"
/*
* Maximum wait time (microseconds) for firmware to boot (for host table
* pointer to be available)
*/
#define HOST_TABLE_PTR_POLL_TIMEOUT_US 1200000
#define HOST_TABLE_PTR_POLL_PERIOD_US 10000
/* Number of times to attempt flashing FW */
#define FW_FLASH_ATTEMPT_COUNT 3
static int mm81x_fw_get_header(const u8 *data, Elf32_Ehdr *ehdr)
{
const struct mm81x_elf32_ehdr *p =
(const struct mm81x_elf32_ehdr *)data;
/* Magic check */
if (p->e_ident[EI_MAG0] != ELFMAG0 || p->e_ident[EI_MAG1] != ELFMAG1 ||
p->e_ident[EI_MAG2] != ELFMAG2 || p->e_ident[EI_MAG3] != ELFMAG3)
return -EINVAL;
/* elf32 and little endian */
if (p->e_ident[EI_DATA] != ELFDATA2LSB ||
p->e_ident[EI_CLASS] != ELFCLASS32)
return -EINVAL;
ehdr->e_phoff = le32_to_cpu(p->e_phoff);
ehdr->e_phentsize = le16_to_cpu(p->e_phentsize);
ehdr->e_phnum = le16_to_cpu(p->e_phnum);
ehdr->e_shoff = le32_to_cpu(p->e_shoff);
ehdr->e_shentsize = le16_to_cpu(p->e_shentsize);
ehdr->e_shnum = le16_to_cpu(p->e_shnum);
ehdr->e_shstrndx = le16_to_cpu(p->e_shstrndx);
ehdr->e_entry = le32_to_cpu(p->e_entry);
return 0;
}
static void mm81x_fw_parse_info(struct mm81x *mors, const u8 *data, int length)
{
const struct mm81x_fw_info_tlv *tlv =
(const struct mm81x_fw_info_tlv *)data;
while ((u8 *)tlv < (data + length)) {
switch (le16_to_cpu(tlv->type)) {
case MM81X_FW_INFO_TLV_BCF_ADDR:
mors->bcf_address = get_unaligned_le32(tlv->val);
break;
default:
break;
}
tlv = (const struct mm81x_fw_info_tlv *)((u8 *)tlv +
le16_to_cpu(
tlv->length) +
sizeof(*tlv));
}
}
static int mm81x_fw_get_section_header(const u8 *data, Elf32_Ehdr *ehdr,
Elf32_Shdr *shdr, int i)
{
const struct mm81x_elf32_shdr *p =
(void *)(data + ehdr->e_shoff + (i * ehdr->e_shentsize));
shdr->sh_name = le32_to_cpu(p->sh_name);
shdr->sh_type = le32_to_cpu(p->sh_type);
shdr->sh_offset = le32_to_cpu(p->sh_offset);
shdr->sh_addr = le32_to_cpu(p->sh_addr);
shdr->sh_size = le32_to_cpu(p->sh_size);
shdr->sh_flags = le32_to_cpu(p->sh_flags);
return 0;
}
static int mm81x_fw_set_boot_addr(struct mm81x *mors, uint32_t addr)
{
int status;
dev_dbg(mors->dev, "Overwriting boot address to 0x%x", addr);
mm81x_claim_bus(mors);
status = mm81x_reg32_write(mors, MM81X_REG_BOOT_ADDR(mors), addr);
mm81x_release_bus(mors);
return status;
}
static int mm81x_fw_load_fw(struct mm81x *mors, const struct firmware *fw)
{
int i;
int ret = 0;
Elf32_Ehdr ehdr;
Elf32_Phdr phdr;
Elf32_Shdr shdr;
Elf32_Shdr sh_strtab;
const char *sh_strs;
u8 *fw_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(fw->size),
GFP_KERNEL);
if (!fw_buf)
return -ENOMEM;
if (mm81x_fw_get_header(fw->data, &ehdr)) {
dev_err(mors->dev, "Wrong file format");
return -EINVAL;
}
if (mm81x_fw_get_section_header(fw->data, &ehdr, &sh_strtab,
ehdr.e_shstrndx)) {
dev_err(mors->dev, "Invalid firmware. Missing string table");
return -ENOENT;
}
sh_strs = (const char *)fw->data + sh_strtab.sh_offset;
for (i = 0; i < ehdr.e_phnum; i++) {
int status;
int address;
const struct mm81x_elf32_phdr *p =
(void *)(fw->data + ehdr.e_phoff +
i * ehdr.e_phentsize);
phdr.p_type = le32_to_cpu(p->p_type);
phdr.p_offset = le32_to_cpu(p->p_offset);
phdr.p_paddr = le32_to_cpu(p->p_paddr);
phdr.p_filesz = le32_to_cpu(p->p_filesz);
phdr.p_memsz = le32_to_cpu(p->p_memsz);
address = phdr.p_paddr;
if (phdr.p_type != PT_LOAD || !phdr.p_memsz)
continue;
if (phdr.p_filesz && phdr.p_offset &&
(phdr.p_offset + phdr.p_filesz) < fw->size) {
u32 padded_size = ROUND_BYTES_TO_WORD(phdr.p_filesz);
memcpy(fw_buf, fw->data + phdr.p_offset, padded_size);
/* Set padding to 0xff */
memset(fw_buf + phdr.p_filesz, 0xff,
padded_size - phdr.p_filesz);
mm81x_claim_bus(mors);
status = mm81x_dm_write(mors, address, fw_buf,
padded_size);
mm81x_release_bus(mors);
if (status) {
ret = -EIO;
break;
}
}
}
for (i = 0; i < ehdr.e_shnum; i++) {
if (mm81x_fw_get_section_header(fw->data, &ehdr, &shdr, i))
continue;
/* This is the firmware info. Parse it */
if (!strncmp(sh_strs + shdr.sh_name, ".fw_info",
sizeof(".fw_info")))
mm81x_fw_parse_info(mors, fw->data + shdr.sh_offset,
shdr.sh_size);
}
if (ehdr.e_entry)
ret = mm81x_fw_set_boot_addr(mors, ehdr.e_entry);
devm_kfree(mors->dev, fw_buf);
return ret;
}
static int __mm81x_fw_load_bcf(struct mm81x *mors, unsigned int addr,
const void *src, size_t src_len, u8 *scratch,
size_t scratch_cap)
{
size_t rounded = ROUND_BYTES_TO_WORD(src_len);
int st;
if (rounded > scratch_cap)
return -EINVAL;
if (rounded > BCF_DATABASE_SIZE)
return -EFBIG;
memcpy(scratch, src, src_len);
if (rounded > src_len)
memset(scratch + src_len, 0xff, rounded - src_len);
mm81x_claim_bus(mors);
st = mm81x_dm_write(mors, addr, scratch, rounded);
mm81x_release_bus(mors);
return st ? -EIO : 0;
}
static int mm81x_fw_load_bcf(struct mm81x *mors, const struct firmware *bcf,
unsigned int bcf_address)
{
int i, ret = 0;
size_t reg_prefix_len, cfg_len_rounded = 0, reg_len_rounded;
Elf32_Ehdr ehdr;
Elf32_Shdr shdr, sh_strtab;
const char *sh_strs, *reg_prefix = ".regdom_", *reg_src;
size_t reg_len;
u8 *bcf_buf;
bcf_buf = devm_kmalloc(mors->dev, ROUND_BYTES_TO_WORD(bcf->size),
GFP_KERNEL);
if (!bcf_buf)
return -ENOMEM;
if (mm81x_fw_get_header(bcf->data, &ehdr)) {
dev_err(mors->dev, "Wrong file format");
ret = -EINVAL;
goto out_free;
}
if (mm81x_fw_get_section_header(bcf->data, &ehdr, &sh_strtab,
ehdr.e_shstrndx)) {
dev_err(mors->dev, "Invalid BCF - missing string table");
ret = -ENOENT;
goto out_free;
}
sh_strs = (const char *)bcf->data + sh_strtab.sh_offset;
reg_prefix_len = strlen(reg_prefix);
for (i = 0; i < ehdr.e_shnum; i++) {
if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
continue;
if (strcmp(sh_strs + shdr.sh_name, ".board_config"))
continue;
cfg_len_rounded = ROUND_BYTES_TO_WORD(shdr.sh_size);
dev_dbg(mors->dev,
"Write BCF board_config - addr 0x%x size %zu",
bcf_address, cfg_len_rounded);
ret = __mm81x_fw_load_bcf(mors, bcf_address,
bcf->data + shdr.sh_offset,
shdr.sh_size, bcf_buf,
ROUND_BYTES_TO_WORD(bcf->size));
if (ret)
goto out_free;
bcf_address += cfg_len_rounded;
break;
}
ret = -EINVAL;
for (; i < ehdr.e_shnum; i++) {
if (mm81x_fw_get_section_header(bcf->data, &ehdr, &shdr, i))
continue;
if (strncmp(sh_strs + shdr.sh_name, reg_prefix, reg_prefix_len))
continue;
if (strncmp(sh_strs + shdr.sh_name + reg_prefix_len,
mors->country, 2))
continue;
reg_src = bcf->data + shdr.sh_offset;
reg_len = shdr.sh_size;
dev_dbg(mors->dev, "Write BCF %s - addr 0x%x size %zu",
sh_strs + shdr.sh_name, bcf_address,
ROUND_BYTES_TO_WORD(reg_len));
ret = 0;
break;
}
if (ret)
goto out_free;
reg_len_rounded = ROUND_BYTES_TO_WORD(reg_len);
if ((cfg_len_rounded + reg_len_rounded) > BCF_DATABASE_SIZE) {
ret = -EFBIG;
goto out_free;
}
ret = __mm81x_fw_load_bcf(mors, bcf_address, reg_src, reg_len, bcf_buf,
ROUND_BYTES_TO_WORD(bcf->size));
out_free:
devm_kfree(mors->dev, bcf_buf);
return ret;
}
static void mm81x_fw_clear_aon(struct mm81x *mors)
{
int idx;
u8 count = MM81X_REG_AON_COUNT(mors);
u32 address = MM81X_REG_AON_ADDR(mors);
for (idx = 0; idx < count; idx++, address += 4) {
if (mors->bus_type == MM81X_BUS_TYPE_USB && idx == 0)
/* Keep the USB power domain enabled in AON. */
mm81x_reg32_write(mors, address,
MM81X_REG_AON_USB_RESET(mors));
else
/* clear AON */
mm81x_reg32_write(mors, address, 0x0);
}
mm81x_hw_toggle_aon_latch(mors);
}
static void mm81x_fw_trigger(struct mm81x *mors)
{
const unsigned int wait_after_msi_trigger_ms = 1;
mm81x_claim_bus(mors);
/*
* If not coming from a full reset, some AON flags may be latched.
* Make sure to clear any hanging AON bits (can affect booting).
*/
mm81x_fw_clear_aon(mors);
if (MM81X_REG_CLK_CTRL(mors))
mm81x_reg32_write(mors, MM81X_REG_CLK_CTRL(mors),
MM81X_REG_CLK_CTRL_VALUE(mors));
mm81x_reg32_write(mors, MM81X_REG_MSI(mors),
MM81X_REG_MSI_HOST_INT(mors));
mm81x_release_bus(mors);
/* Give the chip a chance to boot */
mdelay(wait_after_msi_trigger_ms);
}
static int mm81x_fw_verify_magic(struct mm81x *mors)
{
int ret = 0;
int magic = ~MM81X_REG_HOST_MAGIC_VALUE(mors);
mm81x_claim_bus(mors);
mm81x_reg32_read(mors,
mors->host_table_ptr +
offsetof(struct host_table, magic_number),
&magic);
if (magic != MM81X_REG_HOST_MAGIC_VALUE(mors)) {
dev_err(mors->dev, "FW magic mismatch 0x%08x:0x%08x",
MM81X_REG_HOST_MAGIC_VALUE(mors), magic);
ret = -EIO;
}
mm81x_release_bus(mors);
return ret;
}
static int mm81x_fw_get_flags(struct mm81x *mors)
{
int ret = 0;
int fw_flags = 0;
mm81x_claim_bus(mors);
ret = mm81x_reg32_read(mors,
mors->host_table_ptr +
offsetof(struct host_table, fw_flags),
&fw_flags);
mors->fw_flags = fw_flags;
mm81x_release_bus(mors);
return ret;
}
static int mm81x_fw_check_compatibility(struct mm81x *mors)
{
int ret = 0;
u32 fw_version;
u32 major;
u32 minor;
u32 patch;
mm81x_claim_bus(mors);
ret = mm81x_reg32_read(mors,
mors->host_table_ptr +
offsetof(struct host_table,
fw_version_number),
&fw_version);
mm81x_release_bus(mors);
major = MM81X_SEMVER_GET_MAJOR(fw_version);
minor = MM81X_SEMVER_GET_MINOR(fw_version);
patch = MM81X_SEMVER_GET_PATCH(fw_version);
/* Firmware on device must match the firmware file we requested */
if (ret == 0 && major != mors->fw_major) {
dev_err(mors->dev,
"Incompatible FW version: (Requested) v%u, (Chip) %d.%d.%d\n",
mors->fw_major, major, minor, patch);
ret = -EPERM;
} else if (ret == 0 && major != HOST_CMD_SEMVER_MAJOR) {
dev_warn(
mors->dev,
"Running FW v%d.%d.%d, driver supports up to v%d, some features might not be supported",
major, minor, patch, HOST_CMD_SEMVER_MAJOR);
} else if (ret == 0 && minor != HOST_CMD_SEMVER_MINOR) {
dev_warn(
mors->dev,
"FW version mismatch, some features might not be supported: (Driver) %d.%d.%d, (Chip) %d.%d.%d",
HOST_CMD_SEMVER_MAJOR, HOST_CMD_SEMVER_MINOR,
HOST_CMD_SEMVER_PATCH, major, minor, patch);
}
return ret;
}
static int mm81x_fw_invalidate_host_ptr(struct mm81x *mors)
{
int ret;
mors->host_table_ptr = 0;
mm81x_claim_bus(mors);
ret = mm81x_reg32_write(mors, MM81X_REG_HOST_MANIFEST_PTR(mors), 0);
mm81x_release_bus(mors);
return ret;
}
static int mm81x_fw_get_host_table_ptr(struct mm81x *mors)
{
int ret, err;
mm81x_claim_bus(mors);
ret = read_poll_timeout(mm81x_reg32_read, err,
err || mors->host_table_ptr,
HOST_TABLE_PTR_POLL_PERIOD_US,
HOST_TABLE_PTR_POLL_TIMEOUT_US, false, mors,
MM81X_REG_HOST_MANIFEST_PTR(mors),
&mors->host_table_ptr);
mm81x_release_bus(mors);
return ret ? ret : err;
}
static int mm81x_fw_read_ext_host_table(struct mm81x *mors,
struct ext_host_tbl **ext_host_table)
{
int ret = 0;
u32 host_tbl_ptr = mors->host_table_ptr;
u32 ext_host_tbl_ptr;
u32 ext_host_tbl_ptr_addr =
host_tbl_ptr + offsetof(struct host_table, ext_host_tbl_addr);
u32 ext_host_tbl_len;
u32 ext_host_tbl_len_ptr_addr;
struct ext_host_tbl *host_tbl = NULL;
mm81x_claim_bus(mors);
ret = mm81x_reg32_read(mors, ext_host_tbl_ptr_addr, &ext_host_tbl_ptr);
if (ret)
goto exit;
if (!ext_host_tbl_ptr) {
ret = -ENXIO;
goto exit;
}
ext_host_tbl_len_ptr_addr =
ext_host_tbl_ptr +
offsetof(struct ext_host_tbl, ext_host_tbl_length);
ret = mm81x_reg32_read(mors, ext_host_tbl_len_ptr_addr,
&ext_host_tbl_len);
if (ret)
goto exit;
ext_host_tbl_len = ROUND_BYTES_TO_WORD(ext_host_tbl_len);
if (WARN_ON(ext_host_tbl_len == 0 || ext_host_tbl_len > INT_MAX)) {
ret = -EINVAL;
goto exit;
}
host_tbl = kmalloc(ext_host_tbl_len, GFP_KERNEL);
if (!host_tbl) {
ret = -ENOMEM;
goto exit;
}
ret = mm81x_dm_read(mors, ext_host_tbl_ptr, (u8 *)host_tbl,
(int)ext_host_tbl_len);
if (ret)
goto exit;
mm81x_release_bus(mors);
*ext_host_table = host_tbl;
return ret;
exit:
mm81x_release_bus(mors);
kfree(host_tbl);
return ret;
}
static void mm81x_fw_update_capabilities(struct mm81x *mors,
struct ext_host_tbl_s1g_caps *caps)
{
int i;
for (i = 0; i < FW_CAPABILITIES_FLAGS_WIDTH; i++) {
mors->fw_caps.flags[i] = le32_to_cpu(caps->flags[i]);
dev_dbg(mors->dev, "Firmware Manifest Flags%d: 0x%x", i,
le32_to_cpu(caps->flags[i]));
}
mors->fw_caps.ampdu_mss = caps->ampdu_mss;
mors->fw_caps.mm81x_mmss_offset = caps->mm81x_mmss_offset;
mors->fw_caps.beamformee_sts_capability =
caps->beamformee_sts_capability;
mors->fw_caps.maximum_ampdu_length_exponent =
caps->maximum_ampdu_length;
mors->fw_caps.number_sounding_dimensions =
caps->number_sounding_dimensions;
dev_dbg(mors->dev, "\tAMPDU Minimum start spacing: %u",
caps->ampdu_mss);
dev_dbg(mors->dev, "\tMorse Minimum Start Spacing offset: %u",
caps->mm81x_mmss_offset);
dev_dbg(mors->dev, "\tBeamformee STS Capability: %u",
caps->beamformee_sts_capability);
dev_dbg(mors->dev, "\tNumber of Sounding Dimensions: %u",
caps->number_sounding_dimensions);
dev_dbg(mors->dev, "\tMaximum AMPDU Length Exponent: %u",
caps->maximum_ampdu_length);
}
static void mm81x_fw_update_validate_skb_checksum(
struct mm81x *mors,
struct ext_host_tbl_insert_skb_checksum *validate_checksum)
{
mors->hif.validate_skb_checksum =
validate_checksum->insert_and_validate_checksum;
dev_dbg(mors->dev, "Validate checksum inserted by fw %s",
str_enabled_disabled(mors->hif.validate_skb_checksum));
}
int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors)
{
int ret;
u8 *head;
u8 *end;
struct ext_host_tbl *ext_host_table = NULL;
ret = mm81x_fw_read_ext_host_table(mors, &ext_host_table);
if (ret || !ext_host_table)
goto exit;
/* Parse the TLVs */
head = ext_host_table->ext_host_table_data_tlvs;
end = ((u8 *)ext_host_table) +
le32_to_cpu(ext_host_table->ext_host_tbl_length);
while (head < end) {
struct ext_host_tbl_tlv_hdr *hdr =
(struct ext_host_tbl_tlv_hdr *)head;
switch (le16_to_cpu(hdr->tag)) {
case MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES:
mm81x_fw_update_capabilities(
mors, (struct ext_host_tbl_s1g_caps *)hdr);
break;
case MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM:
mm81x_fw_update_validate_skb_checksum(
mors,
(struct ext_host_tbl_insert_skb_checksum *)hdr);
break;
case MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE:
mm81x_yaps_hw_read_table(
mors, &((struct ext_host_tbl_yaps_table *)hdr)
->yaps_table);
break;
default:
break;
}
head += le16_to_cpu(hdr->length);
if (!hdr->length)
break;
}
kfree(ext_host_table);
return ret;
exit:
dev_err(mors->dev, "failed to parse ext host table %d", ret);
return ret;
}
static int __mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
const struct firmware *bcf, bool reset)
{
int ret;
if (reset || !mors->chip_was_reset) {
ret = mm81x_hw_digital_reset(mors);
if (ret)
return ret;
}
mm81x_hw_pre_firmware_ndr_hook(mors);
ret = mm81x_fw_invalidate_host_ptr(mors);
if (ret)
return ret;
ret = mm81x_fw_load_fw(mors, fw);
if (ret)
return ret;
ret = mm81x_fw_load_bcf(mors, bcf, mors->bcf_address);
if (ret)
return ret;
mm81x_fw_trigger(mors);
mm81x_hw_post_firmware_ndr_hook(mors);
ret = mm81x_fw_get_host_table_ptr(mors);
if (ret)
return ret;
ret = mm81x_fw_verify_magic(mors);
if (ret)
return ret;
return mm81x_fw_check_compatibility(mors);
}
static int mm81x_fw_flash(struct mm81x *mors, const struct firmware *fw,
const struct firmware *bcf, bool reset)
{
int ret;
int retries = FW_FLASH_ATTEMPT_COUNT;
while (retries--) {
ret = __mm81x_fw_flash(mors, fw, bcf, reset);
if (!ret)
return 0;
mors->chip_was_reset = false;
}
return ret;
}
static uint32_t binary_crc(const struct firmware *fw)
{
return ~crc32_le(~0, (unsigned char const *)fw->data, fw->size) &
0xffffffff;
}
static int mm81x_fw_request(struct mm81x *mors, const struct firmware **fw)
{
int ret = -ENOENT;
int ver;
char *fw_path;
for (ver = MM81X_FW_VER_MAX; ver >= MM81X_FW_VER_MIN; ver--) {
fw_path = mm81x_core_get_fw_path(mors->chip_id, ver);
if (!fw_path)
return -ENOMEM;
ret = firmware_request_nowarn(fw, fw_path, mors->dev);
if (!ret) {
dev_info(
mors->dev,
"Loaded firmware from %s, size %zu, crc32 0x%08x\n",
fw_path, (*fw)->size, binary_crc(*fw));
mors->fw_major = ver;
}
kfree(fw_path);
if (!ret)
return 0;
}
dev_err(mors->dev, "no firmware found (tried v%d down to v%d): %d\n",
MM81X_FW_VER_MAX, MM81X_FW_VER_MIN, ret);
return ret;
}
int mm81x_fw_init(struct mm81x *mors, bool reset)
{
int ret;
int board_id;
char *bcf_path = NULL;
const struct firmware *fw = NULL;
const struct firmware *bcf = NULL;
board_id = mm81x_hw_otp_get_board_type(mors);
if (!mm81x_hw_otp_valid_board_type(board_id)) {
dev_err(mors->dev,
"OTP not set, unable to determine BCF to use");
ret = -EINVAL;
goto out;
}
dev_dbg(mors->dev, "Using board type 0x%04x from OTP", board_id);
ret = mm81x_fw_request(mors, &fw);
if (ret)
goto out;
bcf_path = kasprintf(GFP_KERNEL,
MM81X_FW_DIR
"/v%u/bcf_boardtype_%04x" MM81X_FW_EXT,
mors->fw_major, board_id);
if (!bcf_path) {
ret = -ENOMEM;
goto out;
}
ret = request_firmware(&bcf, bcf_path, mors->dev);
if (ret) {
if (ret == -ENOENT)
dev_err(mors->dev, "BCF %s not found\n", bcf_path);
goto out;
}
dev_info(mors->dev, "Loaded BCF from %s, size %zu, crc32 0x%08x\n",
bcf_path, bcf->size, binary_crc(bcf));
ret = mm81x_fw_flash(mors, fw, bcf, reset);
if (ret) {
dev_err(mors->dev, "failed to flash firmware: %d", ret);
goto out;
}
ret = mm81x_fw_get_flags(mors);
out:
release_firmware(fw);
release_firmware(bcf);
kfree(bcf_path);
if (ret)
dev_err(mors->dev, "failed to init firmware: %d", ret);
else
dev_dbg(mors->dev, "firmware initialised");
return ret;
}

View File

@ -0,0 +1,143 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_FW_H_
#define _MM81X_FW_H_
#include <linux/firmware.h>
#include <linux/completion.h>
#include <linux/elf.h>
#include "command_defs.h"
#include "yaps_hw.h"
#define BCF_DATABASE_SIZE (1024)
#define MM81X_FW_DIR "morsemicro/mm81x"
#define MM81X_FW_EXT ".bin"
#define MM81X_FW_VER_MAX HOST_CMD_SEMVER_MAJOR
#define MM81X_FW_VER_MIN 56
/* FW_CAPABILITIES_FLAGS_WIDTH = ceil(MM81X_CAPS_MAX_HW_LEN / 32) */
#define FW_CAPABILITIES_FLAGS_WIDTH (4)
struct mm81x_elf32_ehdr {
unsigned char e_ident[EI_NIDENT];
__le16 e_type;
__le16 e_machine;
__le32 e_version;
__le32 e_entry;
__le32 e_phoff;
__le32 e_shoff;
__le32 e_flags;
__le16 e_ehsize;
__le16 e_phentsize;
__le16 e_phnum;
__le16 e_shentsize;
__le16 e_shnum;
__le16 e_shstrndx;
} __packed;
struct mm81x_elf32_shdr {
__le32 sh_name;
__le32 sh_type;
__le32 sh_flags;
__le32 sh_addr;
__le32 sh_offset;
__le32 sh_size;
__le32 sh_link;
__le32 sh_info;
__le32 sh_addralign;
__le32 sh_entsize;
} __packed;
struct mm81x_elf32_phdr {
__le32 p_type;
__le32 p_offset;
__le32 p_vaddr;
__le32 p_paddr;
__le32 p_filesz;
__le32 p_memsz;
__le32 p_flags;
__le32 p_align;
} __packed;
enum mm81x_fw_info_tlv_type {
MM81X_FW_INFO_TLV_BCF_ADDR = 1,
};
struct mm81x_fw_info_tlv {
__le16 type;
__le16 length;
u8 val[];
} __packed;
enum mm81x_fw_ext_host_tbl_tag {
/* The S1G capability tag */
MM81X_FW_HOST_TABLE_TAG_S1G_CAPABILITIES = 0,
MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_TX_STATUS = 1,
MM81X_FW_HOST_TABLE_TAG_INSERT_SKB_CHECKSUM = 2,
MM81X_FW_HOST_TABLE_TAG_YAPS_TABLE = 3,
MM81X_FW_HOST_TABLE_TAG_PAGER_PKT_MEMORY = 4,
MM81X_FW_HOST_TABLE_TAG_PAGER_BYPASS_CMD_RESP = 5,
};
struct ext_host_tbl_tlv_hdr {
/* The tag used to identify which capability this represents */
__le16 tag;
/* The length of the capability structure including this header */
__le16 length;
} __packed;
struct ext_host_tbl_s1g_caps {
struct ext_host_tbl_tlv_hdr header;
__le32 flags[FW_CAPABILITIES_FLAGS_WIDTH];
/*
* The minimum A-MPDU start spacing required by firmware.
* Value | Description
* ------|------------
* 0 | No restriction
* 1 | 1/4 us
* 2 | 1/2 us
* 3 | 1 us
* 4 | 2 us
* 5 | 4 us
* 6 | 8 us
* 7 | 16 us
*/
u8 ampdu_mss;
u8 beamformee_sts_capability;
u8 number_sounding_dimensions;
/*
* The maximum A-MPDU length. This is the exponent value such that
* (2^(13 + exponent) - 1) is the length
*/
u8 maximum_ampdu_length;
/*
* Offset to apply to the specification's MMSS table to signal further
* minimum MPDU start spacing.
*/
u8 mm81x_mmss_offset;
} __packed;
struct ext_host_tbl_insert_skb_checksum {
struct ext_host_tbl_tlv_hdr header;
u8 insert_and_validate_checksum;
};
struct ext_host_tbl_yaps_table {
struct ext_host_tbl_tlv_hdr header;
struct mm81x_yaps_hw_table yaps_table;
} __packed;
struct ext_host_tbl {
__le32 ext_host_tbl_length;
u8 dev_mac_addr[6];
u8 ext_host_table_data_tlvs[];
} __packed;
int mm81x_fw_init(struct mm81x *mors, bool reset);
int mm81x_fw_parse_ext_host_tbl(struct mm81x *mors);
#endif /* !_MM81X_FW_H_ */

View File

@ -0,0 +1,117 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_HIF_H_
#define _MM81X_HIF_H_
#include "core.h"
struct mm81x_skbq;
#define MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM (15)
#define MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM (29)
#define MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE \
BIT(MM81X_HIF_BYPASS_TX_STATUS_IRQ_NUM)
#define MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE \
BIT(MM81X_HIF_BYPASS_CMD_RESP_IRQ_NUM)
/* Hardware IF interrupt mask. We may use any interrupts in this range */
#define MM81X_HIF_IRQ_MASK_ALL \
(GENMASK(13, 0) | MM81X_HIF_IRQ_BYPASS_TX_STATUS_AVAILABLE | \
MM81X_HIF_IRQ_BYPASS_CMD_RESP_AVAILABLE)
enum mm81x_hif_flags {
MM81X_HIF_FLAGS_DIR_TO_HOST = BIT(0),
MM81X_HIF_FLAGS_DIR_TO_CHIP = BIT(1),
MM81X_HIF_FLAGS_COMMAND = BIT(2),
MM81X_HIF_FLAGS_BEACON = BIT(3),
MM81X_HIF_FLAGS_DATA = BIT(4)
};
struct mm81x_hif_ops {
int (*init)(struct mm81x *mors);
void (*flush_tx_data)(struct mm81x *mors);
void (*flush_cmds)(struct mm81x *mors);
void (*finish)(struct mm81x *mors);
void (*skbq_get_tx_qs)(struct mm81x *mors, struct mm81x_skbq **qs,
int *num_qs);
struct mm81x_skbq *(*get_tx_cmd_queue)(struct mm81x *mors);
struct mm81x_skbq *(*get_tx_beacon_queue)(struct mm81x *mors);
struct mm81x_skbq *(*get_tx_mgmt_queue)(struct mm81x *mors);
struct mm81x_skbq *(*get_tx_data_queue)(struct mm81x *mors, int aci);
int (*handle_irq)(struct mm81x *mors, u32 status);
int (*get_tx_buffered_count)(struct mm81x *mors);
int (*get_tx_status_pending_count)(struct mm81x *mors);
};
static inline void mm81x_hif_clear_events(struct mm81x *mors)
{
mors->hif.event_flags = 0;
}
static inline int mm81x_hif_init(struct mm81x *mors)
{
return mors->hif.ops->init(mors);
}
static inline void mm81x_hif_flush_tx_data(struct mm81x *mors)
{
mors->hif.ops->flush_tx_data(mors);
}
static inline void mm81x_hif_flush_cmds(struct mm81x *mors)
{
mors->hif.ops->flush_cmds(mors);
}
static inline void mm81x_hif_finish(struct mm81x *mors)
{
mors->hif.ops->finish(mors);
}
static inline void mm81x_hif_skbq_get_tx_qs(struct mm81x *mors,
struct mm81x_skbq **qs, int *num_qs)
{
mors->hif.ops->skbq_get_tx_qs(mors, qs, num_qs);
}
static inline struct mm81x_skbq *mm81x_hif_get_tx_cmd_queue(struct mm81x *mors)
{
return mors->hif.ops->get_tx_cmd_queue(mors);
}
static inline struct mm81x_skbq *
mm81x_hif_get_tx_beacon_queue(struct mm81x *mors)
{
return mors->hif.ops->get_tx_beacon_queue(mors);
}
static inline struct mm81x_skbq *mm81x_hif_get_tx_mgmt_queue(struct mm81x *mors)
{
return mors->hif.ops->get_tx_mgmt_queue(mors);
}
static inline struct mm81x_skbq *mm81x_hif_get_tx_data_queue(struct mm81x *mors,
int aci)
{
return mors->hif.ops->get_tx_data_queue(mors, aci);
}
static inline int mm81x_hif_handle_irq(struct mm81x *mors, u32 status)
{
return mors->hif.ops->handle_irq(mors, status);
}
static inline int mm81x_hif_get_tx_buffered_count(struct mm81x *mors)
{
return mors->hif.ops->get_tx_buffered_count(mors);
}
static inline int mm81x_hif_get_tx_status_pending_count(struct mm81x *mors)
{
return mors->hif.ops->get_tx_status_pending_count(mors);
}
#endif /* _MM81X_HIF_H_ */

View File

@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/firmware.h>
#include <linux/delay.h>
#include <linux/stringify.h>
#include <linux/types.h>
#include <linux/unaligned.h>
#include <linux/gpio.h>
#include "hif.h"
#include "mac.h"
#include "bus.h"
#include "core.h"
#include "fw.h"
#include "yaps.h"
#define MM8108_REG_HOST_MAGIC_VALUE 0xDEADBEEF
#define MM8108_REG_RESET_VALUE 0xDEAD
#define MM8108_REG_SDIO_DEVICE_ADDR 0x0000207C
#define MM8108_REG_SDIO_DEVICE_BURST_OFFSET 9
#define MM8108_REG_TRGR_BASE 0x00003c00
#define MM8108_REG_INT_BASE 0x00003c50
#define MM8108_REG_MSI_ADDRESS 0x00004100
#define MM8108_REG_MSI_VALUE 0x1
#define MM8108_REG_MANIFEST_PTR_ADDRESS 0x00002d40
#define MM8108_REG_APPS_BOOT_ADDR 0x00002084
#define MM8108_REG_RESET 0x000020AC
#define MM8108_REG_AON_COUNT 2
#define MM8108_REG_AON_ADDR 0x00002114
#define MM8108_REG_AON_LATCH_ADDR 0x00405020
#define MM8108_REG_AON_LATCH_MASK 0x1
#define MM8108_REG_AON_RESET_USB_VALUE 0x8
#define MM8108_APPS_MAC_DMEM_ADDR_START 0x00100000
#define MM8108_REG_RC_CLK_POWER_OFF_ADDR 0x00405020
#define MM8108_REG_RC_CLK_POWER_OFF_MASK 0x00000040
#define MM8108_SLOW_RC_POWER_ON_DELAY_MS 2
#define MM8108_RESET_DELAY_TIME_MS 400
#define MM8108_REG_OTPCTRL_PLDO 0x00004014
#define MM8108_REG_OTPCTRL_PENVDD2 0x00004010
#define MM8108_REG_OTPCTRL_PDSTB 0x00004018
#define MM8108_REG_OTPCTRL_PTM 0x0000401c
#define MM8108_REG_OTPCTRL_PCE 0x00004020
#define MM8108_REG_OTPCTRL_PA 0x00004034
#define MM8108_REG_OTPCTRL_PECCRDB 0x00004048
#define MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START 0x0000400c
#define MM8108_REG_OTPCTRL_PDOUT 0x00004040
#define MM81X_OTP_MAC_ADDR_2_BANK_NUM 27
#define MM81X_OTP_MAC_ADDR_1_BANK_NUM 26
#define MM81X_OTP_MAC_ADDR_1_MASK GENMASK(31, 16)
#define MM81X_OTP_BOARD_TYPE_BANK_NUM 26
#define MM81X_OTP_BOARD_TYPE_MASK GENMASK(15, 0)
#define MM810X_BOARD_TYPE_MAX_VALUE (MM81X_OTP_BOARD_TYPE_MASK - 1)
static void mm81x_hw_otp_power_up(struct mm81x *mors)
{
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 1);
udelay(2);
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 1);
usleep_range(10, 20);
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 1);
udelay(3);
}
static void mm81x_hw_otp_power_down(struct mm81x *mors)
{
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PDSTB, 0);
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PLDO, 0);
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PENVDD2, 0);
}
static void mm81x_hw_otp_read_enable(struct mm81x *mors)
{
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PTM, 0);
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 1);
usleep_range(10, 20);
}
static void mm81x_hw_otp_read_disable(struct mm81x *mors)
{
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PCE, 0);
udelay(1);
}
static int mm81x_hw_otp_read(struct mm81x *mors, u8 bank_num, u32 *buf,
u8 ignore_ecc)
{
u32 auto_rd_start_tmp;
u32 auto_rd_start = 1;
int i;
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PA, bank_num);
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_PECCRDB, ignore_ecc);
mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
&auto_rd_start_tmp);
auto_rd_start_tmp &= 0xfffffffe;
mm81x_reg32_write(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
auto_rd_start | auto_rd_start_tmp);
/* Attempt reading up to 5 times. */
for (i = 0; i < 5 && auto_rd_start; i++) {
usleep_range(15, 20);
mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_ACTION_AUTO_RD_START,
&auto_rd_start_tmp);
auto_rd_start = auto_rd_start_tmp & 0x1;
}
if (i == 5)
return -EIO;
mm81x_reg32_read(mors, MM8108_REG_OTPCTRL_PDOUT, buf);
return 0;
}
int mm81x_hw_otp_get_board_type(struct mm81x *mors)
{
int board_type = 0;
u32 otp_word = 0;
int ret;
mm81x_claim_bus(mors);
mm81x_hw_otp_power_up(mors);
mm81x_hw_otp_read_enable(mors);
ret = mm81x_hw_otp_read(mors, MM81X_OTP_BOARD_TYPE_BANK_NUM, &otp_word,
1);
mm81x_hw_otp_read_disable(mors);
mm81x_hw_otp_power_down(mors);
mm81x_release_bus(mors);
if (ret)
return -EINVAL;
board_type = otp_word & MM81X_OTP_BOARD_TYPE_MASK;
return board_type;
}
bool mm81x_hw_otp_valid_board_type(u32 board_type)
{
return board_type > 0 && board_type < MM810X_BOARD_TYPE_MAX_VALUE;
}
int mm81x_hw_otp_get_mac_addr(struct mm81x *mors)
{
u32 mac1 = 0;
u32 mac2 = 0;
int ret = 0;
mm81x_claim_bus(mors);
mm81x_hw_otp_power_up(mors);
mm81x_hw_otp_read_enable(mors);
ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_1_BANK_NUM, &mac1, 1);
if (ret)
goto exit;
ret = mm81x_hw_otp_read(mors, MM81X_OTP_MAC_ADDR_2_BANK_NUM, &mac2, 1);
if (ret)
goto exit;
put_unaligned_le16((mac1 & MM81X_OTP_MAC_ADDR_1_MASK) >> 16,
&mors->macaddr[0]);
put_unaligned_le32(mac2, &mors->macaddr[2]);
exit:
mm81x_hw_otp_read_disable(mors);
mm81x_hw_otp_power_down(mors);
mm81x_release_bus(mors);
return ret;
}
void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable)
{
u32 irq_en, irq_en_addr = irq < 32 ? MM81X_REG_INT1_EN(mors) :
MM81X_REG_INT2_EN(mors);
u32 irq_clr_addr = irq < 32 ? MM81X_REG_INT1_CLR(mors) :
MM81X_REG_INT2_CLR(mors);
u32 mask = irq < 32 ? (1 << irq) : (1 << (irq - 32));
mm81x_claim_bus(mors);
mm81x_reg32_read(mors, irq_en_addr, &irq_en);
if (enable)
irq_en |= (mask);
else
irq_en &= ~(mask);
mm81x_reg32_write(mors, irq_clr_addr, mask);
mm81x_reg32_write(mors, irq_en_addr, irq_en);
mm81x_release_bus(mors);
}
int mm81x_hw_irq_handle(struct mm81x *mors)
{
u32 status1 = 0;
mm81x_reg32_read(mors, MM81X_REG_INT1_STS(mors), &status1);
if (status1 & MM81X_HIF_IRQ_MASK_ALL)
mm81x_hif_handle_irq(mors, status1);
if (status1 & MM81X_INT_BEACON_VIF_MASK_ALL)
mm81x_mac_beacon_irq_handle(mors, status1);
mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), status1);
return status1 ? 1 : 0;
}
EXPORT_SYMBOL_GPL(mm81x_hw_irq_handle);
void mm81x_hw_irq_clear(struct mm81x *mors)
{
mm81x_claim_bus(mors);
mm81x_reg32_write(mors, MM81X_REG_INT1_CLR(mors), 0xFFFFFFFF);
mm81x_reg32_write(mors, MM81X_REG_INT2_CLR(mors), 0xFFFFFFFF);
mm81x_release_bus(mors);
}
void mm81x_hw_toggle_aon_latch(struct mm81x *mors)
{
u32 address = MM81X_REG_AON_LATCH_ADDR(mors);
u32 mask = MM81X_REG_AON_LATCH_MASK(mors);
u32 latch;
mm81x_reg32_read(mors, address, &latch);
mm81x_reg32_write(mors, address, latch & ~(mask));
mdelay(5);
mm81x_reg32_write(mors, address, latch | mask);
mdelay(5);
mm81x_reg32_write(mors, address, latch & ~(mask));
mdelay(5);
}
void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable)
{
mm81x_hw_irq_enable(mors, MM81X_INT_HW_STOP_NOTIFICATION_NUM, enable);
}
void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode)
{
u32 reg32_value;
mm81x_claim_bus(mors);
if (mm81x_reg32_read(mors, MM8108_REG_SDIO_DEVICE_ADDR, &reg32_value))
goto end;
reg32_value &= ~(u32)(SDIO_WORD_BURST_MASK
<< MM8108_REG_SDIO_DEVICE_BURST_OFFSET);
reg32_value |= (u32)(burst_mode << MM8108_REG_SDIO_DEVICE_BURST_OFFSET);
dev_dbg(mors->dev,
"Setting Burst mode to %d Writing 0x%08X to the register",
burst_mode, reg32_value);
if (mm81x_reg32_write(mors, MM8108_REG_SDIO_DEVICE_ADDR, reg32_value))
goto end;
end:
mm81x_release_bus(mors);
}
EXPORT_SYMBOL_GPL(mm81x_hw_enable_burst_mode);
static int mm81x_hw_enable_internal_slow_clock(struct mm81x *mors)
{
u32 rc_clock_reg_value;
int ret = 0;
dev_dbg(mors->dev, "Enabling internal slow clock");
ret = mm81x_reg32_read(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,
&rc_clock_reg_value);
if (ret)
goto exit;
rc_clock_reg_value &= ~MM8108_REG_RC_CLK_POWER_OFF_MASK;
ret = mm81x_reg32_write(mors, MM8108_REG_RC_CLK_POWER_OFF_ADDR,
rc_clock_reg_value);
if (ret)
goto exit;
mm81x_hw_toggle_aon_latch(mors);
/* Wait for the clock to turn on and settle */
mdelay(MM8108_SLOW_RC_POWER_ON_DELAY_MS);
exit:
return ret;
}
int mm81x_hw_digital_reset(struct mm81x *mors)
{
int ret = 0;
mm81x_claim_bus(mors);
/* This should be the first step in digital reset, do not reorder */
ret = mm81x_hw_enable_internal_slow_clock(mors);
if (ret)
goto exit;
if (mors->bus_type == MM81X_BUS_TYPE_USB) {
ret = mm81x_bus_digital_reset(mors);
goto usb_done;
}
if (MM81X_REG_RESET(mors) != 0)
ret = mm81x_reg32_write(mors, MM81X_REG_RESET(mors),
MM81X_REG_RESET_VALUE(mors));
usb_done:
msleep(MM8108_RESET_DELAY_TIME_MS);
exit:
mm81x_release_bus(mors);
if (!ret)
mors->chip_was_reset = true;
return ret;
}
void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors)
{
/* We need disable bursting for firmware download/init procedure */
mm81x_bus_config_burst_mode(mors, false);
}
void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors)
{
/* We are safe here to re-enable bursting again, if supported */
mm81x_bus_config_burst_mode(mors, true);
}
const struct mm81x_regs mm8108_regs = {
.chip_id_address = MM8108_REG_CHIP_ID,
.irq_base_address = MM8108_REG_INT_BASE,
.trgr_base_address = MM8108_REG_TRGR_BASE,
.cpu_reset_address = MM8108_REG_RESET,
.cpu_reset_value = MM8108_REG_RESET_VALUE,
.manifest_ptr_address = MM8108_REG_MANIFEST_PTR_ADDRESS,
.msi_address = MM8108_REG_MSI_ADDRESS,
.msi_value = MM8108_REG_MSI_VALUE,
.magic_num_value = MM8108_REG_HOST_MAGIC_VALUE,
.early_clk_ctrl_value = 0,
.pager_base_address = MM8108_APPS_MAC_DMEM_ADDR_START,
.aon_latch = MM8108_REG_AON_LATCH_ADDR,
.aon_latch_mask = MM8108_REG_AON_LATCH_MASK,
.aon_reset_usb_value = MM8108_REG_AON_RESET_USB_VALUE,
.aon = MM8108_REG_AON_ADDR,
.aon_count = MM8108_REG_AON_COUNT,
.boot_address = MM8108_REG_APPS_BOOT_ADDR,
};
MODULE_FIRMWARE(MM81X_FW_DIR "/v" __stringify(MM81X_FW_VER_MAX) "/"
MM8108_FW_BASE MM81X_FW_EXT);

View File

@ -0,0 +1,159 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_HW_H_
#define _MM81X_HW_H_
#include <linux/gpio/consumer.h>
#include "core.h"
#include "command_defs.h"
/* This should be at a fixed location for a family of chipset */
#define MM8108_REG_CHIP_ID 0x00002d20
#define MM81X_SDIO_RW_ADDR_BOUNDARY_MASK ((u32)0xFFFF0000)
#define MM81X_CONFIG_ACCESS_1BYTE 0
#define MM81X_CONFIG_ACCESS_2BYTE 1
#define MM81X_CONFIG_ACCESS_4BYTE 2
#define MM81X_REG_TRGR_BASE(mors) ((mors)->regs->trgr_base_address)
#define MM81X_REG_TRGR1_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x00)
#define MM81X_REG_TRGR1_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x04)
#define MM81X_REG_TRGR1_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x08)
#define MM81X_REG_TRGR1_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x0C)
#define MM81X_REG_TRGR2_STS(mors) (MM81X_REG_TRGR_BASE(mors) + 0x10)
#define MM81X_REG_TRGR2_SET(mors) (MM81X_REG_TRGR_BASE(mors) + 0x14)
#define MM81X_REG_TRGR2_CLR(mors) (MM81X_REG_TRGR_BASE(mors) + 0x18)
#define MM81X_REG_TRGR2_EN(mors) (MM81X_REG_TRGR_BASE(mors) + 0x1C)
#define MM81X_REG_INT_BASE(mors) ((mors)->regs->irq_base_address)
#define MM81X_REG_INT1_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x00)
#define MM81X_REG_INT1_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x04)
#define MM81X_REG_INT1_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x08)
#define MM81X_REG_INT1_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x0C)
#define MM81X_REG_INT2_STS(mors) (MM81X_REG_INT_BASE(mors) + 0x10)
#define MM81X_REG_INT2_SET(mors) (MM81X_REG_INT_BASE(mors) + 0x14)
#define MM81X_REG_INT2_CLR(mors) (MM81X_REG_INT_BASE(mors) + 0x18)
#define MM81X_REG_INT2_EN(mors) (MM81X_REG_INT_BASE(mors) + 0x1C)
#define MM81X_REG_CHIP_ID(mors) ((mors)->regs->chip_id_address)
#define MM81X_REG_MSI(mors) ((mors)->regs->msi_address)
#define MM81X_REG_MSI_HOST_INT(mors) ((mors)->regs->msi_value)
#define MM81X_REG_HOST_MAGIC_VALUE(mors) ((mors)->regs->magic_num_value)
#define MM81X_REG_RESET(mors) ((mors)->regs->cpu_reset_address)
#define MM81X_REG_RESET_VALUE(mors) ((mors)->regs->cpu_reset_value)
#define MM81X_REG_HOST_MANIFEST_PTR(mors) ((mors)->regs->manifest_ptr_address)
#define MM81X_REG_EARLY_CLK_CTRL_VALUE(mors) \
((mors)->regs->early_clk_ctrl_value)
#define MM81X_REG_CLK_CTRL(mors) ((mors)->regs->clk_ctrl_address)
#define MM81X_REG_CLK_CTRL_VALUE(mors) ((mors)->regs->clk_ctrl_value)
#define MM81X_REG_BOOT_ADDR(mors) ((mors)->regs->boot_address)
#define MM81X_REG_BOOT_ADDR_VALUE(mors) ((mors)->regs->boot_value)
#define MM81X_REG_AON_ADDR(mors) ((mors)->regs->aon)
#define MM81X_REG_AON_COUNT(mors) ((mors)->regs->aon_count)
#define MM81X_REG_AON_LATCH_ADDR(mors) ((mors)->regs->aon_latch)
#define MM81X_REG_AON_LATCH_MASK(mors) ((mors)->regs->aon_latch_mask)
#define MM81X_REG_AON_USB_RESET(mors) ((mors)->regs->aon_reset_usb_value)
/* Bit 17 to 24 reserved for the beacon VIF 0 to 7 interrupts */
#define MM81X_INT_BEACON_VIF_MASK_ALL (GENMASK(24, 17))
#define MM81X_INT_BEACON_BASE_NUM (17)
/* PV0 NDP probe interrupts (VIF 0 and 1). */
#define MM81X_INT_NDP_PROBE_REQ_PV0_VIF_MASK_ALL (GENMASK(26, 25))
#define MM81X_INT_NDP_PROBE_REQ_PV0_BASE_NUM (25)
/* Bit 27 Chip to Host stop notify */
#define MM81X_INT_HW_STOP_NOTIFICATION_NUM (27)
#define MM81X_INT_HW_STOP_NOTIFICATION BIT(MM81X_INT_HW_STOP_NOTIFICATION_NUM)
/* Chip IDs */
#define CHIP_ID_MM8108 0x809
/*
* Minimum time we must wait between attempting to reload the HW after a
* stop notification
*/
#define HW_RELOAD_AFTER_STOP_WINDOW 5
enum host_table_firmware_flags {
MM81X_FW_FLAGS_SUPPORT_S1G = BIT(0),
MM81X_FW_FLAGS_BUSY_ACTIVE_LOW = BIT(1),
MM81X_FW_FLAGS_REPORTS_TX_BEACON_COMPLETION = BIT(2),
MM81X_FW_FLAGS_SUPPORT_HW_SCAN = BIT(3),
MM81X_FW_FLAGS_SUPPORT_CHIP_HALT_IRQ = BIT(4),
};
struct host_table {
__le32 magic_number;
__le32 fw_version_number;
__le32 host_flags;
__le32 fw_flags;
__le32 memcmd_cmd_addr;
__le32 memcmd_resp_addr;
__le32 ext_host_tbl_addr;
} __packed;
struct mm81x_regs {
u32 chip_id_address;
u32 irq_base_address;
u32 trgr_base_address;
u32 cpu_reset_address;
u32 cpu_reset_value;
u32 msi_address;
u32 msi_value;
u32 manifest_ptr_address;
u32 magic_num_value;
u32 clk_ctrl_address;
u32 clk_ctrl_value;
u32 early_clk_ctrl_value;
u32 boot_address;
u32 boot_value;
u32 pager_base_address;
u32 aon_latch;
u32 aon_latch_mask;
u32 aon_reset_usb_value;
u32 aon;
u8 aon_count;
};
int mm81x_hw_otp_get_board_type(struct mm81x *mors);
bool mm81x_hw_otp_valid_board_type(u32 board_type);
int mm81x_hw_otp_get_mac_addr(struct mm81x *mors);
void mm81x_hw_irq_enable(struct mm81x *mors, u32 irq, bool enable);
int mm81x_hw_irq_handle(struct mm81x *mors);
void mm81x_hw_irq_clear(struct mm81x *mors);
void mm81x_hw_toggle_aon_latch(struct mm81x *mors);
void mm81x_hw_enable_burst_mode(struct mm81x *mors, const u8 burst_mode);
int mm81x_hw_digital_reset(struct mm81x *mors);
void mm81x_hw_pre_firmware_ndr_hook(struct mm81x *mors);
void mm81x_hw_post_firmware_ndr_hook(struct mm81x *mors);
enum sdio_burst_mode {
SDIO_WORD_BURST_DISABLE =
0, /* Intentionally duplicate to make it clear it's disabled */
SDIO_WORD_BURST_SIZE_0 = 0, /* 000: no bursting (single 32bit word) */
SDIO_WORD_BURST_SIZE_2 = 1, /* 001: bursts of 2 words */
SDIO_WORD_BURST_SIZE_4 = 2, /* 010: bursts of 4 words */
SDIO_WORD_BURST_SIZE_8 = 3, /* 011: bursts of 8 words */
SDIO_WORD_BURST_SIZE_16 = 4, /* 100: bursts of 16 words */
SDIO_WORD_BURST_MASK = 7,
};
extern const struct mm81x_regs mm8108_regs;
void mm81x_hw_enable_stop_notifications(struct mm81x *mors, bool enable);
#endif /* !_MM81X_HW_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,63 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_MAC_H_
#define _MM81X_MAC_H_
#include "core.h"
#include "command.h"
struct mm81x_queue_params {
u8 uapsd;
u8 aci;
u8 aifs;
u16 cw_min;
u16 cw_max;
u32 txop;
};
static inline u32 mm81x_vif_generate_cssid(struct ieee80211_vif *vif)
{
return mm81x_generate_cssid(vif->cfg.ssid, vif->cfg.ssid_len);
}
/*
* Build a little-endian word from the last four octets of a MAC address;
* the first two octets are dropped.
*/
static inline __le32 mac2le32(const unsigned char *addr)
{
return cpu_to_le32(((u32)(addr[2]) << 24) | ((u32)(addr[3]) << 16) |
((u32)(addr[4]) << 8) | ((u32)(addr[5])));
}
static inline struct ieee80211_vif *
mm81x_rcu_dereference_vif_id(struct mm81x *mors, u8 vif_id, bool rcu)
{
if (WARN_ON(vif_id >= ARRAY_SIZE(mors->vifs)))
return NULL;
if (rcu)
return rcu_dereference(mors->vifs[vif_id]);
return rcu_dereference_protected(mors->vifs[vif_id],
lockdep_is_held(&mors->hw->wiphy->mtx));
}
int mm81x_tx_h_get_attempts(struct mm81x *mors,
struct mm81x_skb_tx_status *tx_sts);
struct mm81x *mm81x_mac_alloc(size_t priv_size, struct device *dev);
int mm81x_mac_register(struct mm81x *mors);
void mm81x_mac_free(struct mm81x *mors);
void mm81x_mac_unregister(struct mm81x *mors);
int mm81x_mac_event_recv(struct mm81x *mors, struct sk_buff *skb);
void mm81x_mac_rx_skb(struct mm81x *mors, struct sk_buff *skb,
struct mm81x_skb_rx_status *hdr_rx_status);
void mm81x_mac_beacon_irq_handle(struct mm81x *mors, u32 status);
u8 *mm81x_hw_scan_h_insert_tlvs(struct mm81x_hw_scan_params *params, u8 *buf);
size_t mm81x_hw_scan_h_get_cmd_size(struct mm81x_hw_scan_params *params);
void mm81x_tx_h_check_aggr(struct ieee80211_sta *pubsta, struct sk_buff *skb);
#endif /* !_MM81X_MAC_H_ */

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,193 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_MMRC_H_
#define _MM81X_MMRC_H_
#include <linux/version.h>
#include <linux/types.h>
#include <linux/slab.h>
#include <linux/bitops.h>
#include <linux/random.h>
#include <linux/time.h>
/* The max length of a retry chain for a single packet transmission */
#define MMRC_MAX_CHAIN_LENGTH 4
/* Rate minimum allowed attempts */
#define MMRC_MIN_CHAIN_ATTEMPTS 1
/* Rate upper limit for attempts */
#define MMRC_MAX_CHAIN_ATTEMPTS 2
/* The frequency of MMRC stat table updates */
#define MMRC_UPDATE_FREQUENCY_MS 100
enum mmrc_flags {
MMRC_FLAGS_CTS_RTS,
};
enum mmrc_mcs_rate {
MMRC_MCS0,
MMRC_MCS1,
MMRC_MCS2,
MMRC_MCS3,
MMRC_MCS4,
MMRC_MCS5,
MMRC_MCS6,
MMRC_MCS7,
MMRC_MCS8,
MMRC_MCS9,
MMRC_MCS10,
MMRC_MCS_UNUSED,
};
enum mmrc_bw {
MMRC_BW_1MHZ = 0,
MMRC_BW_2MHZ = 1,
MMRC_BW_4MHZ = 2,
MMRC_BW_8MHZ = 3,
MMRC_BW_16MHZ = 4,
MMRC_BW_MAX = 5,
};
enum mmrc_spatial_stream {
MMRC_SPATIAL_STREAM_1 = 0,
MMRC_SPATIAL_STREAM_2 = 1,
MMRC_SPATIAL_STREAM_3 = 2,
MMRC_SPATIAL_STREAM_4 = 3,
MMRC_SPATIAL_STREAM_MAX,
};
enum mmrc_guard {
MMRC_GUARD_LONG = 0,
MMRC_GUARD_SHORT = 1,
MMRC_GUARD_MAX,
};
#define MMRC_RATE_TO_BITFIELD(x) ((x) & 0xF)
#define MMRC_ATTEMPTS_TO_BITFIELD(x) ((x) & 0x7)
#define MMRC_GUARD_TO_BITFIELD(x) ((x) & 0x1)
#define MMRC_SS_TO_BITFIELD(x) ((x) & 0x3)
#define MMRC_BW_TO_BITFIELD(x) ((x) & 0x7)
#define MMRC_FLAGS_TO_BITFIELD(x) ((x) & 0x7)
struct mmrc_rate {
u8 rate : 4;
u8 attempts : 3;
u8 guard : 1;
u8 ss : 2;
u8 bw : 3;
u8 flags : 3;
u16 index;
};
struct mmrc_rate_table {
struct mmrc_rate rates[MMRC_MAX_CHAIN_LENGTH];
};
#define SGI_PER_BW(bw) (1 << (bw))
struct mmrc_sta_capabilities {
u8 max_rates : 3;
u8 max_retries : 3;
u8 bandwidth : 5;
u8 spatial_streams : 4;
u16 rates : 11;
u8 guard : 2;
u8 sta_flags : 4;
u8 sgi_per_bw : 5;
};
struct mmrc_stats_table {
u32 avg_throughput_counter;
u32 sum_throughput;
u32 max_throughput;
u16 sent;
u16 sent_success;
u16 back_mpdu_success;
u16 back_mpdu_failure;
u32 total_sent;
u32 total_success;
u16 evidence;
u8 prob;
bool have_sent_ampdus;
};
struct mmrc_table {
struct mmrc_sta_capabilities caps;
struct mmrc_rate best_tp;
struct mmrc_rate second_tp;
struct mmrc_rate baseline;
struct mmrc_rate best_prob;
struct mmrc_rate fixed_rate;
u32 cycle_cnt;
u32 last_lookaround_cycle;
u8 lookaround_cnt;
/* The ratio of using normal rate and sampling */
u8 lookaround_wrap;
/*
* A counter that is used to determine when we should force a
* lookaround. Should be a portion of the above lookaround with
* less constraints
*/
u8 forced_lookaround;
u8 current_lookaround_rate_attempts;
u16 current_lookaround_rate_index;
u32 total_lookaround;
/*
* A counter to detect if the current best rate is optimal
* and may slow down sample frequency.
*/
u32 stability_cnt;
u32 stability_cnt_threshold;
u8 probability_variation;
/* The difference in MCS from each of the last 2 rate changes */
s8 best_rate_diff[2];
/* Indication of random versus consistently one-sided variation */
s8 probability_variation_direction;
/* Has rate control detected possible interference */
bool interference_likely;
/* Has rate control detected the best rate is no longer converged */
bool unconverged;
/* Is rate control just entering unconverged state */
bool newly_unconverged;
/*
* Number of rate control cycles the best rate has remained
* unchanged
*/
s32 best_rate_cycle_count;
/*
* The probability table for the STA. This MUST always be the last
* element in the struct.
*/
struct mmrc_stats_table table[];
};
void mmrc_sta_init(struct mmrc_table *tb, struct mmrc_sta_capabilities *caps,
s8 rssi);
size_t mmrc_memory_required_for_caps(struct mmrc_sta_capabilities *caps);
void mmrc_get_rates(struct mmrc_table *tb, struct mmrc_rate_table *out,
size_t size);
void mmrc_feedback(struct mmrc_table *tb, struct mmrc_rate_table *rates,
s32 retry_count, bool was_aggregated);
void mmrc_update(struct mmrc_table *tb);
bool mmrc_set_fixed_rate(struct mmrc_table *tb, struct mmrc_rate fixed_rate);
u32 mmrc_calculate_theoretical_throughput(struct mmrc_rate rate);
u32 mmrc_calculate_rate_tx_time(struct mmrc_rate *rate, size_t size);
#endif /* _MMRC_H_ */

View File

@ -0,0 +1,120 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/workqueue.h>
#include "hif.h"
#include "skbq.h"
#include "mac.h"
#include "bus.h"
#include "ps.h"
static void mm81x_ps_wakeup(struct mm81x_ps *mps)
{
struct mm81x *mors = container_of(mps, struct mm81x, ps);
if (!mps->enable || !mps->suspended)
return;
mm81x_set_bus_enable(mors, true);
mps->suspended = false;
}
static void mm81x_ps_sleep(struct mm81x_ps *mps)
{
struct mm81x *mors = container_of(mps, struct mm81x, ps);
if (!mps->enable || mps->suspended)
return;
mps->suspended = true;
mm81x_set_bus_enable(mors, false);
}
static void mm81x_ps_evaluate(struct mm81x_ps *mps)
{
struct mm81x *mors = container_of(mps, struct mm81x, ps);
bool needs_wake = false;
unsigned long flags_on_entry =
(mors->hif.event_flags &
~BIT(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND));
if (!mps->enable)
return;
needs_wake = (mps->wakers > 0);
needs_wake |= (flags_on_entry > 0);
needs_wake |= (mm81x_hif_get_tx_buffered_count(mors) > 0);
if (needs_wake) {
mm81x_ps_wakeup(mps);
return;
}
mm81x_ps_sleep(mps);
}
static void mm81x_ps_evaluate_work(struct work_struct *work)
{
struct mm81x_ps *mps =
container_of(work, struct mm81x_ps, delayed_eval_work.work);
if (mps->enable) {
mutex_lock(&mps->lock);
mm81x_ps_evaluate(mps);
mutex_unlock(&mps->lock);
}
}
void mm81x_ps_enable(struct mm81x *mors)
{
struct mm81x_ps *mps = &mors->ps;
if (mps->enable) {
mutex_lock(&mps->lock);
if (mps->wakers == 0) {
WARN_ON_ONCE(1);
} else {
mps->wakers--;
mm81x_ps_evaluate(mps);
}
mutex_unlock(&mps->lock);
}
}
void mm81x_ps_disable(struct mm81x *mors)
{
struct mm81x_ps *mps = &mors->ps;
if (mps->enable) {
mutex_lock(&mps->lock);
mps->wakers++;
mm81x_ps_evaluate(mps);
mutex_unlock(&mps->lock);
}
}
int mm81x_ps_init(struct mm81x *mors)
{
struct mm81x_ps *mps = &mors->ps;
mps->enable = (mors->bus_type == MM81X_BUS_TYPE_USB);
mps->suspended = true;
mps->wakers = 1; /* we default to being on */
mutex_init(&mps->lock);
INIT_DELAYED_WORK(&mps->delayed_eval_work, mm81x_ps_evaluate_work);
return 0;
}
void mm81x_ps_finish(struct mm81x *mors)
{
struct mm81x_ps *mps = &mors->ps;
if (mps->enable) {
mps->enable = false;
cancel_delayed_work_sync(&mps->delayed_eval_work);
}
}

View File

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_PS_H_
#define _MM81X_PS_H_
#include "core.h"
/* This should be nominally <= the dynamic ps timeout */
#define NETWORK_BUS_TIMEOUT_MS (90)
/* The default period of time to wait to re-evaluate powersave */
#define DEFAULT_BUS_TIMEOUT_MS (50)
void mm81x_ps_disable(struct mm81x *mors);
void mm81x_ps_enable(struct mm81x *mors);
int mm81x_ps_init(struct mm81x *mors);
void mm81x_ps_finish(struct mm81x *mors);
#endif /* !_MM81X_PS_H_ */

View File

@ -0,0 +1,177 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_RATE_CODE_H_
#define _MM81X_RATE_CODE_H_
#include <linux/types.h>
enum dot11_bandwidth {
DOT11_BANDWIDTH_1MHZ = 0,
DOT11_BANDWIDTH_2MHZ = 1,
DOT11_BANDWIDTH_4MHZ = 2,
DOT11_BANDWIDTH_8MHZ = 3,
DOT11_BANDWIDTH_16MHZ = 4,
DOT11_MAX_BANDWIDTH = DOT11_BANDWIDTH_16MHZ,
DOT11_INVALID_BANDWIDTH = 5
};
enum mm81x_rate_preamble {
/* S1G LONG format (with SIG-A and SIG-B) */
MM81X_RATE_PREAMBLE_S1G_LONG = 0,
/* This is the most common format used */
MM81X_RATE_PREAMBLE_S1G_SHORT = 1,
/* S1G 1M format */
MM81X_RATE_PREAMBLE_S1G_1M = 2,
MM81X_RATE_MAX_PREAMBLE = MM81X_RATE_PREAMBLE_S1G_1M,
MM81X_RATE_INVALID_PREAMBLE = 7
};
typedef __le32 mm81x_rate_code_t;
#define MM81X_RATECODE_PREAMBLE (0x0000000F)
#define MM81X_RATECODE_MCS_INDEX (0x000000F0)
#define MM81X_RATECODE_NSS_INDEX (0x00000700)
#define MM81X_RATECODE_BW_INDEX (0x00003800)
#define MM81X_RATECODE_RTS_FLAG (0x00010000)
#define MM81X_RATECODE_SHORT_GI_FLAG (0x00040000)
#define MM81X_RATECODE_DUP_BW_INDEX (0x01C00000)
static inline enum mm81x_rate_preamble
mm81x_ratecode_preamble_get(mm81x_rate_code_t rc)
{
return (enum mm81x_rate_preamble)(
le32_get_bits(rc, MM81X_RATECODE_PREAMBLE));
}
static inline u8 mm81x_ratecode_mcs_index_get(mm81x_rate_code_t rc)
{
return le32_get_bits(rc, MM81X_RATECODE_MCS_INDEX);
}
static inline u8 mm81x_ratecode_nss_index_get(mm81x_rate_code_t rc)
{
return le32_get_bits(rc, MM81X_RATECODE_NSS_INDEX);
}
static inline enum dot11_bandwidth
mm81x_ratecode_bw_index_get(mm81x_rate_code_t rc)
{
return (enum dot11_bandwidth)(
le32_get_bits(rc, MM81X_RATECODE_BW_INDEX));
}
static inline bool mm81x_ratecode_rts_get(mm81x_rate_code_t rc)
{
return le32_get_bits(rc, MM81X_RATECODE_RTS_FLAG);
}
static inline bool mm81x_ratecode_sgi_get(mm81x_rate_code_t rc)
{
return le32_get_bits(rc, MM81X_RATECODE_SHORT_GI_FLAG);
}
static inline enum dot11_bandwidth
mm81x_ratecode_dup_bw_index_get(mm81x_rate_code_t rc)
{
return (enum dot11_bandwidth)(
le32_get_bits(rc, MM81X_RATECODE_DUP_BW_INDEX));
}
#define MM81X_RATECODE_INIT(bw_idx, nss_idx, mcs_idx, preamble) \
(le32_encode_bits((bw_idx), MM81X_RATECODE_BW_INDEX) | \
le32_encode_bits((nss_idx), MM81X_RATECODE_NSS_INDEX) | \
le32_encode_bits((mcs_idx), MM81X_RATECODE_MCS_INDEX) | \
le32_encode_bits((preamble), MM81X_RATECODE_PREAMBLE))
static inline mm81x_rate_code_t
mm81x_ratecode_init(enum dot11_bandwidth bw_index, u32 nss_index, u32 mcs_index,
enum mm81x_rate_preamble preamble)
{
return MM81X_RATECODE_INIT(bw_index, nss_index, mcs_index, preamble);
}
static inline void
mm81x_ratecode_preamble_set(mm81x_rate_code_t *rc,
enum mm81x_rate_preamble preamble)
{
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_PREAMBLE)) |
le32_encode_bits(preamble, MM81X_RATECODE_PREAMBLE);
}
static inline void mm81x_ratecode_mcs_index_set(mm81x_rate_code_t *rc,
u32 mcs_index)
{
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_MCS_INDEX)) |
le32_encode_bits(mcs_index, MM81X_RATECODE_MCS_INDEX);
}
static inline void mm81x_ratecode_nss_index_set(mm81x_rate_code_t *rc,
u32 nss_index)
{
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_NSS_INDEX)) |
le32_encode_bits(nss_index, MM81X_RATECODE_NSS_INDEX);
}
static inline void mm81x_ratecode_bw_index_set(mm81x_rate_code_t *rc,
enum dot11_bandwidth bw_index)
{
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_BW_INDEX)) |
le32_encode_bits(bw_index, MM81X_RATECODE_BW_INDEX);
}
static inline void
mm81x_ratecode_update_s1g_bw_preamble(mm81x_rate_code_t *rc,
enum dot11_bandwidth bw_index)
{
enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
if (bw_index == DOT11_BANDWIDTH_1MHZ)
pream = MM81X_RATE_PREAMBLE_S1G_1M;
mm81x_ratecode_preamble_set(rc, pream);
mm81x_ratecode_bw_index_set(rc, bw_index);
}
static inline void
mm81x_ratecode_dup_bw_index_set(mm81x_rate_code_t *rc,
enum dot11_bandwidth dup_bw_index)
{
*rc = (*rc & cpu_to_le32(~MM81X_RATECODE_DUP_BW_INDEX)) |
le32_encode_bits(dup_bw_index, MM81X_RATECODE_DUP_BW_INDEX);
}
static inline void mm81x_ratecode_enable_rts(mm81x_rate_code_t *rc)
{
*rc |= cpu_to_le32(MM81X_RATECODE_RTS_FLAG);
}
static inline void mm81x_ratecode_enable_sgi(mm81x_rate_code_t *rc)
{
*rc |= cpu_to_le32(MM81X_RATECODE_SHORT_GI_FLAG);
}
static inline enum dot11_bandwidth mm81x_ratecode_bw_mhz_to_bw_index(u8 bw_mhz)
{
return ((bw_mhz == 1) ? DOT11_BANDWIDTH_1MHZ :
(bw_mhz == 2) ? DOT11_BANDWIDTH_2MHZ :
(bw_mhz == 4) ? DOT11_BANDWIDTH_4MHZ :
(bw_mhz == 8) ? DOT11_BANDWIDTH_8MHZ :
DOT11_BANDWIDTH_2MHZ);
}
static inline u8
mm81x_ratecode_bw_index_to_s1g_bw_mhz(enum dot11_bandwidth bw_idx)
{
return ((bw_idx == DOT11_BANDWIDTH_1MHZ) ? 1 :
(bw_idx == DOT11_BANDWIDTH_2MHZ) ? 2 :
(bw_idx == DOT11_BANDWIDTH_4MHZ) ? 4 :
(bw_idx == DOT11_BANDWIDTH_8MHZ) ? 8 :
2);
}
#endif

View File

@ -0,0 +1,494 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/slab.h>
#include <linux/timer.h>
#include "core.h"
#include "mac.h"
#include "bus.h"
#include "rc.h"
#define MM81X_RC_BW_TO_MMRC_BW(X) \
(((X) == 1) ? MMRC_BW_1MHZ : \
((X) == 2) ? MMRC_BW_2MHZ : \
((X) == 4) ? MMRC_BW_4MHZ : \
((X) == 8) ? MMRC_BW_8MHZ : \
MMRC_BW_2MHZ)
static void mm81x_rc_work(struct work_struct *work)
{
struct mm81x_rc *mrc = container_of(work, struct mm81x_rc, work);
struct list_head *pos;
spin_lock_bh(&mrc->lock);
list_for_each(pos, &mrc->stas) {
struct mm81x_rc_sta *mrc_sta =
container_of(pos, struct mm81x_rc_sta, list);
unsigned long now = jiffies;
mrc_sta->last_update = now;
mmrc_update(mrc_sta->tb);
}
spin_unlock_bh(&mrc->lock);
mod_timer(&mrc->timer, jiffies + msecs_to_jiffies(100));
}
static void mm81x_rc_timer(struct timer_list *t)
{
struct mm81x_rc *mrc = timer_container_of(mrc, t, timer);
struct mm81x *mors = mrc->mors;
queue_work(mors->net_wq, &mors->mrc.work);
}
void mm81x_rc_init(struct mm81x *mors)
{
INIT_LIST_HEAD(&mors->mrc.stas);
spin_lock_init(&mors->mrc.lock);
INIT_WORK(&mors->mrc.work, mm81x_rc_work);
timer_setup(&mors->mrc.timer, mm81x_rc_timer, 0);
mors->mrc.mors = mors;
mod_timer(&mors->mrc.timer, jiffies + msecs_to_jiffies(100));
}
void mm81x_rc_deinit(struct mm81x *mors)
{
cancel_work_sync(&mors->mrc.work);
timer_delete_sync_try(&mors->mrc.timer);
}
static void mm81x_rc_sta_config_guard_per_bw(struct ieee80211_sta *sta,
struct mmrc_sta_capabilities *caps)
{
caps->guard = BIT(MMRC_GUARD_LONG);
if (caps->bandwidth & BIT(MMRC_BW_1MHZ)) {
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_1MHZ);
caps->guard |= BIT(MMRC_GUARD_SHORT);
}
if (caps->bandwidth & BIT(MMRC_BW_2MHZ)) {
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_2MHZ);
caps->guard |= BIT(MMRC_GUARD_SHORT);
}
if (caps->bandwidth & BIT(MMRC_BW_4MHZ)) {
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_4MHZ);
caps->guard |= BIT(MMRC_GUARD_SHORT);
}
if (caps->bandwidth & BIT(MMRC_BW_8MHZ)) {
caps->sgi_per_bw |= SGI_PER_BW(MMRC_BW_8MHZ);
caps->guard |= BIT(MMRC_GUARD_SHORT);
}
}
static void mm81x_rc_sta_add_s1g_sta_caps(struct mm81x *mors,
struct mmrc_sta_capabilities *caps,
struct ieee80211_sta_s1g_cap *s1g_cap)
{
int nss_idx = 0;
u8 rx_mcs = s1g_cap->nss_mcs[0] & 0x3; /* 1SS */
u8 tx_mcs = (s1g_cap->nss_mcs[2] >> 1) & 0x3; /* 1SS */
u8 mcs = min(rx_mcs, tx_mcs);
switch (mcs) {
case IEEE80211_VHT_MCS_SUPPORT_0_9: /* VHT 9 -> S1G 9 */
caps->rates |= BIT(MMRC_MCS9) | BIT(MMRC_MCS8);
fallthrough;
case IEEE80211_VHT_MCS_SUPPORT_0_8: /* VHT 8 -> S1G 7 */
caps->rates |= BIT(MMRC_MCS7) | BIT(MMRC_MCS6) |
BIT(MMRC_MCS5) | BIT(MMRC_MCS4) | BIT(MMRC_MCS3);
fallthrough;
case IEEE80211_VHT_MCS_SUPPORT_0_7: /* VHT 7 -> S1G 2 */
caps->rates |= BIT(MMRC_MCS2) | BIT(MMRC_MCS1) |
BIT(MMRC_MCS0) | BIT(MMRC_MCS10);
caps->spatial_streams |= (BIT(nss_idx) & 0x0F);
break;
default:
dev_warn(mors->dev, "Invalid MCS encoding 0x%02x for stream %d",
mcs, nss_idx);
}
}
int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,
struct ieee80211_sta *sta)
{
struct ieee80211_sta_s1g_cap *s1g_cap = &sta->deflink.s1g_cap;
struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
struct mmrc_sta_capabilities caps;
int oper_bw_mhz = cfg80211_chandef_get_width(&mors->chandef);
size_t table_mem_size;
struct mmrc_table *tb;
memset(&caps, 0, sizeof(caps));
mm81x_rc_sta_add_s1g_sta_caps(mors, &caps, s1g_cap);
/* Configure STA for support up to 8MHZ */
while (oper_bw_mhz > 0) {
caps.bandwidth |= BIT(MM81X_RC_BW_TO_MMRC_BW(oper_bw_mhz));
oper_bw_mhz >>= 1;
}
/* Configure STA for short and long guard */
mm81x_rc_sta_config_guard_per_bw(sta, &caps);
/* Set max rates */
if (mors->hw->max_rates > 0 &&
mors->hw->max_rates < IEEE80211_TX_MAX_RATES)
caps.max_rates = mors->hw->max_rates;
else
caps.max_rates = IEEE80211_TX_MAX_RATES;
/* Set max reties */
if (mors->hw->max_rate_tries >= MMRC_MIN_CHAIN_ATTEMPTS &&
mors->hw->max_rate_tries < MMRC_MAX_CHAIN_ATTEMPTS)
caps.max_retries = mors->hw->max_rate_tries;
else
caps.max_retries = MMRC_MAX_CHAIN_ATTEMPTS;
WARN_ON(msta->rc.tb);
table_mem_size = mmrc_memory_required_for_caps(&caps);
tb = kzalloc(table_mem_size, GFP_KERNEL);
if (!tb)
return -ENOMEM;
/* Initialise the STA rate control table */
mmrc_sta_init(tb, &caps, msta->avg_rssi);
spin_lock_bh(&mors->mrc.lock);
kfree(msta->rc.tb);
msta->rc.tb = tb;
list_add(&msta->rc.list, &mors->mrc.stas);
msta->rc.last_update = jiffies;
spin_unlock_bh(&mors->mrc.lock);
return 0;
}
void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta)
{
struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
spin_lock_bh(&mors->mrc.lock);
if (msta->rc.tb) {
list_del_init(&msta->rc.list);
kfree(msta->rc.tb);
msta->rc.tb = NULL;
}
spin_unlock_bh(&mors->mrc.lock);
}
static void mm81x_rc_sta_fill_basic_rates(struct mm81x_skb_tx_info *tx_info,
struct ieee80211_tx_info *info,
int tx_bw)
{
int i;
enum dot11_bandwidth bw_idx = mm81x_ratecode_bw_mhz_to_bw_index(tx_bw);
enum mm81x_rate_preamble pream = MM81X_RATE_PREAMBLE_S1G_SHORT;
mm81x_ratecode_mcs_index_set(&tx_info->rates[0].mm81x_ratecode, 0);
mm81x_ratecode_nss_index_set(&tx_info->rates[0].mm81x_ratecode,
NSS_TO_NSS_IDX(1));
mm81x_ratecode_bw_index_set(&tx_info->rates[0].mm81x_ratecode, bw_idx);
if (bw_idx == DOT11_BANDWIDTH_1MHZ)
pream = MM81X_RATE_PREAMBLE_S1G_1M;
mm81x_ratecode_preamble_set(&tx_info->rates[0].mm81x_ratecode, pream);
tx_info->rates[0].count = 4;
for (i = 1; i < IEEE80211_TX_MAX_RATES; i++)
tx_info->rates[i].count = 0;
info->control.rates[0].idx = 0;
info->control.rates[0].count = tx_info->rates[0].count;
info->control.rates[0].flags = 0;
info->control.rates[1].idx = -1;
}
static int mm81x_rc_sta_get_rates(struct mm81x *mors, struct mm81x_sta *msta,
struct mmrc_rate_table *rates, size_t size)
{
int ret = -ENOENT;
struct list_head *pos;
spin_lock_bh(&mors->mrc.lock);
list_for_each(pos, &mors->mrc.stas) {
struct mm81x_rc_sta *mrc_sta =
list_entry(pos, struct mm81x_rc_sta, list);
if (&msta->rc == mrc_sta) {
ret = 0;
mmrc_get_rates(msta->rc.tb, rates, size);
break;
}
}
spin_unlock_bh(&mors->mrc.lock);
return ret;
}
static bool mm81x_rc_use_basic_rates(struct ieee80211_sta *sta,
struct sk_buff *skb,
struct ieee80211_hdr *hdr)
{
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
if (!sta)
return true;
if (ieee80211_is_qos_nullfunc(hdr->frame_control) ||
ieee80211_is_nullfunc(hdr->frame_control))
return true;
if (!ieee80211_is_data_qos(hdr->frame_control))
return true;
/* Use basic rates for EAPOL exchanges or when instructed */
if (unlikely((skb->protocol == cpu_to_be16(ETH_P_PAE) ||
info->flags & IEEE80211_TX_CTL_USE_MINRATE)))
return true;
return false;
}
void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,
struct mm81x_skb_tx_info *tx_info,
struct sk_buff *skb, struct ieee80211_sta *sta,
int tx_bw, bool rts_allowed)
{
int ret, i;
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct mm81x_sta *msta;
struct mmrc_rate_table rates;
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(skb);
BUILD_BUG_ON((MMRC_BW_1MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_1MHZ ||
MMRC_BW_2MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_2MHZ ||
MMRC_BW_4MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_4MHZ ||
MMRC_BW_16MHZ != (enum mmrc_bw)DOT11_BANDWIDTH_16MHZ));
memset(&info->control.rates, 0, sizeof(info->control.rates));
memset(&info->status.rates, 0, sizeof(info->status.rates));
mm81x_rc_sta_fill_basic_rates(tx_info, info, tx_bw);
/* Use basic rates for non data packets */
if (mm81x_rc_use_basic_rates(sta, skb, hdr))
return;
msta = (struct mm81x_sta *)sta->drv_priv;
if (!msta)
return;
ret = mm81x_rc_sta_get_rates(mors, msta, &rates, skb->len);
if (ret != 0)
return;
for (i = 0; i < IEEE80211_TX_MAX_RATES; i++) {
info->control.rates[i].flags = 0;
if (rates.rates[i].rate != MMRC_MCS_UNUSED) {
u8 mcs = rates.rates[i].rate;
u8 nss_index = rates.rates[i].ss;
enum dot11_bandwidth bw_idx =
(enum dot11_bandwidth)rates.rates[i].bw;
enum mm81x_rate_preamble pream =
MM81X_RATE_PREAMBLE_S1G_SHORT;
mm81x_ratecode_bw_index_set(
&tx_info->rates[i].mm81x_ratecode, bw_idx);
mm81x_ratecode_mcs_index_set(
&tx_info->rates[i].mm81x_ratecode, mcs);
mm81x_ratecode_nss_index_set(
&tx_info->rates[i].mm81x_ratecode, nss_index);
if (bw_idx == DOT11_BANDWIDTH_1MHZ)
pream = MM81X_RATE_PREAMBLE_S1G_1M;
mm81x_ratecode_preamble_set(
&tx_info->rates[i].mm81x_ratecode, pream);
tx_info->rates[i].count = rates.rates[i].attempts;
if (rts_allowed &&
(rates.rates[i].flags & BIT(MMRC_FLAGS_CTS_RTS))) {
mm81x_ratecode_enable_rts(
&tx_info->rates[i].mm81x_ratecode);
info->control.rates[i].flags |=
IEEE80211_TX_RC_USE_RTS_CTS;
}
if (rates.rates[i].guard == MMRC_GUARD_SHORT) {
mm81x_ratecode_enable_sgi(
&tx_info->rates[i].mm81x_ratecode);
info->control.rates[i].flags |=
IEEE80211_TX_RC_SHORT_GI;
}
/* Update skb tx_info */
info->control.rates[i].idx = rates.rates[i].rate;
info->control.rates[i].count = rates.rates[i].attempts;
} else {
info->control.rates[i].idx = -1;
info->control.rates[i].count = 0;
tx_info->rates[i].count = 0;
}
}
}
static void mm81x_rc_sta_set_rates(struct mm81x *mors, struct mm81x_sta *msta,
struct mmrc_rate_table *rates, int attempts,
bool was_aggregated)
{
struct list_head *pos;
spin_lock_bh(&mors->mrc.lock);
list_for_each(pos, &mors->mrc.stas) {
struct mm81x_rc_sta *mrc_sta =
list_entry(pos, struct mm81x_rc_sta, list);
if (&msta->rc == mrc_sta) {
mmrc_feedback(msta->rc.tb, rates, attempts,
was_aggregated);
break;
}
}
spin_unlock_bh(&mors->mrc.lock);
}
void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,
struct ieee80211_sta *sta,
struct mm81x_skb_tx_status *tx_sts,
int attempts)
{
int i;
u32 tx_airtime = 0;
struct mmrc_rate_table rates;
struct ieee80211_hdr *hdr = (struct ieee80211_hdr *)skb->data;
struct ieee80211_tx_info *txi = IEEE80211_SKB_CB(skb);
struct ieee80211_tx_rate *r = &txi->status.rates[0];
int count = min_t(int, MM81X_SKB_MAX_RATES, IEEE80211_TX_MAX_RATES);
struct mm81x_sta *msta = msta = (struct mm81x_sta *)sta->drv_priv;
/* Don't update rate info if basic rates were used */
if (mm81x_rc_use_basic_rates(sta, skb, hdr))
goto exit;
if (attempts <= 0)
/* Did we really send the packet? */
goto exit;
for (i = 0; i < count; i++) {
rates.rates[i].rate = mm81x_ratecode_mcs_index_get(
tx_sts->rates[i].mm81x_ratecode);
rates.rates[i].ss = mm81x_ratecode_nss_index_get(
tx_sts->rates[i].mm81x_ratecode);
rates.rates[i].guard =
mm81x_ratecode_sgi_get(tx_sts->rates[i].mm81x_ratecode);
rates.rates[i].bw = mm81x_ratecode_bw_index_get(
tx_sts->rates[i].mm81x_ratecode);
rates.rates[i].flags =
mm81x_ratecode_rts_get(tx_sts->rates[i].mm81x_ratecode);
rates.rates[i].attempts = tx_sts->rates[i].count;
tx_airtime +=
mmrc_calculate_rate_tx_time(&rates.rates[i], skb->len);
}
if (msta) {
/*
* Save the rate information. This will be used to update
* station's tx rate stats
*/
msta->last_sta_tx_rate.bw = rates.rates[0].bw;
msta->last_sta_tx_rate.rate = rates.rates[0].rate;
msta->last_sta_tx_rate.ss = rates.rates[0].ss;
msta->last_sta_tx_rate.guard = rates.rates[0].guard;
}
mm81x_rc_sta_set_rates(mors, msta, &rates, attempts,
!!(le32_to_cpu(tx_sts->flags) &
MM81X_TX_STATUS_WAS_AGGREGATED));
ieee80211_sta_register_airtime(sta, tx_sts->tid, tx_airtime, 0);
exit:
ieee80211_tx_info_clear_status(txi);
if (!(le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_NO_ACK) &&
!(txi->flags & IEEE80211_TX_CTL_NO_ACK))
txi->flags |= IEEE80211_TX_STAT_ACK;
if (le32_to_cpu(tx_sts->flags) & MM81X_TX_STATUS_FLAGS_PS_FILTERED) {
txi->flags |= IEEE80211_TX_STAT_TX_FILTERED;
/*
* Clear TX CTL AMPDU flag so that this frame gets rescheduled
* in ieee80211_handle_filtered_frame(). This flag will get set
* again by mac80211's tx path on rescheduling.
*/
txi->flags &= ~IEEE80211_TX_CTL_AMPDU;
if (msta) {
if (!msta->tx_ps_filter_en)
dev_dbg(mors->dev, "TX ps filter set sta[%pM]",
msta->addr);
msta->tx_ps_filter_en = true;
}
}
for (i = 0; i < count; i++) {
if (tx_sts->rates[i].count > 0) {
r[i].count = tx_sts->rates[i].count;
r[i].flags |= IEEE80211_TX_RC_MCS;
} else {
r[i].idx = -1;
}
}
/* single packet per A-MPDU (for now) */
if (txi->flags & IEEE80211_TX_CTL_AMPDU) {
txi->flags |= IEEE80211_TX_STAT_AMPDU;
txi->status.ampdu_len = 1;
txi->status.ampdu_ack_len =
txi->flags & IEEE80211_TX_STAT_ACK ? 1 : 0;
}
/*
* Inform mac80211 that the SP (elicited by a PS-Poll or u-APSD) is
* over
*/
if (sta && (txi->flags & IEEE80211_TX_STATUS_EOSP)) {
txi->flags &= ~IEEE80211_TX_STATUS_EOSP;
ieee80211_sta_eosp(sta);
}
}
void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
enum ieee80211_sta_state old_state,
enum ieee80211_sta_state new_state)
{
struct mm81x_sta *msta = (struct mm81x_sta *)sta->drv_priv;
/* Add to Morse RC STA list */
if (old_state < new_state && new_state == IEEE80211_STA_ASSOC) {
/* Newly associated, add to RC */
mm81x_rc_sta_add(mors, vif, sta);
} else if (old_state > new_state && (old_state == IEEE80211_STA_ASSOC ||
old_state == IEEE80211_STA_AUTH)) {
/* Lost or failed association; remove from list */
mm81x_rc_sta_remove(mors, sta);
} else if (old_state < new_state && old_state == IEEE80211_STA_NONE &&
msta->rc.list.prev) {
/*
* Special case for driver warning issue causing a sta to be
* left on the list
*/
dev_dbg(mors->dev, "Remove stale sta from rc list");
mm81x_rc_sta_remove(mors, sta);
}
}

View File

@ -0,0 +1,51 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_RC_H_
#define _MM81X_RC_H_
#include <linux/list.h>
#include <linux/workqueue.h>
#include "core.h"
#include "mmrc.h"
struct mm81x_vif;
#define INIT_MAX_RATES_NUM 4
struct mm81x_rc {
/* Serialise rate control queue manipulation and timer functions */
spinlock_t lock;
struct list_head stas;
struct timer_list timer;
struct work_struct work;
struct mm81x *mors;
};
struct mm81x_rc_sta {
struct mmrc_table *tb;
struct list_head list;
unsigned long last_update;
};
void mm81x_rc_init(struct mm81x *mors);
void mm81x_rc_deinit(struct mm81x *mors);
int mm81x_rc_sta_add(struct mm81x *mors, struct ieee80211_vif *vif,
struct ieee80211_sta *sta);
void mm81x_rc_sta_remove(struct mm81x *mors, struct ieee80211_sta *sta);
void mm81x_rc_sta_fill_tx_rates(struct mm81x *mors,
struct mm81x_skb_tx_info *tx_info,
struct sk_buff *skb, struct ieee80211_sta *sta,
int tx_bw, bool rts_allowed);
void mm81x_rc_sta_feedback_rates(struct mm81x *mors, struct sk_buff *skb,
struct ieee80211_sta *sta,
struct mm81x_skb_tx_status *tx_sts,
int tx_attempts);
void mm81x_rc_sta_state_check(struct mm81x *mors, struct ieee80211_vif *vif,
struct ieee80211_sta *sta,
enum ieee80211_sta_state old_state,
enum ieee80211_sta_state new_state);
#endif /* !_MM81X_RC_H_ */

View File

@ -0,0 +1,613 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include <linux/mmc/card.h>
#include <linux/mmc/mmc.h>
#include <linux/mmc/host.h>
#include <linux/mmc/sdio_func.h>
#include <linux/mmc/sdio_ids.h>
#include <linux/mmc/sdio.h>
#include <linux/mmc/sd.h>
#include "hw.h"
#include "core.h"
#include "bus.h"
#include "mac.h"
#include "fw.h"
#include "hif.h"
/*
* Value to indicate that the base address for bulk/register
* read/writes has yet to be set
*/
#define MM81X_SDIO_BASE_ADDR_UNSET 0xFFFFFFFF
#define MM81X_SDIO_ALIGNMENT (8)
#define MM81X_SDIO_REG_ADDRESS_BASE 0x10000
#define MM81X_SDIO_REG_ADDRESS_WINDOW_0 MM81X_SDIO_REG_ADDRESS_BASE
#define MM81X_SDIO_REG_ADDRESS_WINDOW_1 (MM81X_SDIO_REG_ADDRESS_BASE + 1)
#define MM81X_SDIO_REG_ADDRESS_CONFIG (MM81X_SDIO_REG_ADDRESS_BASE + 2)
struct mm81x_sdio {
bool enabled;
u32 bulk_addr_base;
u32 register_addr_base;
struct sdio_func *func;
const struct sdio_device_id *id;
};
static void irq_handler(struct sdio_func *func1)
{
struct sdio_func *func = func1->card->sdio_func[1];
struct mm81x *mors = sdio_get_drvdata(func);
mm81x_hw_irq_handle(mors);
}
static int mm81x_sdio_enable_irq(struct mm81x_sdio *sdio)
{
int ret;
struct sdio_func *func = sdio->func;
struct sdio_func *func1 = func->card->sdio_func[0];
struct mm81x *mors = sdio_get_drvdata(func);
sdio_claim_host(func);
ret = sdio_claim_irq(func1, irq_handler);
if (ret)
dev_err(mors->dev, "Failed to enable sdio irq: %d\n", ret);
sdio_release_host(func);
return ret;
}
static void mm81x_sdio_disable_irq(struct mm81x_sdio *sdio)
{
struct sdio_func *func = sdio->func;
struct sdio_func *func1 = func->card->sdio_func[0];
sdio_claim_host(func);
sdio_release_irq(func1);
sdio_release_host(func);
}
static void mm81x_sdio_set_irq(struct mm81x *mors, bool enable)
{
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
if (enable)
mm81x_sdio_enable_irq(sdio);
else
mm81x_sdio_disable_irq(sdio);
}
static u32 mm81x_sdio_calculate_base_address(u32 address, u8 access)
{
return (address & MM81X_SDIO_RW_ADDR_BOUNDARY_MASK) | (access & 0x3);
}
static void mm81x_sdio_reset_base_address(struct mm81x_sdio *sdio)
{
sdio->bulk_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
sdio->register_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
}
static int mm81x_sdio_set_func_address_base(struct mm81x_sdio *sdio,
struct sdio_func *func, u32 address,
u8 access)
{
int ret = 0;
int retries = 0;
static const int max_retries = 3;
struct sdio_func *func2 = sdio->func;
struct mm81x *mors = sdio_get_drvdata(sdio->func);
s32 calculated_addr_base =
mm81x_sdio_calculate_base_address(address, access);
u32 *current_addr_base = func == func2 ? &sdio->bulk_addr_base :
&sdio->register_addr_base;
if ((*current_addr_base) == calculated_addr_base &&
*current_addr_base != MM81X_SDIO_BASE_ADDR_UNSET)
return ret;
retry:
sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(23, 16)),
MM81X_SDIO_REG_ADDRESS_WINDOW_0, &ret);
if (ret)
goto err;
sdio_writeb(func, (u8)u32_get_bits(address, GENMASK(31, 24)),
MM81X_SDIO_REG_ADDRESS_WINDOW_1, &ret);
if (ret)
goto err;
sdio_writeb(func, access & 0x3, MM81X_SDIO_REG_ADDRESS_CONFIG, &ret);
if (ret)
goto err;
*current_addr_base = calculated_addr_base;
if (retries)
dev_dbg(mors->dev, "%s succeeded after %d retries\n", __func__,
retries);
return ret;
err:
retries++;
if (ret == -ETIMEDOUT && retries <= max_retries) {
dev_dbg(mors->dev, "%s failed (%d), retrying (%d/%d)\n",
__func__, ret, retries, max_retries);
goto retry;
}
*current_addr_base = MM81X_SDIO_BASE_ADDR_UNSET;
return ret;
}
static int mm81x_sdio_mem_write_block(struct mm81x_sdio *sdio, u32 address,
u8 *data, ssize_t size)
{
int ret;
struct sdio_func *func2 = sdio->func;
struct mm81x *mors = sdio_get_drvdata(sdio->func);
mm81x_sdio_set_func_address_base(sdio, func2, address,
MM81X_CONFIG_ACCESS_4BYTE);
if (unlikely(!IS_ALIGNED((uintptr_t)data,
mors->bus_ops->bulk_alignment))) {
ret = -EBADE;
goto exit;
}
address &= 0x0000FFFF; /* remove base and keep offset */
ret = sdio_memcpy_toio(func2, address, data, size);
if (ret)
goto exit;
ret = size;
exit:
return ret;
}
static int mm81x_sdio_mem_write_byte(struct mm81x_sdio *sdio, u32 address,
u8 *data, ssize_t size)
{
int i, ret;
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
mm81x_sdio_set_func_address_base(sdio, func1, address,
MM81X_CONFIG_ACCESS_1BYTE);
address &= 0x0000FFFF; /* remove base and keep offset */
for (i = 0; i < size; i++) {
sdio_writeb(func1, data[i], address + i, (int *)&ret);
if (ret)
goto exit;
}
ret = size;
exit:
return ret;
}
static void mm81x_sdio_claim_host(struct mm81x *mors)
{
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
struct sdio_func *func = sdio->func;
sdio_claim_host(func);
}
static void mm81x_sdio_release_host(struct mm81x *mors)
{
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
struct sdio_func *func = sdio->func;
sdio_release_host(func);
}
static int mm81x_sdio_mem_read_block(struct mm81x_sdio *sdio, u32 address,
u8 *data, ssize_t size)
{
int ret;
struct sdio_func *func2 = sdio->func;
struct mm81x *mors = sdio_get_drvdata(sdio->func);
mm81x_sdio_set_func_address_base(sdio, func2, address,
MM81X_CONFIG_ACCESS_4BYTE);
if (unlikely(!IS_ALIGNED((uintptr_t)data,
mors->bus_ops->bulk_alignment))) {
ret = -EBADE;
goto exit;
}
address &= 0x0000FFFF; /* remove base and keep offset */
ret = sdio_memcpy_fromio(func2, data, address, size);
if (ret)
goto exit;
/*
* Observed sometimes that SDIO read repeats the first 4-bytes
* word twice, overwriting second word (hence, tail will be
* overwritten with 'sync' byte). When this happens, reading
* will fetch the correct word. NB: if repeated again, pass it
* anyway and upper layers will handle it
*/
if (size >= 8 && memcmp(data, data + 4, 4) == 0)
sdio_memcpy_fromio(func2, data, address, 8);
ret = size;
exit:
return ret;
}
static int mm81x_sdio_mem_read_byte(struct mm81x_sdio *sdio, u32 address,
u8 *data, ssize_t size)
{
int i, ret;
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
mm81x_sdio_set_func_address_base(sdio, func1, address,
MM81X_CONFIG_ACCESS_1BYTE);
address &= 0x0000FFFF; /* remove base and keep offset */
for (i = 0; i < size; i++) {
data[i] = sdio_readb(func1, address + i, (int *)&ret);
if (ret)
goto exit;
}
ret = size;
exit:
return ret;
}
static int mm81x_sdio_dm_write(struct mm81x *mors, u32 address, const u8 *data,
int len)
{
int ret = 0;
int block_len, byte_len;
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
int remaining = len;
int offset = 0;
if (remaining > 0 && address & 0x3) {
len = 4 - (address & 0x3);
ret = mm81x_sdio_mem_write_byte(sdio, address, (u8 *)data, len);
if (ret != len)
return -EIO;
offset += len;
remaining -= len;
}
while ((remaining) > 0) {
/*
* We can only write up to the end of a single window in
* each write operation.
*/
u32 window_end = (address + offset) |
~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
len = min(remaining, (int)(window_end + 1 - address - offset));
block_len = len & ~0x3;
byte_len = len & 0x3;
if (block_len) {
ret = mm81x_sdio_mem_write_block(sdio, address + offset,
(u8 *)(data + offset),
block_len);
if (ret != block_len)
return -EIO;
offset += block_len;
}
if (byte_len) {
ret = mm81x_sdio_mem_write_byte(sdio, address + offset,
(u8 *)(data + offset),
byte_len);
if (ret != byte_len)
return -EIO;
offset += byte_len;
}
remaining -= len;
}
return 0;
}
static int mm81x_sdio_dm_read(struct mm81x *mors, u32 address, u8 *data,
int len)
{
int ret = 0;
int block_len, byte_len;
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
int remaining = len;
int offset = 0;
if (remaining > 0 && address & 0x3) {
len = 4 - (address & 0x3);
ret = mm81x_sdio_mem_read_byte(sdio, address, data, len);
if (ret != len)
return -EIO;
offset += len;
remaining -= len;
}
while (remaining > 0) {
/*
* We can only read up to the end of a single window in
* each read operation.
*/
u32 window_end = (address + offset) |
~MM81X_SDIO_RW_ADDR_BOUNDARY_MASK;
len = min(remaining, (int)(window_end + 1 - address - offset));
block_len = len & ~0x3;
byte_len = len & 0x3;
if (block_len) {
ret = mm81x_sdio_mem_read_block(sdio, address + offset,
data + offset,
block_len);
if (ret != block_len)
return -EIO;
offset += block_len;
}
if (byte_len) {
ret = mm81x_sdio_mem_read_byte(sdio, address + offset,
data + offset, byte_len);
if (ret != byte_len)
return -EIO;
offset += byte_len;
}
remaining -= len;
}
return 0;
}
static int mm81x_sdio_reg32_write(struct mm81x *mors, u32 address, u32 val)
{
ssize_t ret = 0;
u32 original_address = address;
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
mm81x_sdio_set_func_address_base(sdio, func1, address,
MM81X_CONFIG_ACCESS_4BYTE);
address &= 0x0000FFFF;
sdio_writel(func1, (__force u32)cpu_to_le32(val),
(__force u32)cpu_to_le32(address), (int *)&ret);
if (ret)
goto error;
return 0;
error:
if (original_address == MM81X_REG_RESET(mors) &&
val == MM81X_REG_RESET_VALUE(mors)) {
dev_dbg(mors->dev,
"SDIO reset detected, invalidating base addr\n");
mm81x_sdio_reset_base_address(sdio);
}
return -EIO;
}
static int mm81x_sdio_reg32_read(struct mm81x *mors, u32 address, u32 *val)
{
u32 value;
ssize_t ret = 0;
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
struct sdio_func *func1 = sdio->func->card->sdio_func[0];
mm81x_sdio_set_func_address_base(sdio, func1, address,
MM81X_CONFIG_ACCESS_4BYTE);
address &= 0x0000FFFF;
value = sdio_readl(func1, (__force u32)cpu_to_le32(address),
(int *)&ret);
if (ret)
return ret;
*val = le32_to_cpup((__le32 *)&value);
return 0;
}
static void mm81x_sdio_bus_enable(struct mm81x *mors, bool enable)
{
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
struct sdio_func *func = sdio->func;
struct mmc_host *host = func->card->host;
sdio_claim_host(func);
if (enable) {
/*
* No need to do anything special to re-enable the sdio bus.
* This will happen automatically when a read/write is
* attempted and sdio->bulk_addr_base == 0.
*/
sdio->enabled = true;
host->ops->enable_sdio_irq(host, 1);
dev_dbg(mors->dev, "%s: enabling bus\n", __func__);
} else {
host->ops->enable_sdio_irq(host, 0);
mm81x_sdio_reset_base_address(sdio);
sdio->enabled = false;
dev_dbg(mors->dev, "%s: disabling bus\n", __func__);
}
sdio_release_host(func);
}
static void mm81x_sdio_reset(struct sdio_func *func)
{
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
mdelay(20);
sdio_claim_host(func);
sdio_disable_func(func);
mmc_hw_reset(func->card);
sdio_enable_func(func);
sdio_release_host(func);
}
static void mm81x_sdio_config_burst_mode(struct mm81x *mors, bool enable_burst)
{
u8 burst_mode = (enable_burst) ? SDIO_WORD_BURST_SIZE_16 :
SDIO_WORD_BURST_DISABLE;
mm81x_hw_enable_burst_mode(mors, burst_mode);
}
static const struct mm81x_bus_ops mm81x_sdio_ops = {
.dm_read = mm81x_sdio_dm_read,
.dm_write = mm81x_sdio_dm_write,
.reg32_read = mm81x_sdio_reg32_read,
.reg32_write = mm81x_sdio_reg32_write,
.set_bus_enable = mm81x_sdio_bus_enable,
.claim = mm81x_sdio_claim_host,
.release = mm81x_sdio_release_host,
.config_burst_mode = mm81x_sdio_config_burst_mode,
.set_irq = mm81x_sdio_set_irq,
.bulk_alignment = MM81X_SDIO_ALIGNMENT
};
static int mm81x_sdio_enable(struct mm81x_sdio *sdio)
{
int ret;
struct sdio_func *func = sdio->func;
struct mm81x *mors = sdio_get_drvdata(func);
sdio_claim_host(func);
ret = sdio_enable_func(func);
if (ret)
dev_err(mors->dev, "sdio_enable_func failed: %d\n", ret);
sdio_release_host(func);
return ret;
}
static void mm81x_sdio_release(struct mm81x_sdio *sdio)
{
struct sdio_func *func = sdio->func;
sdio_claim_host(func);
sdio_disable_func(func);
sdio_release_host(func);
}
static int mm81x_sdio_probe(struct sdio_func *func,
const struct sdio_device_id *id)
{
int ret = 0;
struct mm81x *mors = NULL;
struct mm81x_sdio *sdio;
struct device *dev = &func->dev;
if (func->num == 1)
return 0;
if (func->num != 2)
return -ENODEV;
mors = mm81x_core_alloc(sizeof(*sdio), dev);
if (!mors)
return -ENOMEM;
mors->bus_ops = &mm81x_sdio_ops;
mors->bus_type = MM81X_BUS_TYPE_SDIO;
sdio = (struct mm81x_sdio *)mors->drv_priv;
sdio->func = func;
sdio->id = id;
sdio->enabled = true;
mm81x_sdio_reset_base_address(sdio);
sdio_set_drvdata(func, mors);
ret = mm81x_sdio_enable(sdio);
if (ret)
goto err_core_free;
mm81x_sdio_config_burst_mode(mors, true);
ret = mm81x_core_init(mors);
if (ret)
goto err_sdio_release;
ret = mm81x_sdio_enable_irq(sdio);
if (ret)
goto err_core_deinit;
ret = mm81x_core_register(mors);
if (ret)
goto err_disable_irq;
return 0;
err_disable_irq:
mm81x_sdio_disable_irq(sdio);
err_core_deinit:
mm81x_core_deinit(mors);
err_sdio_release:
mm81x_sdio_release(sdio);
err_core_free:
mm81x_core_free(mors);
return ret;
}
static void mm81x_sdio_remove(struct sdio_func *func)
{
struct mm81x *mors = sdio_get_drvdata(func);
struct mm81x_sdio *sdio = (struct mm81x_sdio *)mors->drv_priv;
if (!mors)
return;
mm81x_core_unregister(mors);
mm81x_sdio_disable_irq(sdio);
mm81x_core_deinit(mors);
mm81x_sdio_release(sdio);
mm81x_sdio_reset(func);
mm81x_core_free(mors);
sdio_set_drvdata(func, NULL);
}
static const struct sdio_device_id mm81x_sdio_devices[] = {
{ SDIO_DEVICE(SDIO_VENDOR_ID_MORSEMICRO,
SDIO_DEVICE_ID_MORSEMICRO_MM8108) },
{},
};
MODULE_DEVICE_TABLE(sdio, mm81x_sdio_devices);
static struct sdio_driver mm81x_sdio_driver = {
.name = "mm81x_sdio",
.id_table = mm81x_sdio_devices,
.probe = mm81x_sdio_probe,
.remove = mm81x_sdio_remove,
};
module_sdio_driver(mm81x_sdio_driver);
MODULE_AUTHOR("Morse Micro");
MODULE_DESCRIPTION("Driver support for Morse Micro MM81X SDIO devices");
MODULE_LICENSE("Dual BSD/GPL");

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,218 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_SKBQ_H_
#define _MM81X_SKBQ_H_
#include <linux/skbuff.h>
#include <linux/workqueue.h>
#include "rate_code.h"
/* Sync value of skb header to indicate a valid skb */
#define MM81X_SKB_HEADER_SYNC (0xAA)
/* Sync value indicating that the chip owns this skb */
#define MM81X_SKB_HEADER_CHIP_OWNED_SYNC (0xBB)
enum mm81x_tx_status_and_conf_flags {
MM81X_TX_STATUS_FLAGS_NO_ACK = BIT(0),
MM81X_TX_STATUS_FLAGS_NO_REPORT = BIT(1),
MM81X_TX_CONF_FLAGS_CTL_AMPDU = BIT(2),
MM81X_TX_CONF_FLAGS_HW_ENCRYPT = BIT(3),
MM81X_TX_CONF_FLAGS_VIF_ID = (BIT(4) | BIT(5) | BIT(6) | BIT(7) |
BIT(8) | BIT(9) | BIT(10) | BIT(11)),
MM81X_TX_CONF_FLAGS_KEY_IDX = (BIT(12) | BIT(13) | BIT(14)),
MM81X_TX_STATUS_FLAGS_PS_FILTERED = (BIT(15)),
MM81X_TX_CONF_IGNORE_TWT = (BIT(16)),
MM81X_TX_STATUS_PAGE_INVALID = (BIT(17)),
MM81X_TX_CONF_NO_PS_BUFFER = (BIT(18)),
MM81X_TX_STATUS_DUTY_CYCLE_CANT_SEND = (BIT(19)),
MM81X_TX_CONF_HAS_PV1_BPN_IN_BODY = (BIT(21)),
MM81X_TX_CONF_FLAGS_SEND_AFTER_DTIM = (BIT(22)),
MM81X_TX_STATUS_WAS_AGGREGATED = (BIT(23)),
MM81X_TX_CONF_FLAGS_FULLMAC_REPORT = BIT(24),
MM81X_TX_CONF_FLAGS_IMMEDIATE_REPORT = (BIT(31))
};
/* Getter and setter macros for vif id */
#define MM81X_TX_CONF_FLAGS_VIF_ID_MASK (0xFF)
#define MM81X_TX_CONF_FLAGS_VIF_ID_SET(x) \
(((x) & MM81X_TX_CONF_FLAGS_VIF_ID_MASK) << 4)
#define MM81X_TX_CONF_FLAGS_VIF_ID_GET(x) \
(((x) & MM81X_TX_CONF_FLAGS_VIF_ID) >> 4)
/* Getter and setter macros for key index */
#define MM81X_TX_CONF_FLAGS_KEY_IDX_SET(x) (((x) & 0x07) << 12)
#define MM81X_TX_CONF_FLAGS_KEY_IDX_GET(x) \
(((x) & MM81X_TX_CONF_FLAGS_KEY_IDX) >> 12)
enum mm81x_rx_status_flags {
MM81X_RX_STATUS_FLAGS_ERROR = BIT(0),
MM81X_RX_STATUS_FLAGS_DECRYPTED = BIT(1),
MM81X_RX_STATUS_FLAGS_FCS_INCLUDED = BIT(2),
MM81X_RX_STATUS_FLAGS_EOF = BIT(3),
MM81X_RX_STATUS_FLAGS_AMPDU = BIT(4),
MM81X_RX_STATUS_FLAGS_NDP = BIT(7),
MM81X_RX_STATUS_FLAGS_UPLINK = BIT(8),
MM81X_RX_STATUS_FLAGS_RI = (BIT(9) | BIT(10)),
MM81X_RX_STATUS_FLAGS_NDP_TYPE = (BIT(11) | BIT(12) | BIT(13)),
MM81X_RX_STATUS_FLAGS_CRC_ERROR = BIT(14),
MM81X_RX_STATUS_FLAGS_VIF_ID = GENMASK(24, 17),
};
/* Getter and Setter macros for vif id */
#define MM81X_RX_STATUS_FLAGS_VIF_ID_MASK (0xFF)
#define MM81X_RX_STATUS_FLAGS_VIF_ID_SET(x) \
(((x) & MM81X_RX_STATUS_FLAGS_VIF_ID_MASK) << 17)
#define MM81X_RX_STATUS_FLAGS_VIF_ID_GET(x) \
(((x) & MM81X_RX_STATUS_FLAGS_VIF_ID) >> 17)
#define MM81X_RX_STATUS_FLAGS_VIF_ID_CLEAR(x) \
((x) & ~(MM81X_RX_STATUS_FLAGS_VIF_ID_MASK << 17))
/* Getter macro for guard interval */
#define MM81X_RX_STATUS_FLAGS_UPL_IND_GET(x) \
(((x) & MM81X_RX_STATUS_FLAGS_UPLINK) >> 8)
/* Getter macro for response indication */
#define MM81X_RX_STATUS_FLAGS_RI_GET(x) (((x) & MM81X_RX_STATUS_FLAGS_RI) >> 9)
/* Getter macro for NDP type */
#define MM81X_RX_STATUS_FLAGS_NDP_TYPE_GET(x) \
(((x) & MM81X_RX_STATUS_FLAGS_NDP_TYPE) >> 11)
enum mm81x_skb_channel {
MM81X_SKB_CHAN_DATA = 0x0,
MM81X_SKB_CHAN_NDP_FRAMES = 0x1,
MM81X_SKB_CHAN_DATA_NOACK = 0x2,
MM81X_SKB_CHAN_BEACON = 0x3,
MM81X_SKB_CHAN_MGMT = 0x4,
MM81X_SKB_CHAN_INTERNAL_CRIT_BEACON = 0x80,
MM81X_SKB_CHAN_COMMAND = 0xFE,
MM81X_SKB_CHAN_TX_STATUS = 0xFF
};
#define MM81X_SKB_MAX_RATES (4)
struct mm81x_skb_rate_info {
mm81x_rate_code_t mm81x_ratecode;
u8 count;
} __packed;
struct mm81x_skb_tx_status {
__le32 flags;
__le32 pkt_id;
u8 tid;
u8 channel;
__le16 ampdu_info;
struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];
} __packed;
#define MM81X_TXSTS_AMPDU_INFO_GET_TAG(x) (((x) >> 10) & 0x3F)
#define MM81X_TXSTS_AMPDU_INFO_GET_LEN(x) (((x) >> 5) & 0x1F)
#define MM81X_TXSTS_AMPDU_INFO_GET_SUC(x) ((x) & 0x1F)
struct mm81x_skb_tx_info {
__le32 flags;
__le32 pkt_id;
u8 tid;
u8 tid_params;
u8 mmss_params;
u8 padding[1];
struct mm81x_skb_rate_info rates[MM81X_SKB_MAX_RATES];
} __packed;
#define TX_INFO_TID_PARAMS_MAX_REORDER_BUF 0x1f
#define TX_INFO_TID_PARAMS_AMPDU_ENABLED 0x20
#define TX_INFO_TID_PARAMS_AMSDU_SUPPORTED 0x40
#define TX_INFO_TID_PARAMS_USE_LEGACY_BA 0x80
/* Bitmap for MMSS (Minimum MPDU start spacing) parameters
* +-----------+-----------+
* | Morse | MMSS set |
* | MMSS | by S1G cap|
* | offset | IE |
* |-----------|-----------|
* |b7|b6|b5|b4|b3|b2|b1|b0|
*/
#define TX_INFO_MMSS_PARAMS_MMSS_MASK GENMASK(3, 0)
#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START 4
#define TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK GENMASK(7, 4)
#define TX_INFO_MMSS_PARAMS_SET_MMSS(x) ((x) & TX_INFO_MMSS_PARAMS_MMSS_MASK)
#define TX_INFO_MMSS_PARAMS_SET_MMSS_OFFSET(x) \
(((x) << TX_INFO_MMSS_PARAMS_MMSS_OFFSET_START) & \
TX_INFO_MMSS_PARAMS_MMSS_OFFSET_MASK)
struct mm81x_skb_rx_status {
__le32 flags;
mm81x_rate_code_t mm81x_ratecode;
__le16 rssi;
__le16 freq_100khz;
u8 bss_color;
s8 noise_dbm;
/** Padding for word alignment */
u8 padding[2];
__le64 rx_timestamp_us;
} __packed;
struct mm81x_skb_hdr {
u8 sync;
u8 channel;
__le16 len;
u8 offset;
u8 checksum_lower;
__le16 checksum_upper;
union {
struct mm81x_skb_tx_info tx_info;
struct mm81x_skb_tx_status tx_status;
struct mm81x_skb_rx_status rx_status;
};
} __packed;
#define MM81X_SKBQ_SIZE (4 * 128 * 1024)
struct mm81x;
struct mm81x_skbq {
struct mm81x *mors;
u32 pkt_seq; /* SKB sequence used in tx_status */
u16 flags;
u32 skbq_size; /* current off loaded size */
spinlock_t lock;
struct sk_buff_head skbq;
struct sk_buff_head pending; /* packets sent pending feedback */
struct work_struct dispatch_work;
};
void mm81x_skbq_purge(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
void mm81x_skbq_purge_aged(struct mm81x *mors, struct mm81x_skbq *mq);
u32 mm81x_skbq_space(struct mm81x_skbq *mq);
u32 mm81x_skbq_size(struct mm81x_skbq *mq);
int mm81x_skbq_deq_num_skb(struct mm81x_skbq *mq, struct sk_buff_head *skbq,
int num_skb);
struct sk_buff *mm81x_skbq_alloc_skb(struct mm81x_skbq *mq,
unsigned int length);
int mm81x_skbq_skb_tx(struct mm81x_skbq *mq, struct sk_buff **skb,
struct mm81x_skb_tx_info *tx_info, u8 channel);
int mm81x_skbq_put(struct mm81x_skbq *mq, struct sk_buff *skb);
void mm81x_skbq_enq(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
void mm81x_skbq_enq_prepend(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
void mm81x_skbq_tx_complete(struct mm81x_skbq *mq, struct sk_buff_head *skbq);
struct sk_buff *mm81x_skbq_tx_pending(struct mm81x_skbq *mq);
void mm81x_skbq_init(struct mm81x *mors, struct mm81x_skbq *mq, u16 flags);
void mm81x_skbq_finish(struct mm81x_skbq *mq);
void mm81x_skbq_pull_hdr_post_tx(struct sk_buff *skb);
void mm81x_skbq_mon_dump(struct mm81x *mors, struct seq_file *file);
void mm81x_skbq_skb_finish(struct mm81x_skbq *mq, struct sk_buff *skb,
struct mm81x_skb_tx_status *tx_sts);
void mm81x_skbq_tx_flush(struct mm81x_skbq *mq);
int mm81x_skbq_check_for_stale_tx(struct mm81x *mors, struct mm81x_skbq *mq);
void mm81x_skbq_may_wake_tx_queues(struct mm81x *mors);
u32 mm81x_skbq_count_tx_ready(struct mm81x_skbq *mq);
u32 mm81x_skbq_count(struct mm81x_skbq *mq);
u32 mm81x_skbq_pending_count(struct mm81x_skbq *mq);
void mm81x_skbq_data_traffic_pause(struct mm81x *mors);
void mm81x_skbq_data_traffic_resume(struct mm81x *mors);
bool mm81x_skbq_validate_checksum(u8 *data);
#endif /* !_MM81X_SKBQ_H_ */

View File

@ -0,0 +1,943 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/usb.h>
#include "hif.h"
#include "bus.h"
#include "mac.h"
#include "core.h"
/*
* URB timeout in milliseconds. If an URB does not complete within this
* time, it will be killed. This timeout needs to account for USB suspendand
* resume occurring before the URB can be transferred, and it also needs to
* account for transferring USB_MAX_TRANSFER_SIZE bytes over a potentially
* slow, congested USB Full Speed link.
*/
#define URB_TIMEOUT_MS 250
/* High speed USB 2^(4-1) * 125usec = 1msec */
#define MM81X_USB_INTERRUPT_INTERVAL 4
/* Max bytes per USB read/write */
#define USB_MAX_TRANSFER_SIZE (16 * 1024)
/* INT EP buffer size */
#define MM81X_EP_INT_BUFFER_SIZE 8
/* Morse vendor IDs*/
#define MM81X_VENDOR_ID 0x325b
#define MM81X_MM810X_PRODUCT_ID 0x8100
/* Power management runtime auto-suspend delay value in milliseconds */
#define PM_RUNTIME_AUTOSUSPEND_DELAY_MS 100
enum mm81x_usb_endpoints {
MM81X_EP_CMD = 0,
MM81X_EP_INT,
MM81X_EP_MEM_RD,
MM81X_EP_MEM_WR,
MM81X_EP_REG_RD,
MM81X_EP_REG_WR,
MM81X_EP_EP_MAX,
};
struct mm81x_usb_endpoint {
unsigned char *buffer;
struct urb *urb;
__u8 addr;
int size;
};
enum mm81x_usb_flags { MM81X_USB_FLAG_ATTACHED, MM81X_USB_FLAG_SUSPENDED };
struct mm81x_usb {
struct usb_device *udev;
struct usb_interface *interface;
struct mm81x_usb_endpoint endpoints[MM81X_EP_EP_MAX];
int errors;
/* serialise USB device struct */
struct mutex lock;
/* serialise USB bus access */
struct mutex bus_lock;
bool ongoing_cmd;
bool ongoing_rw;
wait_queue_head_t rw_in_wait;
unsigned long flags;
};
enum mm81x_usb_command_direction {
MM81X_USB_WRITE = 0x00,
MM81X_USB_READ = 0x80,
MM81X_USB_RESET = 0x02,
};
struct mm81x_usb_command {
__le32 dir; /* Next BULK direction */
__le32 address; /* Next BULK address */
__le32 length; /* Next BULK size */
};
static const struct usb_device_id mm81x_usb_table[] = {
{ USB_DEVICE(MM81X_VENDOR_ID, MM81X_MM810X_PRODUCT_ID) },
{} /* Terminating entry */
};
MODULE_DEVICE_TABLE(usb, mm81x_usb_table);
static void mm81x_usb_irq_work(struct work_struct *work)
{
struct mm81x *mors = container_of(work, struct mm81x, usb_irq_work);
mm81x_claim_bus(mors);
mm81x_hw_irq_handle(mors);
mm81x_release_bus(mors);
}
static bool mm81x_usb_urb_status_is_disconnect(const struct urb *urb)
{
return ((urb->status == -EPROTO) || (urb->status == -EILSEQ) ||
(urb->status == -ETIME) || (urb->status == -EPIPE));
}
static void mm81x_usb_int_handler(struct urb *urb)
{
int ret;
struct mm81x *mors = urb->context;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return;
if (urb->status) {
if (mm81x_usb_urb_status_is_disconnect(urb)) {
clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
set_bit(MM81X_STATE_CHIP_UNRESPONSIVE,
&mors->state_flags);
dev_dbg(mors->dev,
"USB sudden disconnect detected in %s",
__func__);
return;
}
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(mors->dev, "- nonzero read status received: %d",
urb->status);
}
ret = usb_submit_urb(urb, GFP_ATOMIC);
/* usb_kill_urb has been called */
if (ret == -EPERM)
return;
else if (ret)
dev_err(mors->dev, "error: resubmit urb %p err code %d", urb,
ret);
queue_work(mors->chip_wq, &mors->usb_irq_work);
}
static int mm81x_usb_int_enable(struct mm81x *mors)
{
int ret = 0;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
struct urb *urb;
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
urb = usb_alloc_urb(0, GFP_KERNEL);
if (!urb) {
ret = -ENOMEM;
goto out;
}
musb->endpoints[MM81X_EP_INT].urb = urb;
musb->endpoints[MM81X_EP_INT].buffer =
usb_alloc_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
GFP_KERNEL, &urb->transfer_dma);
if (!musb->endpoints[MM81X_EP_INT].buffer) {
dev_err(mors->dev, "couldn't allocate transfer_buffer");
ret = -ENOMEM;
goto error_set_urb_null;
}
usb_fill_int_urb(
musb->endpoints[MM81X_EP_INT].urb, musb->udev,
usb_rcvintpipe(musb->udev, musb->endpoints[MM81X_EP_INT].addr),
musb->endpoints[MM81X_EP_INT].buffer, MM81X_EP_INT_BUFFER_SIZE,
mm81x_usb_int_handler, mors, MM81X_USB_INTERRUPT_INTERVAL);
urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
ret = usb_submit_urb(urb, GFP_KERNEL);
if (ret) {
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
goto error;
}
return 0;
error:
usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
musb->endpoints[MM81X_EP_INT].buffer,
urb->transfer_dma);
error_set_urb_null:
musb->endpoints[MM81X_EP_INT].urb = NULL;
usb_free_urb(urb);
out:
return ret;
}
static void mm81x_usb_int_stop(struct mm81x *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
usb_kill_urb(musb->endpoints[MM81X_EP_INT].urb);
cancel_work_sync(&mors->usb_irq_work);
}
static void mm81x_usb_cmd_callback(struct urb *urb)
{
struct mm81x *mors = urb->context;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
/* sync/async unlink faults aren't errors */
if (urb->status) {
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(mors->dev,
"nonzero write bulk status received: %d",
urb->status);
musb->errors = urb->status;
}
musb->ongoing_cmd = false;
wake_up(&musb->rw_in_wait);
}
static int mm81x_usb_cmd(struct mm81x_usb *musb,
const struct mm81x_usb_command *cmd)
{
int retval = 0;
struct mm81x *mors = usb_get_intfdata(musb->interface);
struct mm81x_usb_endpoint *ep = &musb->endpoints[MM81X_EP_CMD];
size_t writesize = sizeof(*cmd);
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
memcpy(ep->buffer, cmd, writesize);
usb_fill_bulk_urb(ep->urb, musb->udev,
usb_sndbulkpipe(musb->udev, ep->addr), ep->buffer,
writesize, mm81x_usb_cmd_callback, mors);
ep->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;
musb->ongoing_cmd = true;
retval = usb_submit_urb(ep->urb, GFP_KERNEL);
if (retval) {
dev_err(mors->dev, "- failed submitting write urb, error %d",
retval);
goto error;
}
retval = wait_event_interruptible_timeout(
musb->rw_in_wait, (!musb->ongoing_cmd),
msecs_to_jiffies(URB_TIMEOUT_MS));
if (retval < 0) {
dev_err(mors->dev, "error waiting for urb %d", retval);
goto error;
} else if (retval == 0) {
dev_err(mors->dev, "timed out waiting for urb");
usb_kill_urb(ep->urb);
retval = -ETIMEDOUT;
goto error;
}
musb->ongoing_cmd = false;
return writesize;
error:
musb->ongoing_cmd = false;
return retval;
}
static int mm81x_usb_ndr_reset(struct mm81x *mors)
{
int ret;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
struct mm81x_usb_command cmd;
mutex_lock(&musb->lock);
musb->ongoing_rw = true;
musb->errors = 0;
cmd.dir = cpu_to_le32(MM81X_USB_RESET);
cmd.address = cpu_to_le32(0);
cmd.length = cpu_to_le32(0);
ret = mm81x_usb_cmd(musb, &cmd);
if (ret < 0)
dev_err(mors->dev, "mm81x_usb_cmd (MM81X_USB_RESET) error %d\n",
ret);
else
ret = 0;
musb->ongoing_rw = false;
mutex_unlock(&musb->lock);
return ret;
}
static void mm81x_usb_mem_rw_callback(struct urb *urb)
{
struct mm81x *mors = urb->context;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
/* sync/async unlink faults aren't errors */
if (urb->status) {
if (!(urb->status == -ENOENT || urb->status == -ECONNRESET ||
urb->status == -ESHUTDOWN))
dev_err(mors->dev,
"nonzero write bulk status received: %d",
urb->status);
musb->errors = urb->status;
}
musb->ongoing_rw = false;
wake_up(&musb->rw_in_wait);
}
static int mm81x_usb_mem_read(struct mm81x_usb *musb, u32 address, u8 *data,
ssize_t size)
{
int ret;
struct mm81x_usb_command cmd;
struct mm81x *mors = usb_get_intfdata(musb->interface);
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
mutex_lock(&musb->lock);
musb->ongoing_rw = true;
musb->errors = 0;
/* Send command ahead to prepare for Tokens */
cmd.dir = cpu_to_le32(MM81X_USB_READ);
cmd.address = cpu_to_le32(address);
cmd.length = cpu_to_le32(size);
ret = mm81x_usb_cmd(musb, &cmd);
if (ret < 0) {
dev_err(mors->dev, "mm81x_usb_cmd error %d", ret);
goto error;
}
/* Let's be fast push the next URB, don't wait until command is done */
usb_fill_bulk_urb(
musb->endpoints[MM81X_EP_MEM_RD].urb, musb->udev,
usb_rcvbulkpipe(musb->udev,
musb->endpoints[MM81X_EP_MEM_RD].addr),
musb->endpoints[MM81X_EP_MEM_RD].buffer, size,
mm81x_usb_mem_rw_callback, mors);
ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_RD].urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(mors->dev, "failed submitting read urb, error %d", ret);
ret = (ret == -ENOMEM) ? ret : -EIO;
goto error;
}
ret = wait_event_interruptible_timeout(
musb->rw_in_wait, (!musb->ongoing_rw),
msecs_to_jiffies(URB_TIMEOUT_MS));
if (ret < 0) {
dev_err(mors->dev, "wait_event_interruptible: error %d", ret);
goto error;
} else if (ret == 0) {
/* Timed out. */
usb_kill_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
}
if (musb->errors) {
ret = musb->errors;
dev_err(mors->dev, "mem read error %d", ret);
goto error;
}
memcpy(data, musb->endpoints[MM81X_EP_MEM_RD].buffer, size);
ret = size;
error:
musb->ongoing_rw = false;
mutex_unlock(&musb->lock);
return ret;
}
static int mm81x_usb_mem_write(struct mm81x_usb *musb, u32 address, u8 *data,
ssize_t size)
{
int ret;
struct mm81x_usb_command cmd;
struct mm81x *mors = usb_get_intfdata(musb->interface);
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
mutex_lock(&musb->lock);
musb->ongoing_rw = true;
musb->errors = 0;
/* Send command ahead to prepare for Tokens */
cmd.dir = cpu_to_le32(MM81X_USB_WRITE);
cmd.address = cpu_to_le32(address);
cmd.length = cpu_to_le32(size);
ret = mm81x_usb_cmd(musb, &cmd);
if (ret < 0) {
dev_err(mors->dev, "mm81x_usb_mem_read error %d", ret);
goto error;
}
memcpy(musb->endpoints[MM81X_EP_MEM_WR].buffer, data, size);
/* prepare a read */
usb_fill_bulk_urb(
musb->endpoints[MM81X_EP_MEM_WR].urb, musb->udev,
usb_sndbulkpipe(musb->udev,
musb->endpoints[MM81X_EP_MEM_WR].addr),
musb->endpoints[MM81X_EP_MEM_WR].buffer, size,
mm81x_usb_mem_rw_callback, mors);
ret = usb_submit_urb(musb->endpoints[MM81X_EP_MEM_WR].urb, GFP_ATOMIC);
if (ret < 0) {
dev_err(mors->dev, "- failed submitting write urb, error %d",
ret);
ret = (ret == -ENOMEM) ? ret : -EIO;
goto error;
}
ret = wait_event_interruptible_timeout(
musb->rw_in_wait, (!musb->ongoing_rw),
msecs_to_jiffies(URB_TIMEOUT_MS));
if (ret < 0) {
dev_err(mors->dev, "error %d", ret);
goto error;
} else if (ret == 0) {
/* Timed out. */
usb_kill_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
}
if (musb->errors) {
ret = musb->errors;
dev_err(mors->dev, "error %d", ret);
goto error;
}
ret = size;
error:
musb->ongoing_rw = false;
mutex_unlock(&musb->lock);
return ret;
}
static int mm81x_usb_dm_read(struct mm81x *mors, u32 address, u8 *data, int len)
{
ssize_t offset = 0;
int ret;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
while (offset < len) {
ret = mm81x_usb_mem_read(musb, address + offset,
(u8 *)(data + offset),
min((ssize_t)(len - offset),
(ssize_t)USB_MAX_TRANSFER_SIZE));
if (ret < 0) {
dev_err(mors->dev, "%s failed (errno=%d)", __func__,
ret);
return ret;
}
offset += ret;
}
return 0;
}
static int mm81x_usb_dm_write(struct mm81x *mors, u32 address, const u8 *data,
int len)
{
ssize_t offset = 0;
int ret;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
while (offset < len) {
ret = mm81x_usb_mem_write(musb, address + offset,
(u8 *)(data + offset),
min((ssize_t)(len - offset),
(ssize_t)USB_MAX_TRANSFER_SIZE));
if (ret < 0) {
dev_err(mors->dev, "%s failed (errno=%d)", __func__,
ret);
return ret;
}
offset += ret;
}
return 0;
}
static int mm81x_usb_reg32_read(struct mm81x *mors, u32 address, u32 *val)
{
int ret = 0;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
ret = mm81x_usb_mem_read(musb, address, (u8 *)val, sizeof(*val));
if (ret == sizeof(*val)) {
*val = le32_to_cpup((__le32 *)val);
return 0;
}
dev_err(mors->dev, "usb reg32 read failed %d", ret);
return ret;
}
static int mm81x_usb_reg32_write(struct mm81x *mors, u32 address, u32 val)
{
int ret = 0;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
__le32 val_le = cpu_to_le32(val);
ret = mm81x_usb_mem_write(musb, address, (u8 *)&val_le, sizeof(val_le));
if (ret == sizeof(val_le))
return 0;
dev_err(mors->dev, "usb reg32 write failed %d", ret);
return ret;
}
static void mm81x_usb_bus_enable(struct mm81x *mors, bool enable)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
if (enable)
usb_autopm_get_interface(musb->interface);
else
usb_autopm_put_interface(musb->interface);
}
static void mm81x_usb_claim_bus(struct mm81x *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
mutex_lock(&musb->bus_lock);
}
static void mm81x_usb_release_bus(struct mm81x *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
mutex_unlock(&musb->bus_lock);
}
static void mm81x_usb_set_irq(struct mm81x *mors, bool enable)
{
}
static const struct mm81x_bus_ops mm81x_usb_ops = {
.dm_read = mm81x_usb_dm_read,
.dm_write = mm81x_usb_dm_write,
.reg32_read = mm81x_usb_reg32_read,
.reg32_write = mm81x_usb_reg32_write,
.digital_reset = mm81x_usb_ndr_reset,
.set_bus_enable = mm81x_usb_bus_enable,
.claim = mm81x_usb_claim_bus,
.release = mm81x_usb_release_bus,
.set_irq = mm81x_usb_set_irq,
.bulk_alignment = MM81X_BUS_DEFAULT_BULK_ALIGNMENT,
};
static int mm81x_usb_detect_endpoints(struct mm81x *mors,
const struct usb_interface *intf)
{
int ret;
unsigned int i;
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
struct usb_endpoint_descriptor *ep_desc;
struct usb_host_interface *intf_desc = intf->cur_altsetting;
for (i = 0; i < intf_desc->desc.bNumEndpoints; i++) {
ep_desc = &intf_desc->endpoint[i].desc;
if (usb_endpoint_is_bulk_in(ep_desc)) {
if (!musb->endpoints[MM81X_EP_MEM_RD].addr) {
musb->endpoints[MM81X_EP_MEM_RD].addr =
usb_endpoint_num(ep_desc);
musb->endpoints[MM81X_EP_MEM_RD].size =
usb_endpoint_maxp(ep_desc);
} else if (!musb->endpoints[MM81X_EP_REG_RD].addr) {
musb->endpoints[MM81X_EP_REG_RD].addr =
usb_endpoint_num(ep_desc);
musb->endpoints[MM81X_EP_REG_RD].size =
usb_endpoint_maxp(ep_desc);
}
} else if (usb_endpoint_is_bulk_out(ep_desc)) {
if (!musb->endpoints[MM81X_EP_MEM_WR].addr) {
musb->endpoints[MM81X_EP_MEM_WR].addr =
usb_endpoint_num(ep_desc);
musb->endpoints[MM81X_EP_MEM_WR].size =
usb_endpoint_maxp(ep_desc);
} else if (!musb->endpoints[MM81X_EP_REG_WR].addr) {
musb->endpoints[MM81X_EP_REG_WR].addr =
usb_endpoint_num(ep_desc);
musb->endpoints[MM81X_EP_REG_WR].size =
usb_endpoint_maxp(ep_desc);
}
} else if (usb_endpoint_is_int_in(ep_desc)) {
musb->endpoints[MM81X_EP_INT].addr =
usb_endpoint_num(ep_desc);
musb->endpoints[MM81X_EP_INT].size =
usb_endpoint_maxp(ep_desc);
}
}
dev_dbg(mors->dev, "\tMemory Endpoint IN %s detected: %u size %u",
musb->endpoints[MM81X_EP_MEM_RD].addr ? "" : "not",
musb->endpoints[MM81X_EP_MEM_RD].addr,
musb->endpoints[MM81X_EP_MEM_RD].size);
dev_dbg(mors->dev, "\tMemory Endpoint OUT %s detected: %u size %u",
musb->endpoints[MM81X_EP_MEM_WR].addr ? "" : "not",
musb->endpoints[MM81X_EP_MEM_WR].addr,
musb->endpoints[MM81X_EP_MEM_WR].size);
dev_dbg(mors->dev, "\tRegister Endpoint IN %s detected: %u",
musb->endpoints[MM81X_EP_REG_RD].addr ? "" : "not",
musb->endpoints[MM81X_EP_REG_RD].addr);
dev_dbg(mors->dev, "\tRegister Endpoint OUT %s detected: %u",
musb->endpoints[MM81X_EP_REG_WR].addr ? "" : "not",
musb->endpoints[MM81X_EP_REG_WR].addr);
dev_dbg(mors->dev, "\tStats IN endpoint %s detected: %u",
musb->endpoints[MM81X_EP_INT].addr ? "" : "not",
musb->endpoints[MM81X_EP_INT].addr);
/* Verify we have an IN and OUT */
if (!(musb->endpoints[MM81X_EP_MEM_RD].addr &&
musb->endpoints[MM81X_EP_MEM_WR].addr))
return -ENODEV;
/* Verify the stats MM81X_EP_INT is detected */
if (!musb->endpoints[MM81X_EP_INT].addr)
return -ENODEV;
/* Verify minimum interrupt status read */
if (musb->endpoints[MM81X_EP_INT].size < 8)
return -ENODEV;
musb->endpoints[MM81X_EP_CMD].urb = usb_alloc_urb(0, GFP_KERNEL);
if (!musb->endpoints[MM81X_EP_CMD].urb) {
ret = -ENOMEM;
goto err_ep;
}
musb->endpoints[MM81X_EP_MEM_RD].urb = usb_alloc_urb(0, GFP_KERNEL);
if (!musb->endpoints[MM81X_EP_MEM_RD].urb) {
ret = -ENOMEM;
goto err_ep;
}
musb->endpoints[MM81X_EP_MEM_WR].urb = usb_alloc_urb(0, GFP_KERNEL);
if (!musb->endpoints[MM81X_EP_MEM_WR].urb) {
ret = -ENOMEM;
goto err_ep;
}
musb->endpoints[MM81X_EP_MEM_RD].buffer =
kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
if (!musb->endpoints[MM81X_EP_MEM_RD].buffer) {
ret = -ENOMEM;
goto err_ep;
}
musb->endpoints[MM81X_EP_MEM_WR].buffer =
kmalloc(USB_MAX_TRANSFER_SIZE, GFP_KERNEL);
if (!musb->endpoints[MM81X_EP_MEM_WR].buffer) {
ret = -ENOMEM;
goto err_ep;
}
musb->endpoints[MM81X_EP_CMD].buffer = usb_alloc_coherent(
musb->udev, sizeof(struct mm81x_usb_command), GFP_KERNEL,
&musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
if (!musb->endpoints[MM81X_EP_CMD].buffer) {
ret = -ENOMEM;
goto err_ep;
}
/* Assign command to memory out end point */
musb->endpoints[MM81X_EP_CMD].addr =
musb->endpoints[MM81X_EP_MEM_WR].addr;
musb->endpoints[MM81X_EP_CMD].size =
musb->endpoints[MM81X_EP_MEM_WR].size;
return 0;
err_ep:
if (musb->endpoints[MM81X_EP_CMD].urb &&
musb->endpoints[MM81X_EP_CMD].buffer)
usb_free_coherent(
musb->udev, sizeof(struct mm81x_usb_command),
musb->endpoints[MM81X_EP_CMD].buffer,
musb->endpoints[MM81X_EP_CMD].urb->transfer_dma);
usb_free_urb(musb->endpoints[MM81X_EP_MEM_RD].urb);
usb_free_urb(musb->endpoints[MM81X_EP_CMD].urb);
usb_free_urb(musb->endpoints[MM81X_EP_MEM_WR].urb);
kfree(musb->endpoints[MM81X_EP_MEM_RD].buffer);
kfree(musb->endpoints[MM81X_EP_MEM_WR].buffer);
return ret;
}
static void mm81x_urb_cleanup(struct mm81x *mors)
{
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
usb_kill_urb(rd_ep->urb);
usb_kill_urb(wr_ep->urb);
usb_kill_urb(cmd_ep->urb);
if (int_ep->urb)
usb_free_coherent(musb->udev, MM81X_EP_INT_BUFFER_SIZE,
int_ep->buffer, int_ep->urb->transfer_dma);
if (cmd_ep->urb)
usb_free_coherent(musb->udev, sizeof(struct mm81x_usb_command),
cmd_ep->buffer, cmd_ep->urb->transfer_dma);
kfree(wr_ep->buffer);
kfree(rd_ep->buffer);
usb_free_urb(int_ep->urb);
usb_free_urb(wr_ep->urb);
usb_free_urb(rd_ep->urb);
usb_free_urb(cmd_ep->urb);
}
static int mm81x_usb_probe(struct usb_interface *interface,
const struct usb_device_id *id)
{
int ret;
struct mm81x *mors;
struct mm81x_usb *musb;
mors = mm81x_core_alloc(sizeof(*musb), &interface->dev);
if (!mors)
return -ENOMEM;
mors->bus_ops = &mm81x_usb_ops;
mors->bus_type = MM81X_BUS_TYPE_USB;
musb = (struct mm81x_usb *)mors->drv_priv;
musb->udev = usb_get_dev(interface_to_usbdev(interface));
musb->interface = usb_get_intf(interface);
mutex_init(&musb->lock);
mutex_init(&musb->bus_lock);
init_waitqueue_head(&musb->rw_in_wait);
usb_set_intfdata(interface, mors);
ret = mm81x_usb_detect_endpoints(mors, interface);
if (ret < 0)
goto err_core_free;
set_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
ret = mm81x_core_init(mors);
if (ret)
goto err_urb_cleanup;
INIT_WORK(&mors->usb_irq_work, mm81x_usb_irq_work);
ret = mm81x_usb_int_enable(mors);
if (ret)
goto err_core_deinit;
ret = mm81x_core_register(mors);
if (ret)
goto err_usb_int_stop;
/* USB requires remote wakeup functionality for suspend */
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
musb->interface->needs_remote_wakeup = 1;
usb_enable_autosuspend(musb->udev);
pm_runtime_set_autosuspend_delay(&musb->udev->dev,
PM_RUNTIME_AUTOSUSPEND_DELAY_MS);
usb_autopm_get_interface(interface);
return 0;
err_usb_int_stop:
mm81x_usb_int_stop(mors);
err_core_deinit:
mm81x_core_deinit(mors);
err_urb_cleanup:
mm81x_urb_cleanup(mors);
err_core_free:
mm81x_core_free(mors);
usb_put_intf(interface);
usb_put_dev(interface_to_usbdev(interface));
return ret;
}
static void mm81x_usb_disconnect(struct usb_interface *interface)
{
struct mm81x *mors = usb_get_intfdata(interface);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
int minor = interface->minor;
struct usb_device *udev = interface_to_usbdev(interface);
if (udev->state == USB_STATE_NOTATTACHED) {
clear_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags);
set_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags);
dev_dbg(mors->dev, "USB suddenly unplugged");
}
usb_disable_autosuspend(udev);
if (test_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags)) {
dev_dbg(mors->dev, "USB was suspended: release locks");
mm81x_usb_release_bus(mors);
mutex_unlock(&musb->lock);
}
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
mm81x_core_unregister(mors);
mm81x_usb_int_stop(mors);
mm81x_core_deinit(mors);
mm81x_urb_cleanup(mors);
mm81x_core_free(mors);
usb_autopm_put_interface(interface);
usb_set_intfdata(interface, NULL);
dev_info(&interface->dev, "USB Morse #%d now disconnected", minor);
usb_put_intf(interface);
usb_put_dev(udev);
}
static int mm81x_usb_suspend(struct usb_interface *intf, pm_message_t message)
{
struct mm81x *mors = usb_get_intfdata(intf);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
struct mm81x_usb_endpoint *rd_ep = &musb->endpoints[MM81X_EP_MEM_RD];
struct mm81x_usb_endpoint *wr_ep = &musb->endpoints[MM81X_EP_MEM_WR];
struct mm81x_usb_endpoint *cmd_ep = &musb->endpoints[MM81X_EP_CMD];
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
usb_kill_urb(int_ep->urb);
usb_kill_urb(rd_ep->urb);
usb_kill_urb(wr_ep->urb);
usb_kill_urb(cmd_ep->urb);
/* Locking the bus. No USB communication after this point */
mm81x_usb_claim_bus(mors);
mutex_lock(&musb->lock);
set_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
return 0;
}
static int mm81x_usb_resume(struct usb_interface *intf)
{
struct mm81x *mors = usb_get_intfdata(intf);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
int ret;
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
if (ret)
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
mm81x_usb_release_bus(mors);
mutex_unlock(&musb->lock);
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
return 0;
}
static int mm81x_usb_reset_resume(struct usb_interface *intf)
{
struct mm81x *mors = usb_get_intfdata(intf);
struct mm81x_usb *musb = (struct mm81x_usb *)mors->drv_priv;
int ret;
struct mm81x_usb_endpoint *int_ep = &musb->endpoints[MM81X_EP_INT];
if (!test_bit(MM81X_USB_FLAG_ATTACHED, &musb->flags))
return -ENODEV;
ret = usb_submit_urb(int_ep->urb, GFP_KERNEL);
if (ret)
dev_err(mors->dev, "Couldn't submit urb. Error number %d", ret);
mm81x_usb_release_bus(mors);
mutex_unlock(&musb->lock);
clear_bit(MM81X_USB_FLAG_SUSPENDED, &musb->flags);
return 0;
}
static int mm81x_usb_pre_reset(struct usb_interface *intf)
{
return 0;
}
static int mm81x_usb_post_reset(struct usb_interface *intf)
{
return 0;
}
static struct usb_driver mm81x_usb_driver = {
.name = "mm81x_usb",
.probe = mm81x_usb_probe,
.disconnect = mm81x_usb_disconnect,
.suspend = mm81x_usb_suspend,
.resume = mm81x_usb_resume,
.reset_resume = mm81x_usb_reset_resume,
.pre_reset = mm81x_usb_pre_reset,
.post_reset = mm81x_usb_post_reset,
.id_table = mm81x_usb_table,
.supports_autosuspend = 1,
.soft_unbind = 1,
};
module_usb_driver(mm81x_usb_driver);
MODULE_AUTHOR("Morse Micro");
MODULE_DESCRIPTION("Driver support for Morse Micro MM81X USB devices");
MODULE_LICENSE("Dual BSD/GPL");

View File

@ -0,0 +1,704 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include <linux/gpio.h>
#include <linux/random.h>
#include <linux/timer.h>
#include <linux/bitops.h>
#include <linux/slab.h>
#include "hif.h"
#include "ps.h"
#include "bus.h"
#include "command.h"
#include "skbq.h"
/* This is a fail safe timeout */
#define CHIP_FULL_RECOVERY_TIMEOUT_MS 30
/* Defined as the max number of MPDUs per AMPDU */
#define MAX_PKTS_PER_TX_TXN 16
#define MAX_PKTS_PER_RX_TXN 32
static int mm81x_yaps_alloc_pkt_buffers(struct mm81x_yaps *yaps)
{
yaps->hw.to_chip_pkts = kcalloc(MAX_PKTS_PER_TX_TXN,
sizeof(*yaps->hw.to_chip_pkts),
GFP_KERNEL);
if (!yaps->hw.to_chip_pkts)
return -ENOMEM;
yaps->hw.from_chip_pkts = kcalloc(MAX_PKTS_PER_RX_TXN,
sizeof(*yaps->hw.from_chip_pkts),
GFP_KERNEL);
if (!yaps->hw.from_chip_pkts) {
kfree(yaps->hw.to_chip_pkts);
yaps->hw.to_chip_pkts = NULL;
return -ENOMEM;
}
return 0;
}
static void mm81x_yaps_free_pkt_buffers(struct mm81x_yaps *yaps)
{
kfree(yaps->hw.from_chip_pkts);
yaps->hw.from_chip_pkts = NULL;
kfree(yaps->hw.to_chip_pkts);
yaps->hw.to_chip_pkts = NULL;
}
static int mm81x_yaps_write_pkts(struct mm81x_yaps *yaps,
struct mm81x_yaps_pkt *pkts, int num_pkts,
int *num_pkts_sent)
{
return yaps->ops->write_pkts(yaps, pkts, num_pkts, num_pkts_sent);
}
static int mm81x_yaps_read_pkts(struct mm81x_yaps *yaps,
struct mm81x_yaps_pkt *pkts, int num_pkts_max,
int *num_pkts_received)
{
return yaps->ops->read_pkts(yaps, pkts, num_pkts_max,
num_pkts_received);
}
static int mm81x_yaps_update_status(struct mm81x_yaps *yaps)
{
return yaps->ops->update_status(yaps);
}
/* Mappings between sk_buff, skbq and yaps */
static struct mm81x_skbq *mm81x_yaps_tc_q_from_aci(struct mm81x *mors, int aci)
{
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
if (aci >= ARRAY_SIZE(yaps->data_tx_qs))
return NULL;
return &yaps->data_tx_qs[aci];
}
static void mm81x_yaps_get_tx_qs(struct mm81x *mors, struct mm81x_skbq **qs,
int *num_qs)
{
*qs = mors->hif.u.yaps.data_tx_qs;
*num_qs = YAPS_TX_SKBQ_MAX;
}
static struct mm81x_skbq *mm81x_yaps_get_bcn_tc_q(struct mm81x *mors)
{
return &mors->hif.u.yaps.beacon_q;
}
static struct mm81x_skbq *mm81x_yaps_get_mgmt_tc_q(struct mm81x *mors)
{
return &mors->hif.u.yaps.mgmt_q;
}
static struct mm81x_skbq *mm81x_yaps_get_tx_cmd_queue(struct mm81x *mors)
{
return &mors->hif.u.yaps.cmd_q;
}
static int mm81x_yaps_irq_handler(struct mm81x *mors, u32 status)
{
if (status & BIT(MM81X_INT_YAPS_FC_PKT_WAITING_IRQN))
set_bit(MM81X_HIF_EVT_RX_PEND, &mors->hif.event_flags);
if (status & BIT(MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN)) {
timer_delete_sync_try(&mors->hif.u.yaps.chip_queue_full.timer);
set_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND,
&mors->hif.event_flags);
}
queue_work(mors->chip_wq, &mors->hif_work);
return 0;
}
const struct mm81x_hif_ops mm81x_yaps_ops = {
.init = mm81x_yaps_init,
.flush_tx_data = mm81x_yaps_flush_tx_data,
.flush_cmds = mm81x_yaps_flush_cmds,
.get_tx_status_pending_count = mm81x_yaps_get_tx_status_pending_count,
.get_tx_buffered_count = mm81x_yaps_get_tx_buffered_count,
.finish = mm81x_yaps_finish,
.skbq_get_tx_qs = mm81x_yaps_get_tx_qs,
.get_tx_beacon_queue = mm81x_yaps_get_bcn_tc_q,
.get_tx_mgmt_queue = mm81x_yaps_get_mgmt_tc_q,
.get_tx_cmd_queue = mm81x_yaps_get_tx_cmd_queue,
.get_tx_data_queue = mm81x_yaps_tc_q_from_aci,
.handle_irq = mm81x_yaps_irq_handler
};
static int mm81x_yaps_read_pkt(struct mm81x_yaps *yaps, struct sk_buff *skb)
{
struct mm81x *mors = yaps->mors;
struct sk_buff_head skbq;
struct mm81x_skbq *mq = NULL;
struct mm81x_skb_hdr *hdr;
int skb_bytes_remaining;
int skb_len;
int ret = 0;
if (!skb) {
ret = -EINVAL;
goto exit_return_page;
}
__skb_queue_head_init(&skbq);
hdr = (struct mm81x_skb_hdr *)skb->data;
if (hdr->sync != MM81X_SKB_HEADER_SYNC) {
dev_err(mors->dev, "sync value error [0xAA:%d], hdr.len %d",
hdr->sync, hdr->len);
ret = -EIO;
goto exit_return_page;
}
if (yaps->mors->hif.validate_skb_checksum &&
!mm81x_skbq_validate_checksum(skb->data)) {
dev_dbg(yaps->mors->dev,
"SKB checksum is invalid hdr:[c:%02X s:%02X len:%d]",
hdr->channel, hdr->sync, hdr->len);
if (hdr->channel != MM81X_SKB_CHAN_TX_STATUS) {
ret = -EIO;
goto exit;
}
}
switch (hdr->channel) {
case MM81X_SKB_CHAN_DATA:
case MM81X_SKB_CHAN_NDP_FRAMES:
case MM81X_SKB_CHAN_TX_STATUS:
case MM81X_SKB_CHAN_DATA_NOACK:
case MM81X_SKB_CHAN_BEACON:
case MM81X_SKB_CHAN_MGMT:
mq = &yaps->data_rx_q;
break;
case MM81X_SKB_CHAN_COMMAND:
mq = &yaps->cmd_resp_q;
break;
default:
dev_err(mors->dev, "channel value error [%d]", hdr->channel);
ret = -EIO;
goto exit_return_page;
}
skb_len = sizeof(*hdr) + hdr->offset + le16_to_cpu(hdr->len);
skb_bytes_remaining = mm81x_skbq_space(mq);
if (skb_len > skb_bytes_remaining) {
dev_err(mors->dev,
"Page will not fit in SKBQ, dropping - len %d remain %d",
skb_len, skb_bytes_remaining);
ret = -ENOMEM;
/* Queue work to clear backlog */
queue_work(mors->net_wq, &mq->dispatch_work);
goto exit_return_page;
}
skb_trim(skb, skb_len);
__skb_queue_tail(&skbq, skb);
if (skb_queue_len(&skbq))
mm81x_skbq_enq(mq, &skbq);
/* push packets up in a different context */
queue_work(mors->net_wq, &mq->dispatch_work);
goto exit;
exit_return_page:
if (ret && mq) {
dev_err(mors->dev, "failed %d", ret);
mm81x_skbq_purge(mq, &skbq);
goto exit;
}
exit:
if (ret && skb)
dev_kfree_skb(skb);
return ret;
}
static int mm81x_yaps_tx(struct mm81x_yaps *yaps, struct mm81x_skbq *mq)
{
int i;
int ret = 0;
int num_skbs = 0;
int tc_pkt_idx = 0;
int num_pkts_sent = 0;
struct sk_buff *skb;
struct sk_buff_head skbq_to_send;
struct sk_buff_head skbq_sent;
struct sk_buff_head skbq_failed;
struct sk_buff *pfirst, *pnext;
struct mm81x *mors = yaps->mors;
struct mm81x_skb_hdr *hdr;
/* Check there is something on the queue */
spin_lock_bh(&mq->lock);
skb = skb_peek(&mq->skbq);
spin_unlock_bh(&mq->lock);
if (!skb)
return 0;
__skb_queue_head_init(&skbq_to_send);
__skb_queue_head_init(&skbq_sent);
__skb_queue_head_init(&skbq_failed);
if (mq == &yaps->cmd_q)
/* Purge timed-out commands (this should not happen) */
mm81x_skbq_purge(mq, &mq->pending);
else if (mq == &yaps->mgmt_q && skb_queue_len(&mq->skbq) > 0)
/*
* Purge old mgmt frames that have not been sent due to
* congestion
*/
mm81x_skbq_purge_aged(mors, mq);
num_skbs =
mm81x_skbq_deq_num_skb(mq, &skbq_to_send, MAX_PKTS_PER_TX_TXN);
skb_queue_walk_safe(&skbq_to_send, pfirst, pnext) {
enum mm81x_yaps_to_chip_q tc_queue;
hdr = (struct mm81x_skb_hdr *)pfirst->data;
switch (hdr->channel) {
case MM81X_SKB_CHAN_COMMAND:
tc_queue = MM81X_YAPS_CMD_Q;
break;
case MM81X_SKB_CHAN_BEACON:
tc_queue = MM81X_YAPS_BEACON_Q;
break;
case MM81X_SKB_CHAN_MGMT:
tc_queue = MM81X_YAPS_MGMT_Q;
break;
default:
tc_queue = MM81X_YAPS_TX_Q;
break;
}
yaps->hw.to_chip_pkts[tc_pkt_idx].tc_queue = tc_queue;
yaps->hw.to_chip_pkts[tc_pkt_idx].skb = pfirst;
tc_pkt_idx++;
}
/* Send queued packets to chip */
ret = mm81x_yaps_update_status(yaps);
if (ret)
return ret;
ret = mm81x_yaps_write_pkts(yaps, yaps->hw.to_chip_pkts, tc_pkt_idx,
&num_pkts_sent);
/* Move sent packets to done queue */
for (i = 0; i < num_pkts_sent; ++i) {
pfirst = __skb_dequeue(&skbq_to_send);
__skb_queue_tail(&skbq_sent, pfirst);
}
for (i = num_pkts_sent; i < num_skbs; ++i) {
pfirst = __skb_dequeue(&skbq_to_send);
__skb_queue_tail(&skbq_failed, pfirst);
}
if (skb_queue_len(&skbq_failed) > 0) {
mm81x_skbq_enq_prepend(mq, &skbq_failed);
/* queue full, can't requeue */
if (skb_queue_len(&skbq_failed) > 0) {
dev_warn(mors->dev,
"can't requeue failed pkts, purging");
__skb_queue_purge(&skbq_failed);
}
}
if (skb_queue_len(&skbq_sent) > 0)
mm81x_skbq_tx_complete(mq, &skbq_sent);
return ret;
}
/* Returns true if there are TX data pages waiting to be sent */
static bool mm81x_yaps_tx_data_handler(struct mm81x_yaps *yaps)
{
s16 aci;
u32 count = 0;
struct mm81x *mors = yaps->mors;
for (aci = MM81X_ACI_VO; aci >= 0; aci--) {
struct mm81x_skbq *data_q = mm81x_yaps_tc_q_from_aci(mors, aci);
if (!mm81x_is_data_tx_allowed(mors))
break;
yaps->chip_queue_full.is_full = mm81x_yaps_tx(yaps, data_q);
count += mm81x_skbq_count(data_q);
if (yaps->chip_queue_full.is_full)
break;
if (aci == MM81X_ACI_BE)
break;
}
/*
* Data has potentially been transmitted from the data SKBQs.
* If the mac80211 TX data Qs were previously stopped, now would
* be a good time to check if they can be started again.
*/
mm81x_skbq_may_wake_tx_queues(mors);
return (count > 0) && mm81x_is_data_tx_allowed(mors);
}
/* Returns true if there are commands waiting to be sent */
static bool mm81x_yaps_tx_cmd_handler(struct mm81x_yaps *yaps)
{
struct mm81x_skbq *cmd_q = &yaps->cmd_q;
mm81x_yaps_tx(yaps, cmd_q);
return mm81x_skbq_count(cmd_q) > 0;
}
static bool mm81x_yaps_tx_beacon_handler(struct mm81x_yaps *yaps)
{
struct mm81x_skbq *beacon_q = &yaps->beacon_q;
mm81x_yaps_tx(yaps, beacon_q);
return mm81x_skbq_count(beacon_q) > 0;
}
static bool mm81x_yaps_tx_mgmt_handler(struct mm81x_yaps *yaps)
{
struct mm81x_skbq *mgmt_q = &yaps->mgmt_q;
mm81x_yaps_tx(yaps, mgmt_q);
return mm81x_skbq_count(mgmt_q) > 0;
}
/* Returns true if there are populated RX pages left in the device */
static bool mm81x_yaps_rx_handler(struct mm81x_yaps *yaps)
{
int ret = 0;
int i;
int num_pks_received;
ret = mm81x_yaps_update_status(yaps);
if (ret)
goto exit;
ret = mm81x_yaps_read_pkts(yaps, yaps->hw.from_chip_pkts,
MAX_PKTS_PER_RX_TXN, &num_pks_received);
if (ret && ret != -EAGAIN) {
dev_err(yaps->mors->dev, "YAPS read_pkts fail: %d", ret);
goto exit;
}
for (i = 0; i < num_pks_received; ++i) {
mm81x_yaps_read_pkt(yaps, yaps->hw.from_chip_pkts[i].skb);
yaps->hw.from_chip_pkts[i].skb = NULL;
}
exit:
if (ret == -ENOMEM || ret == -EAGAIN)
return true;
else
return false;
}
void mm81x_yaps_stale_tx_work(struct work_struct *work)
{
int i;
int flushed = 0;
struct mm81x *mors = container_of(work, struct mm81x, tx_stale_work);
struct mm81x_yaps *yaps;
yaps = &mors->hif.u.yaps;
flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->beacon_q);
flushed += mm81x_skbq_check_for_stale_tx(mors, &yaps->mgmt_q);
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
flushed += mm81x_skbq_check_for_stale_tx(mors,
&yaps->data_tx_qs[i]);
if (!flushed)
return;
dev_dbg(mors->dev, "Flushed %d stale TX SKBs", flushed);
if (mors->ps.enable && !mors->ps.suspended &&
(mm81x_yaps_get_tx_buffered_count(mors) == 0)) {
/* Evaluate ps to check if it was gated on a stale tx status */
queue_delayed_work(mors->chip_wq, &mors->ps.delayed_eval_work,
0);
}
}
void mm81x_yaps_work(struct work_struct *work)
{
struct mm81x *mors = container_of(work, struct mm81x, hif_work);
unsigned long *flags = &mors->hif.event_flags;
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
if (test_bit(MM81X_STATE_CHIP_UNRESPONSIVE, &mors->state_flags))
return;
if (!*flags)
return;
/* Disable power save in case it is running */
mm81x_ps_disable(mors);
mm81x_claim_bus(mors);
/*
* Handle any populated RX pages from chip first to
* avoid dropping pkts due to full on-chip buffers.
* Check if all pages were removed, set event flags if not.
*/
if (test_and_clear_bit(MM81X_HIF_EVT_RX_PEND, flags)) {
if (mm81x_yaps_rx_handler(yaps))
set_bit(MM81X_HIF_EVT_RX_PEND, flags);
}
/* TX any commands before considering data */
if (test_and_clear_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags)) {
if (mm81x_yaps_tx_cmd_handler(yaps))
set_bit(MM81X_HIF_EVT_TX_COMMAND_PEND, flags);
}
/* TX beacons before considering mgmt/data */
if (test_and_clear_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags)) {
if (mm81x_yaps_tx_beacon_handler(yaps))
set_bit(MM81X_HIF_EVT_TX_BEACON_PEND, flags);
}
/* TX mgmt before considering data */
if (test_and_clear_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags)) {
if (mm81x_yaps_tx_mgmt_handler(yaps))
set_bit(MM81X_HIF_EVT_TX_MGMT_PEND, flags);
}
/* Pause TX data Qs */
if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_PAUSE_PEND, flags)) {
test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND,
flags);
mm81x_skbq_data_traffic_pause(mors);
}
/* Resume TX data Qs */
if (test_and_clear_bit(MM81X_HIF_EVT_DATA_TRAFFIC_RESUME_PEND, flags))
mm81x_skbq_data_traffic_resume(mors);
/* Handle chip queue status */
if (test_and_clear_bit(MM81X_HIF_EVT_TX_PACKET_FREED_UP_PEND, flags))
yaps->chip_queue_full.is_full = false;
/* Check to see if the queue is full or
* long enough has past since the queue was full
*/
if (yaps->chip_queue_full.is_full &&
time_before(jiffies, yaps->chip_queue_full.retry_expiry))
goto exit;
/* Finally TX any data */
if (test_and_clear_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags)) {
if (mm81x_yaps_tx_data_handler(yaps))
set_bit(MM81X_HIF_EVT_TX_DATA_PEND, flags);
if (yaps->chip_queue_full.is_full) {
yaps->chip_queue_full.retry_expiry =
jiffies +
msecs_to_jiffies(CHIP_FULL_RECOVERY_TIMEOUT_MS);
mod_timer(&yaps->chip_queue_full.timer,
yaps->chip_queue_full.retry_expiry);
}
}
exit:
/* Disable power save in case it is running */
mm81x_release_bus(mors);
mm81x_ps_enable(mors);
/* Don't requeue work if we are shutting down. */
if (yaps->finish)
return;
/*
* Evaluate all events except MM81X_HIF_EVT_TX_DATA_PEND in case data
* tx queue is full
*/
if ((*flags) & ~(1 << MM81X_HIF_EVT_TX_DATA_PEND))
queue_work(mors->chip_wq, &mors->hif_work);
/*
* if data tx queue is not full and the work hasn't been queued let's
* queue it
*/
else if (!yaps->chip_queue_full.is_full && *flags)
queue_work(mors->chip_wq, &mors->hif_work);
}
int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors)
{
int i = 0;
int count = 0;
struct mm81x_yaps *yaps;
yaps = &mors->hif.u.yaps;
count += skb_queue_len(&yaps->beacon_q.pending);
count += skb_queue_len(&yaps->mgmt_q.pending);
count += skb_queue_len(&yaps->cmd_q.pending);
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
count += skb_queue_len(&yaps->data_tx_qs[i].pending);
return count;
}
int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors)
{
int i = 0;
int count = 0;
struct mm81x_yaps *yaps;
yaps = &mors->hif.u.yaps;
count += skb_queue_len(&yaps->beacon_q.skbq) +
skb_queue_len(&yaps->beacon_q.pending);
count += skb_queue_len(&yaps->mgmt_q.skbq) +
skb_queue_len(&yaps->mgmt_q.pending);
count += skb_queue_len(&yaps->cmd_q.skbq) +
skb_queue_len(&yaps->cmd_q.pending);
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
count += mm81x_skbq_count_tx_ready(&yaps->data_tx_qs[i]) +
skb_queue_len(&yaps->data_tx_qs[i].pending);
return count;
}
static void mm81x_yaps_tx_q_full_timer(struct timer_list *t)
{
struct mm81x_yaps *yaps =
timer_container_of(yaps, t, chip_queue_full.timer);
queue_work(yaps->mors->chip_wq, &yaps->mors->hif_work);
}
static void mm81x_yaps_q_chip_full_timer_init(struct mm81x_yaps *yaps)
{
timer_setup(&yaps->chip_queue_full.timer, mm81x_yaps_tx_q_full_timer,
0);
}
static void mm81x_yaps_q_chip_full_timer_finish(struct mm81x_yaps *yaps)
{
timer_delete_sync_try(&yaps->chip_queue_full.timer);
}
int mm81x_yaps_init(struct mm81x *mors)
{
int i, ret;
struct mm81x_yaps *yaps;
ret = mm81x_yaps_hw_init(mors);
if (ret) {
dev_err(mors->dev, "mm81x_yaps_hw_init failed %d", ret);
return ret;
}
yaps = &mors->hif.u.yaps;
yaps->mors = mors;
mm81x_claim_bus(mors);
ret = mm81x_yaps_alloc_pkt_buffers(yaps);
if (ret) {
dev_err(mors->dev, "Failed to allocate YAPS packet buffers: %d",
ret);
mm81x_yaps_hw_finish(mors);
mm81x_release_bus(mors);
return ret;
}
/* YAPS is bi-directional */
mm81x_skbq_init(mors, &yaps->data_rx_q,
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
mm81x_skbq_init(mors, &yaps->beacon_q,
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
mm81x_skbq_init(mors, &yaps->mgmt_q,
MM81X_HIF_FLAGS_DATA | MM81X_HIF_FLAGS_DIR_TO_HOST);
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++) {
mm81x_skbq_init(mors, &yaps->data_tx_qs[i],
MM81X_HIF_FLAGS_DATA |
MM81X_HIF_FLAGS_DIR_TO_CHIP);
}
mm81x_skbq_init(mors, &yaps->cmd_q,
MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_CHIP);
mm81x_skbq_init(mors, &yaps->cmd_resp_q,
MM81X_HIF_FLAGS_COMMAND | MM81X_HIF_FLAGS_DIR_TO_HOST);
mm81x_yaps_q_chip_full_timer_init(yaps);
INIT_WORK(&mors->hif_work, mm81x_yaps_work);
INIT_WORK(&mors->tx_stale_work, mm81x_yaps_stale_tx_work);
mm81x_release_bus(mors);
mm81x_hw_enable_stop_notifications(mors, true);
return 0;
}
void mm81x_yaps_finish(struct mm81x *mors)
{
int i;
struct mm81x_yaps *yaps;
mm81x_yaps_hw_enable_irqs(mors, false);
yaps = &mors->hif.u.yaps;
yaps->finish = true;
mm81x_skbq_finish(&yaps->data_rx_q);
mm81x_skbq_finish(&yaps->beacon_q);
mm81x_skbq_finish(&yaps->mgmt_q);
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
mm81x_skbq_finish(&yaps->data_tx_qs[i]);
mm81x_skbq_finish(&yaps->cmd_q);
mm81x_skbq_finish(&yaps->cmd_resp_q);
mm81x_yaps_q_chip_full_timer_finish(yaps);
cancel_work_sync(&mors->hif_work);
cancel_work_sync(&mors->tx_stale_work);
mm81x_yaps_free_pkt_buffers(yaps);
mm81x_yaps_hw_finish(mors);
}
void mm81x_yaps_flush_tx_data(struct mm81x *mors)
{
int i;
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
mm81x_skbq_tx_flush(&yaps->beacon_q);
mm81x_skbq_tx_flush(&yaps->mgmt_q);
for (i = 0; i < ARRAY_SIZE(yaps->data_tx_qs); i++)
mm81x_skbq_tx_flush(&yaps->data_tx_qs[i]);
}
void mm81x_yaps_flush_cmds(struct mm81x *mors)
{
struct mm81x_yaps *yaps = &mors->hif.u.yaps;
if (yaps->flags & MM81X_HIF_FLAGS_COMMAND) {
mm81x_skbq_finish(&yaps->cmd_q);
mm81x_skbq_finish(&yaps->cmd_resp_q);
}
}

View File

@ -0,0 +1,77 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_YAPS_H_
#define _MM81X_YAPS_H_
#include <linux/skbuff.h>
#include <linux/workqueue.h>
#include "skbq.h"
#define YAPS_TX_SKBQ_MAX 4
struct mm81x_hif_ops;
extern const struct mm81x_hif_ops mm81x_yaps_ops;
enum mm81x_yaps_to_chip_q {
MM81X_YAPS_TX_Q = 0,
MM81X_YAPS_CMD_Q,
MM81X_YAPS_BEACON_Q,
MM81X_YAPS_MGMT_Q,
/* Keep this last */
MM81X_YAPS_NUM_TC_Q
};
struct mm81x_yaps_pkt {
struct sk_buff *skb;
enum mm81x_yaps_to_chip_q tc_queue;
};
struct mm81x_yaps {
struct mm81x *mors;
struct mm81x_yaps_hw_aux_data *aux_data;
const struct mm81x_yaps_ops *ops;
u8 flags;
struct {
struct mm81x_yaps_pkt *to_chip_pkts;
struct mm81x_yaps_pkt *from_chip_pkts;
} hw;
/* Chip interface is stopping, new work should not be enqueued. */
bool finish;
struct mm81x_skbq data_tx_qs[YAPS_TX_SKBQ_MAX];
struct mm81x_skbq beacon_q;
struct mm81x_skbq mgmt_q;
struct mm81x_skbq data_rx_q;
struct mm81x_skbq cmd_q;
struct mm81x_skbq cmd_resp_q;
struct {
struct timer_list timer;
unsigned long retry_expiry;
bool is_full;
} chip_queue_full;
};
struct mm81x_yaps_ops {
int (*write_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,
int num_pkts, int *num_pkts_sent);
int (*read_pkts)(struct mm81x_yaps *yaps, struct mm81x_yaps_pkt *pkts,
int num_pkts_max, int *num_pkts_received);
int (*update_status)(struct mm81x_yaps *yaps);
};
int mm81x_yaps_init(struct mm81x *mors);
void mm81x_yaps_show(struct mm81x_yaps *yaps, struct seq_file *file);
void mm81x_yaps_finish(struct mm81x *mors);
void mm81x_yaps_flush_tx_data(struct mm81x *mors);
void mm81x_yaps_flush_cmds(struct mm81x *mors);
void mm81x_yaps_work(struct work_struct *work);
void mm81x_yaps_stale_tx_work(struct work_struct *work);
int mm81x_yaps_get_tx_status_pending_count(struct mm81x *mors);
int mm81x_yaps_get_tx_buffered_count(struct mm81x *mors);
#endif /* !_MM81X_YAPS_H_ */

View File

@ -0,0 +1,702 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#include "yaps_hw.h"
#include "bus.h"
#include "hif.h"
#include "yaps.h"
#define YAPS_HW_WINDOW_SIZE_BYTES 32768
#define YAPS_MAX_PKT_SIZE_BYTES 16128
#define YAPS_METADATA_PAGE_COUNT 1
#define YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE 1
#define YAPS_PAGE_SIZE 256
/* Calculate padding required for yaps transaction */
#define YAPS_CALC_PADDING(_bytes) ((_bytes) & 0x3 ? (4 - ((_bytes) & 0x3)) : 0)
#define YAPS_RESERVED_PAGE_SIZE 256
/*
* Yaps data stream delimiter is a 32 bit word with the following fields:
*
* pkt_size (14 bits) - Packet size not including delimiter or padding
* pool_id (3 bits) - Pool that pages should be allocated from.
* padding (2 bits) - Padding required to bring packet to word (4 byte)
* irq (1 bit ) - Raise a PKT_IRQ on the YDS this is sent to
* reserved (5 bits) - Reserved, must write as 0
* crc (7 bits) - YAPS CRC
*/
/* Packet size not including delimiter or padding */
#define YAPS_DELIM_GET_PKT_SIZE(_delim) \
(((_delim) & 0x3FFF) - YAPS_RESERVED_PAGE_SIZE)
#define YAPS_DELIM_SET_PKT_SIZE(_pkt_size) \
(((_pkt_size) & 0x3FFF) + YAPS_RESERVED_PAGE_SIZE)
#define YAPS_DELIM_GET_PHANDLE_SIZE(_delim) (((_delim) & 0x3FFF))
/* Pool that pages should be allocated from. */
#define YAPS_DELIM_SET_POOL_ID(_pool_id) (((_pool_id) & 0x7) << 14)
/* Padding required to bring packet to word (4 byte) boundary */
#define YAPS_DELIM_GET_PADDING(_delim) (((_delim) >> 17) & 0x3)
#define YAPS_DELIM_SET_PADDING(_padding) (((_padding) & 0x3) << 17)
/* Raise a PKT_IRQ on the YDS this is sent to */
#define YAPS_DELIM_SET_IRQ(_irq) (((_irq) & 0x1) << 19)
/* YAPS CRC */
#define YAPS_DELIM_GET_CRC(_delim) (((_delim) >> 25) & 0x7F)
#define YAPS_DELIM_SET_CRC(_crc) (((_crc) & 0x7F) << 25)
struct mm81x_yaps_status_regs {
/* Allocation pools */
u32 tc_tx_pool_num_pages;
u32 tc_cmd_pool_num_pages;
u32 tc_beacon_pool_num_pages;
u32 tc_mgmt_pool_num_pages;
u32 fc_rx_pool_num_pages;
u32 fc_resp_pool_num_pages;
u32 fc_tx_sts_pool_num_pages;
u32 fc_aux_pool_num_pages;
u32 tc_tx_num_pkts;
u32 tc_cmd_num_pkts;
u32 tc_beacon_num_pkts;
u32 tc_mgmt_num_pkts;
u32 fc_num_pkts;
u32 fc_done_num_pkts;
u32 fc_rx_bytes_in_queue;
u32 tc_delim_crc_fail_detected;
u32 fc_host_ysl_status;
u32 lock;
} __packed __aligned(8);
struct mm81x_yaps_hw_status_regs {
__le32 tc_tx_pool_num_pages;
__le32 tc_cmd_pool_num_pages;
__le32 tc_beacon_pool_num_pages;
__le32 tc_mgmt_pool_num_pages;
__le32 fc_rx_pool_num_pages;
__le32 fc_resp_pool_num_pages;
__le32 fc_tx_sts_pool_num_pages;
__le32 fc_aux_pool_num_pages;
__le32 tc_tx_num_pkts;
__le32 tc_cmd_num_pkts;
__le32 tc_beacon_num_pkts;
__le32 tc_mgmt_num_pkts;
__le32 fc_num_pkts;
__le32 fc_done_num_pkts;
__le32 fc_rx_bytes_in_queue;
__le32 tc_delim_crc_fail_detected;
__le32 fc_host_ysl_status;
__le32 lock;
} __packed __aligned(8);
struct mm81x_yaps_hw_aux_data {
unsigned long access_lock;
u32 yds_addr;
u32 ysl_addr;
u32 status_regs_addr;
/* Alloc pool sizes */
u16 tc_tx_pool_size;
u16 tc_cmd_pool_size;
u8 tc_beacon_pool_size;
u8 tc_mgmt_pool_size;
u8 fc_rx_pool_size;
u8 fc_resp_pool_size;
u8 fc_tx_sts_pool_size;
u8 fc_aux_pool_size;
/* To chip/from chip queue sizes */
u8 tc_tx_q_size;
u8 tc_cmd_q_size;
u8 tc_beacon_q_size;
u8 tc_mgmt_q_size;
u8 fc_q_size;
u8 fc_done_q_size;
u16 reserved_yaps_page_size;
/* Buffers to/from chip to support large contiguous reads/writes */
char *to_chip_buffer;
char *from_chip_buffer;
/* status registers in host endian */
struct mm81x_yaps_status_regs status_regs;
/* DMA target buffer in firmware endian */
struct mm81x_yaps_hw_status_regs hw_status_regs;
};
static int mm81x_yaps_hw_lock(struct mm81x_yaps *yaps)
{
if (test_and_set_bit_lock(0, &yaps->aux_data->access_lock))
return -1;
return 0;
}
static void mm81x_yaps_hw_unlock(struct mm81x_yaps *yaps)
{
clear_bit_unlock(0, &yaps->aux_data->access_lock);
}
static void
mm81x_yaps_hw_fill_aux_data_from_hw_tbl(struct mm81x_yaps_hw_aux_data *a,
struct mm81x_yaps_hw_table *t)
{
a->ysl_addr = __le32_to_cpu(t->ysl_addr);
a->yds_addr = __le32_to_cpu(t->yds_addr);
a->status_regs_addr = __le32_to_cpu(t->status_regs_addr);
a->tc_tx_pool_size = __le16_to_cpu(t->tc_tx_pool_size);
a->fc_rx_pool_size = __le16_to_cpu(t->fc_rx_pool_size);
a->tc_cmd_pool_size = t->tc_cmd_pool_size;
a->tc_beacon_pool_size = t->tc_beacon_pool_size;
a->tc_mgmt_pool_size = t->tc_mgmt_pool_size;
a->fc_resp_pool_size = t->fc_resp_pool_size;
a->fc_tx_sts_pool_size = t->fc_tx_sts_pool_size;
a->fc_aux_pool_size = t->fc_aux_pool_size;
a->tc_tx_q_size = t->tc_tx_q_size;
a->tc_cmd_q_size = t->tc_cmd_q_size;
a->tc_beacon_q_size = t->tc_beacon_q_size;
a->tc_mgmt_q_size = t->tc_mgmt_q_size;
a->fc_q_size = t->fc_q_size;
a->fc_done_q_size = t->fc_done_q_size;
a->reserved_yaps_page_size = le16_to_cpu(t->yaps_reserved_page_size);
}
static u8 mm81x_yaps_hw_crc(u32 word)
{
u8 crc = 0;
u8 byte;
int i;
/* Mask to look at only non-CRC bits */
word &= 0x1ffffff;
for (i = 0; i < 4; i++) {
byte = (word >> 24) & 0xff;
crc = crc7_be(crc, &byte, 1);
word <<= 8;
}
return crc >> 1;
}
static u32 mm81x_write_pkts_h_build_delim(struct mm81x_yaps *yaps,
unsigned int size, u8 pool_id,
bool irq)
{
u32 delim = 0;
delim |= YAPS_DELIM_SET_PKT_SIZE(size);
delim |= YAPS_DELIM_SET_PADDING(YAPS_CALC_PADDING(size));
delim |= YAPS_DELIM_SET_POOL_ID(pool_id);
delim |= YAPS_DELIM_SET_IRQ(irq);
delim |= YAPS_DELIM_SET_CRC(mm81x_yaps_hw_crc(delim));
return delim;
}
void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable)
{
mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PKT_WAITING_IRQN, enable);
mm81x_hw_irq_enable(mors, MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN,
enable);
}
void mm81x_yaps_hw_read_table(struct mm81x *mors,
struct mm81x_yaps_hw_table *tbl_ptr)
{
mm81x_yaps_hw_fill_aux_data_from_hw_tbl(mors->hif.u.yaps.aux_data,
tbl_ptr);
mm81x_yaps_hw_enable_irqs(mors, true);
}
static unsigned int mm81x_write_pkts_h_pages_required(struct mm81x_yaps *yaps,
unsigned int size_bytes)
{
/* Always account for the first metadata page */
return DIV_ROUND_UP(size_bytes +
yaps->aux_data->reserved_yaps_page_size,
YAPS_PAGE_SIZE) +
YAPS_METADATA_PAGE_COUNT +
YAPS_PHANDLE_CORRUPTION_WAR_EXTRA_PAGE;
}
/*
* Checks if a single pkt will fit in the chip using the pool/alloc holding
* information from the last status register read.
*/
static bool mm81x_write_pkts_h_will_fit(struct mm81x_yaps *yaps,
struct mm81x_yaps_pkt *pkt, bool update)
{
bool will_fit = true;
const int pages_required =
mm81x_write_pkts_h_pages_required(yaps, pkt->skb->len);
int *pool_pages_avail = NULL;
int *pkts_in_queue = NULL;
int queue_pkts_avail = 0;
switch (pkt->tc_queue) {
case MM81X_YAPS_TX_Q:
pool_pages_avail =
&yaps->aux_data->status_regs.tc_tx_pool_num_pages;
pkts_in_queue = &yaps->aux_data->status_regs.tc_tx_num_pkts;
queue_pkts_avail =
yaps->aux_data->tc_tx_q_size - *pkts_in_queue;
break;
case MM81X_YAPS_CMD_Q:
pool_pages_avail =
&yaps->aux_data->status_regs.tc_cmd_pool_num_pages;
pkts_in_queue = &yaps->aux_data->status_regs.tc_cmd_num_pkts;
queue_pkts_avail =
yaps->aux_data->tc_cmd_q_size - *pkts_in_queue;
break;
case MM81X_YAPS_BEACON_Q:
pool_pages_avail =
&yaps->aux_data->status_regs.tc_beacon_pool_num_pages;
pkts_in_queue = &yaps->aux_data->status_regs.tc_beacon_num_pkts;
queue_pkts_avail =
yaps->aux_data->tc_beacon_q_size - *pkts_in_queue;
break;
case MM81X_YAPS_MGMT_Q:
pool_pages_avail =
&yaps->aux_data->status_regs.tc_mgmt_pool_num_pages;
pkts_in_queue = &yaps->aux_data->status_regs.tc_mgmt_num_pkts;
queue_pkts_avail =
yaps->aux_data->tc_mgmt_q_size - *pkts_in_queue;
break;
default:
dev_err(yaps->mors->dev, "yaps invalid tc queue");
return false;
}
WARN_ON(queue_pkts_avail < 0);
if (pages_required > *pool_pages_avail)
will_fit = false;
if (queue_pkts_avail == 0)
will_fit = false;
if (will_fit && update) {
*pool_pages_avail -= pages_required;
*pkts_in_queue += 1;
}
return will_fit;
}
static int mm81x_write_pkts_h_err_check(struct mm81x_yaps *yaps,
struct mm81x_yaps_pkt *pkt)
{
if (pkt->skb->len + yaps->aux_data->reserved_yaps_page_size >
YAPS_MAX_PKT_SIZE_BYTES)
return -EMSGSIZE;
if (pkt->tc_queue >= MM81X_YAPS_NUM_TC_Q)
return -EINVAL;
if (!mm81x_write_pkts_h_will_fit(yaps, pkt, true))
return -EAGAIN;
return 0;
}
static int mm81x_yaps_hw_write_pkts(struct mm81x_yaps *yaps,
struct mm81x_yaps_pkt *pkts, int num_pkts,
int *num_pkts_sent)
{
int ret = 0;
int i;
u32 delim = 0;
int tx_len;
int batch_txn_len = 0;
int pkts_pending = 0;
bool delim_irq = false;
char *to_chip_buffer_aligned =
PTR_ALIGN(yaps->aux_data->to_chip_buffer,
mm81x_bus_get_alignment(yaps->mors));
char *write_buf = to_chip_buffer_aligned;
ret = mm81x_yaps_hw_lock(yaps);
if (ret) {
dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
return ret;
}
*num_pkts_sent = 0;
/* Check packet conditions */
ret = mm81x_write_pkts_h_err_check(yaps, &pkts[0]);
if (ret)
goto exit;
/* Batch packets into larger transactions */
for (i = 0; i < num_pkts; ++i) {
u32 pkt_size =
pkts[i].skb->len + YAPS_CALC_PADDING(pkts[i].skb->len);
tx_len = pkt_size + sizeof(delim);
/*
* Send when we have reached window size, don't split pkt over
* boundary
*/
if ((batch_txn_len + tx_len) > YAPS_HW_WINDOW_SIZE_BYTES) {
ret = mm81x_dm_write(yaps->mors,
yaps->aux_data->yds_addr,
to_chip_buffer_aligned,
batch_txn_len);
batch_txn_len = 0;
if (ret)
goto exit;
write_buf = to_chip_buffer_aligned;
*num_pkts_sent += pkts_pending;
pkts_pending = 0;
}
if ((i + 1) == num_pkts) {
/* The last packet in the queue has IRQ set */
delim_irq = true;
} else {
/*
* Since this is not the last packet, we can check for
* the next one. In case of errors in the next packet
* set the IRQ
*/
ret = mm81x_write_pkts_h_err_check(yaps, &pkts[i + 1]);
if (ret)
delim_irq = true;
}
/* Build stream header*/
delim = mm81x_write_pkts_h_build_delim(
yaps, pkt_size, pkts[i].tc_queue, delim_irq);
*((__le32 *)write_buf) = cpu_to_le32(delim);
memcpy(write_buf + sizeof(delim), pkts[i].skb->data,
pkts[i].skb->len);
write_buf += tx_len;
batch_txn_len += tx_len;
pkts_pending++;
if (ret)
goto exit;
}
exit:
if (batch_txn_len > 0) {
ret = mm81x_dm_write(yaps->mors, yaps->aux_data->yds_addr,
to_chip_buffer_aligned, batch_txn_len);
*num_pkts_sent += pkts_pending;
}
mm81x_yaps_hw_unlock(yaps);
return ret;
}
static bool mm81x_read_pkts_h_is_valid_delim(u32 delim)
{
u8 calc_crc = mm81x_yaps_hw_crc(delim);
int pkt_size = YAPS_DELIM_GET_PHANDLE_SIZE(delim);
int padding = YAPS_DELIM_GET_PADDING(delim);
if (calc_crc != YAPS_DELIM_GET_CRC(delim))
return false;
if (pkt_size == 0)
return false;
if ((pkt_size + padding) > YAPS_MAX_PKT_SIZE_BYTES)
return false;
/* Pkt length + padding should not require more padding */
if (YAPS_CALC_PADDING(pkt_size) != padding)
return false;
return true;
}
static int mm81x_read_pkts_h_bytes_remaining(struct mm81x_yaps *yaps)
{
u32 bytes_in_queue = yaps->aux_data->status_regs.fc_rx_bytes_in_queue;
u32 delim_overhead =
yaps->aux_data->status_regs.fc_num_pkts * sizeof(u32);
u32 reserved_bytes = yaps->aux_data->status_regs.fc_num_pkts *
yaps->aux_data->reserved_yaps_page_size;
if (WARN_ON(bytes_in_queue > INT_MAX) ||
WARN_ON(delim_overhead > INT_MAX) ||
WARN_ON(reserved_bytes > INT_MAX))
return -EIO;
return (int)bytes_in_queue;
}
static int mm81x_yaps_hw_read_pkts(struct mm81x_yaps *yaps,
struct mm81x_yaps_pkt *pkts,
int num_pkts_max, int *num_pkts_received)
{
int ret;
int i = 0;
char *from_chip_buffer_aligned =
PTR_ALIGN(yaps->aux_data->from_chip_buffer,
mm81x_bus_get_alignment(yaps->mors));
char *read_ptr = from_chip_buffer_aligned;
int bytes_remaining = mm81x_read_pkts_h_bytes_remaining(yaps);
bool again = false;
*num_pkts_received = 0;
if (num_pkts_max == 0 || bytes_remaining == 0)
return 0;
if (bytes_remaining < 0)
return bytes_remaining;
if (bytes_remaining > YAPS_HW_WINDOW_SIZE_BYTES) {
bytes_remaining = YAPS_HW_WINDOW_SIZE_BYTES;
again = true;
}
/*
* This is more coarse-grained than it needs to be - once the data
* is read into a local buffer the lock can be released, however
* access to from_chip_buffer will need to be protected with its
* own lock
*/
ret = mm81x_yaps_hw_lock(yaps);
if (ret) {
dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
return ret;
}
/* Read all available packets to the buffer */
ret = mm81x_dm_read(yaps->mors, yaps->aux_data->ysl_addr,
from_chip_buffer_aligned, bytes_remaining);
if (ret)
goto exit;
/* Split serialised packets from buffer */
while (i < num_pkts_max && bytes_remaining > 0) {
u32 delim;
int total_len;
int pkt_size;
delim = le32_to_cpu(*((__le32 *)read_ptr));
read_ptr += sizeof(delim);
bytes_remaining -= sizeof(delim);
/* End of stream */
if (!delim)
break;
if (!mm81x_read_pkts_h_is_valid_delim(delim)) {
/*
* This will start a hunt for a valid delimiter. Given
* the CRC is only 7 bit it's possible to find an
* invalid block with a valid delimiter, leading to
* desynchronisation.
*/
dev_warn(yaps->mors->dev, "yaps invalid delim");
break;
}
/* Total length in chip */
pkt_size = YAPS_DELIM_GET_PKT_SIZE(delim);
total_len = pkt_size + YAPS_DELIM_GET_PADDING(delim);
if (pkts[i].skb)
dev_err(yaps->mors->dev, "yaps packet leak");
/* SKB doesn't want padding */
pkts[i].skb = dev_alloc_skb(pkt_size);
if (!pkts[i].skb) {
ret = -ENOMEM;
dev_err(yaps->mors->dev, "yaps no mem for skb");
goto exit;
}
skb_put(pkts[i].skb, pkt_size);
if (total_len <= bytes_remaining) {
memcpy(pkts[i].skb->data, read_ptr, pkt_size);
read_ptr += total_len;
bytes_remaining -= total_len;
} else {
const int read_overhang_len =
total_len - bytes_remaining;
const int pkt_overhang_len = pkt_size - bytes_remaining;
memcpy(pkts[i].skb->data, read_ptr, bytes_remaining);
read_ptr = from_chip_buffer_aligned;
ret = mm81x_dm_read(
yaps->mors,
/* Offset by 4 to avoid retry logic */
yaps->aux_data->ysl_addr + 4, read_ptr,
read_overhang_len);
if (ret)
goto exit;
memcpy(pkts[i].skb->data + bytes_remaining, read_ptr,
pkt_overhang_len);
read_ptr += read_overhang_len;
bytes_remaining = 0;
}
*num_pkts_received += 1;
i++;
}
if (again)
ret = -EAGAIN;
exit:
mm81x_yaps_hw_unlock(yaps);
return ret;
}
static int mm81x_yaps_hw_update_status(struct mm81x_yaps *yaps)
{
int ret;
int tc_total_pkt_count;
unsigned long reg_read_timeout;
struct mm81x_yaps_status_regs *r = &yaps->aux_data->status_regs;
struct mm81x_yaps_hw_status_regs *hw_r = &yaps->aux_data->hw_status_regs;
ret = mm81x_yaps_hw_lock(yaps);
if (ret) {
dev_dbg(yaps->mors->dev, "yaps lock failed %d", ret);
return ret;
}
reg_read_timeout = jiffies + msecs_to_jiffies(100);
do {
if (time_after(jiffies, reg_read_timeout)) {
dev_err(yaps->mors->dev,
"timed out reading status registers: %d", ret);
ret = -ETIMEDOUT;
break;
}
ret = mm81x_dm_read(yaps->mors,
yaps->aux_data->status_regs_addr,
(u8 *)hw_r, sizeof(*hw_r));
} while (!ret && le32_to_cpu(hw_r->lock));
if (ret) {
if (ret != -ENODEV) {
dev_err(yaps->mors->dev,
"error reading yaps status registers: %d", ret);
}
goto exit_unlock;
}
r->tc_tx_pool_num_pages = le32_to_cpu(hw_r->tc_tx_pool_num_pages);
r->tc_cmd_pool_num_pages = le32_to_cpu(hw_r->tc_cmd_pool_num_pages);
r->tc_beacon_pool_num_pages = le32_to_cpu(hw_r->tc_beacon_pool_num_pages);
r->tc_mgmt_pool_num_pages = le32_to_cpu(hw_r->tc_mgmt_pool_num_pages);
r->fc_rx_pool_num_pages = le32_to_cpu(hw_r->fc_rx_pool_num_pages);
r->fc_resp_pool_num_pages = le32_to_cpu(hw_r->fc_resp_pool_num_pages);
r->fc_tx_sts_pool_num_pages = le32_to_cpu(hw_r->fc_tx_sts_pool_num_pages);
r->fc_aux_pool_num_pages = le32_to_cpu(hw_r->fc_aux_pool_num_pages);
r->tc_tx_num_pkts = le32_to_cpu(hw_r->tc_tx_num_pkts);
r->tc_cmd_num_pkts = le32_to_cpu(hw_r->tc_cmd_num_pkts);
r->tc_beacon_num_pkts = le32_to_cpu(hw_r->tc_beacon_num_pkts);
r->tc_mgmt_num_pkts = le32_to_cpu(hw_r->tc_mgmt_num_pkts);
r->fc_num_pkts = le32_to_cpu(hw_r->fc_num_pkts);
r->fc_done_num_pkts = le32_to_cpu(hw_r->fc_done_num_pkts);
r->fc_rx_bytes_in_queue = le32_to_cpu(hw_r->fc_rx_bytes_in_queue);
r->tc_delim_crc_fail_detected = le32_to_cpu(hw_r->tc_delim_crc_fail_detected);
r->lock = le32_to_cpu(hw_r->lock);
r->fc_host_ysl_status = le32_to_cpu(hw_r->fc_host_ysl_status);
tc_total_pkt_count = r->tc_tx_num_pkts + r->tc_cmd_num_pkts +
r->tc_beacon_num_pkts + r->tc_mgmt_num_pkts;
if (r->tc_delim_crc_fail_detected) {
/*
* Host and chip have become desynchronised. This can happen if
* the chip crashes during a YAPS transaction. We cannot
* recover from this.
*/
dev_err(yaps->mors->dev,
"to-chip yaps delimiter CRC fail, pkt_count=%d",
tc_total_pkt_count);
ret = -EIO;
}
if (mm81x_read_pkts_h_bytes_remaining(yaps))
set_bit(MM81X_HIF_EVT_RX_PEND, &yaps->mors->hif.event_flags);
exit_unlock:
mm81x_yaps_hw_unlock(yaps);
return ret;
}
static const struct mm81x_yaps_ops mm81x_yaps_hw_ops = {
.write_pkts = mm81x_yaps_hw_write_pkts,
.read_pkts = mm81x_yaps_hw_read_pkts,
.update_status = mm81x_yaps_hw_update_status,
};
int mm81x_yaps_hw_init(struct mm81x *mors)
{
int ret = 0;
struct mm81x_yaps *yaps = NULL;
int aux_data_len = sizeof(struct mm81x_yaps_hw_aux_data);
int alignment = mm81x_bus_get_alignment(mors);
yaps = &mors->hif.u.yaps;
yaps->aux_data = kzalloc(aux_data_len, GFP_KERNEL);
if (!yaps->aux_data) {
ret = -ENOMEM;
goto err_exit;
}
yaps->aux_data->to_chip_buffer =
kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);
if (!yaps->aux_data->to_chip_buffer) {
ret = -ENOMEM;
goto err_exit;
}
yaps->aux_data->from_chip_buffer =
kzalloc(YAPS_HW_WINDOW_SIZE_BYTES + alignment - 1, GFP_KERNEL);
if (!yaps->aux_data->from_chip_buffer) {
ret = -ENOMEM;
goto err_exit;
}
if (!IS_ALIGNED((uintptr_t)&yaps->aux_data->status_regs, alignment)) {
dev_warn(mors->dev,
"Status registers are not aligned to %d bytes",
alignment);
}
yaps->ops = &mm81x_yaps_hw_ops;
return ret;
err_exit:
mm81x_yaps_hw_finish(mors);
return ret;
}
void mm81x_yaps_hw_finish(struct mm81x *mors)
{
struct mm81x_yaps *yaps;
yaps = &mors->hif.u.yaps;
if (yaps->aux_data) {
kfree(yaps->aux_data->from_chip_buffer);
yaps->aux_data->from_chip_buffer = NULL;
kfree(yaps->aux_data->to_chip_buffer);
yaps->aux_data->to_chip_buffer = NULL;
kfree(yaps->aux_data);
yaps->aux_data = NULL;
}
}

View File

@ -0,0 +1,52 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* Copyright (c) 2017-2026 Morse Micro
*/
#ifndef _MM81X_YAPS_HW_H_
#define _MM81X_YAPS_HW_H_
#include <linux/types.h>
#include <linux/crc7.h>
#define MM81X_INT_YAPS_FC_PKT_WAITING_IRQN 0
#define MM81X_INT_YAPS_FC_PACKET_FREED_UP_IRQN 1
struct mm81x_yaps_hw_table {
/* NOTE: We need these padding bytes for yaps to work */
u8 padding[4];
__le32 ysl_addr;
__le32 yds_addr;
__le32 status_regs_addr;
/* Alloc pool sizes */
__le16 tc_tx_pool_size;
__le16 fc_rx_pool_size;
u8 tc_cmd_pool_size;
u8 tc_beacon_pool_size;
u8 tc_mgmt_pool_size;
u8 fc_resp_pool_size;
u8 fc_tx_sts_pool_size;
u8 fc_aux_pool_size;
/* To chip/from chip queue sizes */
u8 tc_tx_q_size;
u8 tc_cmd_q_size;
u8 tc_beacon_q_size;
u8 tc_mgmt_q_size;
u8 fc_q_size;
u8 fc_done_q_size;
__le16 yaps_reserved_page_size;
__le16 reserved_unused;
} __packed;
struct mm81x;
void mm81x_yaps_hw_enable_irqs(struct mm81x *mors, bool enable);
int mm81x_yaps_hw_init(struct mm81x *mors);
void mm81x_yaps_hw_finish(struct mm81x *mors);
void mm81x_yaps_hw_read_table(struct mm81x *mors,
struct mm81x_yaps_hw_table *tbl_ptr);
#endif /* !_MM81X_YAPS_HW_H_ */

View File

@ -0,0 +1,17 @@
# SPDX-License-Identifier: GPL-2.0-only
config WLAN_VENDOR_NXP
bool "NXP devices"
default y
help
If you have a wireless card belonging to this class, say Y.
Note that the answer to this question doesn't directly affect the
kernel: saying N will just cause the configurator to skip all the
questions about these cards. If you say Y, you will be asked for
your specific card in the following questions.
if WLAN_VENDOR_NXP
source "drivers/net/wireless/nxp/nxpwifi/Kconfig"
endif # WLAN_VENDOR_NXP

View File

@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_NXPWIFI) += nxpwifi/

View File

@ -0,0 +1,280 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* nxpwifi 802.11ac helpers
* Copyright 2011-2024 NXP
*/
#include "cfg.h"
#include "fw.h"
#include "main.h"
#include "11ac.h"
/* Map VHT MCS/NSS to highest data rate (Mbps), long GI. */
static const u16 max_rate_lgi_80MHZ[8][3] = {
{0x124, 0x15F, 0x186}, /* NSS = 1 */
{0x249, 0x2BE, 0x30C}, /* NSS = 2 */
{0x36D, 0x41D, 0x492}, /* NSS = 3 */
{0x492, 0x57C, 0x618}, /* NSS = 4 */
{0x5B6, 0x6DB, 0x79E}, /* NSS = 5 */
{0x6DB, 0x83A, 0x0}, /* NSS = 6 */
{0x7FF, 0x999, 0xAAA}, /* NSS = 7 */
{0x924, 0xAF8, 0xC30} /* NSS = 8 */
};
static const u16 max_rate_lgi_160MHZ[8][3] = {
{0x249, 0x2BE, 0x30C}, /* NSS = 1 */
{0x492, 0x57C, 0x618}, /* NSS = 2 */
{0x6DB, 0x83A, 0x0}, /* NSS = 3 */
{0x924, 0xAF8, 0xC30}, /* NSS = 4 */
{0xB6D, 0xDB6, 0xF3C}, /* NSS = 5 */
{0xDB6, 0x1074, 0x1248}, /* NSS = 6 */
{0xFFF, 0x1332, 0x1554}, /* NSS = 7 */
{0x1248, 0x15F0, 0x1860} /* NSS = 8 */
};
/* Convert 2-bit MCS map to highest long-GI VHT data rate. */
static u16
nxpwifi_convert_mcsmap_to_maxrate(struct nxpwifi_private *priv,
u16 bands, u16 mcs_map)
{
u8 i, nss, mcs;
u16 max_rate = 0;
u32 usr_vht_cap_info = 0;
struct nxpwifi_adapter *adapter = priv->adapter;
if (bands & BAND_AAC)
usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a;
else
usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg;
/* Find max supported NSS. */
nss = 1;
for (i = 1; i <= 8; i++) {
mcs = GET_VHTNSSMCS(mcs_map, i);
if (mcs < IEEE80211_VHT_MCS_NOT_SUPPORTED)
nss = i;
}
mcs = GET_VHTNSSMCS(mcs_map, nss);
/* If not supported, fall back to 0-9. */
if (mcs == IEEE80211_VHT_MCS_NOT_SUPPORTED)
mcs = IEEE80211_VHT_MCS_SUPPORT_0_9;
if (u32_get_bits(usr_vht_cap_info, IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK)) {
/* Support 160 MHz. */
max_rate = max_rate_lgi_160MHZ[nss - 1][mcs];
if (!max_rate)
/* MCS9 not supported in NSS6. */
max_rate = max_rate_lgi_160MHZ[nss - 1][mcs - 1];
} else {
max_rate = max_rate_lgi_80MHZ[nss - 1][mcs];
if (!max_rate)
/* MCS9 not supported in NSS3. */
max_rate = max_rate_lgi_80MHZ[nss - 1][mcs - 1];
}
return max_rate;
}
static void
nxpwifi_fill_vht_cap_info(struct nxpwifi_private *priv,
struct ieee80211_vht_cap *vht_cap, u16 bands)
{
struct nxpwifi_adapter *adapter = priv->adapter;
if (bands & BAND_A)
vht_cap->vht_cap_info =
cpu_to_le32(adapter->usr_dot_11ac_dev_cap_a);
else
vht_cap->vht_cap_info =
cpu_to_le32(adapter->usr_dot_11ac_dev_cap_bg);
}
void
nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv,
struct ieee80211_vht_cap *vht_cap, u16 bands)
{
struct nxpwifi_adapter *adapter = priv->adapter;
u16 mcs_map_user, mcs_map_resp, mcs_map_result;
u16 mcs_user, mcs_resp, nss, tmp;
/* Fill VHT capability info. */
nxpwifi_fill_vht_cap_info(priv, vht_cap, bands);
/* RX MCS set: min(user, AP). */
mcs_map_user = GET_DEVRXMCSMAP(adapter->usr_dot_11ac_mcs_support);
mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.rx_mcs_map);
mcs_map_result = 0;
for (nss = 1; nss <= 8; nss++) {
mcs_user = GET_VHTNSSMCS(mcs_map_user, nss);
mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss);
if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED ||
mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED)
SET_VHTNSSMCS(mcs_map_result, nss,
IEEE80211_VHT_MCS_NOT_SUPPORTED);
else
SET_VHTNSSMCS(mcs_map_result, nss,
min(mcs_user, mcs_resp));
}
vht_cap->supp_mcs.rx_mcs_map = cpu_to_le16(mcs_map_result);
tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result);
vht_cap->supp_mcs.rx_highest = cpu_to_le16(tmp);
/* TX MCS set: min(user, AP). */
mcs_map_user = GET_DEVTXMCSMAP(adapter->usr_dot_11ac_mcs_support);
mcs_map_resp = le16_to_cpu(vht_cap->supp_mcs.tx_mcs_map);
mcs_map_result = 0;
for (nss = 1; nss <= 8; nss++) {
mcs_user = GET_VHTNSSMCS(mcs_map_user, nss);
mcs_resp = GET_VHTNSSMCS(mcs_map_resp, nss);
if (mcs_user == IEEE80211_VHT_MCS_NOT_SUPPORTED ||
mcs_resp == IEEE80211_VHT_MCS_NOT_SUPPORTED)
SET_VHTNSSMCS(mcs_map_result, nss,
IEEE80211_VHT_MCS_NOT_SUPPORTED);
else
SET_VHTNSSMCS(mcs_map_result, nss,
min(mcs_user, mcs_resp));
}
vht_cap->supp_mcs.tx_mcs_map = cpu_to_le16(mcs_map_result);
tmp = nxpwifi_convert_mcsmap_to_maxrate(priv, bands, mcs_map_result);
vht_cap->supp_mcs.tx_highest = cpu_to_le16(tmp);
}
int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc,
u8 **buffer)
{
struct nxpwifi_ie_types_vhtcap *vht_cap;
struct nxpwifi_ie_types_oper_mode_ntf *oper_ntf;
struct ieee_types_oper_mode_ntf *ieee_oper_ntf;
struct nxpwifi_ie_types_vht_oper *vht_op;
struct nxpwifi_adapter *adapter = priv->adapter;
u8 supp_chwd_set;
u32 usr_vht_cap_info;
int ret_len = 0;
if (bss_desc->bss_band & BAND_A)
usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_a;
else
usr_vht_cap_info = adapter->usr_dot_11ac_dev_cap_bg;
/* VHT Capabilities element. */
if (bss_desc->bcn_vht_cap) {
vht_cap = (struct nxpwifi_ie_types_vhtcap *)*buffer;
memset(vht_cap, 0, sizeof(*vht_cap));
vht_cap->header.type = cpu_to_le16(WLAN_EID_VHT_CAPABILITY);
vht_cap->header.len =
cpu_to_le16(sizeof(struct ieee80211_vht_cap));
memcpy((u8 *)vht_cap + sizeof(struct nxpwifi_ie_types_header),
(u8 *)bss_desc->bcn_vht_cap,
le16_to_cpu(vht_cap->header.len));
nxpwifi_fill_vht_cap_tlv(priv, &vht_cap->vht_cap,
bss_desc->bss_band);
*buffer += sizeof(*vht_cap);
ret_len += sizeof(*vht_cap);
}
/* VHT Operation element. */
if (bss_desc->bcn_vht_oper) {
if (priv->bss_mode == NL80211_IFTYPE_STATION) {
vht_op = (struct nxpwifi_ie_types_vht_oper *)*buffer;
memset(vht_op, 0, sizeof(*vht_op));
vht_op->header.type =
cpu_to_le16(WLAN_EID_VHT_OPERATION);
vht_op->header.len = cpu_to_le16(sizeof(*vht_op) -
sizeof(struct nxpwifi_ie_types_header));
memcpy((u8 *)vht_op +
sizeof(struct nxpwifi_ie_types_header),
(u8 *)bss_desc->bcn_vht_oper,
le16_to_cpu(vht_op->header.len));
/* Negotiate channel width; keep peer's center freq. */
supp_chwd_set = u32_get_bits(usr_vht_cap_info,
IEEE80211_VHT_CAP_SUPP_CHAN_WIDTH_MASK);
switch (supp_chwd_set) {
case 0:
vht_op->chan_width =
min_t(u8, IEEE80211_VHT_CHANWIDTH_80MHZ,
bss_desc->bcn_vht_oper->chan_width);
break;
case 1:
vht_op->chan_width =
min_t(u8, IEEE80211_VHT_CHANWIDTH_160MHZ,
bss_desc->bcn_vht_oper->chan_width);
break;
case 2:
vht_op->chan_width =
min_t(u8, IEEE80211_VHT_CHANWIDTH_80P80MHZ,
bss_desc->bcn_vht_oper->chan_width);
break;
default:
vht_op->chan_width =
IEEE80211_VHT_CHANWIDTH_USE_HT;
break;
}
*buffer += sizeof(*vht_op);
ret_len += sizeof(*vht_op);
}
}
/* Operating Mode Notification element. */
if (bss_desc->oper_mode) {
ieee_oper_ntf = bss_desc->oper_mode;
oper_ntf = (void *)*buffer;
memset(oper_ntf, 0, sizeof(*oper_ntf));
oper_ntf->header.type = cpu_to_le16(WLAN_EID_OPMODE_NOTIF);
oper_ntf->header.len = cpu_to_le16(sizeof(u8));
oper_ntf->oper_mode = ieee_oper_ntf->oper_mode;
*buffer += sizeof(*oper_ntf);
ret_len += sizeof(*oper_ntf);
}
return ret_len;
}
int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd, u16 cmd_action,
struct nxpwifi_11ac_vht_cfg *cfg)
{
struct host_cmd_11ac_vht_cfg *vhtcfg = &cmd->params.vht_cfg;
cmd->command = cpu_to_le16(HOST_CMD_11AC_CFG);
cmd->size = cpu_to_le16(sizeof(struct host_cmd_11ac_vht_cfg) +
S_DS_GEN);
vhtcfg->action = cpu_to_le16(cmd_action);
vhtcfg->band_config = cfg->band_config;
vhtcfg->misc_config = cfg->misc_config;
vhtcfg->cap_info = cpu_to_le32(cfg->cap_info);
vhtcfg->mcs_tx_set = cpu_to_le32(cfg->mcs_tx_set);
vhtcfg->mcs_rx_set = cpu_to_le32(cfg->mcs_rx_set);
return 0;
}
/* Initialize BlockAck parameters for 11ac. */
void nxpwifi_set_11ac_ba_params(struct nxpwifi_private *priv)
{
priv->add_ba_param.timeout = NXPWIFI_DEFAULT_BLOCK_ACK_TIMEOUT;
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_UAP) {
priv->add_ba_param.tx_win_size =
NXPWIFI_11AC_UAP_AMPDU_DEF_TXWINSIZE;
priv->add_ba_param.rx_win_size =
NXPWIFI_11AC_UAP_AMPDU_DEF_RXWINSIZE;
} else {
priv->add_ba_param.tx_win_size =
NXPWIFI_11AC_STA_AMPDU_DEF_TXWINSIZE;
priv->add_ba_param.rx_win_size =
NXPWIFI_11AC_STA_AMPDU_DEF_RXWINSIZE;
}
}

View File

@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/*
* nxpwifi: 802.11ac (VHT) definitions
*
* Copyright 2011-2024 NXP
*/
#ifndef _NXPWIFI_11AC_H_
#define _NXPWIFI_11AC_H_
#define VHT_CFG_2GHZ BIT(0)
#define VHT_CFG_5GHZ BIT(1)
enum vht_cfg_misc_config {
VHT_CAP_TX_OPERATION = 1,
VHT_CAP_ASSOCIATION,
VHT_CAP_UAP_ONLY
};
#define DEFAULT_VHT_MCS_SET 0xfffe
#define DISABLE_VHT_MCS_SET 0xffff
#define VHT_BW_80_160_80P80 BIT(2)
int nxpwifi_cmd_append_11ac_tlv(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc,
u8 **buffer);
int nxpwifi_cmd_11ac_cfg(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd, u16 cmd_action,
struct nxpwifi_11ac_vht_cfg *cfg);
void nxpwifi_fill_vht_cap_tlv(struct nxpwifi_private *priv,
struct ieee80211_vht_cap *vht_cap, u16 bands);
#endif /* _NXPWIFI_11AC_H_ */

View File

@ -0,0 +1,594 @@
// SPDX-License-Identifier: GPL-2.0-only
/* nxpwifi: 802.11ax (HE) support
* Copyright (C) 2011-2024 NXP
*/
#include "cfg.h"
#include "fw.h"
#include "main.h"
#include "11ax.h"
void nxpwifi_update_11ax_cap(struct nxpwifi_adapter *adapter,
struct hw_spec_extension *hw_he_cap)
{
struct nxpwifi_private *priv;
struct nxpwifi_ie_types_he_cap *he_cap = NULL;
struct nxpwifi_ie_types_he_cap *user_he_cap = NULL;
u8 header_len = sizeof(struct nxpwifi_ie_types_header);
u16 data_len = le16_to_cpu(hw_he_cap->header.len);
bool he_cap_2g = false;
int i;
if ((data_len + header_len) > sizeof(adapter->hw_he_cap)) {
nxpwifi_dbg(adapter, ERROR,
"hw_he_cap too big, len=%d\n",
data_len);
return;
}
he_cap = (struct nxpwifi_ie_types_he_cap *)hw_he_cap;
if (he_cap->he_phy_cap[0] &
(AX_2G_40MHZ_SUPPORT | AX_2G_20MHZ_SUPPORT)) {
adapter->hw_2g_he_cap_len = data_len + header_len;
memcpy(adapter->hw_2g_he_cap, (u8 *)hw_he_cap,
adapter->hw_2g_he_cap_len);
adapter->fw_bands |= BAND_GAX;
he_cap_2g = true;
nxpwifi_dbg_dump(adapter, CMD_D, "2.4G HE capability element ",
adapter->hw_2g_he_cap,
adapter->hw_2g_he_cap_len);
} else {
adapter->hw_he_cap_len = data_len + header_len;
memcpy(adapter->hw_he_cap, (u8 *)hw_he_cap,
adapter->hw_he_cap_len);
adapter->fw_bands |= BAND_AAX;
nxpwifi_dbg_dump(adapter, CMD_D, "5G HE capability element ",
adapter->hw_he_cap,
adapter->hw_he_cap_len);
}
for (i = 0; i < adapter->priv_num; i++) {
priv = adapter->priv[i];
if (he_cap_2g) {
priv->user_2g_he_cap_len = adapter->hw_2g_he_cap_len;
memcpy(priv->user_2g_he_cap, adapter->hw_2g_he_cap,
sizeof(adapter->hw_2g_he_cap));
user_he_cap = (struct nxpwifi_ie_types_he_cap *)
priv->user_2g_he_cap;
} else {
priv->user_he_cap_len = adapter->hw_he_cap_len;
memcpy(priv->user_he_cap, adapter->hw_he_cap,
sizeof(adapter->hw_he_cap));
user_he_cap = (struct nxpwifi_ie_types_he_cap *)
priv->user_he_cap;
}
if (GET_BSS_ROLE(priv) == NXPWIFI_BSS_ROLE_STA)
user_he_cap->he_mac_cap[0] &=
~HE_MAC_CAP_TWT_RESP_SUPPORT;
else
user_he_cap->he_mac_cap[0] &=
~HE_MAC_CAP_TWT_REQ_SUPPORT;
}
adapter->is_hw_11ax_capable = true;
}
bool nxpwifi_11ax_bandconfig_allowed(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc)
{
u16 bss_band = bss_desc->bss_band;
if (bss_desc->disable_11n)
return false;
if (bss_band & BAND_G)
return (priv->config_bands & BAND_GAX);
else if (bss_band & BAND_A)
return (priv->config_bands & BAND_AAX);
return false;
}
int nxpwifi_fill_he_cap_tlv(struct nxpwifi_private *priv,
struct nxpwifi_ie_types_he_cap *he_cap,
u16 bands)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct nxpwifi_ie_types_he_cap *hw_he_cap = NULL;
u16 rx_nss, tx_nss;
u8 nss;
u16 cfg_value;
u16 hw_value;
int ret_len;
if (bands & BAND_A) {
memcpy(he_cap, priv->user_he_cap, priv->user_he_cap_len);
hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_he_cap;
ret_len = priv->user_he_cap_len;
} else {
memcpy(he_cap, priv->user_2g_he_cap, priv->user_2g_he_cap_len);
hw_he_cap = (struct nxpwifi_ie_types_he_cap *)adapter->hw_2g_he_cap;
ret_len = priv->user_2g_he_cap_len;
}
if (bands & BAND_A) {
rx_nss = GET_RXMCSSUPP(adapter->user_htstream >> 8);
tx_nss = GET_TXMCSSUPP(adapter->user_htstream >> 8) & 0x0f;
} else {
rx_nss = GET_RXMCSSUPP(adapter->user_htstream);
tx_nss = GET_TXMCSSUPP(adapter->user_htstream) & 0x0f;
}
for (nss = 1; nss <= 8; nss++) {
cfg_value = nxpwifi_get_he_nss_mcs(he_cap->rx_mcs_80, nss);
hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->rx_mcs_80, nss);
if (rx_nss != 0 && nss > rx_nss)
cfg_value = NO_NSS_SUPPORT;
if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT)
nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss,
NO_NSS_SUPPORT);
else
nxpwifi_set_he_nss_mcs(&he_cap->rx_mcs_80, nss,
min(cfg_value, hw_value));
}
for (nss = 1; nss <= 8; nss++) {
cfg_value = nxpwifi_get_he_nss_mcs(he_cap->tx_mcs_80, nss);
hw_value = nxpwifi_get_he_nss_mcs(hw_he_cap->tx_mcs_80, nss);
if (tx_nss != 0 && nss > tx_nss)
cfg_value = NO_NSS_SUPPORT;
if (hw_value == NO_NSS_SUPPORT || cfg_value == NO_NSS_SUPPORT)
nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss,
NO_NSS_SUPPORT);
else
nxpwifi_set_he_nss_mcs(&he_cap->tx_mcs_80, nss,
min(cfg_value, hw_value));
}
return ret_len;
}
int nxpwifi_cmd_append_11ax_tlv(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc,
u8 **buffer)
{
struct nxpwifi_ie_types_he_cap *he_cap = NULL;
int ret_len;
if (!bss_desc->bcn_he_cap)
return -EOPNOTSUPP;
he_cap = (struct nxpwifi_ie_types_he_cap *)*buffer;
ret_len = nxpwifi_fill_he_cap_tlv(priv, he_cap, bss_desc->bss_band);
*buffer += ret_len;
return ret_len;
}
int nxpwifi_cmd_11ax_cfg(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd, u16 cmd_action,
struct nxpwifi_11ax_he_cfg *ax_cfg)
{
struct host_cmd_11ax_cfg *he_cfg = &cmd->params.ax_cfg;
u16 cmd_size;
struct nxpwifi_ie_types_header *header;
cmd->command = cpu_to_le16(HOST_CMD_11AX_CFG);
cmd_size = sizeof(struct host_cmd_11ax_cfg) + S_DS_GEN;
he_cfg->action = cpu_to_le16(cmd_action);
he_cfg->band_config = ax_cfg->band;
if (ax_cfg->he_cap_cfg.len &&
ax_cfg->he_cap_cfg.ext_id == WLAN_EID_EXT_HE_CAPABILITY) {
header = (struct nxpwifi_ie_types_header *)he_cfg->tlv;
header->type = cpu_to_le16(ax_cfg->he_cap_cfg.id);
header->len = cpu_to_le16(ax_cfg->he_cap_cfg.len);
memcpy(he_cfg->tlv + sizeof(*header),
&ax_cfg->he_cap_cfg.ext_id,
ax_cfg->he_cap_cfg.len);
cmd_size += (sizeof(*header) + ax_cfg->he_cap_cfg.len);
}
cmd->size = cpu_to_le16(cmd_size);
return 0;
}
int nxpwifi_ret_11ax_cfg(struct nxpwifi_private *priv,
struct host_cmd_ds_command *resp,
struct nxpwifi_11ax_he_cfg *ax_cfg)
{
struct host_cmd_11ax_cfg *he_cfg = &resp->params.ax_cfg;
struct nxpwifi_ie_types_header *header;
u16 left_len, tlv_type, tlv_len;
u8 ext_id;
struct nxpwifi_11ax_he_cap_cfg *he_cap = &ax_cfg->he_cap_cfg;
left_len = le16_to_cpu(resp->size) - sizeof(*he_cfg) - S_DS_GEN;
header = (struct nxpwifi_ie_types_header *)he_cfg->tlv;
while (left_len > sizeof(*header)) {
tlv_type = le16_to_cpu(header->type);
tlv_len = le16_to_cpu(header->len);
if (tlv_type == TLV_TYPE_EXTENSION_ID) {
ext_id = *((u8 *)header + sizeof(*header) + 1);
if (ext_id == WLAN_EID_EXT_HE_CAPABILITY) {
he_cap->id = tlv_type;
he_cap->len = tlv_len;
memcpy((u8 *)&he_cap->ext_id,
(u8 *)header + sizeof(*header) + 1,
tlv_len);
if (he_cfg->band_config & BIT(1)) {
memcpy(priv->user_he_cap,
(u8 *)header,
sizeof(*header) + tlv_len);
priv->user_he_cap_len =
sizeof(*header) + tlv_len;
} else {
memcpy(priv->user_2g_he_cap,
(u8 *)header,
sizeof(*header) + tlv_len);
priv->user_2g_he_cap_len =
sizeof(*header) + tlv_len;
}
}
}
left_len -= (sizeof(*header) + tlv_len);
header = (struct nxpwifi_ie_types_header *)((u8 *)header +
sizeof(*header) +
tlv_len);
}
return 0;
}
int nxpwifi_cmd_11ax_cmd(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd, u16 cmd_action,
struct nxpwifi_11ax_cmd_cfg *ax_cmd)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct host_cmd_11ax_cmd *he_cmd = &cmd->params.ax_cmd;
u16 cmd_size;
struct nxpwifi_11ax_sr_cmd *sr_cmd;
struct nxpwifi_ie_types_data *tlv;
struct nxpwifi_11ax_beam_cmd *beam_cmd;
struct nxpwifi_11ax_htc_cmd *htc_cmd;
struct nxpwifi_11ax_txomi_cmd *txmoi_cmd;
struct nxpwifi_11ax_toltime_cmd *toltime_cmd;
struct nxpwifi_11ax_txop_cmd *txop_cmd;
struct nxpwifi_11ax_set_bsrp_cmd *set_bsrp_cmd;
struct nxpwifi_11ax_llde_cmd *llde_cmd;
cmd->command = cpu_to_le16(HOST_CMD_11AX_CMD);
cmd_size = sizeof(struct host_cmd_11ax_cmd) + S_DS_GEN;
he_cmd->action = cpu_to_le16(cmd_action);
he_cmd->sub_id = cpu_to_le16(ax_cmd->sub_id);
switch (ax_cmd->sub_command) {
case NXPWIFI_11AXCMD_SR_SUBID:
sr_cmd = (struct nxpwifi_11ax_sr_cmd *)&ax_cmd->param;
tlv = (struct nxpwifi_ie_types_data *)he_cmd->val;
tlv->header.type = cpu_to_le16(sr_cmd->type);
tlv->header.len = cpu_to_le16(sr_cmd->len);
memcpy(tlv->data, sr_cmd->param.obss_pd_offset.offset,
sr_cmd->len);
cmd_size += (sizeof(tlv->header) + sr_cmd->len);
break;
case NXPWIFI_11AXCMD_BEAM_SUBID:
beam_cmd = (struct nxpwifi_11ax_beam_cmd *)&ax_cmd->param;
he_cmd->val[0] = beam_cmd->value;
cmd_size += sizeof(*beam_cmd);
break;
case NXPWIFI_11AXCMD_HTC_SUBID:
htc_cmd = (struct nxpwifi_11ax_htc_cmd *)&ax_cmd->param;
he_cmd->val[0] = htc_cmd->value;
cmd_size += sizeof(*htc_cmd);
break;
case NXPWIFI_11AXCMD_TXOMI_SUBID:
txmoi_cmd = (struct nxpwifi_11ax_txomi_cmd *)&ax_cmd->param;
memcpy((void *)he_cmd->val, txmoi_cmd, sizeof(*txmoi_cmd));
cmd_size += sizeof(*txmoi_cmd);
break;
case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID:
toltime_cmd = (struct nxpwifi_11ax_toltime_cmd *)&ax_cmd->param;
memcpy(he_cmd->val, &toltime_cmd->tol_time,
sizeof(toltime_cmd->tol_time));
cmd_size += sizeof(*toltime_cmd);
break;
case NXPWIFI_11AXCMD_TXOPRTS_SUBID:
txop_cmd = (struct nxpwifi_11ax_txop_cmd *)&ax_cmd->param;
memcpy(he_cmd->val, &txop_cmd->rts_thres,
sizeof(txop_cmd->rts_thres));
cmd_size += sizeof(*txop_cmd);
break;
case NXPWIFI_11AXCMD_SET_BSRP_SUBID:
set_bsrp_cmd = (struct nxpwifi_11ax_set_bsrp_cmd *)&ax_cmd->param;
he_cmd->val[0] = set_bsrp_cmd->value;
cmd_size += sizeof(*set_bsrp_cmd);
break;
case NXPWIFI_11AXCMD_LLDE_SUBID:
llde_cmd = (struct nxpwifi_11ax_llde_cmd *)&ax_cmd->param;
memcpy((void *)he_cmd->val, llde_cmd, sizeof(*llde_cmd));
cmd_size += sizeof(*llde_cmd);
break;
default:
nxpwifi_dbg(adapter, ERROR,
"%s: Unknown sub command: %d\n",
__func__, ax_cmd->sub_command);
return -EINVAL;
}
cmd->size = cpu_to_le16(cmd_size);
return 0;
}
int nxpwifi_ret_11ax_cmd(struct nxpwifi_private *priv,
struct host_cmd_ds_command *resp,
struct nxpwifi_11ax_cmd_cfg *ax_cmd)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct host_cmd_11ax_cmd *he_cmd = &resp->params.ax_cmd;
struct nxpwifi_ie_types_data *tlv;
ax_cmd->sub_id = le16_to_cpu(he_cmd->sub_id);
switch (ax_cmd->sub_command) {
case NXPWIFI_11AXCMD_SR_SUBID:
tlv = (struct nxpwifi_ie_types_data *)he_cmd->val;
memcpy(ax_cmd->param.sr_cfg.param.obss_pd_offset.offset,
tlv->data,
ax_cmd->param.sr_cfg.len);
break;
case NXPWIFI_11AXCMD_BEAM_SUBID:
ax_cmd->param.beam_cfg.value = *he_cmd->val;
break;
case NXPWIFI_11AXCMD_HTC_SUBID:
ax_cmd->param.htc_cfg.value = *he_cmd->val;
break;
case NXPWIFI_11AXCMD_TXOMI_SUBID:
memcpy(&ax_cmd->param.txomi_cfg,
he_cmd->val, sizeof(ax_cmd->param.txomi_cfg));
break;
case NXPWIFI_11AXCMD_OBSS_TOLTIME_SUBID:
memcpy(&ax_cmd->param.toltime_cfg.tol_time,
he_cmd->val, sizeof(ax_cmd->param.toltime_cfg));
break;
case NXPWIFI_11AXCMD_TXOPRTS_SUBID:
memcpy(&ax_cmd->param.txop_cfg.rts_thres,
he_cmd->val, sizeof(ax_cmd->param.txop_cfg));
break;
case NXPWIFI_11AXCMD_SET_BSRP_SUBID:
ax_cmd->param.setbsrp_cfg.value = *he_cmd->val;
break;
case NXPWIFI_11AXCMD_LLDE_SUBID:
memcpy(&ax_cmd->param.llde_cfg,
he_cmd->val, sizeof(ax_cmd->param.llde_cfg));
break;
default:
nxpwifi_dbg(adapter, ERROR,
"%s: Unknown sub command: %d\n",
__func__, ax_cmd->sub_command);
return -EINVAL;
}
return 0;
}
static u8 nxpwifi_is_ap_11ax_twt_supported(struct nxpwifi_bssdescriptor *bss_desc)
{
struct element *ext_cap;
if (!bss_desc->bcn_he_cap)
return false;
if (!(bss_desc->bcn_he_cap->mac_cap_info[0] & HE_MAC_CAP_TWT_RESP_SUPPORT))
return false;
if (!bss_desc->bcn_ext_cap)
return false;
ext_cap = (struct element *)bss_desc->bcn_ext_cap;
if (!(ext_cap->data[9] & WLAN_EXT_CAPA10_TWT_RESPONDER_SUPPORT))
return false;
return true;
}
bool nxpwifi_is_11ax_twt_supported(struct nxpwifi_private *priv,
struct nxpwifi_bssdescriptor *bss_desc)
{
struct nxpwifi_ie_types_he_cap *user_he_cap;
struct nxpwifi_ie_types_he_cap *hw_he_cap;
if (bss_desc && (!nxpwifi_is_ap_11ax_twt_supported(bss_desc))) {
nxpwifi_dbg(priv->adapter, MSG,
"AP don't support twt feature\n");
return false;
}
if (bss_desc->bss_band & BAND_A) {
hw_he_cap = (struct nxpwifi_ie_types_he_cap *)
priv->adapter->hw_he_cap;
user_he_cap = (struct nxpwifi_ie_types_he_cap *)
priv->user_he_cap;
} else {
hw_he_cap = (struct nxpwifi_ie_types_he_cap *)
priv->adapter->hw_2g_he_cap;
user_he_cap = (struct nxpwifi_ie_types_he_cap *)
priv->user_2g_he_cap;
}
if (!(hw_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) {
nxpwifi_dbg(priv->adapter, MSG,
"FW don't support TWT\n");
return false;
}
if (!(user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT)) {
nxpwifi_dbg(priv->adapter, MSG,
"USER HE_MAC_CAP don't support TWT\n");
return false;
}
return true;
}
u8 nxpwifi_is_sta_11ax_twt_req_supported(struct nxpwifi_private *priv)
{
struct nxpwifi_ie_types_he_cap *user_he_cap;
u8 ret = 0;
if (ISSUPP_11AXENABLED(priv->adapter->fw_cap_ext) &&
(priv->config_bands & BAND_GAX || priv->config_bands & BAND_AAX)) {
if (priv->config_bands & BAND_AAX)
user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_he_cap;
else
user_he_cap = (struct nxpwifi_ie_types_he_cap *)priv->user_2g_he_cap;
ret = user_he_cap->he_mac_cap[0] & HE_MAC_CAP_TWT_REQ_SUPPORT;
}
return ret;
}
int nxpwifi_cmd_twt_cfg(struct nxpwifi_private *priv,
struct host_cmd_ds_command *cmd, u16 cmd_action,
struct nxpwifi_twt_cfg *twt_cfg)
{
struct nxpwifi_adapter *adapter = priv->adapter;
struct host_cmd_twt_cfg *twt_cfg_cmd = &cmd->params.twt_cfg;
struct nxpwifi_twt_setup *twt_setup;
struct nxpwifi_twt_teardown *twt_teardown;
struct nxpwifi_twt_report *twt_report;
struct nxpwifi_twt_information *twt_information;
struct nxpwifi_btwt_ap_config *btwt_ap_config;
u8 i;
u16 cmd_size;
cmd->command = cpu_to_le16(HOST_CMD_TWT_CFG);
cmd_size = sizeof(struct host_cmd_twt_cfg) + S_DS_GEN;
twt_cfg_cmd->action = cpu_to_le16(cmd_action);
twt_cfg_cmd->sub_id = cpu_to_le16(twt_cfg->sub_id);
switch (twt_cfg->sub_id) {
case NXPWIFI_11AX_TWT_SETUP_SUBID:
twt_setup = (struct nxpwifi_twt_setup *)
twt_cfg_cmd->val;
memset(twt_setup, 0x00, sizeof(struct nxpwifi_twt_setup));
twt_setup->implicit = twt_cfg->param.twt_setup.implicit;
twt_setup->announced = twt_cfg->param.twt_setup.announced;
twt_setup->trigger_enabled = twt_cfg->param.twt_setup.trigger_enabled;
twt_setup->twt_info_disabled = twt_cfg->param.twt_setup.twt_info_disabled;
twt_setup->negotiation_type = twt_cfg->param.twt_setup.negotiation_type;
twt_setup->twt_wakeup_duration =
twt_cfg->param.twt_setup.twt_wakeup_duration;
twt_setup->flow_identifier = twt_cfg->param.twt_setup.flow_identifier;
twt_setup->hard_constraint = twt_cfg->param.twt_setup.hard_constraint;
twt_setup->twt_exponent = twt_cfg->param.twt_setup.twt_exponent;
twt_setup->twt_mantissa = twt_cfg->param.twt_setup.twt_mantissa;
twt_setup->twt_request = twt_cfg->param.twt_setup.twt_request;
twt_setup->bcn_miss_threshold = twt_cfg->param.twt_setup.bcn_miss_threshold;
cmd_size += sizeof(struct nxpwifi_twt_setup);
break;
case NXPWIFI_11AX_TWT_TEARDOWN_SUBID:
twt_teardown = (struct nxpwifi_twt_teardown *)
twt_cfg_cmd->val;
memset(twt_teardown, 0x00,
sizeof(struct nxpwifi_twt_teardown));
twt_teardown->flow_identifier =
twt_cfg->param.twt_teardown.flow_identifier;
twt_teardown->negotiation_type =
twt_cfg->param.twt_teardown.negotiation_type;
twt_teardown->teardown_all_twt =
twt_cfg->param.twt_teardown.teardown_all_twt;
cmd_size += sizeof(struct nxpwifi_twt_teardown);
break;
case NXPWIFI_11AX_TWT_REPORT_SUBID:
twt_report = (struct nxpwifi_twt_report *)
twt_cfg_cmd->val;
memset(twt_report, 0x00, sizeof(struct nxpwifi_twt_report));
twt_report->type = twt_cfg->param.twt_report.type;
cmd_size += sizeof(struct nxpwifi_twt_report);
break;
case NXPWIFI_11AX_TWT_INFORMATION_SUBID:
twt_information = (struct nxpwifi_twt_information *)
twt_cfg_cmd->val;
memset(twt_information, 0x00,
sizeof(struct nxpwifi_twt_information));
twt_information->flow_identifier =
twt_cfg->param.twt_information.flow_identifier;
twt_information->suspend_duration =
twt_cfg->param.twt_information.suspend_duration;
cmd_size += sizeof(struct nxpwifi_twt_information);
break;
case NXPWIFI_11AX_BTWT_AP_CONFIG_SUBID:
btwt_ap_config = (struct nxpwifi_btwt_ap_config *)
twt_cfg_cmd->val;
memset(btwt_ap_config, 0x00,
sizeof(struct nxpwifi_btwt_ap_config));
btwt_ap_config->ap_bcast_bet_sta_wait =
twt_cfg->param.btwt_ap_config.ap_bcast_bet_sta_wait;
btwt_ap_config->ap_bcast_offset =
twt_cfg->param.btwt_ap_config.ap_bcast_offset;
btwt_ap_config->bcast_twtli =
twt_cfg->param.btwt_ap_config.bcast_twtli;
btwt_ap_config->count =
twt_cfg->param.btwt_ap_config.count;
for (i = 0; i < BTWT_AGREEMENT_MAX; i++) {
btwt_ap_config->btwt_sets[i].btwt_id =
twt_cfg->param.btwt_ap_config.btwt_sets[i].btwt_id;
btwt_ap_config->btwt_sets[i].ap_bcast_mantissa =
twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_mantissa;
btwt_ap_config->btwt_sets[i].ap_bcast_exponent =
twt_cfg->param.btwt_ap_config.btwt_sets[i].ap_bcast_exponent;
btwt_ap_config->btwt_sets[i].nominalwake =
twt_cfg->param.btwt_ap_config.btwt_sets[i].nominalwake;
}
cmd_size += sizeof(struct nxpwifi_btwt_ap_config);
break;
default:
nxpwifi_dbg(adapter, ERROR,
"Unknown sub id: %d\n", twt_cfg->sub_id);
return -EINVAL;
}
cmd->size = cpu_to_le16(cmd_size);
return 0;
}
int nxpwifi_ret_twt_cfg(struct nxpwifi_private *priv,
struct host_cmd_ds_command *resp,
struct nxpwifi_twt_cfg *twt_cfg)
{
struct host_cmd_twt_cfg *twt_cfg_cmd = &resp->params.twt_cfg;
u16 action;
action = le16_to_cpu(twt_cfg_cmd->action);
twt_cfg->sub_id = le16_to_cpu(twt_cfg_cmd->sub_id);
if (action == HOST_ACT_GEN_GET &&
twt_cfg->sub_id == NXPWIFI_11AX_TWT_REPORT_SUBID) {
struct nxpwifi_twt_report *twt_report =
(struct nxpwifi_twt_report *)twt_cfg_cmd->val;
memcpy(&twt_cfg->param.twt_report, twt_report, sizeof(struct nxpwifi_twt_report));
}
return 0;
}

Some files were not shown because too many files have changed in this diff Show More