Path: blob/main/sys/compat/linuxkpi/common/src/linux_firmware.c
102426 views
/*-1* SPDX-License-Identifier: BSD-2-Clause2*3* Copyright (c) 2020-2021 The FreeBSD Foundation4* Copyright (c) 2022 Bjoern A. Zeeb5*6* This software was developed by Björn Zeeb under sponsorship from7* the FreeBSD Foundation.8*9* Redistribution and use in source and binary forms, with or without10* modification, are permitted provided that the following conditions11* are met:12* 1. Redistributions of source code must retain the above copyright13* notice, this list of conditions and the following disclaimer.14* 2. Redistributions in binary form must reproduce the above copyright15* notice, this list of conditions and the following disclaimer in the16* documentation and/or other materials provided with the distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND19* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE20* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE21* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE22* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL23* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS24* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)25* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT26* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY27* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF28* SUCH DAMAGE.29*/3031#include <sys/param.h>32#include <sys/kernel.h>33#include <sys/types.h>34#include <sys/malloc.h>35#include <sys/firmware.h>36#include <sys/queue.h>37#include <sys/taskqueue.h>3839#include <linux/types.h>40#include <linux/device.h>4142#include <linux/firmware.h>43#undef firmware4445MALLOC_DEFINE(M_LKPI_FW, "lkpifw", "LinuxKPI firmware");4647struct lkpi_fw_task {48/* Task and arguments for the "nowait" callback. */49struct task fw_task;50gfp_t gfp;51const char *fw_name;52struct device *dev;53void *drv;54void(*cont)(const struct linuxkpi_firmware *, void *);55};5657static int58_linuxkpi_request_firmware(const char *fw_name, const struct linuxkpi_firmware **fw,59struct device *dev, gfp_t gfp __unused, bool enoentok, bool warn)60{61const struct firmware *fbdfw;62struct linuxkpi_firmware *lfw;63const char *fwimg;64char *p;65uint32_t flags;6667if (fw_name == NULL || fw == NULL || dev == NULL) {68if (fw != NULL)69*fw = NULL;70return (-EINVAL);71}7273/* Set independent on "warn". To debug, bootverbose is avail. */74flags = FIRMWARE_GET_NOWARN;7576KASSERT(gfp == GFP_KERNEL, ("%s: gfp %#x\n", __func__, gfp));77lfw = malloc(sizeof(*lfw), M_LKPI_FW, M_WAITOK | M_ZERO);7879/*80* Linux can have a path in the firmware which is hard to replicate81* for auto-firmware-module-loading.82* On FreeBSD, depending on what people do, the firmware will either83* be called "fw", or "dir_fw", or "modname_dir_fw". The latter the84* driver author has to deal with herself (requesting the special name).85* We also optionally flatten '/'s and '.'s as some firmware modules do.86* We probe in the least-of-work order avoiding memory operations.87* It will be preferred to build the firmware .ko in a well matching88* way rather than adding more name-mangling-hacks here in the future89* (though we could if needed).90*/91/* (1) Try the original name. */92fbdfw = firmware_get_flags(fw_name, flags);93/* (2) Try any name removed of path, if we have not yet. */94if (fbdfw == NULL) {95fwimg = strrchr(fw_name, '/');96if (fwimg != NULL)97fwimg++;98if (fwimg == NULL || *fwimg == '\0')99fwimg = fw_name;100if (fwimg != fw_name)101fbdfw = firmware_get_flags(fwimg, flags);102}103/* (3) Flatten '/', '.' and '-' to '_' and try with adjusted name. */104if (fbdfw == NULL &&105(strchr(fw_name, '/') != NULL || strchr(fw_name, '.') != NULL ||106strchr(fw_name, '-'))) {107fwimg = strdup(fw_name, M_LKPI_FW);108if (fwimg != NULL) {109while ((p = strchr(fwimg, '/')) != NULL)110*p = '_';111fbdfw = firmware_get_flags(fwimg, flags);112if (fbdfw == NULL) {113while ((p = strchr(fwimg, '.')) != NULL)114*p = '_';115fbdfw = firmware_get_flags(fwimg, flags);116}117if (fbdfw == NULL) {118while ((p = strchr(fwimg, '-')) != NULL)119*p = '_';120fbdfw = firmware_get_flags(fwimg, flags);121}122free(__DECONST(void *, fwimg), M_LKPI_FW);123}124}125if (fbdfw == NULL) {126if (enoentok)127*fw = lfw;128else {129free(lfw, M_LKPI_FW);130*fw = NULL;131}132if (warn)133device_printf(dev->bsddev, "could not load firmware "134"image '%s'\n", fw_name);135return (-ENOENT);136}137138device_printf(dev->bsddev,"successfully loaded firmware image '%s'\n",139fw_name);140lfw->fbdfw = fbdfw;141lfw->data = (const uint8_t *)fbdfw->data;142lfw->size = fbdfw->datasize;143*fw = lfw;144return (0);145}146147static void148lkpi_fw_task(void *ctx, int pending)149{150struct lkpi_fw_task *lfwt;151const struct linuxkpi_firmware *fw;152153KASSERT(ctx != NULL && pending == 1, ("%s: lfwt %p, pending %d\n",154__func__, ctx, pending));155156lfwt = ctx;157if (lfwt->cont == NULL)158goto out;159160_linuxkpi_request_firmware(lfwt->fw_name, &fw, lfwt->dev,161lfwt->gfp, true, true);162163/*164* Linux seems to run the callback if it cannot find the firmware.165* We call it in all cases as it is the only feedback to the requester.166*/167lfwt->cont(fw, lfwt->drv);168/* Do not assume fw is still valid! */169170out:171free(lfwt, M_LKPI_FW);172}173174int175linuxkpi_request_firmware_nowait(struct module *mod __unused, bool _t __unused,176const char *fw_name, struct device *dev, gfp_t gfp, void *drv,177void(*cont)(const struct linuxkpi_firmware *, void *))178{179struct lkpi_fw_task *lfwt;180int error;181182lfwt = malloc(sizeof(*lfwt), M_LKPI_FW, M_WAITOK | M_ZERO);183lfwt->gfp = gfp;184lfwt->fw_name = fw_name;185lfwt->dev = dev;186lfwt->drv = drv;187lfwt->cont = cont;188TASK_INIT(&lfwt->fw_task, 0, lkpi_fw_task, lfwt);189error = taskqueue_enqueue(taskqueue_thread, &lfwt->fw_task);190191if (error)192return (-error);193return (0);194}195196int197linuxkpi_request_firmware(const struct linuxkpi_firmware **fw,198const char *fw_name, struct device *dev)199{200201return (_linuxkpi_request_firmware(fw_name, fw, dev, GFP_KERNEL, false,202true));203}204205int206linuxkpi_firmware_request_nowarn(const struct linuxkpi_firmware **fw,207const char *fw_name, struct device *dev)208{209210return (_linuxkpi_request_firmware(fw_name, fw, dev, GFP_KERNEL, false,211false));212}213214void215linuxkpi_release_firmware(const struct linuxkpi_firmware *fw)216{217218if (fw == NULL)219return;220221if (fw->fbdfw)222firmware_put(fw->fbdfw, FIRMWARE_UNLOAD);223free(__DECONST(void *, fw), M_LKPI_FW);224}225226int227linuxkpi_request_partial_firmware_into_buf(const struct linuxkpi_firmware **fw,228const char *fw_name, struct device *dev, uint8_t *buf, size_t buflen,229size_t offset)230{231const struct linuxkpi_firmware *lfw;232int error;233234error = linuxkpi_request_firmware(fw, fw_name, dev);235if (error != 0)236return (error);237238lfw = *fw;239if ((offset + buflen) >= lfw->size) {240linuxkpi_release_firmware(lfw);241return (-ERANGE);242}243244memcpy(buf, lfw->data + offset, buflen);245246return (0);247}248249250