git: c7b1e980ae16 - main - firmware: load binary firmware files
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Thu, 29 Feb 2024 16:37:04 UTC
The branch main has been updated by imp: URL: https://cgit.FreeBSD.org/src/commit/?id=c7b1e980ae16c094e64f318ad2feb98e17bd8f2f commit c7b1e980ae16c094e64f318ad2feb98e17bd8f2f Author: Warner Losh <imp@FreeBSD.org> AuthorDate: 2024-02-29 16:36:20 +0000 Commit: Warner Losh <imp@FreeBSD.org> CommitDate: 2024-02-29 16:36:20 +0000 firmware: load binary firmware files When we can't find a .ko module to satisfy the firmware request, try harder by looking for a file to read in directly. We compose this file's name by appending the imagename parameter to the firmware path (currently hard-wired to be /boot/firmware, future plans are for a path). Allow this file to be unloaded when firmware_put() releases the last reference, but we don't need to do the indirection and dance we need to do when unloading the .ko that will unregister the firmware. Sponsored by: Netflix Reviewed by: manu, jhb Differential Revision: https://reviews.freebsd.org/D43555 --- sys/kern/subr_firmware.c | 122 +++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 114 insertions(+), 8 deletions(-) diff --git a/sys/kern/subr_firmware.c b/sys/kern/subr_firmware.c index 9c9c831d83a8..059bedeaac1c 100644 --- a/sys/kern/subr_firmware.c +++ b/sys/kern/subr_firmware.c @@ -29,6 +29,7 @@ #include <sys/param.h> #include <sys/errno.h> #include <sys/eventhandler.h> +#include <sys/fcntl.h> #include <sys/firmware.h> #include <sys/kernel.h> #include <sys/linker.h> @@ -36,9 +37,12 @@ #include <sys/malloc.h> #include <sys/module.h> #include <sys/mutex.h> +#include <sys/namei.h> #include <sys/priv.h> #include <sys/proc.h> #include <sys/queue.h> +#include <sys/sbuf.h> +#include <sys/sysctl.h> #include <sys/systm.h> #include <sys/taskqueue.h> @@ -85,8 +89,9 @@ struct priv_fw { */ struct priv_fw *parent; - int flags; /* record FIRMWARE_UNLOAD requests */ -#define FW_UNLOAD 0x100 + int flags; +#define FW_BINARY 0x080 /* Firmware directly loaded, file == NULL */ +#define FW_UNLOAD 0x100 /* record FIRMWARE_UNLOAD requests */ /* * 'file' is private info managed by the autoload/unload code. @@ -120,7 +125,8 @@ static LIST_HEAD(, priv_fw) firmware_table; /* * Firmware module operations are handled in a separate task as they - * might sleep and they require directory context to do i/o. + * might sleep and they require directory context to do i/o. We also + * use this when loading binaries directly. */ static struct taskqueue *firmware_tq; static struct task firmware_unload_task; @@ -133,6 +139,11 @@ MTX_SYSINIT(firmware, &firmware_mtx, "firmware table", MTX_DEF); static MALLOC_DEFINE(M_FIRMWARE, "firmware", "device firmware images"); +static uint64_t firmware_max_size = 8u << 20; /* Default to 8MB cap */ +SYSCTL_U64(_debug, OID_AUTO, firmware_max_size, + CTLFLAG_RWTUN, &firmware_max_size, 0, + "Max size permitted for a firmware file."); + /* * Helper function to lookup a name. * As a side effect, it sets the pointer to a free slot, if any. @@ -240,6 +251,85 @@ struct fw_loadimage { uint32_t flags; }; +static const char *fw_path = "/boot/firmware/"; + +static void +try_binary_file(const char *imagename, uint32_t flags) +{ + struct nameidata nd; + struct thread *td = curthread; + struct ucred *cred = td ? td->td_ucred : NULL; + struct sbuf *sb; + struct priv_fw *fp; + const char *fn; + struct vattr vattr; + void *data = NULL; + const struct firmware *fw; + int flags; + size_t resid; + int error; + bool warn = flags & FIRMWARE_GET_NOWARN; + + /* + * XXX TODO: Loop over some path instead of a single element path. + * and fetch this path from the 'firmware_path' kenv the loader sets. + */ + sb = sbuf_new_auto(); + sbuf_printf(sb, "%s%s", fw_path, imagename); + sbuf_finish(sb); + fn = sbuf_data(sb); + if (bootverbose) + printf("Trying to load binary firmware from %s\n", fn); + + NDINIT(&nd, LOOKUP, FOLLOW, UIO_SYSSPACE, fn); + flags = FREAD; + error = vn_open(&nd, &flags, 0, NULL); + if (error) + goto err; + NDFREE_PNBUF(&nd); + if (nd.ni_vp->v_type != VREG) + goto err2; + error = VOP_GETATTR(nd.ni_vp, &vattr, cred); + if (error) + goto err2; + + /* + * Limit this to something sane, 8MB by default. + */ + if (vattr.va_size > firmware_max_size) { + printf("Firmware %s is too big: %ld bytes, %ld bytes max.\n", + fn, vattr.va_size, firmware_max_size); + goto err2; + } + data = malloc(vattr.va_size, M_FIRMWARE, M_WAITOK); + error = vn_rdwr(UIO_READ, nd.ni_vp, (caddr_t)data, vattr.va_size, 0, + UIO_SYSSPACE, IO_NODELOCKED, cred, NOCRED, &resid, td); + /* XXX make data read only? */ + VOP_UNLOCK(nd.ni_vp); + vn_close(nd.ni_vp, FREAD, cred, td); + nd.ni_vp = NULL; + if (error != 0 || resid != 0) + goto err; + fw = firmware_register(fn, data, vattr.va_size, 0, NULL); + if (fw == NULL) + goto err; + fp = PRIV_FW(fw); + fp->flags |= FW_BINARY; + if (bootverbose) + printf("%s: Loaded binary firmware using %s\n", imagename, fn); + sbuf_delete(sb); + return; + +err2: /* cleanup in vn_open through vn_close */ + VOP_UNLOCK(nd.ni_vp); + vn_close(nd.ni_vp, FREAD, cred, td); +err: + free(data, M_FIRMWARE); + if (bootverbose || warn) + printf("%s: could not load binary firmware %s either\n", imagename, fn); + sbuf_delete(sb); +} + static void loadimage(void *arg, int npending __unused) { @@ -253,6 +343,7 @@ loadimage(void *arg, int npending __unused) if (bootverbose || (fwli->flags & FIRMWARE_GET_NOWARN) == 0) printf("%s: could not load firmware image, error %d\n", fwli->imagename, error); + try_binary_file(fwli->imagename, fwli->flags); mtx_lock(&firmware_mtx); goto done; } @@ -408,17 +499,32 @@ EVENTHANDLER_DEFINE(mountroot, firmware_mountroot, NULL, 0); static void unloadentry(void *unused1, int unused2) { - struct priv_fw *fp; + struct priv_fw *fp, *tmp; mtx_lock(&firmware_mtx); restart: - LIST_FOREACH(fp, &firmware_table, link) { - if (fp->file == NULL || fp->refcnt != 0 || - (fp->flags & FW_UNLOAD) == 0) + LIST_FOREACH_SAFE(fp, &firmware_table, link, tmp) { + if (((fp->flags & FW_BINARY) == 0 && fp->file == NULL) || + fp->refcnt != 0 || (fp->flags & FW_UNLOAD) == 0) + continue; + + /* + * If we directly loaded the firmware, then we just need to + * remove the entry from the list and free the entry and go to + * the next one. There's no need for the indirection of the kld + * module case, we free memory and go to the next one. + */ + if ((fp->flags & FW_BINARY) != 0) { + LIST_REMOVE(fp, link); + free(__DECONST(char *, fp->fw.data), M_FIRMWARE); + free(__DECONST(char *, fp->fw.name), M_FIRMWARE); + free(fp, M_FIRMWARE); continue; + } /* - * Found an entry. Now: + * Found an entry. This is the kld case, so we have a more + * complex dance. Now: * 1. make sure we scan the table again * 2. clear FW_UNLOAD so we don't try this entry again. * 3. release the lock while trying to unload the module.