* Re: [PATCH 7/8] drm/panthor: Add support for entering and exiting protected mode
From: Boris Brezillon @ 2026-05-06 8:51 UTC (permalink / raw)
To: Ketil Johnsen
Cc: David Airlie, Simona Vetter, Maarten Lankhorst, Maxime Ripard,
Thomas Zimmermann, Jonathan Corbet, Shuah Khan, Sumit Semwal,
Benjamin Gaignard, Brian Starkey, John Stultz, T.J. Mercier,
Christian König, Steven Price, Liviu Dudau, Daniel Almeida,
Alice Ryhl, Matthias Brugger, AngeloGioacchino Del Regno,
dri-devel, linux-doc, linux-kernel, linux-media, linaro-mm-sig,
linux-arm-kernel, linux-mediatek, Florent Tomasin, Paul Toadere,
Samuel Percival
In-Reply-To: <20260505140516.1372388-8-ketil.johnsen@arm.com>
On Tue, 5 May 2026 16:05:13 +0200
Ketil Johnsen <ketil.johnsen@arm.com> wrote:
Here comes the second part of the review :-).
> diff --git a/drivers/gpu/drm/panthor/panthor_gpu.c b/drivers/gpu/drm/panthor/panthor_gpu.c
> index 2ab444ee8c710..e93042eaf3fc8 100644
> --- a/drivers/gpu/drm/panthor/panthor_gpu.c
> +++ b/drivers/gpu/drm/panthor/panthor_gpu.c
> @@ -100,8 +100,11 @@ static void panthor_gpu_irq_handler(struct panthor_device *ptdev, u32 status)
> fault_status, panthor_exception_name(ptdev, fault_status & 0xFF),
> address);
> }
> - if (status & GPU_IRQ_PROTM_FAULT)
> + if (status & GPU_IRQ_PROTM_FAULT) {
> drm_warn(&ptdev->base, "GPU Fault in protected mode\n");
> + panthor_gpu_disable_protm_fault_interrupt(ptdev);
It's only used in a single place, so I'd just inline the content of
panthor_gpu_disable_protm_fault_interrupt() here. Also, I think
panthor_gpu_disable_protm_fault_interrupt() is not taking the right
lock (see below).
> + panthor_device_schedule_reset(ptdev);
> + }
>
> spin_lock(&ptdev->gpu->reqs_lock);
> if (status & ptdev->gpu->pending_reqs) {
> @@ -367,6 +370,10 @@ int panthor_gpu_soft_reset(struct panthor_device *ptdev)
> unsigned long flags;
>
> spin_lock_irqsave(&ptdev->gpu->reqs_lock, flags);
> +
> + /** Re-enable the protm_irq_fault when reset is complete */
> + ptdev->gpu->irq.mask |= GPU_IRQ_PROTM_FAULT;
panthor_irq::mask should only be modified with the
panthor_irq::mask_lock held. Besides, we have a helper for
that:
panthor_gpu_irq_enable_events(&ptdev->gpu->irq, GPU_IRQ_PROTM_FAULT);
> +
> if (!drm_WARN_ON(&ptdev->base,
> ptdev->gpu->pending_reqs & GPU_IRQ_RESET_COMPLETED)) {
> ptdev->gpu->pending_reqs |= GPU_IRQ_RESET_COMPLETED;
> @@ -427,3 +434,8 @@ void panthor_gpu_resume(struct panthor_device *ptdev)
> panthor_hw_l2_power_on(ptdev);
> }
>
> +void panthor_gpu_disable_protm_fault_interrupt(struct panthor_device *ptdev)
> +{
> + scoped_guard(spinlock_irqsave, &ptdev->gpu->reqs_lock)
> + ptdev->gpu->irq.mask &= ~GPU_IRQ_PROTM_FAULT;
Same problem with wrong lock being taken to modify the mask, and
panthor_gpu_irq_disable_events() probably being a better option?
> +}
> diff --git a/drivers/gpu/drm/panthor/panthor_gpu.h b/drivers/gpu/drm/panthor/panthor_gpu.h
> index 12c263a399281..ca66c73f543e6 100644
> --- a/drivers/gpu/drm/panthor/panthor_gpu.h
> +++ b/drivers/gpu/drm/panthor/panthor_gpu.h
> @@ -54,4 +54,10 @@ int panthor_gpu_soft_reset(struct panthor_device *ptdev);
> void panthor_gpu_power_changed_off(struct panthor_device *ptdev);
> int panthor_gpu_power_changed_on(struct panthor_device *ptdev);
>
> +/**
> + * panthor_gpu_disable_protm_fault_interrupt() - Disable GPU_PROTECTED_FAULT interrupt
> + * @ptdev: Device.
> + */
> +void panthor_gpu_disable_protm_fault_interrupt(struct panthor_device *ptdev);
> +
> #endif
> diff --git a/drivers/gpu/drm/panthor/panthor_mmu.c b/drivers/gpu/drm/panthor/panthor_mmu.c
> index 07f54176ec1bf..702f537905b56 100644
> --- a/drivers/gpu/drm/panthor/panthor_mmu.c
> +++ b/drivers/gpu/drm/panthor/panthor_mmu.c
> @@ -31,6 +31,7 @@
> #include <linux/sizes.h>
>
> #include "panthor_device.h"
> +#include "panthor_fw.h"
> #include "panthor_gem.h"
> #include "panthor_gpu.h"
> #include "panthor_heap.h"
> @@ -1704,8 +1705,12 @@ static int panthor_vm_lock_region(struct panthor_vm *vm, u64 start, u64 size)
> if (drm_WARN_ON(&ptdev->base, vm->locked_region.size))
> return -EINVAL;
>
> + down_read(&ptdev->protm.lock);
> +
> mutex_lock(&ptdev->mmu->as.slots_lock);
> if (vm->as.id >= 0 && size) {
> + panthor_fw_protm_exit_sync(ptdev);
> +
> /* Lock the region that needs to be updated */
> gpu_write64(ptdev, AS_LOCKADDR(vm->as.id),
> pack_region_range(ptdev, &start, &size));
> @@ -1720,6 +1725,9 @@ static int panthor_vm_lock_region(struct panthor_vm *vm, u64 start, u64 size)
> }
> mutex_unlock(&ptdev->mmu->as.slots_lock);
>
> + if (ret)
> + up_read(&ptdev->protm.lock);
Let's try to keep the locked section local to a function: the protm.lock
should, IMHO, be taken/released in panthor_vm_exec_op(). If we go for
that, this also makes the panthor_vm_lock_region() vs
panthor_vm_expand_locked_region() distinction useless, though it's
probably fine to keep it for clarity.
> +
> return ret;
> }
>
> @@ -1805,6 +1813,8 @@ static void panthor_vm_unlock_region(struct panthor_vm *vm)
> vm->locked_region.start = 0;
> vm->locked_region.size = 0;
> mutex_unlock(&ptdev->mmu->as.slots_lock);
> +
> + up_read(&ptdev->protm.lock);
> }
>
> static void panthor_mmu_irq_handler(struct panthor_device *ptdev, u32 status)
> diff --git a/drivers/gpu/drm/panthor/panthor_sched.c b/drivers/gpu/drm/panthor/panthor_sched.c
> index 987072bd867c4..acb04250c7def 100644
> --- a/drivers/gpu/drm/panthor/panthor_sched.c
> +++ b/drivers/gpu/drm/panthor/panthor_sched.c
> @@ -308,6 +308,15 @@ struct panthor_scheduler {
> */
> struct list_head stopped_groups;
> } reset;
> +
> + /** @protm: Protected mode related fields. */
> + struct {
> + /** @protected_mode: True if GPU is in protected mode. */
> + bool protected_mode;
nit: s/protected_mode/enabled/s. But do we even need that boolean?
Isn't active_group != NULL providing the same info?
> +
> + /** @active_group: The active protected group. */
> + struct panthor_group *active_group;
> + } protm;
> };
>
> /**
> @@ -570,6 +579,16 @@ struct panthor_group {
> /** @fatal_queues: Bitmask reflecting the queues that hit a fatal exception. */
> u32 fatal_queues;
>
> + /**
> + * @protm_pending_queues: Bitmask reflecting the queues that are waiting
> + * on a CS_PROTM_PENDING.
> + *
> + * The GPU will set the bit associated to the queue pending protected mode
> + * when a PROT_REGION command is executing or when trying to resume previously
> + * suspended protected mode jobs.
> + */
> + u32 protm_pending_queues;
> +
> /** @tiler_oom: Mask of queues that have a tiler OOM event to process. */
> atomic_t tiler_oom;
>
> @@ -1176,6 +1195,7 @@ queue_resume_timeout(struct panthor_queue *queue)
> * @ptdev: Device.
> * @csg_id: Group slot ID.
> * @cs_id: Queue slot ID.
> + * @protm_ack: Acknowledge pending protected mode queues
> *
> * Program a queue slot with the queue information so things can start being
> * executed on this queue.
> @@ -1472,6 +1492,34 @@ csg_slot_prog_locked(struct panthor_device *ptdev, u32 csg_id, u32 priority)
> return 0;
> }
>
> +static void
> +cs_slot_process_protm_pending_event_locked(struct panthor_device *ptdev,
> + u32 csg_id, u32 cs_id)
> +{
> + struct panthor_scheduler *sched = ptdev->scheduler;
> + struct panthor_csg_slot *csg_slot = &sched->csg_slots[csg_id];
> + struct panthor_group *group = csg_slot->group;
> +
> + lockdep_assert_held(&sched->lock);
> +
> + if (!group)
> + return;
> +
> + /* No protected memory heap, a user space program tried to
> + * submit a protected mode jobs resulting in the GPU raising
> + * a CS_PROTM_PENDING request.
> + *
> + * This scenario is invalid and the protected mode jobs must
> + * not be allowed to progress.
> + */
> + if (!ptdev->protm.heap)
> + return;
Should we flag the group unusable when that happens and schedule it out
as soon as possible?
if (!ptdev->protm.heap)
group->fatal_queues |= BIT(cs_id);
else
group->protm_pending_queues |= BIT(cs_id);
sched_queue_delayed_work(sched, tick, 0);
> +
> + group->protm_pending_queues |= BIT(cs_id);
> +
> + sched_queue_delayed_work(sched, tick, 0);
> +}
> +
> static void
> cs_slot_process_fatal_event_locked(struct panthor_device *ptdev,
> u32 csg_id, u32 cs_id)
> @@ -1718,6 +1766,9 @@ static bool cs_slot_process_irq_locked(struct panthor_device *ptdev,
> if (events & CS_TILER_OOM)
> cs_slot_process_tiler_oom_event_locked(ptdev, csg_id, cs_id);
>
> + if (events & CS_PROTM_PENDING)
> + cs_slot_process_protm_pending_event_locked(ptdev, csg_id, cs_id);
> +
> /* We don't acknowledge the TILER_OOM event since its handling is
> * deferred to a separate work.
> */
> @@ -1848,6 +1899,17 @@ static void sched_process_idle_event_locked(struct panthor_device *ptdev)
> sched_queue_delayed_work(ptdev->scheduler, tick, 0);
> }
>
> +static void sched_process_protm_exit_event_locked(struct panthor_device *ptdev)
> +{
> + lockdep_assert_held(&ptdev->scheduler->lock);
> +
> + /* Acknowledge the protm exit and schedule a tick. */
> + panthor_fw_protm_exit(ptdev);
Let's just inline the content of panthor_fw_protm_exit() here.*
It doesn't make sense to have all these indirections, especially
since PROTM and scheduling are intertwined anyway, so I consider
it part of the scheduler responsibility (just like the scheduler
deals with other GLB events).
The same goes for the other panthor_fw_protm_ helpers defined in
panthor_fw.c, I think panthor_sched.c would be a better fit for
those, or even inlining their content if they are only used in
a single place.
> + sched_queue_delayed_work(ptdev->scheduler, tick, 0);
> + ptdev->scheduler->protm.protected_mode = false;
> + ptdev->scheduler->protm.active_group = NULL;
> +}
> +
> /**
> * sched_process_global_irq_locked() - Process the scheduling part of a global IRQ
> * @ptdev: Device.
> @@ -1863,6 +1925,9 @@ static void sched_process_global_irq_locked(struct panthor_device *ptdev)
> ack = READ_ONCE(glb_iface->output->ack);
> evts = (req ^ ack) & GLB_EVT_MASK;
>
> + if (evts & GLB_PROTM_EXIT)
> + sched_process_protm_exit_event_locked(ptdev);
> +
> if (evts & GLB_IDLE)
> sched_process_idle_event_locked(ptdev);
> }
> @@ -1872,23 +1937,71 @@ static void process_fw_events_work(struct work_struct *work)
> struct panthor_scheduler *sched = container_of(work, struct panthor_scheduler,
> fw_events_work);
> u32 events = atomic_xchg(&sched->fw_events, 0);
> + u32 csg_events = events & ~JOB_INT_GLOBAL_IF;
> struct panthor_device *ptdev = sched->ptdev;
>
> mutex_lock(&sched->lock);
>
> + while (csg_events) {
> + u32 csg_id = ffs(csg_events) - 1;
> +
> + sched_process_csg_irq_locked(ptdev, csg_id);
> + csg_events &= ~BIT(csg_id);
> + }
I'm sure you have a good reason to re-order the processing
of CSG and GLB events, and it'd be good to have it documented
in a comment.
> +
> if (events & JOB_INT_GLOBAL_IF) {
> sched_process_global_irq_locked(ptdev);
> events &= ~JOB_INT_GLOBAL_IF;
> }
>
> - while (events) {
> - u32 csg_id = ffs(events) - 1;
> + mutex_unlock(&sched->lock);
> +}
>
> - sched_process_csg_irq_locked(ptdev, csg_id);
> - events &= ~BIT(csg_id);
> +static void handle_protm_fault(struct panthor_device *ptdev)
This is a bit of a misnomer, I think. It doesn't seem to be triggered
by a FAULT event, it's more a timeout on a PROTM section that would
lead to a reset being scheduled, and this "blocked-in-protm" situation
being detected as part of the reset.
> +{
> + struct panthor_scheduler *sched = ptdev->scheduler;
> + u32 csg_id;
> + struct panthor_group *protm_group;
> +
> + guard(mutex)(&sched->lock);
> +
> + if (!sched->protm.protected_mode)
> + return;
> +
> + protm_group = sched->protm.active_group;
> +
> + if (drm_WARN_ON(&ptdev->base, !protm_group))
> + return;
See, that's a perfect example of sched->protm.protected_mode being redundant.
Now you're left with a potential protected_mode=true,active_group=NULL
inconsistency you don't expect.
> +
> + /* Group will be terminated by the device reset */
> + protm_group->fatal_queues |= GENMASK(protm_group->queue_count - 1, 0);
> +
> + if (!panthor_fw_protm_exit_wait_event_timeout(ptdev))
> + goto cleanup_protm;
> +
> + /**
> + * GPU failed to exit protected mode. Mark all non-protected mode CSGs
/* GPU failed to exit protected mode. Mark all non-protected mode CSGs
> + * as suspended so that they are unaffected by the GPU reset.
> + */
> +
> + for (csg_id = 0; csg_id < sched->csg_slot_count; csg_id++) {
> + struct panthor_group *group = sched->csg_slots[csg_id].group;
> +
> + if (!group || group == protm_group)
> + continue;
> +
> + group->state = PANTHOR_CS_GROUP_SUSPENDED;
> +
> + group_unbind_locked(group);
> +
> + list_move(&group->run_node, group_is_idle(group) ?
> + &sched->groups.idle[group->priority] :
> + &sched->groups.runnable[group->priority]);
nit: Let's use a local struct list_head * variable to select the right list.
> }
>
> - mutex_unlock(&sched->lock);
> +cleanup_protm:
> + sched->protm.protected_mode = false;
> + sched->protm.active_group = NULL;
> }
>
> /**
> @@ -2029,6 +2142,7 @@ struct panthor_sched_tick_ctx {
> bool immediate_tick;
> bool stop_tick;
> u32 csg_upd_failed_mask;
> + struct panthor_group *protm_group;
> };
>
> static bool
> @@ -2299,6 +2413,7 @@ tick_ctx_evict_group(struct panthor_scheduler *sched,
>
> static void
> tick_ctx_reschedule_group(struct panthor_scheduler *sched,
> + struct panthor_sched_tick_ctx *ctx,
> struct panthor_csg_slots_upd_ctx *upd_ctx,
> struct panthor_group *group,
> int new_csg_prio)
> @@ -2321,6 +2436,30 @@ tick_ctx_reschedule_group(struct panthor_scheduler *sched,
> csg_iface->output->ack ^ CSG_ENDPOINT_CONFIG,
> CSG_ENDPOINT_CONFIG);
> }
> +
> + if (ctx->protm_group == group) {
> + for (u32 q = 0; q < group->queue_count; q++) {
> + struct panthor_fw_cs_iface *cs_iface;
> +
> + if (!(group->protm_pending_queues & BIT(q)))
> + continue;
> +
> + cs_iface = panthor_fw_get_cs_iface(ptdev, group->csg_id, q);
> + panthor_fw_update_reqs(cs_iface, req, cs_iface->output->ack,
> + CS_PROTM_PENDING);
> + }
> +
> + panthor_fw_toggle_reqs(csg_iface, doorbell_req, doorbell_ack,
> + group->protm_pending_queues);
> + csgs_upd_ctx_ring_doorbell(upd_ctx, group->csg_id);
> + group->protm_pending_queues = 0;
> +
> + /*
> + * We only allow one protected group to run at same time,
> + * as it makes it easier to handle faults in protected mode.
It's more something to document in the panthor_scheduler::protm::active_group
section.
> + */
> + sched->protm.active_group = group;
Would it make sense to move this logic to a tick_ctx_handle_protm_group()
helper that's called before/after tick_ctx_reschedule_group()? This way
there's no extra if (ctx->protm_group == group) conditional branch in here.
static void
tick_ctx_handle_protm_group(struct panthor_scheduler *sched,
struct panthor_sched_tick_ctx *ctx,
struct panthor_csg_slots_upd_ctx *upd_ctx)
{
struct panthor_device *ptdev = sched->ptdev;
struct panthor_group *group = ctx->protm_group;
struct panthor_fw_csg_iface *csg_iface;
if (!group || drm_WARN_ON(&ptdev->base, group->csg_id < 0))
return;
csg_iface = panthor_fw_get_csg_iface(ptdev, group->csg_id);
for (u32 q = 0; q < group->queue_count; q++) {
struct panthor_fw_cs_iface *cs_iface;
if (!(group->protm_pending_queues & BIT(q)))
continue;
cs_iface = panthor_fw_get_cs_iface(ptdev, group->csg_id, q);
panthor_fw_update_reqs(cs_iface, req, cs_iface->output->ack,
CS_PROTM_PENDING);
}
panthor_fw_toggle_reqs(csg_iface, doorbell_req, doorbell_ack,
group->protm_pending_queues);
csgs_upd_ctx_ring_doorbell(upd_ctx, group->csg_id);
group->protm_pending_queues = 0;
sched->protm.active_group = group;
}
> + }
> }
>
> static void
> @@ -2336,6 +2475,17 @@ tick_ctx_schedule_group(struct panthor_scheduler *sched,
> group_bind_locked(group, csg_id);
> csg_slot_prog_locked(ptdev, csg_id, csg_prio);
>
> + /* If the group was waiting for protected mode before suspension,
> + * and the tick context enters this mode, it should be serviced
> + * immediately because the slot reset should have set the
> + * CS_PROTM_PENDING bit to zero, and cs_prog_slot_locked() sets it to
> + * zero too.
> + * It's not clear if we will get a new CS_PROTM_PENDING event in that
> + * case, but it should be safe either way.
> + */
> + if (group->protm_pending_queues && ctx->protm_group)
> + group->protm_pending_queues = 0;
I'd move this to the path where we do the SUSPEND, or group_unbind(), even.
> +
> csgs_upd_ctx_queue_reqs(ptdev, upd_ctx, csg_id,
> group->state == PANTHOR_CS_GROUP_SUSPENDED ?
> CSG_STATE_RESUME : CSG_STATE_START,
> @@ -2365,7 +2515,7 @@ tick_ctx_apply(struct panthor_scheduler *sched, struct panthor_sched_tick_ctx *c
>
> /* Update priorities on already running groups. */
> list_for_each_entry(group, &ctx->groups[prio], run_node) {
> - tick_ctx_reschedule_group(sched, &upd_ctx, group, new_csg_prio--);
> + tick_ctx_reschedule_group(sched, ctx, &upd_ctx, group, new_csg_prio--);
> }
> }
>
> @@ -2457,6 +2607,15 @@ tick_ctx_apply(struct panthor_scheduler *sched, struct panthor_sched_tick_ctx *c
>
> sched->used_csg_slot_count = ctx->group_count;
> sched->might_have_idle_groups = ctx->idle_group_count > 0;
> +
> + if (ctx->protm_group) {
> + ret = panthor_fw_protm_enter(ptdev);
> + if (ret) {
> + panthor_device_schedule_reset(ptdev);
> + ctx->csg_upd_failed_mask = U32_MAX;
It's weird to flag it as all CSGs update failed. Should we instead
have
/* If we failed to enter PROTM, consider the group who
* requested it as failed.
*/
ctx->csg_upd_failed_mask |= BIT(ctx->protm_group->csg_id);
> + }
> + sched->protm.protected_mode = true;
I'd move that to a tick_ctx_service_protm_req() helper that has the
panthor_fw_protm_enter() inlined, because again, it doesn't make
sense to have this defined in panthor_fw.c if the only user lives
in panthor_sched.c
> + }
> }
>
> static u64
> @@ -2490,7 +2649,7 @@ static void tick_work(struct work_struct *work)
> u64 resched_target = sched->resched_target;
> u64 remaining_jiffies = 0, resched_delay;
> u64 now = get_jiffies_64();
> - int prio, ret, cookie;
> + int prio, protm_prio, ret, cookie;
> bool full_tick;
>
> if (!drm_dev_enter(&ptdev->base, &cookie))
> @@ -2564,14 +2723,49 @@ static void tick_work(struct work_struct *work)
> }
> }
>
> + /* Check if the highest priority group want to switch to protected mode */
> + for (protm_prio = PANTHOR_CSG_PRIORITY_COUNT - 1; protm_prio >= 0; protm_prio--) {
> + struct panthor_group *group;
> +
> + group = list_first_entry_or_null(&ctx.groups[protm_prio],
> + struct panthor_group,
> + run_node);
> + if (group) {
> + ctx.protm_group = group;
> + break;
> + }
Should this be
if (group) {
if (group->protm_pending_queues)
ctx.protm_group = group;
break;
}
?
> + }
> +
> /* If we have free CSG slots left, pick idle groups */
> - for (prio = PANTHOR_CSG_PRIORITY_COUNT - 1;
> - prio >= 0 && !tick_ctx_is_full(sched, &ctx);
> - prio--) {
How about we keep it a single indentation level and skip higher prios if
PROTM is requested:
/* Pick only idle groups with equal or lower priority than the
* group triggering protected mode. Do not bother picking
* unscheduled idle groups.
*/
if (ctx.protm_group && prio < protm_prio)
continue;
This saves us an indentation level and limits the code duplication.
> - /* Check the old_group queue first to avoid reprogramming the slots */
> - tick_ctx_pick_groups_from_list(sched, &ctx, &ctx.old_groups[prio], false, true);
> - tick_ctx_pick_groups_from_list(sched, &ctx, &sched->groups.idle[prio],
> - false, false);
> + if (ctx.protm_group) {
> + /* Pick only idle groups with equal or lower priority than the
> + * group triggering protected mode. Do not bother picking
> + * unscheduled idle groups.
> + */
> + for (prio = protm_prio;
> + prio >= 0 && !tick_ctx_is_full(sched, &ctx);
> + prio--)
> + tick_ctx_pick_groups_from_list(sched, &ctx,
> + &ctx.old_groups[prio],
> + false, true);
> + } else {
> + /* No switch to protected, just pick any idle group according
> + * to priority
> + */
> + for (prio = PANTHOR_CSG_PRIORITY_COUNT - 1;
> + prio >= 0 && !tick_ctx_is_full(sched, &ctx);
> + prio--) {
> + /* Check the old_group queue first to avoid
> + * reprogramming the slots
> + */
> + tick_ctx_pick_groups_from_list(sched, &ctx,
> + &ctx.old_groups[prio],
> + false, true);
> + tick_ctx_pick_groups_from_list(sched, &ctx,
> + &sched->groups.idle[prio],
> + false, false);
> + }
> +
> }
>
> tick_ctx_apply(sched, &ctx);
> @@ -2993,6 +3187,8 @@ void panthor_sched_pre_reset(struct panthor_device *ptdev)
> cancel_work_sync(&sched->sync_upd_work);
> cancel_delayed_work_sync(&sched->tick_work);
>
> + handle_protm_fault(ptdev);
I actually wonder if this should be part of the panthor_sched_suspend()
logic. That is, we would automatically flag all non-protm groups as
suspended if the GPU was in PROTM mode at the time the hang happened.
> +
> panthor_sched_suspend(ptdev);
>
> /* Stop all groups that might still accept jobs, so we don't get passed
Regards,
Boris
^ permalink raw reply
* [PATCH v3] wifi: mt76: mt792x: fix use-after-free in mt76_rx_poll_complete
From: JB Tsai @ 2026-05-06 8:43 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, eason.lai, jb.tsai, Eason Lai
From: Eason Lai <Eason.Lai@mediatek.com>
A use-after-free issue occurs in mt76_rx_poll_complete due to a race
condition. The STA has already been removed, but the rx_status still
had a pointer to the wcid in the STA.
Use wcid_idx instead of storing the wcid pointer, and look up the wcid
via rcu_dereference() by wcid_idx.
BUG: KASAN: invalid-access in mt76_rx_poll_complete+0x280/0x470 [mt76]
Call trace:
dump_backtrace+0xec/0x128
show_stack+0x18/0x28
dump_stack_lvl+0x40/0xc8
print_report+0x1b8/0x710
kasan_report+0xe0/0x144
do_bad_area+0x120/0x260
do_tag_check_fault+0x20/0x34
do_mem_abort+0x54/0xa8
el1_abort+0x3c/0x5c
el1h_64_sync_handler+0x40/0xcc
el1h_64_sync+0x7c/0x80
mt76_rx_poll_complete+0x280/0x470
mt76_dma_rx_poll+0x114/0x51c
mt792x_poll_rx+0x60/0xf8
napi_threaded_poll_loop+0xe0/0x450
napi_threaded_poll+0x80/0x9c
kthread+0x11c/0x158
ret_from_fork+0x10/0x20
Fixes: 9c68a57bc22d ("mt76: get station pointer by wcid and pass it to mac80211")
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
v2: fix mt76x02 build errors
v3: fix mt76x02 build error due to variable set but not used
---
drivers/net/wireless/mediatek/mt76/agg-rx.c | 18 ++++---
drivers/net/wireless/mediatek/mt76/mac80211.c | 47 ++++++++-----------
drivers/net/wireless/mediatek/mt76/mt76.h | 9 ++--
.../net/wireless/mediatek/mt76/mt7603/mac.c | 6 ++-
.../net/wireless/mediatek/mt76/mt7615/mac.c | 19 +++++---
.../net/wireless/mediatek/mt76/mt76_connac.h | 3 +-
.../wireless/mediatek/mt76/mt76_connac_mac.c | 12 ++++-
.../net/wireless/mediatek/mt76/mt76x02_mac.c | 9 ++--
.../net/wireless/mediatek/mt76/mt7915/mac.c | 16 +++++--
.../net/wireless/mediatek/mt76/mt7921/mac.c | 13 +++--
.../net/wireless/mediatek/mt76/mt7925/mac.c | 19 +++++---
.../net/wireless/mediatek/mt76/mt7996/mac.c | 29 ++++++++----
12 files changed, 118 insertions(+), 82 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/agg-rx.c b/drivers/net/wireless/mediatek/mt76/agg-rx.c
index bf1babac3895..b72d13975466 100644
--- a/drivers/net/wireless/mediatek/mt76/agg-rx.c
+++ b/drivers/net/wireless/mediatek/mt76/agg-rx.c
@@ -116,14 +116,15 @@ mt76_rx_aggr_reorder_work(struct work_struct *work)
}
static void
-mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
+mt76_rx_aggr_check_ctl(struct mt76_dev *dev, struct sk_buff *skb,
+ struct sk_buff_head *frames)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ieee80211_bar *bar = mt76_skb_get_hdr(skb);
- struct mt76_wcid *wcid = status->wcid;
struct mt76_rx_tid *tid;
- u8 tidno;
+ struct mt76_wcid *wcid;
u16 seqno;
+ u8 tidno;
if (!ieee80211_is_ctl(bar->frame_control))
return;
@@ -131,6 +132,10 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
if (!ieee80211_is_back_req(bar->frame_control))
return;
+ wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+ if (!wcid)
+ return;
+
status->qos_ctl = tidno = le16_to_cpu(bar->control) >> 12;
seqno = IEEE80211_SEQ_TO_SN(le16_to_cpu(bar->start_seq_num));
tid = rcu_dereference(wcid->aggr[tidno]);
@@ -145,10 +150,11 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
spin_unlock_bh(&tid->lock);
}
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+ struct sk_buff_head *frames)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
struct ieee80211_sta *sta;
struct mt76_rx_tid *tid;
bool sn_less;
@@ -164,7 +170,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
if (!status->aggr) {
if (!(status->flag & RX_FLAG_8023))
- mt76_rx_aggr_check_ctl(skb, frames);
+ mt76_rx_aggr_check_ctl(dev, skb, frames);
return;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 4ae5e4715a9c..2933b29782d3 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1283,21 +1283,24 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
memcpy(status->chain_signal, mstat.chain_signal,
sizeof(mstat.chain_signal));
- if (mstat.wcid) {
- status->link_valid = mstat.wcid->link_valid;
- status->link_id = mstat.wcid->link_id;
- }
+ if (mstat.wcid_idx != MT76_WCID_IDX_INVALID) {
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, mstat.wcid_idx);
- *sta = wcid_to_sta(mstat.wcid);
+ if (wcid) {
+ status->link_valid = wcid->link_valid;
+ status->link_id = wcid->link_id;
+ *sta = wcid_to_sta(wcid);
+ }
+ }
*hw = mt76_phy_hw(dev, mstat.phy_idx);
}
static void
-mt76_check_ccmp_pn(struct sk_buff *skb)
+mt76_check_ccmp_pn(struct mt76_dev *dev, struct sk_buff *skb)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
- struct mt76_wcid *wcid = status->wcid;
struct ieee80211_hdr *hdr;
+ struct mt76_wcid *wcid;
int security_idx;
int ret;
@@ -1307,6 +1310,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb)
if (status->flag & RX_FLAG_ONLY_MONITOR)
return;
+ wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
if (!wcid || !wcid->rx_check_pn)
return;
@@ -1354,7 +1358,7 @@ static void
mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
int len)
{
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
struct ieee80211_rx_status info = {
.enc_flags = status->enc_flags,
.rate_idx = status->rate_idx,
@@ -1382,19 +1386,9 @@ mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
static void
mt76_airtime_flush_ampdu(struct mt76_dev *dev)
{
- struct mt76_wcid *wcid;
- int wcid_idx;
-
if (!dev->rx_ampdu_len)
return;
- wcid_idx = dev->rx_ampdu_status.wcid_idx;
- if (wcid_idx < ARRAY_SIZE(dev->wcid))
- wcid = rcu_dereference(dev->wcid[wcid_idx]);
- else
- wcid = NULL;
- dev->rx_ampdu_status.wcid = wcid;
-
mt76_airtime_report(dev, &dev->rx_ampdu_status, dev->rx_ampdu_len);
dev->rx_ampdu_len = 0;
@@ -1405,7 +1399,7 @@ static void
mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
if (!(dev->drv->drv_flags & MT_DRV_SW_RX_AIRTIME))
return;
@@ -1418,8 +1412,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
if (!ether_addr_equal(hdr->addr1, dev->phy.macaddr))
return;
-
- wcid = NULL;
}
if (!(status->flag & RX_FLAG_AMPDU_DETAILS) ||
@@ -1430,7 +1422,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
if (!dev->rx_ampdu_len ||
status->ampdu_ref != dev->rx_ampdu_ref) {
dev->rx_ampdu_status = *status;
- dev->rx_ampdu_status.wcid_idx = wcid ? wcid->idx : 0xff;
dev->rx_ampdu_ref = status->ampdu_ref;
}
@@ -1448,7 +1439,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
struct ieee80211_sta *sta;
struct ieee80211_hw *hw;
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK;
bool ps;
@@ -1456,8 +1447,10 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
if (ieee80211_is_pspoll(hdr->frame_control) && !wcid &&
!(status->flag & RX_FLAG_8023)) {
sta = ieee80211_find_sta_by_ifaddr(hw, hdr->addr2, NULL);
- if (sta)
- wcid = status->wcid = (struct mt76_wcid *)sta->drv_priv;
+ if (sta) {
+ wcid = (struct mt76_wcid *)sta->drv_priv;
+ status->wcid_idx = wcid->idx;
+ }
}
mt76_airtime_check(dev, skb);
@@ -1521,7 +1514,7 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
while ((skb = __skb_dequeue(frames)) != NULL) {
struct sk_buff *nskb = skb_shinfo(skb)->frag_list;
- mt76_check_ccmp_pn(skb);
+ mt76_check_ccmp_pn(dev, skb);
skb_shinfo(skb)->frag_list = NULL;
mt76_rx_convert(dev, skb, &hw, &sta);
ieee80211_rx_list(hw, sta, skb, &list);
@@ -1563,7 +1556,7 @@ void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
mt76_npu_device_active(dev))
__skb_queue_tail(&frames, skb);
else
- mt76_rx_aggr_reorder(skb, &frames);
+ mt76_rx_aggr_reorder(dev, skb, &frames);
}
mt76_rx_complete(dev, &frames, napi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..1e1bd410a7a7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -364,6 +364,7 @@ enum mt76_wcid_flags {
};
#define MT76_N_WCIDS 1088
+#define MT76_WCID_IDX_INVALID 0xffff
#define MT76_BEACON_MON_MAX_MISS 7
/* stored in ieee80211_tx_info::hw_queue */
@@ -728,10 +729,7 @@ struct mt76_mmio {
};
struct mt76_rx_status {
- union {
- struct mt76_wcid *wcid;
- u16 wcid_idx;
- };
+ u16 wcid_idx;
u32 reorder_time;
@@ -1793,7 +1791,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
struct napi_struct *napi);
void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
struct napi_struct *napi);
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames);
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+ struct sk_buff_head *frames);
void mt76_testmode_tx_pending(struct mt76_phy *phy);
void mt76_queue_tx_complete(struct mt76_dev *dev, struct mt76_queue *q,
struct mt76_queue_entry *e);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
index d3110eeb45d7..aeb890df808e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
@@ -513,6 +513,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
u32 rxd2 = le32_to_cpu(rxd[2]);
bool unicast = rxd1 & MT_RXD1_NORMAL_U2M;
bool insert_ccmp_hdr = false;
+ struct mt76_wcid *wcid;
bool remove_pad;
int idx;
int i;
@@ -524,7 +525,8 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
i >>= 1;
idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
- status->wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+ wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
status->band = sband->band;
if (i < sband->n_channels)
@@ -672,7 +674,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
}
hdr = (struct ieee80211_hdr *)skb->data;
- if (!status->wcid || !ieee80211_is_data_qos(hdr->frame_control))
+ if (!wcid || !ieee80211_is_data_qos(hdr->frame_control))
return 0;
status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
index ce0051468501..84be00a13930 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
@@ -246,17 +246,20 @@ static void mt7615_mac_fill_tm_rx(struct mt7615_phy *phy, __le32 *rxv)
}
/* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7615_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7615_reverse_frag0_hdr_trans(struct mt7615_dev *dev,
+ struct sk_buff *skb, u16 hdr_gap)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
- struct mt7615_sta *msta = (struct mt7615_sta *)status->wcid;
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_vif *vif;
struct ieee80211_hdr hdr;
+ struct mt7615_sta *msta;
u16 frame_control;
+ msta = (struct mt7615_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
if (le32_get_bits(rxd[1], MT_RXD1_NORMAL_ADDR_TYPE) !=
MT_RXD1_NORMAL_U2M)
return -EINVAL;
@@ -335,6 +338,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
struct ieee80211_supported_band *sband;
struct ieee80211_hdr *hdr;
struct mt7615_phy *phy2;
+ struct mt76_wcid *wcid;
__le32 *rxd = (__le32 *)skb->data;
u32 rxd0 = le32_to_cpu(rxd[0]);
u32 rxd1 = le32_to_cpu(rxd[1]);
@@ -378,12 +382,13 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
unicast = (rxd1 & MT_RXD1_NORMAL_ADDR_TYPE) == MT_RXD1_NORMAL_U2M;
idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
- status->wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+ wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
- if (status->wcid) {
+ if (wcid) {
struct mt7615_sta *msta;
- msta = container_of(status->wcid, struct mt7615_sta, wcid);
+ msta = container_of(wcid, struct mt7615_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
}
@@ -590,7 +595,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
if (hdr_trans && ieee80211_has_morefrags(fc)) {
- if (mt7615_reverse_frag0_hdr_trans(skb, hdr_gap))
+ if (mt7615_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
return -EINVAL;
hdr_trans = false;
} else {
@@ -638,7 +643,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
status->flag |= RX_FLAG_8023;
}
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (!wcid || !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 51423c7740bd..d9ae8d204442 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -448,7 +448,8 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
struct sk_buff *skb,
__le32 *rxv, u32 mode);
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+ struct ieee80211_vif *vif,
struct sk_buff *skb, u16 hdr_offset);
int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
struct mt76_rx_status *status,
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index 0339e2e7ab60..376d59d517cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -952,7 +952,8 @@ void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
EXPORT_SYMBOL_GPL(mt76_connac2_mac_decode_he_radiotap);
/* The HW does not translate the mac header to 802.3 for mesh point */
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+ struct ieee80211_vif *vif,
struct sk_buff *skb, u16 hdr_offset)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
@@ -960,6 +961,7 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_hdr hdr;
+ struct mt76_wcid *wcid;
u16 frame_control;
if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -969,7 +971,13 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
return -EINVAL;
- sta = container_of((void *)status->wcid, struct ieee80211_sta, drv_priv);
+ wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+ if (!wcid)
+ return -EINVAL;
+
+ sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
+ if (!sta)
+ return -EINVAL;
/* store the info from RXD and ethhdr to avoid being overridden */
frame_control = le32_get_bits(rxd[6], MT_RXD6_FRAME_CONTROL);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
index 14ee5b3b94d3..0bcc3417ae6a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
@@ -774,16 +774,14 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ieee80211_hdr *hdr;
struct mt76x02_rxwi *rxwi = rxi;
- struct mt76x02_sta *sta;
u32 rxinfo = le32_to_cpu(rxwi->rxinfo);
u32 ctl = le32_to_cpu(rxwi->ctl);
u16 rate = le16_to_cpu(rxwi->rate);
u16 tid_sn = le16_to_cpu(rxwi->tid_sn);
- bool unicast = rxwi->rxinfo & cpu_to_le32(MT_RXINFO_UNICAST);
int pad_len = 0, nstreams = dev->mphy.chainmask & 0xf;
+ u8 wcid_idx;
s8 signal;
u8 pn_len;
- u8 wcid;
int len;
if (!test_bit(MT76_STATE_RUNNING, &dev->mphy.state))
@@ -799,9 +797,8 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
status->flag |= RX_FLAG_IV_STRIPPED;
}
- wcid = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
- sta = mt76x02_rx_get_sta(&dev->mt76, wcid);
- status->wcid = mt76x02_rx_get_sta_wcid(sta, unicast);
+ wcid_idx = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
+ status->wcid_idx = wcid_idx;
len = FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl);
pn_len = FIELD_GET(MT_RXINFO_PN_LEN, rxinfo);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index cec2c4208255..30672713323f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -280,6 +280,7 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
u8 mode = 0, qos_ctl = 0;
struct mt7915_sta *msta = NULL;
u32 csum_status = *(u32 *)skb->cb;
+ struct mt76_wcid *wcid;
bool hdr_trans;
u16 hdr_gap;
u16 seq_ctrl = 0;
@@ -313,10 +314,13 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt7915_rx_get_wcid(dev, idx, unicast);
- if (status->wcid) {
- msta = container_of(status->wcid, struct mt7915_sta, wcid);
+ wcid = mt7915_rx_get_wcid(dev, idx, unicast);
+
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+ if (wcid) {
+ msta = container_of(wcid, struct mt7915_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
}
@@ -475,7 +479,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
vif = container_of((void *)msta->vif, struct ieee80211_vif,
drv_priv);
- err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+ err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+ skb, hdr_gap);
if (err)
return err;
@@ -532,7 +537,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (status->wcid_idx == MT76_WCID_IDX_INVALID ||
+ !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..59c64b951678 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -184,6 +184,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
u32 rxd4 = le32_to_cpu(rxd[4]);
struct mt792x_sta *msta = NULL;
struct mt792x_link_sta *mlink;
+ struct mt76_wcid *wcid;
u16 seq_ctrl = 0;
__le16 fc = 0;
u8 mode = 0;
@@ -211,10 +212,11 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
- if (status->wcid) {
- mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+ if (wcid) {
+ mlink = container_of(wcid, struct mt792x_link_sta, wcid);
msta = container_of(mlink, struct mt792x_sta, deflink);
mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
}
@@ -395,7 +397,8 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
vif = container_of((void *)msta->vif, struct ieee80211_vif,
drv_priv);
- err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+ err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+ skb, hdr_gap);
if (err)
return err;
@@ -433,7 +436,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (!wcid || !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index c47bd812b66b..e429f78596e2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -166,17 +166,20 @@ void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev,
}
/* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7925_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7925_reverse_frag0_hdr_trans(struct mt792x_dev *dev,
+ struct sk_buff *skb, u16 hdr_gap)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
- struct mt792x_sta *msta = (struct mt792x_sta *)status->wcid;
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_vif *vif;
struct ieee80211_hdr hdr;
+ struct mt792x_sta *msta;
u16 frame_control;
+ msta = (struct mt792x_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
MT_RXD3_NORMAL_U2M)
return -EINVAL;
@@ -368,6 +371,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
u32 rxd3 = le32_to_cpu(rxd[3]);
u32 rxd4 = le32_to_cpu(rxd[4]);
struct mt792x_link_sta *mlink;
+ struct mt76_wcid *wcid;
u8 mode = 0; /* , band_idx; */
u16 seq_ctrl = 0;
__le16 fc = 0;
@@ -392,10 +396,11 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
- if (status->wcid) {
- mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+ if (wcid) {
+ mlink = container_of(wcid, struct mt792x_link_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
}
@@ -545,7 +550,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
if (hdr_trans && ieee80211_has_morefrags(fc)) {
- if (mt7925_reverse_frag0_hdr_trans(skb, hdr_gap))
+ if (mt7925_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
return -EINVAL;
hdr_trans = false;
} else {
@@ -608,7 +613,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
}
}
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (!wcid || !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e2a83da3a09c..98c30957dd76 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -186,17 +186,19 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
}
/* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7996_reverse_frag0_hdr_trans(struct mt7996_dev *dev,
+ struct sk_buff *skb, u16 hdr_gap)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
- struct mt7996_sta_link *msta_link = (void *)status->wcid;
- struct mt7996_sta *msta = msta_link->sta;
+ struct mt76_wcid *wcid = mt76_wcid_ptr(dev, status->wcid_idx);
+ struct mt7996_sta_link *msta_link;
struct ieee80211_bss_conf *link_conf;
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_vif *vif;
struct ieee80211_hdr hdr;
+ struct mt7996_sta *msta;
u16 frame_control;
if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -206,10 +208,16 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
return -EINVAL;
+ if (!wcid)
+ return -EINVAL;
+
+ msta_link = container_of(wcid, struct mt7996_sta_link, wcid);
+ msta = msta_link->sta;
+
if (!msta || !msta->vif)
return -EINVAL;
- sta = wcid_to_sta(status->wcid);
+ sta = wcid_to_sta(wcid);
vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
link_conf = rcu_dereference(vif->link_conf[msta_link->wcid.link_id]);
if (!link_conf)
@@ -429,6 +437,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
bool unicast, insert_ccmp_hdr = false;
u8 remove_pad, amsdu_info, band_idx;
u8 mode = 0, qos_ctl = 0;
+ struct mt76_wcid *wcid;
bool hdr_trans;
u16 hdr_gap;
u16 seq_ctrl = 0;
@@ -461,12 +470,14 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
- if (status->wcid) {
+ wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+ if (wcid) {
struct mt7996_sta_link *msta_link;
- msta_link = container_of(status->wcid, struct mt7996_sta_link,
+ msta_link = container_of(wcid, struct mt7996_sta_link,
wcid);
msta = msta_link->sta;
mt76_wcid_add_poll(&dev->mt76, &msta_link->wcid);
@@ -620,7 +631,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
if (hdr_trans && ieee80211_has_morefrags(fc)) {
- if (mt7996_reverse_frag0_hdr_trans(skb, hdr_gap))
+ if (mt7996_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
return -EINVAL;
hdr_trans = false;
} else {
@@ -697,7 +708,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
}
}
- if (!status->wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
+ if (!wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
return 0;
status->aggr = unicast &&
--
2.45.2
^ permalink raw reply related
* [PATCH 4/4] media: mediatek: vcodec: free working buf in vdec_vp9_slice_setup_single()
From: Haoxiang Li @ 2026-05-06 8:42 UTC (permalink / raw)
To: tiffany.lin, andrew-ct.chen, yunfei.dong, mchehab, matthias.bgg,
angelogioacchino.delregno, hverkuil+cisco, laurent.pinchart,
p.zabel, benjamin.gaignard, nicolas
Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
Haoxiang Li, stable
In-Reply-To: <20260506084203.202882-1-lihaoxiang@isrc.iscas.ac.cn>
Add an error path label in vdec_vp9_slice_setup_single()
and call vdec_vp9_slice_free_working_buffer() to free
working buffer.
Fixes: b0f407c19648 ("media: mediatek: vcodec: add vp9 decoder driver for mt8186")
Cc: stable@vger.kernel.org
Signed-off-by: Haoxiang Li <lihaoxiang@isrc.iscas.ac.cn>
---
.../mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
index 673b17d0dd8d..1dd0f7fd642f 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
@@ -1806,10 +1806,12 @@ static int vdec_vp9_slice_setup_single(struct vdec_vp9_slice_instance *instance,
ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs);
if (ret)
- goto err;
+ goto alloc_err;
return 0;
+alloc_err:
+ vdec_vp9_slice_free_working_buffer(instance);
err:
return ret;
}
--
2.25.1
^ permalink raw reply related
* [PATCH 1/4] media: mediatek: vcodec: remove redundant return value of vdec_vp9_slice_setup_lat_buffer()
From: Haoxiang Li @ 2026-05-06 8:42 UTC (permalink / raw)
To: tiffany.lin, andrew-ct.chen, yunfei.dong, mchehab, matthias.bgg,
angelogioacchino.delregno, hverkuil+cisco, laurent.pinchart,
p.zabel, benjamin.gaignard, nicolas
Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
Haoxiang Li
In-Reply-To: <20260506084203.202882-1-lihaoxiang@isrc.iscas.ac.cn>
vdec_vp9_slice_setup_lat_buffer() never fails, remove the 'return 0' from
vdec_vp9_slice_setup_lat_buffer() and modify its signature.
Also, remove the return value check in vdec_vp9_slice_setup_lat().
Signed-off-by: Haoxiang Li <lihaoxiang@isrc.iscas.ac.cn>
---
.../mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c | 8 ++------
1 file changed, 2 insertions(+), 6 deletions(-)
diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
index cd1935014d76..092fd34e21d2 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
@@ -974,7 +974,7 @@ static int vdec_vp9_slice_setup_pfc(struct vdec_vp9_slice_instance *instance,
return 0;
}
-static int vdec_vp9_slice_setup_lat_buffer(struct vdec_vp9_slice_instance *instance,
+static void vdec_vp9_slice_setup_lat_buffer(struct vdec_vp9_slice_instance *instance,
struct vdec_vp9_slice_vsi *vsi,
struct mtk_vcodec_mem *bs,
struct vdec_lat_buf *lat_buf)
@@ -1011,8 +1011,6 @@ static int vdec_vp9_slice_setup_lat_buffer(struct vdec_vp9_slice_instance *insta
vsi->row_info.buf = 0;
vsi->row_info.size = 0;
-
- return 0;
}
static int vdec_vp9_slice_setup_prob_buffer(struct vdec_vp9_slice_instance *instance,
@@ -1166,9 +1164,7 @@ static int vdec_vp9_slice_setup_lat(struct vdec_vp9_slice_instance *instance,
if (ret)
goto err;
- ret = vdec_vp9_slice_setup_lat_buffer(instance, vsi, bs, lat_buf);
- if (ret)
- goto err;
+ vdec_vp9_slice_setup_lat_buffer(instance, vsi, bs, lat_buf);
vdec_vp9_slice_setup_seg_buffer(instance, vsi, &instance->seg[0]);
--
2.25.1
^ permalink raw reply related
* [PATCH 3/4] media: mediatek: vcodec: free working buf on error path in vdec_vp9_slice_setup_lat()
From: Haoxiang Li @ 2026-05-06 8:42 UTC (permalink / raw)
To: tiffany.lin, andrew-ct.chen, yunfei.dong, mchehab, matthias.bgg,
angelogioacchino.delregno, hverkuil+cisco, laurent.pinchart,
p.zabel, benjamin.gaignard, nicolas
Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
Haoxiang Li, stable
In-Reply-To: <20260506084203.202882-1-lihaoxiang@isrc.iscas.ac.cn>
Add an error path label in vdec_vp9_slice_setup_lat()
and call vdec_vp9_slice_free_working_buffer() to free
working buffer to prevent potential memory leak.
Fixes: 5d418351ca8f ("media: mediatek: vcodec: support stateless VP9 decoding")
Cc: stable@vger.kernel.org
Signed-off-by: Haoxiang Li <lihaoxiang@isrc.iscas.ac.cn>
---
.../mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
index 0f47b2671353..673b17d0dd8d 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
@@ -1172,10 +1172,12 @@ static int vdec_vp9_slice_setup_lat(struct vdec_vp9_slice_instance *instance,
ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs);
if (ret)
- goto err;
+ goto alloc_err;
return 0;
+alloc_err:
+ vdec_vp9_slice_free_working_buffer(instance);
err:
return ret;
}
--
2.25.1
^ permalink raw reply related
* [PATCH 2/4] media: mediatek: vcodec: remove redundant return value of vdec_vp9_slice_setup_prob_buffer()
From: Haoxiang Li @ 2026-05-06 8:42 UTC (permalink / raw)
To: tiffany.lin, andrew-ct.chen, yunfei.dong, mchehab, matthias.bgg,
angelogioacchino.delregno, hverkuil+cisco, laurent.pinchart,
p.zabel, benjamin.gaignard, nicolas
Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
Haoxiang Li
In-Reply-To: <20260506084203.202882-1-lihaoxiang@isrc.iscas.ac.cn>
vdec_vp9_slice_setup_prob_buffer() never fails, remove 'return 0' from
vdec_vp9_slice_setup_prob_buffer() and modify its signature.
Also, remove the return value check in vdec_vp9_slice_setup_single()
and vdec_vp9_slice_setup_lat().
Signed-off-by: Haoxiang Li <lihaoxiang@isrc.iscas.ac.cn>
---
.../vcodec/decoder/vdec/vdec_vp9_req_lat_if.c | 13 +++----------
1 file changed, 3 insertions(+), 10 deletions(-)
diff --git a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
index 092fd34e21d2..0f47b2671353 100644
--- a/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
+++ b/drivers/media/platform/mediatek/vcodec/decoder/vdec/vdec_vp9_req_lat_if.c
@@ -1013,7 +1013,7 @@ static void vdec_vp9_slice_setup_lat_buffer(struct vdec_vp9_slice_instance *inst
vsi->row_info.size = 0;
}
-static int vdec_vp9_slice_setup_prob_buffer(struct vdec_vp9_slice_instance *instance,
+static void vdec_vp9_slice_setup_prob_buffer(struct vdec_vp9_slice_instance *instance,
struct vdec_vp9_slice_vsi *vsi)
{
struct vdec_vp9_slice_frame_ctx *frame_ctx;
@@ -1030,8 +1030,6 @@ static int vdec_vp9_slice_setup_prob_buffer(struct vdec_vp9_slice_instance *inst
else
frame_ctx = vdec_vp9_slice_default_frame_ctx;
memcpy(instance->prob.va, frame_ctx, sizeof(*frame_ctx));
-
- return 0;
}
static void vdec_vp9_slice_setup_seg_buffer(struct vdec_vp9_slice_instance *instance,
@@ -1170,9 +1168,7 @@ static int vdec_vp9_slice_setup_lat(struct vdec_vp9_slice_instance *instance,
/* setup prob/tile buffers for LAT */
- ret = vdec_vp9_slice_setup_prob_buffer(instance, vsi);
- if (ret)
- goto err;
+ vdec_vp9_slice_setup_prob_buffer(instance, vsi);
ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs);
if (ret)
@@ -1804,10 +1800,7 @@ static int vdec_vp9_slice_setup_single(struct vdec_vp9_slice_instance *instance,
vdec_vp9_slice_setup_single_buffer(instance, pfc, vsi, bs, fb);
vdec_vp9_slice_setup_seg_buffer(instance, vsi, &instance->seg[0]);
-
- ret = vdec_vp9_slice_setup_prob_buffer(instance, vsi);
- if (ret)
- goto err;
+ vdec_vp9_slice_setup_prob_buffer(instance, vsi);
ret = vdec_vp9_slice_setup_tile_buffer(instance, vsi, bs);
if (ret)
--
2.25.1
^ permalink raw reply related
* [PATCH 0/4] media: mediatek: vcodec: VP9 slice setup cleanups and fixes
From: Haoxiang Li @ 2026-05-06 8:41 UTC (permalink / raw)
To: tiffany.lin, andrew-ct.chen, yunfei.dong, mchehab, matthias.bgg,
angelogioacchino.delregno, hverkuil+cisco, laurent.pinchart,
p.zabel, benjamin.gaignard, nicolas
Cc: linux-media, linux-kernel, linux-arm-kernel, linux-mediatek,
Haoxiang Li
Patches 1 and 2 change the signatures of two functions to void, removing
the now-unnecessary return value and its associated dead error checks.
Patches 3 and 4 add missing memory release operations in error paths,
fixing resource leaks.
These patches are carried out under the guidance of Nicolas Dufresne.
Thanks, Nicolas!
Haoxiang Li (4):
media: mediatek: vcodec: remove redundant return value of
vdec_vp9_slice_setup_lat_buffer()
media: mediatek: vcodec: remove redundant return value of
vdec_vp9_slice_setup_prob_buffer()
media: mediatek: vcodec: free working buf on error path in
vdec_vp9_slice_setup_lat()
media: mediatek: vcodec: free working buf in
vdec_vp9_slice_setup_single()
.../vcodec/decoder/vdec/vdec_vp9_req_lat_if.c | 29 +++++++------------
1 file changed, 11 insertions(+), 18 deletions(-)
--
2.25.1
^ permalink raw reply
* Re: [PATCH 13/13] MAINTAINERS: add mt6323 drivers maintainer
From: Krzysztof Kozlowski @ 2026-05-06 8:26 UTC (permalink / raw)
To: Roman Vivchar
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Srinivas Kandagatla,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Lee Jones, linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-pm, Ben Grisdale
In-Reply-To: <20260504-mt6323-v1-13-799b58b355ff@protonmail.com>
On Mon, May 04, 2026 at 09:25:05PM +0300, Roman Vivchar wrote:
> Add myself as MediaTek mt6323 AUXADC, EFUSE and thermal driver maintainer.
>
> Signed-off-by: Roman Vivchar <rva333@protonmail.com>
> ---
> MAINTAINERS | 18 ++++++++++++++++++
> 1 file changed, 18 insertions(+)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index d1cc0e12fe1f..53493a24d00a 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16337,6 +16337,18 @@ S: Orphan
> F: Documentation/devicetree/bindings/mtd/mediatek,mtk-nfc.yaml
> F: drivers/mtd/nand/raw/mtk_*
>
> +MEDIATEK PMIC AUXADC DRIVER
> +M: Roman Vivchar <rva333@protonmail.com>
> +S: Maintained
> +F: Documentation/devicetree/bindings/iio/adc/mediatek,mt6323-auxadc.yaml
> +F: drivers/iio/adc/mt6323-auxadc.c
This should be squashed with individual patches. Otherwise how do you
imagine this being applied? By which maintainer?
You also should not merge independent patchsets into one patchset. I see
no dependencies here at all, although if you fix your MFD binding then
there would be a dependency...
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 11/13] ARM: dts: mediatek: mt6323: add support for EFUSE
From: Krzysztof Kozlowski @ 2026-05-06 7:59 UTC (permalink / raw)
To: rva333, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Srinivas Kandagatla,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Lee Jones
Cc: linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-pm, Ben Grisdale
In-Reply-To: <20260504-mt6323-v1-11-799b58b355ff@protonmail.com>
On 04/05/2026 20:25, Roman Vivchar via B4 Relay wrote:
> diff --git a/arch/arm/boot/dts/mediatek/mt6323.dtsi b/arch/arm/boot/dts/mediatek/mt6323.dtsi
> index c070f4b0936c..3fe6a5e35e70 100644
> --- a/arch/arm/boot/dts/mediatek/mt6323.dtsi
> +++ b/arch/arm/boot/dts/mediatek/mt6323.dtsi
> @@ -19,6 +19,12 @@ mt6323_adc: adc {
> #io-channel-cells = <1>;
> };
>
> + mt6323_efuse: efuse {
> + compatible = "mediatek,mt6323-efuse";
You clearly did not checked the DTS with tools. This will fail, because
mt6323 schema does not allow such nodes.
All bindings patch are incomplete or oddly organized. You need to
provide AND TEST complete picture.
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 02/13] dt-bindings: nvmem: add mt6323 PMIC EFUSE
From: Krzysztof Kozlowski @ 2026-05-06 7:57 UTC (permalink / raw)
To: rva333, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Srinivas Kandagatla,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Lee Jones
Cc: linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-pm, Ben Grisdale
In-Reply-To: <20260504-mt6323-v1-2-799b58b355ff@protonmail.com>
On 04/05/2026 20:24, Roman Vivchar via B4 Relay wrote:
> From: Roman Vivchar <rva333@protonmail.com>
>
> The MediaTek mt6323 PMIC includes an EFUSE used for storing calibration
> data.
>
> Add the device tree binding documentation for the MediaTek mt6323 EFUSE.
>
> Tested-by: Ben Grisdale <bengris32@protonmail.ch> # Amazon Echo Dot (2nd Generation)
Drop.
I actually wonder how v1 could even receive such tags.
> Signed-off-by: Roman Vivchar <rva333@protonmail.com>
> ---
> .../bindings/nvmem/mediatek,mt6323-efuse.yaml | 42 ++++++++++++++++++++++
> 1 file changed, 42 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/nvmem/mediatek,mt6323-efuse.yaml b/Documentation/devicetree/bindings/nvmem/mediatek,mt6323-efuse.yaml
> new file mode 100644
> index 000000000000..eb2437b0c1f0
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/nvmem/mediatek,mt6323-efuse.yaml
> @@ -0,0 +1,42 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/nvmem/mediatek,mt6323-efuse.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: MediaTek MT6323 PMIC EFUSE
> +
> +description: |
Do not need '|' unless you need to preserve formatting.
> + MT6323 efuse is used for storing calibration data, such as thermal sensor
> + calibration.
> +
> +maintainers:
> + - Roman Vivchar <rva333@protonmail.com>
> +
> +allOf:
> + - $ref: nvmem.yaml#
> +
> +properties:
> + compatible:
> + const: mediatek,mt6323-efuse
> +
> +required:
> + - compatible
Again, heavily incomplete binding. How do you access the efuse memory if
device has no resources?
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 01/13] dt-bindings: iio: adc: add mt6323 PMIC AUXADC
From: Krzysztof Kozlowski @ 2026-05-06 7:56 UTC (permalink / raw)
To: rva333, Jonathan Cameron, David Lechner, Nuno Sá,
Andy Shevchenko, Rob Herring, Krzysztof Kozlowski, Conor Dooley,
Matthias Brugger, AngeloGioacchino Del Regno, Srinivas Kandagatla,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Lee Jones
Cc: linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-pm, Ben Grisdale
In-Reply-To: <20260504-mt6323-v1-1-799b58b355ff@protonmail.com>
On 04/05/2026 20:24, Roman Vivchar via B4 Relay wrote:
> From: Roman Vivchar <rva333@protonmail.com>
>
> The MediaTek mt6323 PMIC includes an AUXADC used for battery voltage,
> temperature, and other internal measurements.
>
> Add the devicetree binding documentation and the associated header file
> defining the ADC channel constants.
>
> Tested-by: Ben Grisdale <bengris32@protonmail.ch> # Amazon Echo Dot (2nd Generation)
Sorry, but that's not possible or explain please how YAML file can be
tested on a device.
> Signed-off-by: Roman Vivchar <rva333@protonmail.com>
> ---
> .../bindings/iio/adc/mediatek,mt6323-auxadc.yaml | 39 ++++++++++++++++++++++
> .../dt-bindings/iio/adc/mediatek,mt6323-auxadc.h | 16 +++++++++
> 2 files changed, 55 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/iio/adc/mediatek,mt6323-auxadc.yaml b/Documentation/devicetree/bindings/iio/adc/mediatek,mt6323-auxadc.yaml
> new file mode 100644
> index 000000000000..7dc1ac8bf253
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/iio/adc/mediatek,mt6323-auxadc.yaml
> @@ -0,0 +1,39 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/iio/adc/mediatek,mt6323-auxadc.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: MediaTek MT6323 PMIC AUXADC
> +
> +maintainers:
> + - Roman Vivchar <rva333@protonmail.com>
> +
> +description: >
> + The Auxiliary Analog/Digital Converter (AUXADC) is an ADC found
> + in the MediaTek MT6323 PMIC, performing various PMIC related measurements
> + such as battery and PMIC internal voltage regulators temperatures,
> + accessory detection resistance (usually, for a 3.5mm audio jack)
> + other than voltages for various PMIC internal components.
> +
> +properties:
> + compatible:
> + const: mediatek,mt6323-auxadc
> +
> + "#io-channel-cells":
> + const: 1
> +
> +required:
> + - compatible
> + - "#io-channel-cells"
This is heavily incomplete node... or unnecessarily split. Why it cannot
be part of parent binding? Or even parent node?
Best regards,
Krzysztof
^ permalink raw reply
* Re: [PATCH 05/13] nvmem: add mt6323 PMIC EFUSE driver
From: Andy Shevchenko @ 2026-05-06 7:30 UTC (permalink / raw)
To: Roman Vivchar
Cc: Jonathan Cameron, David Lechner, Nuno Sá, Andy Shevchenko,
Rob Herring, Krzysztof Kozlowski, Conor Dooley, Matthias Brugger,
AngeloGioacchino Del Regno, Srinivas Kandagatla,
Rafael J. Wysocki, Daniel Lezcano, Zhang Rui, Lukasz Luba,
Lee Jones, linux-iio, devicetree, linux-kernel, linux-arm-kernel,
linux-mediatek, linux-pm, Ben Grisdale
In-Reply-To: <Bn1O24yJ2aBvKUTpiFUzxnqbuuybRirR73G7R38ghYtwhFvQqWJb3HPgcikEa1otDw6n07nX_g8khZUHXJ_QRA--LrV5yj5Mcg1QzrWt6vA=@protonmail.com>
On Tue, May 05, 2026 at 04:24:41PM +0000, Roman Vivchar wrote:
> On Tuesday, May 5th, 2026 at 11:00 AM, Andy Shevchenko <andriy.shevchenko@intel.com> wrote:
> > On Mon, May 04, 2026 at 09:24:57PM +0300, Roman Vivchar via B4 Relay wrote:
First of all, please drop the pieces you are agree with or not going to comment
on (for whatever reason). It makes easier not miss anything important and saves
reviewer's time on doing that for you.
...
> > for (size_t i = 0; i < bytes; i += sizeof(*buf)) {
> >
> > > + ret = regmap_read(efuse->regmap,
> > > + MT6323_EFUSE_DOUT_BASE + offset + i, &tmp);
> > > + if (ret)
> > > + return ret;
> > > + buf[i / 2] = tmp;
> > > + }
> >
> > + blank line.
> >
> > Isn't this reimplementation of bulk read? Why the latter may not be used?
>
> Yes, but the PWRAP (the regmap which the driver uses) doesn't support read
> callback, only reg_read is implemented in the driver.
> It hits the map->cache_type == REGCACHE_NONE in the regmap_read_raw, and falls
> for the !map->read check, so regmap_bulk_read neither the regmap_read_raw
> can be used.
>
> Technically the PMIC is not continuous address space, but rather non-MMIO FSM,
> so it makes sense to not implement the read callback. I guess the custom
> implementation is fine then?
No objections from me, but please make sure the summary of this is mentioned
in the comment in the code and/or in the commit message.
--
With Best Regards,
Andy Shevchenko
^ permalink raw reply
* [PATCH v3] wifi: mt76: mt7921: Add PCIe AER handler support to prevent system crash
From: JB Tsai @ 2026-05-06 7:04 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, eason.lai, jb.tsai, Eason Lai,
Sean Wang, Jeff Hsu, Michael Lo
From: Eason Lai <Eason.Lai@mediatek.com>
When an AER error occurs and the bus is hung, the register reads return
0xFFFFFFFF, causing the DMA queue state to be corrupted and resulting in
an invalid memory access when accessing q->desc[] or q->entry[].
Unable to handle kernel paging request at virtual address
ffffffc01099eac0
pc : mt76_dma_add_buf+0x124/0x188 [mt76]
lr : mt76_dma_rx_fill+0x11c/0x1d8 [mt76]
sp : ffffffc016d9bbf0
x29: ffffffc016d9bc10 x28: 0000000000000000
x27: 0000000000000000 x26: ffffffb7855e50b8
x25: ffffffb80d04f000 x24: 0000000000000000
x23: 0000000000000ec0 x22: ffffffb796803648
x21: ffffffb796801f80 x20: ffffffb7968035f8
x19: 0000000000000ec0 x18: 0000000000000000
x17: 000000004ec00000 x16: 000000000ec00000
x15: ffffffc01099eac0 x14: 000000004ec00000
x13: 00000000ffc5a000 x12: ffffffc016d9bc32
x11: 00000000ffffffff x10: 0000000000000002
x9 : 0000000000000000 x8 : 000000000000b4ac
x7 : 0000000000000a20 x6 : ffffffb6c1806400
x5 : 0000000000000000 x4 : ffffffb80d04f000
x3 : 0000000000000000 x2 : 0000000000000001
x1 : 000000000ec04000 x0 : ffffffb7968035f8
Call trace:
mt76_dma_add_buf+0x124/0x188 [mt76 (HASH:1029 4)]
mt76_dma_rx_reset+0xe8/0xfc [mt76 (HASH:1029 4)]
mt7921_wpdma_reset+0x188/0x1b0 [mt7921e (HASH:ee48 5)]
mt7921e_mac_reset+0x128/0x418 [mt7921e (HASH:ee48 5)]
mt7921_mac_reset_work+0xac/0x1a8 [mt7921_common (HASH:f721 6)]
process_one_work+0x188/0x514
worker_thread+0x12c/0x300
kthread+0x140/0x1fc
ret_from_fork+0x10/0x30
Fix this by checking the bus_hung flag in mt7921_mac_reset_work() before
attempting the reset sequence, and by installing no-op bus operations
when an unrecoverable AER error is detected, preventing further invalid
hardware accesses.
Due to hardware limitations - such as the lack of a connected hardware
reset pin or the absence of host re-probe functionality - affected Wi-Fi
devices may not fully recover to a normal operational state after
certain errors, even with AER enabled.
Fixes: 17f1de56df05 ("mt76: add common code shared between multiple chipsets")
Co-developed-by: Sean Wang <sean.wang@mediatek.com>
Signed-off-by: Sean Wang <sean.wang@mediatek.com>
Co-developed-by: Jeff Hsu <jeff.hsu@mediatek.com>
Signed-off-by: Jeff Hsu <jeff.hsu@mediatek.com>
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
Co-developed-by: Michael Lo <michael.lo@mediatek.com>
---
v2: remove the timestamp of error call trace
v3: fix bus hung and causing invalid access issue
---
drivers/net/wireless/mediatek/mt76/dma.c | 10 +-
drivers/net/wireless/mediatek/mt76/dma.h | 16 ++-
drivers/net/wireless/mediatek/mt76/mcu.c | 12 +-
.../net/wireless/mediatek/mt76/mt76_connac.h | 5 +
.../net/wireless/mediatek/mt76/mt7921/mac.c | 3 +
.../net/wireless/mediatek/mt76/mt7921/pci.c | 103 ++++++++++++++++++
6 files changed, 140 insertions(+), 9 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/dma.c b/drivers/net/wireless/mediatek/mt76/dma.c
index f8c2fe5f2f58..ec84961b11db 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.c
+++ b/drivers/net/wireless/mediatek/mt76/dma.c
@@ -189,6 +189,8 @@ mt76_dma_queue_magic_cnt_init(struct mt76_dev *dev, struct mt76_queue *q)
static void
mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
{
+ int last = 0;
+
if ((q->flags & MT_QFLAG_WED_RRO_EN) &&
(!is_mt7992(dev) || !mt76_npu_device_active(dev)))
Q_WRITE(q, ring_size, MT_DMA_RRO_EN | q->ndesc);
@@ -201,7 +203,9 @@ mt76_dma_sync_idx(struct mt76_dev *dev, struct mt76_queue *q)
}
Q_WRITE(q, desc_base, q->desc_dma);
- q->head = Q_READ(q, dma_idx);
+
+ last = Q_READ(q, dma_idx);
+ q->head = (last >= 0 && last < q->ndesc) ? last : 0;
q->tail = q->head;
}
@@ -625,8 +629,8 @@ mt76_dma_tx_queue_skb_raw(struct mt76_dev *dev, struct mt76_queue *q,
buf.len = skb->len;
spin_lock_bh(&q->lock);
- mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL);
- mt76_dma_kick_queue(dev, q);
+ if (mt76_dma_add_buf(dev, q, &buf, 1, tx_info, skb, NULL) >= 0)
+ mt76_dma_kick_queue(dev, q);
spin_unlock_bh(&q->lock);
return 0;
diff --git a/drivers/net/wireless/mediatek/mt76/dma.h b/drivers/net/wireless/mediatek/mt76/dma.h
index 2a0226c83f3c..7f395a4c509f 100644
--- a/drivers/net/wireless/mediatek/mt76/dma.h
+++ b/drivers/net/wireless/mediatek/mt76/dma.h
@@ -108,8 +108,20 @@
#else
-#define Q_READ(_q, _field) readl(&(_q)->regs->_field)
-#define Q_WRITE(_q, _field, _val) writel(_val, &(_q)->regs->_field)
+#define Q_READ(_q, _field) ({ \
+ u32 _val; \
+ if (unlikely(atomic_read(&(_q)->dev->bus_hung))) \
+ _val = 0; \
+ else \
+ _val = readl(&(_q)->regs->(_field)); \
+ _val; \
+})
+
+#define Q_WRITE(_q, _field, _val) do { \
+ if (unlikely(atomic_read(&(_q)->dev->bus_hung))) \
+ break; \
+ writel(_val, &(_q)->regs->(_field)); \
+} while (0)
#endif
diff --git a/drivers/net/wireless/mediatek/mt76/mcu.c b/drivers/net/wireless/mediatek/mt76/mcu.c
index cbfb3bbec503..7149b2f7aafd 100644
--- a/drivers/net/wireless/mediatek/mt76/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mcu.c
@@ -78,15 +78,19 @@ int mt76_mcu_skb_send_and_get_msg(struct mt76_dev *dev, struct sk_buff *skb,
unsigned long expires;
int ret, seq;
- if (mt76_is_sdio(dev))
- if (test_bit(MT76_RESET, &dev->phy.state) && atomic_read(&dev->bus_hung))
- return -EIO;
-
if (ret_skb)
*ret_skb = NULL;
mutex_lock(&dev->mcu.mutex);
+ if ((mt76_is_mmio(dev) && atomic_read(&dev->bus_hung)) ||
+ (mt76_is_sdio(dev) && test_bit(MT76_RESET, &dev->phy.state) &&
+ atomic_read(&dev->bus_hung))) {
+ orig_skb = skb;
+ ret = -EIO;
+ goto out;
+ }
+
if (dev->mcu_ops->mcu_skb_prepare_msg) {
orig_skb = skb;
ret = dev->mcu_ops->mcu_skb_prepare_msg(dev, skb, cmd, &seq);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 51423c7740bd..17f9c6e65849 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -48,6 +48,11 @@ enum rx_pkt_type {
#define MT_TXD_LEN_MSDU_LAST BIT(14)
#define MT_TXD_LEN_AMSDU_LAST BIT(15)
+/* PCIE part */
+#define PCIE_AER_UNC_STATUS_OFFSET 0x204
+#define PCIE_AER_UNC_MASK_OFFSET 0x208
+#define PCIE_AER_CO_STATUS_OFFSET 0x210
+
enum {
CMD_CBW_20MHZ = IEEE80211_STA_RX_BW_20,
CMD_CBW_40MHZ = IEEE80211_STA_RX_BW_40,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..30f431896b1c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -668,6 +668,9 @@ void mt7921_mac_reset_work(struct work_struct *work)
cancel_work_sync(&pm->wake_work);
for (i = 0; i < 10; i++) {
+ if (atomic_read(&dev->mt76.bus_hung))
+ return;
+
mutex_lock(&dev->mt76.mutex);
ret = mt792x_dev_reset(dev);
mutex_unlock(&dev->mt76.mutex);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
index 7a790ddf43bb..6b9d267a7e7b 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/pci.c
@@ -594,6 +594,108 @@ static int mt7921_pci_resume(struct device *device)
return err;
}
+static u32 mt7921_aer_rr(struct mt76_dev *mdev, u32 offset)
+{
+ return 0;
+}
+
+static void mt7921_aer_wr(struct mt76_dev *mdev, u32 offset, u32 val)
+{
+ ;
+}
+
+static u32 mt791_aer_rmw(struct mt76_dev *mdev, u32 offset, u32 mask, u32 val)
+{
+ return 0;
+}
+
+static const struct mt76_bus_ops mt7921_aer_bus_hung_ops = {
+ .rr = mt7921_aer_rr,
+ .wr = mt7921_aer_wr,
+ .rmw = mt791_aer_rmw,
+ .type = MT76_BUS_MMIO
+};
+
+static void mt7921_pci_set_aer_bus_hung_ops(struct mt792x_dev *dev)
+{
+ if (READ_ONCE(dev->mt76.bus) == &mt7921_aer_bus_hung_ops)
+ return;
+
+ atomic_set(&dev->mt76.bus_hung, true);
+ WRITE_ONCE(dev->mt76.bus, &mt7921_aer_bus_hung_ops);
+}
+
+static pci_ers_result_t mt7921_error_detected(struct pci_dev *pdev,
+ pci_channel_state_t state)
+{
+ struct mt76_dev *mdev = pci_get_drvdata(pdev);
+ struct mt792x_dev *dev = container_of(mdev, struct mt792x_dev, mt76);
+ u32 aer_unc_val = 0, aer_co_val = 0;
+
+ dev_err(mdev->dev, "PCIE error detect state: %d\n", state);
+
+ /* Clear SW IRQ tasklet first */
+ tasklet_kill(&mdev->irq_tasklet);
+
+ if (state == pci_channel_io_perm_failure) {
+ mt7921_pci_set_aer_bus_hung_ops(dev);
+ return PCI_ERS_RESULT_DISCONNECT;
+ }
+
+ pci_read_config_dword(pdev, PCIE_AER_UNC_STATUS_OFFSET, &aer_unc_val);
+ pci_read_config_dword(pdev, PCIE_AER_CO_STATUS_OFFSET, &aer_co_val);
+
+ dev_warn(mdev->dev, "PCIE_AER_UNC_STATUS_OFFSET: 0x%x\n", aer_unc_val);
+ dev_warn(mdev->dev, "PCIE_AER_CO_STATUS_OFFSET: 0x%x\n", aer_co_val);
+
+ /**
+ * Due to this error is from link error and this AER is un-correctable,
+ * so can't covered by device
+ **/
+ if (aer_unc_val != 0) {
+ mt7921_pci_set_aer_bus_hung_ops(dev);
+ return PCI_ERS_RESULT_DISCONNECT;
+ }
+
+ /**
+ * Try to recover it when state is pci_channel_io_frozen or
+ * AER is correctable error
+ **/
+ if (state == pci_channel_io_frozen || aer_co_val != 0) {
+ /* Disable PCIE activity first. */
+ pci_disable_device(pdev);
+ return PCI_ERS_RESULT_NEED_RESET;
+ }
+
+ return PCI_ERS_RESULT_NONE;
+}
+
+static pci_ers_result_t mt7921_slot_reset(struct pci_dev *pdev)
+{
+ struct mt76_dev *mdev = pci_get_drvdata(pdev);
+ int ret = 0;
+
+ ret = pci_enable_device_mem(pdev);
+
+ if (ret) {
+ dev_err(mdev->dev, "pci_enable_device_mem failed: %d\n", ret);
+ return PCI_ERS_RESULT_DISCONNECT;
+ }
+
+ pci_set_master(pdev);
+ pci_restore_state(pdev);
+ pci_save_state(pdev);
+ /* Also try do the vendor reset to let it more clear. */
+ mt792x_reset(mdev);
+
+ return PCI_ERS_RESULT_RECOVERED;
+}
+
+static const struct pci_error_handlers mt7921_err_handler = {
+ .error_detected = mt7921_error_detected,
+ .slot_reset = mt7921_slot_reset,
+};
+
static void mt7921_pci_shutdown(struct pci_dev *pdev)
{
mt7921_pci_remove(pdev);
@@ -608,6 +710,7 @@ static struct pci_driver mt7921_pci_driver = {
.remove = mt7921_pci_remove,
.shutdown = mt7921_pci_shutdown,
.driver.pm = pm_sleep_ptr(&mt7921_pm_ops),
+ .err_handler = &mt7921_err_handler,
};
module_pci_driver(mt7921_pci_driver);
--
2.45.2
^ permalink raw reply related
* [PATCH v2] wifi: mt76: mt792x: fix use-after-free in mt76_rx_poll_complete
From: JB Tsai @ 2026-05-06 5:29 UTC (permalink / raw)
To: nbd, lorenzo
Cc: linux-wireless, linux-mediatek, Deren.Wu, Sean.Wang, Quan.Zhou,
Ryder.Lee, Leon.Yen, litien.chang, eason.lai, jb.tsai, Eason Lai
From: Eason Lai <Eason.Lai@mediatek.com>
A use-after-free issue occurs in mt76_rx_poll_complete due to a race
condition. The STA has already been removed, but the rx_status still
had a pointer to the wcid in the STA.
Use wcid_idx instead of storing the wcid pointer, and look up the wcid
via rcu_dereference() by wcid_idx.
BUG: KASAN: invalid-access in mt76_rx_poll_complete+0x280/0x470 [mt76]
Call trace:
dump_backtrace+0xec/0x128
show_stack+0x18/0x28
dump_stack_lvl+0x40/0xc8
print_report+0x1b8/0x710
kasan_report+0xe0/0x144
do_bad_area+0x120/0x260
do_tag_check_fault+0x20/0x34
do_mem_abort+0x54/0xa8
el1_abort+0x3c/0x5c
el1h_64_sync_handler+0x40/0xcc
el1h_64_sync+0x7c/0x80
mt76_rx_poll_complete+0x280/0x470
mt76_dma_rx_poll+0x114/0x51c
mt792x_poll_rx+0x60/0xf8
napi_threaded_poll_loop+0xe0/0x450
napi_threaded_poll+0x80/0x9c
kthread+0x11c/0x158
ret_from_fork+0x10/0x20
Fixes: 9c68a57bc22d ("mt76: get station pointer by wcid and pass it to mac80211")
Signed-off-by: Eason Lai <Eason.Lai@mediatek.com>
---
v2: fix mt76x02 build errors
---
drivers/net/wireless/mediatek/mt76/agg-rx.c | 18 ++++---
drivers/net/wireless/mediatek/mt76/mac80211.c | 47 ++++++++-----------
drivers/net/wireless/mediatek/mt76/mt76.h | 9 ++--
.../net/wireless/mediatek/mt76/mt7603/mac.c | 6 ++-
.../net/wireless/mediatek/mt76/mt7615/mac.c | 19 +++++---
.../net/wireless/mediatek/mt76/mt76_connac.h | 3 +-
.../wireless/mediatek/mt76/mt76_connac_mac.c | 12 ++++-
.../net/wireless/mediatek/mt76/mt76x02_mac.c | 9 ++--
.../net/wireless/mediatek/mt76/mt7915/mac.c | 16 +++++--
.../net/wireless/mediatek/mt76/mt7921/mac.c | 13 +++--
.../net/wireless/mediatek/mt76/mt7925/mac.c | 19 +++++---
.../net/wireless/mediatek/mt76/mt7996/mac.c | 29 ++++++++----
12 files changed, 119 insertions(+), 81 deletions(-)
diff --git a/drivers/net/wireless/mediatek/mt76/agg-rx.c b/drivers/net/wireless/mediatek/mt76/agg-rx.c
index bf1babac3895..b72d13975466 100644
--- a/drivers/net/wireless/mediatek/mt76/agg-rx.c
+++ b/drivers/net/wireless/mediatek/mt76/agg-rx.c
@@ -116,14 +116,15 @@ mt76_rx_aggr_reorder_work(struct work_struct *work)
}
static void
-mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
+mt76_rx_aggr_check_ctl(struct mt76_dev *dev, struct sk_buff *skb,
+ struct sk_buff_head *frames)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ieee80211_bar *bar = mt76_skb_get_hdr(skb);
- struct mt76_wcid *wcid = status->wcid;
struct mt76_rx_tid *tid;
- u8 tidno;
+ struct mt76_wcid *wcid;
u16 seqno;
+ u8 tidno;
if (!ieee80211_is_ctl(bar->frame_control))
return;
@@ -131,6 +132,10 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
if (!ieee80211_is_back_req(bar->frame_control))
return;
+ wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+ if (!wcid)
+ return;
+
status->qos_ctl = tidno = le16_to_cpu(bar->control) >> 12;
seqno = IEEE80211_SEQ_TO_SN(le16_to_cpu(bar->start_seq_num));
tid = rcu_dereference(wcid->aggr[tidno]);
@@ -145,10 +150,11 @@ mt76_rx_aggr_check_ctl(struct sk_buff *skb, struct sk_buff_head *frames)
spin_unlock_bh(&tid->lock);
}
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+ struct sk_buff_head *frames)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
struct ieee80211_sta *sta;
struct mt76_rx_tid *tid;
bool sn_less;
@@ -164,7 +170,7 @@ void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames)
if (!status->aggr) {
if (!(status->flag & RX_FLAG_8023))
- mt76_rx_aggr_check_ctl(skb, frames);
+ mt76_rx_aggr_check_ctl(dev, skb, frames);
return;
}
diff --git a/drivers/net/wireless/mediatek/mt76/mac80211.c b/drivers/net/wireless/mediatek/mt76/mac80211.c
index 4ae5e4715a9c..2933b29782d3 100644
--- a/drivers/net/wireless/mediatek/mt76/mac80211.c
+++ b/drivers/net/wireless/mediatek/mt76/mac80211.c
@@ -1283,21 +1283,24 @@ mt76_rx_convert(struct mt76_dev *dev, struct sk_buff *skb,
memcpy(status->chain_signal, mstat.chain_signal,
sizeof(mstat.chain_signal));
- if (mstat.wcid) {
- status->link_valid = mstat.wcid->link_valid;
- status->link_id = mstat.wcid->link_id;
- }
+ if (mstat.wcid_idx != MT76_WCID_IDX_INVALID) {
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, mstat.wcid_idx);
- *sta = wcid_to_sta(mstat.wcid);
+ if (wcid) {
+ status->link_valid = wcid->link_valid;
+ status->link_id = wcid->link_id;
+ *sta = wcid_to_sta(wcid);
+ }
+ }
*hw = mt76_phy_hw(dev, mstat.phy_idx);
}
static void
-mt76_check_ccmp_pn(struct sk_buff *skb)
+mt76_check_ccmp_pn(struct mt76_dev *dev, struct sk_buff *skb)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
- struct mt76_wcid *wcid = status->wcid;
struct ieee80211_hdr *hdr;
+ struct mt76_wcid *wcid;
int security_idx;
int ret;
@@ -1307,6 +1310,7 @@ mt76_check_ccmp_pn(struct sk_buff *skb)
if (status->flag & RX_FLAG_ONLY_MONITOR)
return;
+ wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
if (!wcid || !wcid->rx_check_pn)
return;
@@ -1354,7 +1358,7 @@ static void
mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
int len)
{
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
struct ieee80211_rx_status info = {
.enc_flags = status->enc_flags,
.rate_idx = status->rate_idx,
@@ -1382,19 +1386,9 @@ mt76_airtime_report(struct mt76_dev *dev, struct mt76_rx_status *status,
static void
mt76_airtime_flush_ampdu(struct mt76_dev *dev)
{
- struct mt76_wcid *wcid;
- int wcid_idx;
-
if (!dev->rx_ampdu_len)
return;
- wcid_idx = dev->rx_ampdu_status.wcid_idx;
- if (wcid_idx < ARRAY_SIZE(dev->wcid))
- wcid = rcu_dereference(dev->wcid[wcid_idx]);
- else
- wcid = NULL;
- dev->rx_ampdu_status.wcid = wcid;
-
mt76_airtime_report(dev, &dev->rx_ampdu_status, dev->rx_ampdu_len);
dev->rx_ampdu_len = 0;
@@ -1405,7 +1399,7 @@ static void
mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
if (!(dev->drv->drv_flags & MT_DRV_SW_RX_AIRTIME))
return;
@@ -1418,8 +1412,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
if (!ether_addr_equal(hdr->addr1, dev->phy.macaddr))
return;
-
- wcid = NULL;
}
if (!(status->flag & RX_FLAG_AMPDU_DETAILS) ||
@@ -1430,7 +1422,6 @@ mt76_airtime_check(struct mt76_dev *dev, struct sk_buff *skb)
if (!dev->rx_ampdu_len ||
status->ampdu_ref != dev->rx_ampdu_ref) {
dev->rx_ampdu_status = *status;
- dev->rx_ampdu_status.wcid_idx = wcid ? wcid->idx : 0xff;
dev->rx_ampdu_ref = status->ampdu_ref;
}
@@ -1448,7 +1439,7 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
struct ieee80211_hdr *hdr = mt76_skb_get_hdr(skb);
struct ieee80211_sta *sta;
struct ieee80211_hw *hw;
- struct mt76_wcid *wcid = status->wcid;
+ struct mt76_wcid *wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
u8 tidno = status->qos_ctl & IEEE80211_QOS_CTL_TID_MASK;
bool ps;
@@ -1456,8 +1447,10 @@ mt76_check_sta(struct mt76_dev *dev, struct sk_buff *skb)
if (ieee80211_is_pspoll(hdr->frame_control) && !wcid &&
!(status->flag & RX_FLAG_8023)) {
sta = ieee80211_find_sta_by_ifaddr(hw, hdr->addr2, NULL);
- if (sta)
- wcid = status->wcid = (struct mt76_wcid *)sta->drv_priv;
+ if (sta) {
+ wcid = (struct mt76_wcid *)sta->drv_priv;
+ status->wcid_idx = wcid->idx;
+ }
}
mt76_airtime_check(dev, skb);
@@ -1521,7 +1514,7 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
while ((skb = __skb_dequeue(frames)) != NULL) {
struct sk_buff *nskb = skb_shinfo(skb)->frag_list;
- mt76_check_ccmp_pn(skb);
+ mt76_check_ccmp_pn(dev, skb);
skb_shinfo(skb)->frag_list = NULL;
mt76_rx_convert(dev, skb, &hw, &sta);
ieee80211_rx_list(hw, sta, skb, &list);
@@ -1563,7 +1556,7 @@ void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
mt76_npu_device_active(dev))
__skb_queue_tail(&frames, skb);
else
- mt76_rx_aggr_reorder(skb, &frames);
+ mt76_rx_aggr_reorder(dev, skb, &frames);
}
mt76_rx_complete(dev, &frames, napi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..1e1bd410a7a7 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -364,6 +364,7 @@ enum mt76_wcid_flags {
};
#define MT76_N_WCIDS 1088
+#define MT76_WCID_IDX_INVALID 0xffff
#define MT76_BEACON_MON_MAX_MISS 7
/* stored in ieee80211_tx_info::hw_queue */
@@ -728,10 +729,7 @@ struct mt76_mmio {
};
struct mt76_rx_status {
- union {
- struct mt76_wcid *wcid;
- u16 wcid_idx;
- };
+ u16 wcid_idx;
u32 reorder_time;
@@ -1793,7 +1791,8 @@ void mt76_rx_complete(struct mt76_dev *dev, struct sk_buff_head *frames,
struct napi_struct *napi);
void mt76_rx_poll_complete(struct mt76_dev *dev, enum mt76_rxq_id q,
struct napi_struct *napi);
-void mt76_rx_aggr_reorder(struct sk_buff *skb, struct sk_buff_head *frames);
+void mt76_rx_aggr_reorder(struct mt76_dev *dev, struct sk_buff *skb,
+ struct sk_buff_head *frames);
void mt76_testmode_tx_pending(struct mt76_phy *phy);
void mt76_queue_tx_complete(struct mt76_dev *dev, struct mt76_queue *q,
struct mt76_queue_entry *e);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
index d3110eeb45d7..aeb890df808e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7603/mac.c
@@ -513,6 +513,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
u32 rxd2 = le32_to_cpu(rxd[2]);
bool unicast = rxd1 & MT_RXD1_NORMAL_U2M;
bool insert_ccmp_hdr = false;
+ struct mt76_wcid *wcid;
bool remove_pad;
int idx;
int i;
@@ -524,7 +525,8 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
i >>= 1;
idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
- status->wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+ wcid = mt7603_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
status->band = sband->band;
if (i < sband->n_channels)
@@ -672,7 +674,7 @@ mt7603_mac_fill_rx(struct mt7603_dev *dev, struct sk_buff *skb)
}
hdr = (struct ieee80211_hdr *)skb->data;
- if (!status->wcid || !ieee80211_is_data_qos(hdr->frame_control))
+ if (!wcid || !ieee80211_is_data_qos(hdr->frame_control))
return 0;
status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
index ce0051468501..84be00a13930 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7615/mac.c
@@ -246,17 +246,20 @@ static void mt7615_mac_fill_tm_rx(struct mt7615_phy *phy, __le32 *rxv)
}
/* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7615_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7615_reverse_frag0_hdr_trans(struct mt7615_dev *dev,
+ struct sk_buff *skb, u16 hdr_gap)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
- struct mt7615_sta *msta = (struct mt7615_sta *)status->wcid;
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_vif *vif;
struct ieee80211_hdr hdr;
+ struct mt7615_sta *msta;
u16 frame_control;
+ msta = (struct mt7615_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
if (le32_get_bits(rxd[1], MT_RXD1_NORMAL_ADDR_TYPE) !=
MT_RXD1_NORMAL_U2M)
return -EINVAL;
@@ -335,6 +338,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
struct ieee80211_supported_band *sband;
struct ieee80211_hdr *hdr;
struct mt7615_phy *phy2;
+ struct mt76_wcid *wcid;
__le32 *rxd = (__le32 *)skb->data;
u32 rxd0 = le32_to_cpu(rxd[0]);
u32 rxd1 = le32_to_cpu(rxd[1]);
@@ -378,12 +382,13 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
unicast = (rxd1 & MT_RXD1_NORMAL_ADDR_TYPE) == MT_RXD1_NORMAL_U2M;
idx = FIELD_GET(MT_RXD2_NORMAL_WLAN_IDX, rxd2);
- status->wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+ wcid = mt7615_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
- if (status->wcid) {
+ if (wcid) {
struct mt7615_sta *msta;
- msta = container_of(status->wcid, struct mt7615_sta, wcid);
+ msta = container_of(wcid, struct mt7615_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
}
@@ -590,7 +595,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
if (hdr_trans && ieee80211_has_morefrags(fc)) {
- if (mt7615_reverse_frag0_hdr_trans(skb, hdr_gap))
+ if (mt7615_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
return -EINVAL;
hdr_trans = false;
} else {
@@ -638,7 +643,7 @@ static int mt7615_mac_fill_rx(struct mt7615_dev *dev, struct sk_buff *skb)
status->flag |= RX_FLAG_8023;
}
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (!wcid || !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac.h b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
index 51423c7740bd..d9ae8d204442 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac.h
@@ -448,7 +448,8 @@ bool mt76_connac2_mac_add_txs_skb(struct mt76_dev *dev, struct mt76_wcid *wcid,
void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
struct sk_buff *skb,
__le32 *rxv, u32 mode);
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+ struct ieee80211_vif *vif,
struct sk_buff *skb, u16 hdr_offset);
int mt76_connac2_mac_fill_rx_rate(struct mt76_dev *dev,
struct mt76_rx_status *status,
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
index 0339e2e7ab60..376d59d517cc 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mac.c
@@ -952,7 +952,8 @@ void mt76_connac2_mac_decode_he_radiotap(struct mt76_dev *dev,
EXPORT_SYMBOL_GPL(mt76_connac2_mac_decode_he_radiotap);
/* The HW does not translate the mac header to 802.3 for mesh point */
-int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
+int mt76_connac2_reverse_frag0_hdr_trans(struct mt76_dev *dev,
+ struct ieee80211_vif *vif,
struct sk_buff *skb, u16 hdr_offset)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
@@ -960,6 +961,7 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_hdr hdr;
+ struct mt76_wcid *wcid;
u16 frame_control;
if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -969,7 +971,13 @@ int mt76_connac2_reverse_frag0_hdr_trans(struct ieee80211_vif *vif,
if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
return -EINVAL;
- sta = container_of((void *)status->wcid, struct ieee80211_sta, drv_priv);
+ wcid = __mt76_wcid_ptr(dev, status->wcid_idx);
+ if (!wcid)
+ return -EINVAL;
+
+ sta = container_of((void *)wcid, struct ieee80211_sta, drv_priv);
+ if (!sta)
+ return -EINVAL;
/* store the info from RXD and ethhdr to avoid being overridden */
frame_control = le32_get_bits(rxd[6], MT_RXD6_FRAME_CONTROL);
diff --git a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
index 14ee5b3b94d3..ce5d3f8f019a 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76x02_mac.c
@@ -779,11 +779,10 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
u32 ctl = le32_to_cpu(rxwi->ctl);
u16 rate = le16_to_cpu(rxwi->rate);
u16 tid_sn = le16_to_cpu(rxwi->tid_sn);
- bool unicast = rxwi->rxinfo & cpu_to_le32(MT_RXINFO_UNICAST);
int pad_len = 0, nstreams = dev->mphy.chainmask & 0xf;
+ u8 wcid_idx;
s8 signal;
u8 pn_len;
- u8 wcid;
int len;
if (!test_bit(MT76_STATE_RUNNING, &dev->mphy.state))
@@ -799,9 +798,9 @@ int mt76x02_mac_process_rx(struct mt76x02_dev *dev, struct sk_buff *skb,
status->flag |= RX_FLAG_IV_STRIPPED;
}
- wcid = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
- sta = mt76x02_rx_get_sta(&dev->mt76, wcid);
- status->wcid = mt76x02_rx_get_sta_wcid(sta, unicast);
+ wcid_idx = FIELD_GET(MT_RXWI_CTL_WCID, ctl);
+ sta = mt76x02_rx_get_sta(&dev->mt76, wcid_idx);
+ status->wcid_idx = wcid_idx;
len = FIELD_GET(MT_RXWI_CTL_MPDU_LEN, ctl);
pn_len = FIELD_GET(MT_RXINFO_PN_LEN, rxinfo);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
index cec2c4208255..30672713323f 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/mac.c
@@ -280,6 +280,7 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
u8 mode = 0, qos_ctl = 0;
struct mt7915_sta *msta = NULL;
u32 csum_status = *(u32 *)skb->cb;
+ struct mt76_wcid *wcid;
bool hdr_trans;
u16 hdr_gap;
u16 seq_ctrl = 0;
@@ -313,10 +314,13 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt7915_rx_get_wcid(dev, idx, unicast);
- if (status->wcid) {
- msta = container_of(status->wcid, struct mt7915_sta, wcid);
+ wcid = mt7915_rx_get_wcid(dev, idx, unicast);
+
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+ if (wcid) {
+ msta = container_of(wcid, struct mt7915_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &msta->wcid);
}
@@ -475,7 +479,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
vif = container_of((void *)msta->vif, struct ieee80211_vif,
drv_priv);
- err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+ err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+ skb, hdr_gap);
if (err)
return err;
@@ -532,7 +537,8 @@ mt7915_mac_fill_rx(struct mt7915_dev *dev, struct sk_buff *skb,
if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (status->wcid_idx == MT76_WCID_IDX_INVALID ||
+ !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast &&
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
index 03b4960db73f..59c64b951678 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/mac.c
@@ -184,6 +184,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
u32 rxd4 = le32_to_cpu(rxd[4]);
struct mt792x_sta *msta = NULL;
struct mt792x_link_sta *mlink;
+ struct mt76_wcid *wcid;
u16 seq_ctrl = 0;
__le16 fc = 0;
u8 mode = 0;
@@ -211,10 +212,11 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
- if (status->wcid) {
- mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+ if (wcid) {
+ mlink = container_of(wcid, struct mt792x_link_sta, wcid);
msta = container_of(mlink, struct mt792x_sta, deflink);
mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
}
@@ -395,7 +397,8 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
vif = container_of((void *)msta->vif, struct ieee80211_vif,
drv_priv);
- err = mt76_connac2_reverse_frag0_hdr_trans(vif, skb, hdr_gap);
+ err = mt76_connac2_reverse_frag0_hdr_trans(&dev->mt76, vif,
+ skb, hdr_gap);
if (err)
return err;
@@ -433,7 +436,7 @@ mt7921_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
if (rxv && mode >= MT_PHY_TYPE_HE_SU && !(status->flag & RX_FLAG_8023))
mt76_connac2_mac_decode_he_radiotap(&dev->mt76, skb, rxv, mode);
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (!wcid || !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
index c47bd812b66b..e429f78596e2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mac.c
@@ -166,17 +166,20 @@ void mt7925_mac_set_fixed_rate_table(struct mt792x_dev *dev,
}
/* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7925_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7925_reverse_frag0_hdr_trans(struct mt792x_dev *dev,
+ struct sk_buff *skb, u16 hdr_gap)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
- struct mt792x_sta *msta = (struct mt792x_sta *)status->wcid;
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_vif *vif;
struct ieee80211_hdr hdr;
+ struct mt792x_sta *msta;
u16 frame_control;
+ msta = (struct mt792x_sta *)mt76_wcid_ptr(dev, status->wcid_idx);
+
if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
MT_RXD3_NORMAL_U2M)
return -EINVAL;
@@ -368,6 +371,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
u32 rxd3 = le32_to_cpu(rxd[3]);
u32 rxd4 = le32_to_cpu(rxd[4]);
struct mt792x_link_sta *mlink;
+ struct mt76_wcid *wcid;
u8 mode = 0; /* , band_idx; */
u16 seq_ctrl = 0;
__le16 fc = 0;
@@ -392,10 +396,11 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
chfreq = FIELD_GET(MT_RXD3_NORMAL_CH_FREQ, rxd3);
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ wcid = mt792x_rx_get_wcid(dev, idx, unicast);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
- if (status->wcid) {
- mlink = container_of(status->wcid, struct mt792x_link_sta, wcid);
+ if (wcid) {
+ mlink = container_of(wcid, struct mt792x_link_sta, wcid);
mt76_wcid_add_poll(&dev->mt76, &mlink->wcid);
}
@@ -545,7 +550,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
if (hdr_trans && ieee80211_has_morefrags(fc)) {
- if (mt7925_reverse_frag0_hdr_trans(skb, hdr_gap))
+ if (mt7925_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
return -EINVAL;
hdr_trans = false;
} else {
@@ -608,7 +613,7 @@ mt7925_mac_fill_rx(struct mt792x_dev *dev, struct sk_buff *skb)
}
}
- if (!status->wcid || !ieee80211_is_data_qos(fc))
+ if (!wcid || !ieee80211_is_data_qos(fc))
return 0;
status->aggr = unicast && !ieee80211_is_qos_nullfunc(fc);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
index e2a83da3a09c..98c30957dd76 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mac.c
@@ -186,17 +186,19 @@ static void mt7996_mac_sta_poll(struct mt7996_dev *dev)
}
/* The HW does not translate the mac header to 802.3 for mesh point */
-static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
+static int mt7996_reverse_frag0_hdr_trans(struct mt7996_dev *dev,
+ struct sk_buff *skb, u16 hdr_gap)
{
struct mt76_rx_status *status = (struct mt76_rx_status *)skb->cb;
struct ethhdr *eth_hdr = (struct ethhdr *)(skb->data + hdr_gap);
- struct mt7996_sta_link *msta_link = (void *)status->wcid;
- struct mt7996_sta *msta = msta_link->sta;
+ struct mt76_wcid *wcid = mt76_wcid_ptr(dev, status->wcid_idx);
+ struct mt7996_sta_link *msta_link;
struct ieee80211_bss_conf *link_conf;
__le32 *rxd = (__le32 *)skb->data;
struct ieee80211_sta *sta;
struct ieee80211_vif *vif;
struct ieee80211_hdr hdr;
+ struct mt7996_sta *msta;
u16 frame_control;
if (le32_get_bits(rxd[3], MT_RXD3_NORMAL_ADDR_TYPE) !=
@@ -206,10 +208,16 @@ static int mt7996_reverse_frag0_hdr_trans(struct sk_buff *skb, u16 hdr_gap)
if (!(le32_to_cpu(rxd[1]) & MT_RXD1_NORMAL_GROUP_4))
return -EINVAL;
+ if (!wcid)
+ return -EINVAL;
+
+ msta_link = container_of(wcid, struct mt7996_sta_link, wcid);
+ msta = msta_link->sta;
+
if (!msta || !msta->vif)
return -EINVAL;
- sta = wcid_to_sta(status->wcid);
+ sta = wcid_to_sta(wcid);
vif = container_of((void *)msta->vif, struct ieee80211_vif, drv_priv);
link_conf = rcu_dereference(vif->link_conf[msta_link->wcid.link_id]);
if (!link_conf)
@@ -429,6 +437,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
bool unicast, insert_ccmp_hdr = false;
u8 remove_pad, amsdu_info, band_idx;
u8 mode = 0, qos_ctl = 0;
+ struct mt76_wcid *wcid;
bool hdr_trans;
u16 hdr_gap;
u16 seq_ctrl = 0;
@@ -461,12 +470,14 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
unicast = FIELD_GET(MT_RXD3_NORMAL_ADDR_TYPE, rxd3) == MT_RXD3_NORMAL_U2M;
idx = FIELD_GET(MT_RXD1_NORMAL_WLAN_IDX, rxd1);
- status->wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
- if (status->wcid) {
+ wcid = mt7996_rx_get_wcid(dev, idx, band_idx);
+ status->wcid_idx = wcid ? wcid->idx : MT76_WCID_IDX_INVALID;
+
+ if (wcid) {
struct mt7996_sta_link *msta_link;
- msta_link = container_of(status->wcid, struct mt7996_sta_link,
+ msta_link = container_of(wcid, struct mt7996_sta_link,
wcid);
msta = msta_link->sta;
mt76_wcid_add_poll(&dev->mt76, &msta_link->wcid);
@@ -620,7 +631,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
hdr_gap = (u8 *)rxd - skb->data + 2 * remove_pad;
if (hdr_trans && ieee80211_has_morefrags(fc)) {
- if (mt7996_reverse_frag0_hdr_trans(skb, hdr_gap))
+ if (mt7996_reverse_frag0_hdr_trans(dev, skb, hdr_gap))
return -EINVAL;
hdr_trans = false;
} else {
@@ -697,7 +708,7 @@ mt7996_mac_fill_rx(struct mt7996_dev *dev, enum mt76_rxq_id q,
}
}
- if (!status->wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
+ if (!wcid || !ieee80211_is_data_qos(fc) || hw_aggr)
return 0;
status->aggr = unicast &&
--
2.45.2
^ permalink raw reply related
* [PATCH v2 2/2] wifi: mt76: mt7925: don't disable AP BSS when removing TDLS peer
From: ElXreno @ 2026-05-06 1:39 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu
Cc: linux-wireless, linux-kernel, linux-arm-kernel, linux-mediatek,
ElXreno, stable
In-Reply-To: <20260506-mt7925-tdls-fixes-v2-0-46aa826ba8bb@gmail.com>
On a STATION vif, removing a TDLS peer takes the mt7925_mac_sta_remove
-> mt7925_mac_sta_remove_links path. The first loop in that function
calls mt7925_mcu_add_bss_info(..., enable=false) for every link of the
station being removed. For a non-MLO STATION vif there is exactly one
link, link 0, whose bss_conf is the AP's. TDLS peers do not have their
own bss_conf - they share the AP's BSS.
The result is that every TDLS peer teardown sends a BSS_INFO_UPDATE
with enable=0 for the AP's BSS to the firmware, which wipes the AP-side
rate-control context. The connection stays associated and TX from the
host still works at the negotiated rate, but the AP's downlink to us
collapses to the lowest mandatory OFDM rate (HE-MCS 0 / 6 Mbit/s OFDM)
and only slowly recovers as rate adaptation re-learns under sustained
traffic. With brief or bursty traffic the link can stay at 6-72 Mbit/s
indefinitely, requiring a manual reconnect.
mt7925_mac_link_sta_remove() already guards its own
mt7925_mcu_add_bss_info(..., false) call with
"vif->type == NL80211_IFTYPE_STATION && !link_sta->sta->tdls".
Add the equivalent guard at the top of the cleanup loop in
mt7925_mac_sta_remove_links(), above the link_sta / link_conf /
mlink / mconf lookups, so TDLS peer teardown skips the loop body
entirely without doing the per-link work that would just be thrown
away.
Verified on mt7925e by triggering Samsung-S938B auto-TDLS via iperf3
and watching iw rx bitrate after teardown:
Before: rx bitrate collapses to 6.0-72.0 Mbit/s, oscillates 17/72/
137/288/432 Mbit/s for 30+ seconds, no full recovery without
a manual reassoc.
After: rx bitrate stays at 1200.9 Mbit/s HE-MCS 11 NSS 2 80 MHz
across the entire TDLS lifecycle.
bpftrace confirms a single mt7925_mcu_add_bss_info(enable=0) call per
teardown before the fix; zero such calls after.
Fixes: 3878b4333602 ("wifi: mt76: mt7925: update mt7925_mac_link_sta_[add, assoc, remove] for MLO")
Cc: stable@vger.kernel.org
Signed-off-by: ElXreno <elxreno@gmail.com>
Assisted-by: Claude:claude-opus-4-7 bpftrace
---
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 3 +++
1 file changed, 3 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 61330e3c18b2..041d06143ef1 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -1268,6 +1268,9 @@ mt7925_mac_sta_remove_links(struct mt792x_dev *dev, struct ieee80211_vif *vif,
if (vif->type == NL80211_IFTYPE_AP)
break;
+ if (vif->type == NL80211_IFTYPE_STATION && sta->tdls)
+ continue;
+
link_sta = mt792x_sta_to_link_sta(vif, sta, link_id);
if (!link_sta)
continue;
--
2.53.0
^ permalink raw reply related
* [PATCH v2 1/2] wifi: mt76: route TDLS-peer frames as 3-addr non-DS in HW encap
From: ElXreno @ 2026-05-06 1:39 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu
Cc: linux-wireless, linux-kernel, linux-arm-kernel, linux-mediatek,
ElXreno
In-Reply-To: <20260506-mt7925-tdls-fixes-v2-0-46aa826ba8bb@gmail.com>
With HW TX encap offload enabled, the mt76 firmware builds the 802.11
header for the 802.3 frame using the per-WCID context. For a STATION
vif the HDR_TRANS TLV currently sets ToDS=1, which makes the firmware
default to the BSSID as A1 and emit STA->AP-formatted frames
regardless of which peer the WCID points to.
For TDLS-paired peers this is wrong. Data frames go on air addressed
to the AP, the AP MAC-ACKs and silently drops them per IEEE 802.11z
(an AP must not forward to a TDLS-paired peer). Management and
control frames bypass the HW encap path and still reach the peer;
only user data fails.
Add MT_WCID_FLAG_TDLS_PEER, set it in mt7915, mt7921, mt7925 and
mt7996 sta-add paths when sta->tdls is true, and override the
HDR_TRANS TLV in mt76_connac_mcu_wtbl_hdr_trans_tlv() (Connac2 -
mt7915 / mt7921 / mt7922), mt7925_mcu_sta_hdr_trans_tlv() (mt7925)
and mt7996_mcu_sta_hdr_trans_tlv() (mt7996) to set ToDS=0, FromDS=0
when the flag is set. The 3-addr non-DS form matches what 802.11z
uses for direct links; the firmware then constructs the frame with
A1=peer rather than A1=BSSID. HW encap offload remains enabled for
AP and any non-TDLS traffic.
Verified on mt7925e + Samsung S938B over a 5 GHz HE 80 MHz channel
with iperf3 -t 30 to the TDLS peer:
before fix: over the TDLS direct link, 7 TDLS Setup action
frames and 3 RTS frames reach the peer; 0 QoS
Data frames make it through (mgmt/control paths
bypass HW encap, the data path does not). iperf3
stalls.
after fix: 2.90 GBytes transferred at 830 Mbit/s sustained,
0 TCP retransmits.
mt7915, mt7921, mt7922 and mt7996 are not regression-tested in this
change for lack of hardware. Their HDR_TRANS handling mirrors the
verified mt7925 change; the firmware behavior is shared across these
chips.
Signed-off-by: ElXreno <elxreno@gmail.com>
Assisted-by: Claude:claude-opus-4-7 bpftrace tcpdump
---
drivers/net/wireless/mediatek/mt76/mt76.h | 1 +
drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 5 +++++
drivers/net/wireless/mediatek/mt76/mt7915/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7921/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 5 +++++
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 5 +++++
8 files changed, 28 insertions(+)
diff --git a/drivers/net/wireless/mediatek/mt76/mt76.h b/drivers/net/wireless/mediatek/mt76/mt76.h
index 527bef97e122..07955555f84d 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76.h
+++ b/drivers/net/wireless/mediatek/mt76/mt76.h
@@ -361,6 +361,7 @@ enum mt76_wcid_flags {
MT_WCID_FLAG_PS,
MT_WCID_FLAG_4ADDR,
MT_WCID_FLAG_HDR_TRANS,
+ MT_WCID_FLAG_TDLS_PEER,
};
#define MT76_N_WCIDS 1088
diff --git a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
index 89bd52ea8bf7..f61d0625ef51 100644
--- a/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c
@@ -491,6 +491,11 @@ void mt76_connac_mcu_wtbl_hdr_trans_tlv(struct sk_buff *skb,
htr->to_ds = true;
htr->from_ds = true;
}
+
+ if (test_bit(MT_WCID_FLAG_TDLS_PEER, &wcid->flags)) {
+ htr->to_ds = false;
+ htr->from_ds = false;
+ }
}
EXPORT_SYMBOL_GPL(mt76_connac_mcu_wtbl_hdr_trans_tlv);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7915/main.c b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
index e1d83052aa6d..51643a48ed15 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7915/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7915/main.c
@@ -760,6 +760,9 @@ int mt7915_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
msta->wcid.phy_idx = ext_phy;
msta->jiffies = jiffies;
+ if (sta->tdls)
+ set_bit(MT_WCID_FLAG_TDLS_PEER, &msta->wcid.flags);
+
ewma_avg_signal_init(&msta->avg_ack_signal);
mt7915_mac_wtbl_update(dev, idx,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7921/main.c b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
index 3d74fabe7408..d39cb881d75e 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7921/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7921/main.c
@@ -828,6 +828,9 @@ int mt7921_mac_sta_add(struct mt76_dev *mdev, struct ieee80211_vif *vif,
msta->deflink.last_txs = jiffies;
msta->deflink.sta = msta;
+ if (sta->tdls)
+ set_bit(MT_WCID_FLAG_TDLS_PEER, &msta->deflink.wcid.flags);
+
ret = mt76_connac_pm_wake(&dev->mphy, &dev->pm);
if (ret)
return ret;
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/main.c b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
index 73d3722739d0..61330e3c18b2 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/main.c
@@ -887,6 +887,9 @@ static int mt7925_mac_link_sta_add(struct mt76_dev *mdev,
mlink->wcid.link_valid = !!link_sta->sta->valid_links;
mlink->sta = msta;
+ if (link_sta->sta->tdls)
+ set_bit(MT_WCID_FLAG_TDLS_PEER, &mlink->wcid.flags);
+
wcid = &mlink->wcid;
ewma_signal_init(&wcid->rssi);
rcu_assign_pointer(dev->mt76.wcid[wcid->idx], wcid);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
index 22bad3cba8df..333cacfaca9c 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7925/mcu.c
@@ -1091,6 +1091,11 @@ mt7925_mcu_sta_hdr_trans_tlv(struct sk_buff *skb,
hdr_trans->to_ds = true;
hdr_trans->from_ds = true;
}
+
+ if (test_bit(MT_WCID_FLAG_TDLS_PEER, &wcid->flags)) {
+ hdr_trans->to_ds = false;
+ hdr_trans->from_ds = false;
+ }
}
int mt7925_mcu_wtbl_update_hdr_trans(struct mt792x_dev *dev,
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/main.c b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
index a8a6552d49f6..9b8db6efb5ac 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/main.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/main.c
@@ -1164,6 +1164,9 @@ mt7996_mac_sta_init_link(struct mt7996_dev *dev,
msta_link->wcid.link_valid = !!sta->valid_links;
msta_link->wcid.def_wcid = &msta->deflink.wcid;
+ if (link_sta->sta->tdls)
+ set_bit(MT_WCID_FLAG_TDLS_PEER, &msta_link->wcid.flags);
+
ewma_avg_signal_init(&msta_link->avg_ack_signal);
ewma_signal_init(&msta_link->wcid.rssi);
diff --git a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
index 16420375112d..6aaf3ed94221 100644
--- a/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
+++ b/drivers/net/wireless/mediatek/mt76/mt7996/mcu.c
@@ -2139,6 +2139,11 @@ mt7996_mcu_sta_hdr_trans_tlv(struct mt7996_dev *dev, struct sk_buff *skb,
hdr_trans->from_ds = true;
hdr_trans->mesh = true;
}
+
+ if (test_bit(MT_WCID_FLAG_TDLS_PEER, &wcid->flags)) {
+ hdr_trans->to_ds = false;
+ hdr_trans->from_ds = false;
+ }
}
static enum mcu_mmps_mode
--
2.53.0
^ permalink raw reply related
* [PATCH v2 0/2] wifi: mt76: fix TDLS direct-link on MediaTek MT7925
From: ElXreno @ 2026-05-06 1:39 UTC (permalink / raw)
To: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Ming Yen Hsieh, Deren Wu
Cc: linux-wireless, linux-kernel, linux-arm-kernel, linux-mediatek,
ElXreno, stable
This series fixes two TDLS direct-link issues on the MediaTek mt76
driver, observed reproducibly with Samsung phones and other peers
that auto-initiate TDLS direct links on a shared BSS.
Patch 1 fixes silent loss of TDLS-peer data on mt76 chips with HW
TX encap offload. The HDR_TRANS TLV defaults to ToDS=1 for STATION
vifs, which makes the firmware build A1=BSSID for every WCID under
that vif -- including TDLS-peer WCIDs. Frames bound for a TDLS
peer therefore go on air to the AP, the AP silently discards them
per IEEE 802.11z, and no user data flows over the TDLS direct
link. v2 adds an MT_WCID_FLAG_TDLS_PEER flag (set in sta-add when
sta->tdls is true) and overrides the HDR_TRANS TLV to ToDS=0/
FromDS=0 (3-addr non-DS, per 802.11z) only for those peers. HW
encap stays on for AP and non-TDLS traffic. The override is
applied in three HDR_TRANS helpers:
- mt76_connac_mcu_wtbl_hdr_trans_tlv() for mt7915/mt7921/mt7922
- mt7925_mcu_sta_hdr_trans_tlv() for mt7925
- mt7996_mcu_sta_hdr_trans_tlv() for mt7996
Verified on mt7925e + a Samsung phone over 5 GHz HE 80 MHz:
iperf3 -t 30 transferred 2.90 GBytes at 830 Mbit/s with 0 TCP
retransmits. The other chips are not regression-tested for lack
of hardware; their HDR_TRANS handling mirrors the verified
mt7925 change.
Patch 2 fixes a regression introduced by the MLO refactor in
commit 3878b4333602 ("wifi: mt76: mt7925: update
mt7925_mac_link_sta_[add, assoc, remove] for MLO"): the cleanup
loop in mt7925_mac_sta_remove_links() unconditionally calls
mt7925_mcu_add_bss_info(..., enable=false) for every link of the
station being removed, including TDLS peers on a STATION vif which
share the AP's bss_conf -- wiping the AP-side rate-control context
on every TDLS teardown and collapsing rx bitrate to 6 Mbit/s for
tens of seconds.
v2 design notes:
I tried adding the TDLS-aware firmware-facing peer setup that
v1's commit message identified as missing first -- defining
CONNECTION_TDLS = (STA_TYPE_STA | NETWORK_INFRA | BIT(3)) and
setting it in mt76_connac_mcu_sta_basic_tlv() for sta->tdls peers
hung the MCU on STA_REC_UPDATE within ~5 seconds of iperf3
traffic, requiring a chip reset. The proprietary out-of-tree
mt_wifi driver also defines CONNECTION_TDLS in mt_cmd.h but never
assigns it; TDLS there is implemented purely as an 802.11
action-frame state machine on top of CONNECTION_INFRA_STA, with
no TDLS-specific MCU command, and is gated to Connac1-era chips
only -- no firmware-facing TDLS peer setup to align to. The
HDR_TRANS ToDS override is the simplest per-peer correction that
works.
A note on the methodology behind the misroute observation: my
v1 captures used an RTL8821AU USB monitor which is single-stream
and cannot decode mt7925's 2-stream HE data frames. The
misroute-to-AP pattern only became visible after swapping to a
2x2 Intel AC 7265 in monitor mode on a separate host -- frames
go on air with A1=BSSID to the AP, the AP MAC-ACKs and discards
per 802.11z, which matches the iw counters (tx_packets++ but no
TCP ACKs returning).
v2:
- patch 1 rewritten per Sean Wang's v1 NACK: per-TDLS-peer
HDR_TRANS override instead of global SUPPORTS_TX_ENCAP_OFFLOAD
removal in mt792x_init_wiphy(). Coverage extended to mt7915
and mt7996.
- patch 1 dropped Fixes: 5c14a5f944b9 and Cc: stable -- the new
fix introduces a flag plus three helper overrides; not
realistic for a clean cherry-pick into older stables.
- patch 2 unchanged in logic; inline comment trimmed.
v1: https://lore.kernel.org/linux-wireless/20260503-mt7925-tdls-fixes-v1-0-dde847e21081@gmail.com/
Signed-off-by: ElXreno <elxreno@gmail.com>
---
ElXreno (2):
wifi: mt76: route TDLS-peer frames as 3-addr non-DS in HW encap
wifi: mt76: mt7925: don't disable AP BSS when removing TDLS peer
drivers/net/wireless/mediatek/mt76/mt76.h | 1 +
drivers/net/wireless/mediatek/mt76/mt76_connac_mcu.c | 5 +++++
drivers/net/wireless/mediatek/mt76/mt7915/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7921/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7925/main.c | 6 ++++++
drivers/net/wireless/mediatek/mt76/mt7925/mcu.c | 5 +++++
drivers/net/wireless/mediatek/mt76/mt7996/main.c | 3 +++
drivers/net/wireless/mediatek/mt76/mt7996/mcu.c | 5 +++++
8 files changed, 31 insertions(+)
---
base-commit: d2ffdadd4aca41d120e25f6a675dd6a4d77ce360
change-id: 20260503-mt7925-tdls-fixes-1d1397420900
Best regards,
--
ElXreno <elxreno@gmail.com>
^ permalink raw reply
* Re: [PATCH 1/2] wifi: mt76: mt792x: disable HW TX/RX encap offload to fix TDLS direct-link
From: ElXreno @ 2026-05-06 1:00 UTC (permalink / raw)
To: Sean Wang
Cc: Felix Fietkau, Lorenzo Bianconi, Ryder Lee, Shayne Chen,
Sean Wang, Matthias Brugger, AngeloGioacchino Del Regno,
Soul Huang, Ming Yen Hsieh, Deren Wu, linux-wireless,
linux-kernel, linux-arm-kernel, linux-mediatek, stable
In-Reply-To: <CAGp9LzogKfGovfDw+=m4BkqWAakFTStXH20cQ_FA_5-zo+rmGA@mail.gmail.com>
Hi Sean,
Thanks for the review. You're right that the global disable was too
broad. v2 scopes the fix per-TDLS-peer; rationale and changelog are in
the v2 cover letter.
Best regards,
ElXreno
^ permalink raw reply
* [PATCH v3 15/15] pmdomain: mediatek: mtk-mfg: use clk_determine_rate_noop()
From: Brian Masney @ 2026-05-06 0:49 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney
Cc: linux-clk, linux-kernel, Ulf Hansson, Matthias Brugger,
AngeloGioacchino Del Regno, linux-pm, linux-arm-kernel,
linux-mediatek
In-Reply-To: <20260505-clk-determine-rate-noop-v3-0-f3f829fbacdf@redhat.com>
Drop the driver-specific empty determine_rate() function and use the new
shared clk_determine_rate_noop() helper.
Signed-off-by: Brian Masney <bmasney@redhat.com>
---
To: Ulf Hansson <ulfh@kernel.org>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
To: Michael Turquette <mturquette@baylibre.com>
To: Stephen Boyd <sboyd@kernel.org>
To: Brian Masney <bmasney@redhat.com>
Cc: linux-clk@vger.kernel.org
Cc: linux-pm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-mediatek@lists.infradead.org
---
drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c | 23 ++---------------------
1 file changed, 2 insertions(+), 21 deletions(-)
diff --git a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
index 3ce6fb74dd53..64fef838776e 100644
--- a/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
+++ b/drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c
@@ -310,25 +310,6 @@ static unsigned long mtk_mfg_recalc_rate_gpu(struct clk_hw *hw,
return readl(mfg->shared_mem + GF_REG_FREQ_OUT_GPU) * HZ_PER_KHZ;
}
-static int mtk_mfg_determine_rate(struct clk_hw *hw,
- struct clk_rate_request *req)
-{
- /*
- * The determine_rate callback needs to be implemented to avoid returning
- * the current clock frequency, rather than something even remotely
- * close to the frequency that was asked for.
- *
- * Instead of writing considerable amounts of possibly slow code just to
- * somehow figure out which of the three PLLs to round for, or even to
- * do a search through one of two OPP tables in order to find the closest
- * OPP of a frequency, just return the rate as-is. This avoids devfreq
- * "rounding" a request for the lowest frequency to the possibly very
- * high current frequency, breaking the powersave governor in the process.
- */
-
- return 0;
-}
-
static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw,
unsigned long parent_rate)
{
@@ -339,12 +320,12 @@ static unsigned long mtk_mfg_recalc_rate_stack(struct clk_hw *hw,
static const struct clk_ops mtk_mfg_clk_gpu_ops = {
.recalc_rate = mtk_mfg_recalc_rate_gpu,
- .determine_rate = mtk_mfg_determine_rate,
+ .determine_rate = clk_determine_rate_noop,
};
static const struct clk_ops mtk_mfg_clk_stack_ops = {
.recalc_rate = mtk_mfg_recalc_rate_stack,
- .determine_rate = mtk_mfg_determine_rate,
+ .determine_rate = clk_determine_rate_noop,
};
static const struct clk_init_data mtk_mfg_clk_gpu_init = {
--
2.54.0
^ permalink raw reply related
* [PATCH v3 14/15] pmdomain: mediatek: airoha: use clk_determine_rate_noop()
From: Brian Masney @ 2026-05-06 0:49 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney
Cc: linux-clk, linux-kernel, Ulf Hansson, Matthias Brugger,
AngeloGioacchino Del Regno, linux-pm, linux-arm-kernel,
linux-mediatek
In-Reply-To: <20260505-clk-determine-rate-noop-v3-0-f3f829fbacdf@redhat.com>
Drop the driver-specific empty determine_rate() function and use the new
shared clk_determine_rate_noop() helper.
Signed-off-by: Brian Masney <bmasney@redhat.com>
---
To: Ulf Hansson <ulfh@kernel.org>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
To: Michael Turquette <mturquette@baylibre.com>
To: Stephen Boyd <sboyd@kernel.org>
To: Brian Masney <bmasney@redhat.com>
Cc: linux-clk@vger.kernel.org
Cc: linux-pm@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-mediatek@lists.infradead.org
---
drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c b/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c
index 3b1d202f89dc..5efd7a38f4da 100644
--- a/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c
+++ b/drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c
@@ -21,12 +21,6 @@ struct airoha_cpu_pmdomain_priv {
struct generic_pm_domain pd;
};
-static int airoha_cpu_pmdomain_clk_determine_rate(struct clk_hw *hw,
- struct clk_rate_request *req)
-{
- return 0;
-}
-
static unsigned long airoha_cpu_pmdomain_clk_get(struct clk_hw *hw,
unsigned long parent_rate)
{
@@ -48,7 +42,7 @@ static int airoha_cpu_pmdomain_clk_is_enabled(struct clk_hw *hw)
static const struct clk_ops airoha_cpu_pmdomain_clk_ops = {
.recalc_rate = airoha_cpu_pmdomain_clk_get,
.is_enabled = airoha_cpu_pmdomain_clk_is_enabled,
- .determine_rate = airoha_cpu_pmdomain_clk_determine_rate,
+ .determine_rate = clk_determine_rate_noop,
};
static int airoha_cpu_pmdomain_set_performance_state(struct generic_pm_domain *domain,
--
2.54.0
^ permalink raw reply related
* [PATCH v3 13/15] phy: mediatek: phy-mtk-hdmi-mt2701: use clk_determine_rate_noop()
From: Brian Masney @ 2026-05-06 0:49 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney
Cc: linux-clk, linux-kernel, Chun-Kuang Hu, Philipp Zabel,
Chunfeng Yun, Vinod Koul, Matthias Brugger,
AngeloGioacchino Del Regno, Neil Armstrong, dri-devel,
linux-mediatek, linux-arm-kernel, linux-phy
In-Reply-To: <20260505-clk-determine-rate-noop-v3-0-f3f829fbacdf@redhat.com>
Drop the driver-specific empty determine_rate() function and use the new
shared clk_determine_rate_noop() helper.
Signed-off-by: Brian Masney <bmasney@redhat.com>
---
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Chunfeng Yun <chunfeng.yun@mediatek.com>
To: Vinod Koul <vkoul@kernel.org>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
To: Michael Turquette <mturquette@baylibre.com>
To: Stephen Boyd <sboyd@kernel.org>
To: Brian Masney <bmasney@redhat.com>
Cc: linux-clk@vger.kernel.org
Cc: Neil Armstrong <neil.armstrong@linaro.org>
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-phy@lists.infradead.org
Cc: linux-kernel@vger.kernel.org
---
drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c | 8 +-------
1 file changed, 1 insertion(+), 7 deletions(-)
diff --git a/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c
index b0b6497e7eed..c9a1f20e939b 100644
--- a/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c
+++ b/drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c
@@ -90,12 +90,6 @@ static void mtk_hdmi_pll_unprepare(struct clk_hw *hw)
usleep_range(80, 100);
}
-static int mtk_hdmi_pll_determine_rate(struct clk_hw *hw,
- struct clk_rate_request *req)
-{
- return 0;
-}
-
static int mtk_hdmi_pll_set_rate(struct clk_hw *hw, unsigned long rate,
unsigned long parent_rate)
{
@@ -170,7 +164,7 @@ static const struct clk_ops mtk_hdmi_phy_pll_ops = {
.prepare = mtk_hdmi_pll_prepare,
.unprepare = mtk_hdmi_pll_unprepare,
.set_rate = mtk_hdmi_pll_set_rate,
- .determine_rate = mtk_hdmi_pll_determine_rate,
+ .determine_rate = clk_determine_rate_noop,
.recalc_rate = mtk_hdmi_pll_recalc_rate,
};
--
2.54.0
^ permalink raw reply related
* [PATCH v3 02/15] clk: add clk_determine_rate_noop()
From: Brian Masney @ 2026-05-06 0:48 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney
Cc: linux-clk, linux-kernel, Abel Vesa, Peng Fan, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, Anson Huang,
Bjorn Andersson, Geert Uytterhoeven, Andrea della Porta,
Tudor Ambarus, Krzysztof Kozlowski, Sylwester Nawrocki,
Chanwoo Choi, Alim Akhtar, Sudeep Holla, Cristian Marussi,
Orson Zhai, Baolin Wang, Chunyan Zhang, Chun-Kuang Hu,
Philipp Zabel, Chunfeng Yun, Vinod Koul, Neil Armstrong,
Matthias Brugger, AngeloGioacchino Del Regno, Ulf Hansson, imx,
linux-arm-kernel, linux-arm-msm, linux-renesas-soc,
linux-samsung-soc, arm-scmi, dri-devel, linux-mediatek, linux-phy,
linux-pm
In-Reply-To: <20260505-clk-determine-rate-noop-v3-0-f3f829fbacdf@redhat.com>
Add a new helper clk_determine_rate_noop() that's for clocks where the
rate rounding is handled by the firmware/hardware, or the clock is
capable of any rate. The requested rate is passed through unchanged,
and the actual rate will be learned via recalc_rate() after the rate
is set.
This shared helper will be used to get rid of the driver-specific empty
determine rate implementations that are present in the tree.
Signed-off-by: Brian Masney <bmasney@redhat.com>
---
To: Michael Turquette <mturquette@baylibre.com>
To: Stephen Boyd <sboyd@kernel.org>
To: Brian Masney <bmasney@redhat.com>
To: Abel Vesa <abelvesa@kernel.org>
To: Peng Fan <peng.fan@nxp.com>
To: Frank Li <Frank.Li@nxp.com>
To: Sascha Hauer <s.hauer@pengutronix.de>
To: Pengutronix Kernel Team <kernel@pengutronix.de>
To: Fabio Estevam <festevam@gmail.com>
To: Anson Huang <anson.huang@nxp.com>
To: Bjorn Andersson <andersson@kernel.org>
To: Geert Uytterhoeven <geert+renesas@glider.be>
To: Andrea della Porta <andrea.porta@suse.com>
To: Tudor Ambarus <tudor.ambarus@linaro.org>
To: Krzysztof Kozlowski <krzk@kernel.org>
To: Sylwester Nawrocki <s.nawrocki@samsung.com>
To: Chanwoo Choi <cw00.choi@samsung.com>
To: Alim Akhtar <alim.akhtar@samsung.com>
To: Sudeep Holla <sudeep.holla@kernel.org>
To: Cristian Marussi <cristian.marussi@arm.com>
To: Orson Zhai <orsonzhai@gmail.com>
To: Baolin Wang <baolin.wang@linux.alibaba.com>
To: Chunyan Zhang <zhang.lyra@gmail.com>
To: Chun-Kuang Hu <chunkuang.hu@kernel.org>
To: Philipp Zabel <p.zabel@pengutronix.de>
To: Chunfeng Yun <chunfeng.yun@mediatek.com>
To: Vinod Koul <vkoul@kernel.org>
To: Neil Armstrong <neil.armstrong@linaro.org>
To: Matthias Brugger <matthias.bgg@gmail.com>
To: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
To: Ulf Hansson <ulfh@kernel.org>
Cc: linux-clk@vger.kernel.org
Cc: linux-kernel@vger.kernel.org
Cc: imx@lists.linux.dev
Cc: linux-arm-kernel@lists.infradead.org
Cc: linux-arm-msm@vger.kernel.org
Cc: linux-renesas-soc@vger.kernel.org
Cc: linux-samsung-soc@vger.kernel.org
Cc: arm-scmi@vger.kernel.org
Cc: dri-devel@lists.freedesktop.org
Cc: linux-mediatek@lists.infradead.org
Cc: linux-phy@lists.infradead.org
Cc: linux-pm@vger.kernel.org
---
drivers/clk/clk.c | 18 ++++++++++++++++++
include/linux/clk-provider.h | 1 +
2 files changed, 19 insertions(+)
diff --git a/drivers/clk/clk.c b/drivers/clk/clk.c
index 048adfa86a5d..956e147f4d4e 100644
--- a/drivers/clk/clk.c
+++ b/drivers/clk/clk.c
@@ -882,6 +882,24 @@ int clk_hw_determine_rate_no_reparent(struct clk_hw *hw,
}
EXPORT_SYMBOL_GPL(clk_hw_determine_rate_no_reparent);
+/**
+ * clk_determine_rate_noop - clk_ops::determine_rate noop implementation
+ * @hw: clk to determine rate on
+ * @req: rate request
+ *
+ * Noop determine rate for clocks where the rate rounding is handled by the
+ * firmware/hardware, or the clock is capable of any rate. The requested rate is
+ * passed through unchanged, and the actual rate will be learned via
+ * recalc_rate() after the rate is set.
+ *
+ * Returns: 0 always
+ */
+int clk_determine_rate_noop(struct clk_hw *hw, struct clk_rate_request *req)
+{
+ return 0;
+}
+EXPORT_SYMBOL_GPL(clk_determine_rate_noop);
+
/*** clk api ***/
static void clk_core_rate_unprotect(struct clk_core *core)
diff --git a/include/linux/clk-provider.h b/include/linux/clk-provider.h
index b01a38fef8cf..334b9211a157 100644
--- a/include/linux/clk-provider.h
+++ b/include/linux/clk-provider.h
@@ -1431,6 +1431,7 @@ int clk_mux_determine_rate_flags(struct clk_hw *hw,
unsigned long flags);
int clk_hw_determine_rate_no_reparent(struct clk_hw *hw,
struct clk_rate_request *req);
+int clk_determine_rate_noop(struct clk_hw *hw, struct clk_rate_request *req);
void clk_hw_reparent(struct clk_hw *hw, struct clk_hw *new_parent);
void clk_hw_get_rate_range(struct clk_hw *hw, unsigned long *min_rate,
unsigned long *max_rate);
--
2.54.0
^ permalink raw reply related
* [PATCH v3 00/15] clk: introduce clk_determine_rate_noop() and update drivers to use it
From: Brian Masney @ 2026-05-06 0:48 UTC (permalink / raw)
To: Michael Turquette, Stephen Boyd, Brian Masney
Cc: linux-clk, linux-kernel, Peng Fan, Abel Vesa, Frank Li,
Sascha Hauer, Pengutronix Kernel Team, Fabio Estevam, imx,
linux-arm-kernel, Anson Huang, Bjorn Andersson,
Geert Uytterhoeven, Andrea della Porta, Tudor Ambarus,
Krzysztof Kozlowski, Sylwester Nawrocki, Chanwoo Choi,
Alim Akhtar, Sudeep Holla, Cristian Marussi, Orson Zhai,
Baolin Wang, Chunyan Zhang, Chun-Kuang Hu, Philipp Zabel,
Chunfeng Yun, Vinod Koul, Neil Armstrong, Matthias Brugger,
AngeloGioacchino Del Regno, Ulf Hansson, linux-arm-msm,
linux-renesas-soc, linux-samsung-soc, arm-scmi, dri-devel,
linux-mediatek, linux-phy, linux-pm
There are some clocks where the determine_rate clk op is just an empty
function that returns 0. This can be either because the rounding is
managed by the firmware/hardware, or the clock is capable of any rate.
Let's add a new new shared function to clk.c named
clk_determine_rate_noop(), and update all of the drivers that have an
empty determine_rate() ops to use this new shared function.
The first patch in this series also includes a minor cleanup to
drivers/clk/imx/clk-scu.c. I included it as the first patch so it
doesn't get buried in the middle of this series. I change this same
driver later in the series to use clk_determine_rate_noop().
Changes since v2:
https://lore.kernel.org/all/20260309-clk-det-rate-fw-managed-v2-0-c48ef5a3100a@redhat.com/
- Dropped the NOOP flag and just use a dedicated noop function.
Merge Strategy
--------------
All of this needs to be directly merged by Stephen as one series into
his tree. Subsystem maintainers: please leave a Reviewed-by or Acked-by
for Stephen. To reduce the noise, I am only CCing people on their
respective drivers.
Since there's only 3 drivers outside of drivers/clk that need to be
updated, I included them in this same series for completeness. These
should go through Stephen's tree as well.
Signed-off-by: Brian Masney <bmasney@redhat.com>
---
Brian Masney (15):
clk: imx: scu: drop redundant init.ops variable assignment
clk: add clk_determine_rate_noop()
clk: hisilicon: hi3660-stub: use clk_determine_rate_noop()
clk: imx: scu: use clk_determine_rate_noop()
clk: qcom: rpm: use clk_determine_rate_noop()
clk: qcom: rpmh: use clk_determine_rate_noop()
clk: qcom: smd-rpm: use clk_determine_rate_noop()
clk: renesas: rzg2l-cpg: use clk_determine_rate_noop()
clk: rp1: use clk_determine_rate_noop()
clk: samsung: acpm: use clk_determine_rate_noop()
clk: scpi: use clk_determine_rate_noop()
clk: sprd: use clk_determine_rate_noop()
phy: mediatek: phy-mtk-hdmi-mt2701: use clk_determine_rate_noop()
pmdomain: mediatek: airoha: use clk_determine_rate_noop()
pmdomain: mediatek: mtk-mfg: use clk_determine_rate_noop()
drivers/clk/clk-rp1.c | 8 +-------
drivers/clk/clk-scpi.c | 14 +-------------
drivers/clk/clk.c | 18 ++++++++++++++++++
drivers/clk/hisilicon/clk-hi3660-stub.c | 12 +-----------
drivers/clk/imx/clk-scu.c | 24 +++---------------------
drivers/clk/qcom/clk-rpm.c | 15 ++-------------
drivers/clk/qcom/clk-rpmh.c | 8 +-------
drivers/clk/qcom/clk-smd-rpm.c | 13 +------------
drivers/clk/renesas/rzg2l-cpg.c | 8 +-------
drivers/clk/samsung/clk-acpm.c | 14 +-------------
drivers/clk/sprd/pll.c | 8 +-------
drivers/phy/mediatek/phy-mtk-hdmi-mt2701.c | 8 +-------
drivers/pmdomain/mediatek/airoha-cpu-pmdomain.c | 8 +-------
drivers/pmdomain/mediatek/mtk-mfg-pmdomain.c | 23 ++---------------------
include/linux/clk-provider.h | 1 +
15 files changed, 36 insertions(+), 146 deletions(-)
---
base-commit: 4cd074ae20bbcc293bbbce9163abe99d68ae6ae0
change-id: 20260505-clk-determine-rate-noop-17a544f78018
Best regards,
--
Brian Masney <bmasney@redhat.com>
^ permalink raw reply
* Re: [net-next RFC PATCH v5 08/10] dt-bindings: net: pcs: Document support for Airoha Ethernet PCS
From: Benjamin Larsson @ 2026-05-06 0:00 UTC (permalink / raw)
To: Christian Marangi, Andrew Lunn, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Rob Herring, Krzysztof Kozlowski,
Conor Dooley, Lorenzo Bianconi, Heiner Kallweit, Russell King,
Philipp Zabel, Nathan Chancellor, Nick Desaulniers, Bill Wendling,
Justin Stitt, Daniel Golle, netdev, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, llvm
In-Reply-To: <20260505182713.27644-9-ansuelsmth@gmail.com>
Hi.
On 05/05/2026 20:27, Christian Marangi wrote:
> Document support for Airoha Ethernet PCS for AN7581 SoC.
>
> Airoha AN7581 SoC expose multiple Physical Coding Sublayer (PCS) for
> the various Serdes port supporting different Media Independent Interface
> (10BASE-R, USXGMII, 2500BASE-X, 1000BASE-X, SGMII).
10GBASE-R
> This follow the new PCS provider with the use of #pcs-cells property.
>
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> ---
> .../bindings/net/pcs/airoha,pcs.yaml | 112 ++++++++++++++++++
> 1 file changed, 112 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml
>
> diff --git a/Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml b/Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml
> new file mode 100644
> index 000000000000..8bcf7757c728
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/net/pcs/airoha,pcs.yaml
> @@ -0,0 +1,112 @@
> +# SPDX-License-Identifier: GPL-2.0-only OR BSD-2-Clause
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/net/pcs/airoha,pcs.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Airoha Ethernet PCS and Serdes
> +
> +maintainers:
> + - Christian Marangi <ansuelsmth@gmail.com>
> +
> +description:
> + Airoha AN7581 SoC expose multiple Physical Coding Sublayer (PCS) for
> + the various Serdes port supporting different Media Independent Interface
> + (10BASE-R, USXGMII, 2500BASE-X, 1000BASE-X, SGMII).
10GBASE-R
> +
> +properties:
> + compatible:
> + enum:
> + - airoha,an7581-pcs-eth
> + - airoha,an7581-pcs-pon
> +
> + reg:
> + items:
> + - description: XFI MAC reg
> + - description: HSGMII AN reg
> + - description: HSGMII PCS reg
> + - description: MULTI SGMII reg
> + - description: USXGMII reg
> + - description: HSGMII rate adaption reg
> + - description: XFI Analog register
> + - description: XFI PMA (Physical Medium Attachment) register
reg or register ?
MvH
Benjamin Larsson
^ permalink raw reply
* Re: [PATCH] MAINTAINERS: add myself and Lorenzo Bianconi as Airoha SoC co-maintainers
From: Lorenzo Bianconi @ 2026-05-05 21:57 UTC (permalink / raw)
To: Christian Marangi
Cc: Matthias Brugger, AngeloGioacchino Del Regno, linux-kernel,
linux-arm-kernel, linux-mediatek
In-Reply-To: <20260505122416.2609-1-ansuelsmth@gmail.com>
[-- Attachment #1: Type: text/plain, Size: 1592 bytes --]
> The development and integration of patches for the Airoha SoC stalled a
> bit lately with the result of some patches getting lost, not applied or
> even flagged as accepted, archived but never merged. The Airoha SoC, given
> the similarities with the Mediatek SoC, all goes through the mediatek tree
> and it's understandable, given the amount of traffic, that some patch for
> a different SoC are missed.
>
> To help and improve the situation, add myself and Lorenzo Bianconi as
> co-maintainers of the Airoha SoC.
>
> While at it also change the status from "Odd fixes" to "Maintained" as
> there is an active effort of maintaining this SoC.
Acked-by: Lorenzo Bianconi <lorenzo@kernel.org>
>
> Cc: Lorenzo Bianconi <lorenzo@kernel.org>
> Signed-off-by: Christian Marangi <ansuelsmth@gmail.com>
> ---
> MAINTAINERS | 4 +++-
> 1 file changed, 3 insertions(+), 1 deletion(-)
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 21c0ef0b9ce5..bd814f20213d 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -2385,9 +2385,11 @@ N: owl
> ARM/AIROHA SOC SUPPORT
> M: Matthias Brugger <matthias.bgg@gmail.com>
> M: AngeloGioacchino Del Regno <angelogioacchino.delregno@collabora.com>
> +M: Christian Marangi <ansuelsmth@gmail.com>
> +M: Lorenzo Bianconi <lorenzo@kernel.org>
> L: linux-arm-kernel@lists.infradead.org (moderated for non-subscribers)
> L: linux-mediatek@lists.infradead.org (moderated for non-subscribers)
> -S: Odd Fixes
> +S: Maintained
> F: arch/arm/boot/dts/airoha/
> F: arch/arm64/boot/dts/airoha/
>
> --
> 2.53.0
>
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 228 bytes --]
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox