Skip to content

6.12 bpi r2 vpu - #1

Draft
filipleple wants to merge 7 commits into
6.12-mainfrom
6.12_bpi-r2_vpu
Draft

6.12 bpi r2 vpu#1
filipleple wants to merge 7 commits into
6.12-mainfrom
6.12_bpi-r2_vpu

Conversation

@filipleple

Copy link
Copy Markdown
Owner

No description provided.

@filipleple filipleple self-assigned this Jun 25, 2026
…userspace

The MT7623N (BPI-R2) hardware video decoder has no mainline driver: the
working stack is a prebuilt armhf userspace (libvcodec_utility.so ->
libvcodecdrv.so) that drives the VPU by direct register programming through a
/dev/Vcodec char device. The android-mtk-3.18 "videocodec" driver it targets
does not build on mainline (mtk_smi_larb_get() was removed, there is no
drivers/misc/mediatek/ tree, and it depends on the BSP VAL/HAL framework).

Add a minimal modern re-implementation providing exactly the recovered ABI:

- /dev/Vcodec misc device;
- mmap() of the VDEC register banks (0x16000000 + 0x16020000..0x16028fff,
noncached) and of driver-allocated DMA-coherent buffers;
- the _IOW('M', nr) ioctl family: real LOCKHW/UNLOCKHW (mutex), WAITISR,
ALLOC/FREE_NON_CACHE_BUFFER, GET_CORE_NUMBER; the rest no-op success;
- VDEC power via a PM device link to larb1 + pm_runtime_resume_and_get(),
the same pattern as the lima MT7623 SMI-larb fix (pulls up
MT2701_POWER_DOMAIN_VDEC + CLK_VDEC_CKGEN/CLK_VDEC_LARB);
- poll-based decode completion (VDEC_BASE+0xA4 bit16), because the MT7623
VDEC GIC SPI is not published in the datasheet or any device tree.

The ioctl numbers and argument structs are vendored byte-identical from the
android driver so the prebuilt blob sees an unchanged ABI. Wire up Kconfig,
Makefile, the vcodec@16020000 DT node in mt7623n.dtsi, and enable
CONFIG_MTK_VCODEC_SHIM=y in mt7623n_evb_fwu_defconfig.

Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
Three defects from review of the /dev/Vcodec shim, all around client
lifetime and the poll-decode path:

1. LOCKHW/UNLOCKHW used a bare mutex. The prebuilt userspace may lock in
one thread and unlock in another, and mutex_unlock() from a task that
didn't lock is UB; worse, a client that crashes while holding it (e.g.
vcodec_probe's fork-isolated children) wedges the HW forever. Replace
with a counting semaphore (init 1) plus explicit fd ownership tracked
under a spinlock. LOCKHW honors VAL_HW_LOCK_T.u4TimeoutMs (0 -> 2000ms
default) via down_timeout(). UNLOCKHW only ups the semaphore if this fd
owns it. .release() drops a lock left held by a dead client.

2. DMA buffers lived on one module-global list, so a crashed client
leaked them until reboot. Move the buffer list to per-open state
(struct vcodec_file, file->private_data) and free the remainder in
.release(); mmap/alloc/free now operate on the fd's own buffers.

3. WAITISR returned 0 on poll timeout, which the blob reads as
decode-done -> it then consumes garbage register state,
indistinguishable from bad programming. Return -2, matching the
android videocodec driver's VAL_RESULT_INVALID_ISR path (the blob
treats a negative return as "no ISR / retry"). Timeout stays a
ratelimited warn.

Adds vcodec_shim_priv.h holding the shared struct vcodec_shim (device,
larb link, HW-lock semaphore/owner) so the forthcoming /proc/M4U_device
front-end can share this module's single struct device / dma domain.

Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
The prebuilt VPU userspace maps its (malloc'd, physically scattered)
decoder buffers through MediaTek's legacy M4U ABI -- libm4u.so opens
/proc/M4U_device and issues the MTK_M4U_T_* ('g' magic) ioctl family to
get back a single contiguous MVA the VDEC masters with. Mainline exposes
that gen1 IOMMU only through MTK_IOMMU_V1, so eVDecDrvCreate() currently
fails at its first allocation (ALLOC_MVA -> no device).

Add the M4U front-end as part of this module (shared struct device, one
dma/IOMMU domain). The key bridge is in ALLOC_MVA:

pin_user_pages_fast(FOLL_WRITE|FOLL_LONGTERM) the scattered range ->
sg_alloc_table_from_pages() (keeps the sub-page start offset) ->
dma_map_sgtable(BIDIRECTIONAL).

With an "iommus" property on the vcodec DT node, MTK_IOMMU_V1 has attached
an arm_iommu mapping and arm_iommu_map_sg() coalesces the pages into ONE
IOVA -> nents==1 -> that IOVA (offset included) is the MVA. Without it
(bypass / first bring-up) only a physically-contiguous range collapses to
nents==1; a scattered multi-page buffer yields nents>1 and we fail loudly
so the missing translation is obvious in dmesg. Same contract either way.

CACHE_SYNC -> dma_sync on the pin found by VA; DEALLOC -> unmap+unpin;
QUERY_MVA -> VA lookup. CONSTRUCT/DECONSTRUCT accept a NULL arg; port
config / TLB / power / monitor requests succeed as no-ops. Pins are tied
to the open file and released in .proc_release.

ioctl numbers and the gen1 arg layout (MVA read back at offset 0x0c,
0x2c-byte struct -- differs from the 3.18 header) are in vcodec_m4u_abi.h,
recovered per docs/vpu/m4u-notes.md. Module is now built from
vcodec_shim.o + m4u_shim.o and renamed mtk-vcodec-shim.ko.

Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
Give the vcodec@16020000 node the larb1 VDEC master ports (flat numbering
11,12,14,15,16,17; PPWRAP/13 skipped) as generic "iommus" entries. With
this, MTK_IOMMU_V1 attaches an arm_iommu mapping to the shim's struct
device, so the /proc/M4U_device bridge's dma_map_sgtable coalesces the
decoder's malloc'd, physically scattered buffers into a single MVA the
VDEC can master.

The port set is exactly the one the prebuilt libm4u enables on the decode
path (see docs/vpu/m4u-notes.md). Kept as a standalone commit so the board
session can A/B translation-on vs bypass by reverting just this patch.

Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
With .of_xlate in place, client probing now reaches
mtk_iommu_v1_probe_device() -> mtk_iommu_v1_create_mapping(), which calls
arm_iommu_create_mapping(dev, ...). Since 6.12 that helper allocates the
paging domain through the client device (iommu_paging_domain_alloc()),
which rejects any device where dev->iommu->iommu_dev is unset -- and the
core only sets iommu_dev after ->probe_device() returns. The result is a
silent -ENODEV: 'Adding to IOMMU failed: -19' at client probe, no master
ever attaches, /sys/kernel/iommu_groups stays empty.

Create the shared dma_iommu_mapping lazily in ->probe_finalize() instead,
which runs after iommu_dev is set (and right before the existing
arm_iommu_attach_device() call). Guard attach_dev against a NULL
data->mapping for domains attached before the mapping exists (e.g. a
core-allocated default domain).

Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
ALLOC_MVA maps a scattered user buffer through the IOMMU and requires it to
collapse to a single IOVA (nents==1) so the MVA is contiguous for the VPU.
dma_map_sg() only merges segments up to the device max_seg_size, which
defaults to 64KB. H.264 work/frame buffers exceed that (observed: a 77312-byte
19-page buffer split into nents=2 at the 64KB boundary), so ALLOC_MVA returned
-ENOMEM and eVDecDrvInit failed with 'Fail to open Vdec Drv H264 instance -81'.
Set max_seg_size to the full 32-bit range so the whole buffer merges into one
IOVA regardless of size. (smoke_m4u's 20KB test stayed under the cap, which is
why it passed while real decode did not.)

Signed-off-by: Filip Lewiński <filip.lewinski@3mdeb.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant