mirror of
https://github.com/torvalds/linux.git
synced 2026-09-14 16:10:02 +02:00
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:
commit
edc84a9396
24
MAINTAINERS
24
MAINTAINERS
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
298
drivers/firmware/qcom/qcom_pas.c
Normal file
298
drivers/firmware/qcom/qcom_pas.c
Normal 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");
|
||||
50
drivers/firmware/qcom/qcom_pas.h
Normal file
50
drivers/firmware/qcom/qcom_pas.h
Normal 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 */
|
||||
479
drivers/firmware/qcom/qcom_pas_tee.c
Normal file
479
drivers/firmware/qcom/qcom_pas_tee.c
Normal 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");
|
||||
|
|
@ -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[] = {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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/
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 */ }
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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(¶ms->hint_bssid[i].bssid.addr[0],
|
||||
&hint_bssid->bssid.addr[0]);
|
||||
ether_addr_copy(&hint_bssid->bssid.addr[0],
|
||||
¶ms->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],
|
||||
®_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 */
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 */
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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! */
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
*/
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
15
drivers/net/wireless/morsemicro/Kconfig
Normal file
15
drivers/net/wireless/morsemicro/Kconfig
Normal 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
|
||||
2
drivers/net/wireless/morsemicro/Makefile
Normal file
2
drivers/net/wireless/morsemicro/Makefile
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
obj-$(CONFIG_MM81X) += mm81x/
|
||||
24
drivers/net/wireless/morsemicro/mm81x/Kconfig
Normal file
24
drivers/net/wireless/morsemicro/mm81x/Kconfig
Normal 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.
|
||||
21
drivers/net/wireless/morsemicro/mm81x/Makefile
Normal file
21
drivers/net/wireless/morsemicro/mm81x/Makefile
Normal 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
|
||||
99
drivers/net/wireless/morsemicro/mm81x/bus.h
Normal file
99
drivers/net/wireless/morsemicro/mm81x/bus.h
Normal 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_ */
|
||||
563
drivers/net/wireless/morsemicro/mm81x/command.c
Normal file
563
drivers/net/wireless/morsemicro/mm81x/command.c
Normal 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);
|
||||
}
|
||||
85
drivers/net/wireless/morsemicro/mm81x/command.h
Normal file
85
drivers/net/wireless/morsemicro/mm81x/command.h
Normal 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_ */
|
||||
1658
drivers/net/wireless/morsemicro/mm81x/command_defs.h
Normal file
1658
drivers/net/wireless/morsemicro/mm81x/command_defs.h
Normal file
File diff suppressed because it is too large
Load Diff
138
drivers/net/wireless/morsemicro/mm81x/core.c
Normal file
138
drivers/net/wireless/morsemicro/mm81x/core.c
Normal 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");
|
||||
456
drivers/net/wireless/morsemicro/mm81x/core.h
Normal file
456
drivers/net/wireless/morsemicro/mm81x/core.h
Normal 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_ */
|
||||
752
drivers/net/wireless/morsemicro/mm81x/fw.c
Normal file
752
drivers/net/wireless/morsemicro/mm81x/fw.c
Normal 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;
|
||||
}
|
||||
143
drivers/net/wireless/morsemicro/mm81x/fw.h
Normal file
143
drivers/net/wireless/morsemicro/mm81x/fw.h
Normal 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_ */
|
||||
117
drivers/net/wireless/morsemicro/mm81x/hif.h
Normal file
117
drivers/net/wireless/morsemicro/mm81x/hif.h
Normal 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_ */
|
||||
367
drivers/net/wireless/morsemicro/mm81x/hw.c
Normal file
367
drivers/net/wireless/morsemicro/mm81x/hw.c
Normal 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, ®32_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);
|
||||
159
drivers/net/wireless/morsemicro/mm81x/hw.h
Normal file
159
drivers/net/wireless/morsemicro/mm81x/hw.h
Normal 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_ */
|
||||
2443
drivers/net/wireless/morsemicro/mm81x/mac.c
Normal file
2443
drivers/net/wireless/morsemicro/mm81x/mac.c
Normal file
File diff suppressed because it is too large
Load Diff
63
drivers/net/wireless/morsemicro/mm81x/mac.h
Normal file
63
drivers/net/wireless/morsemicro/mm81x/mac.h
Normal 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_ */
|
||||
1354
drivers/net/wireless/morsemicro/mm81x/mmrc.c
Normal file
1354
drivers/net/wireless/morsemicro/mm81x/mmrc.c
Normal file
File diff suppressed because it is too large
Load Diff
193
drivers/net/wireless/morsemicro/mm81x/mmrc.h
Normal file
193
drivers/net/wireless/morsemicro/mm81x/mmrc.h
Normal 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_ */
|
||||
120
drivers/net/wireless/morsemicro/mm81x/ps.c
Normal file
120
drivers/net/wireless/morsemicro/mm81x/ps.c
Normal 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);
|
||||
}
|
||||
}
|
||||
22
drivers/net/wireless/morsemicro/mm81x/ps.h
Normal file
22
drivers/net/wireless/morsemicro/mm81x/ps.h
Normal 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_ */
|
||||
177
drivers/net/wireless/morsemicro/mm81x/rate_code.h
Normal file
177
drivers/net/wireless/morsemicro/mm81x/rate_code.h
Normal 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
|
||||
494
drivers/net/wireless/morsemicro/mm81x/rc.c
Normal file
494
drivers/net/wireless/morsemicro/mm81x/rc.c
Normal 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);
|
||||
}
|
||||
}
|
||||
51
drivers/net/wireless/morsemicro/mm81x/rc.h
Normal file
51
drivers/net/wireless/morsemicro/mm81x/rc.h
Normal 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_ */
|
||||
613
drivers/net/wireless/morsemicro/mm81x/sdio.c
Normal file
613
drivers/net/wireless/morsemicro/mm81x/sdio.c
Normal 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");
|
||||
1064
drivers/net/wireless/morsemicro/mm81x/skbq.c
Normal file
1064
drivers/net/wireless/morsemicro/mm81x/skbq.c
Normal file
File diff suppressed because it is too large
Load Diff
218
drivers/net/wireless/morsemicro/mm81x/skbq.h
Normal file
218
drivers/net/wireless/morsemicro/mm81x/skbq.h
Normal 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_ */
|
||||
943
drivers/net/wireless/morsemicro/mm81x/usb.c
Normal file
943
drivers/net/wireless/morsemicro/mm81x/usb.c
Normal 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");
|
||||
704
drivers/net/wireless/morsemicro/mm81x/yaps.c
Normal file
704
drivers/net/wireless/morsemicro/mm81x/yaps.c
Normal 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);
|
||||
}
|
||||
}
|
||||
77
drivers/net/wireless/morsemicro/mm81x/yaps.h
Normal file
77
drivers/net/wireless/morsemicro/mm81x/yaps.h
Normal 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_ */
|
||||
702
drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
Normal file
702
drivers/net/wireless/morsemicro/mm81x/yaps_hw.c
Normal 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;
|
||||
}
|
||||
}
|
||||
52
drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
Normal file
52
drivers/net/wireless/morsemicro/mm81x/yaps_hw.h
Normal 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_ */
|
||||
17
drivers/net/wireless/nxp/Kconfig
Normal file
17
drivers/net/wireless/nxp/Kconfig
Normal 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
|
||||
3
drivers/net/wireless/nxp/Makefile
Normal file
3
drivers/net/wireless/nxp/Makefile
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
obj-$(CONFIG_NXPWIFI) += nxpwifi/
|
||||
280
drivers/net/wireless/nxp/nxpwifi/11ac.c
Normal file
280
drivers/net/wireless/nxp/nxpwifi/11ac.c
Normal 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;
|
||||
}
|
||||
}
|
||||
33
drivers/net/wireless/nxp/nxpwifi/11ac.h
Normal file
33
drivers/net/wireless/nxp/nxpwifi/11ac.h
Normal 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_ */
|
||||
594
drivers/net/wireless/nxp/nxpwifi/11ax.c
Normal file
594
drivers/net/wireless/nxp/nxpwifi/11ax.c
Normal 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
Loading…
Reference in New Issue
Block a user