diff --git a/sys/kern/subr_firmware.c b/sys/kern/subr_firmware.c --- a/sys/kern/subr_firmware.c +++ b/sys/kern/subr_firmware.c @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -36,9 +37,12 @@ #include #include #include +#include #include #include #include +#include +#include #include #include @@ -85,8 +89,9 @@ */ struct priv_fw *parent; - int flags; /* record FIRMWARE_UNLOAD requests */ -#define FW_UNLOAD 0x100 + int flags; +#define FW_DIRECT 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 @@ /* * 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 director for the same reasons. */ static struct taskqueue *firmware_tq; static struct task firmware_unload_task; @@ -133,6 +139,11 @@ static MALLOC_DEFINE(M_FIRMWARE, "firmware", "device firmware images"); +static uint64_t firmware_max_size = 16u << 20; /* Default to 16MB 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,78 @@ uint32_t flags; }; +static const char *fw_path = "/boot/firmware/"; + +static void +try_fallback(const char *imagename) +{ + struct nameidata nd; + struct thread *td = curthread; /* XXX */ + 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; + + /* + * XXX TODO: Loop over some path instead of a single element path. + */ + sb = sbuf_new_auto(); + sbuf_printf(sb, "%s%s", fw_path, imagename); + sbuf_finish(sb); + fn = sbuf_data(sb); + + 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, 16MB by default. + */ + if (vattr.va_size > firmware_max_size) { + printf("Firmware of %ld bytes is longer than %ld max.\n", + 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(imagename, data, vattr.va_size, 0, NULL); + if (fw == NULL) + goto err; + fp = PRIV_FW(fw); + fp->flags |= FW_DIRECT; /* XXX NEED A LOCK? */ + printf("%s: Loaded 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); + printf("%s: could not load %s either\n", imagename, fn); + sbuf_delete(sb); +} + static void loadimage(void *arg, int npending __unused) { @@ -253,6 +336,7 @@ if (bootverbose || (fwli->flags & FIRMWARE_GET_NOWARN) == 0) printf("%s: could not load firmware image, error %d\n", fwli->imagename, error); + try_fallback(fwli->imagename); mtx_lock(&firmware_mtx); goto done; } @@ -408,17 +492,32 @@ 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_DIRECT) == 0 && fp->file == NULL) || + fp->refcnt != 0 || (fp->flags & FW_UNLOAD) == 0) continue; /* - * Found an entry. Now: + * 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_DIRECT) { + 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. 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. @@ -473,20 +572,23 @@ size = preload_fetch_size(file); /* * version hard wired to 0 and the firmware name matches what's - * loaded with the .fw stripped off the end (if any). The args - * could be used to pass in parameters to override this, but not - * currently. We strip the path and look only at the filename - * part. + * loaded. The args could be used to pass in parameters to + * override this, but not currently. For full paths, we strip the + * path and look only at the filename part. + * + * XXX unsure what the right thing to do here is, since we'll + * likely want to support /boot/firmware/rtw88/rtw8828b_fw.bin + * showing up as rtw88/rwt8828b_fw.bin, since that's one of the + * things if_rtw88 looks up. */ - name = strrchr(fullname, '/'); - if (name == NULL) + if (*fullname == '/') { + name = strrchr(fullname, '/'); + if (name == NULL) + name = fullname; + else + name++; + } else { name = fullname; - else - name++; - if (strlen(name) > 3) { - char *t = name + strlen(name) - 3; - if (strcmp(t, ".fw") == 0) - *t = '\0'; } firmware_register(name, addr, size, version, NULL); free(name, M_FIRMWARE);