How does Linux Kernel know where to look for driver firmware?
I’m compiling a custom kernel under Ubuntu and I’m running into the problem that my kernel doesn’t seem to know where to look for firmware. Under Ubuntu 8.04, firmware is tied to kernel version the same way driver modules are. For example, kernel 2.6.24-24-generic stores its kernel modules in:
When I compile the 2.6.24-24-generic Ubuntu kernel according the «Alternate Build Method: The Old-Fashioned Debian Way» I get the appropriate modules directory and all my devices work except those requiring firmware such as my Intel wireless card (ipw2200 module). The kernel log shows for example that when ipw2200 tries to load the firmware the kernel subsystem controlling the loading of firmware is unable to locate it:
ipw2200: Detected Intel PRO/Wireless 2200BG Network Connection ipw2200: ipw2200-bss.fw request_firmware failed: Reason -2
#define ENOENT 2 /* No such file or directory */
(The function returning ENOENT puts a minus in front of it.) I tried creating a symlink in /lib/firmware where my kernel’s name pointed to the 2.6.24-24-generic directory, however this resulted in the same error. This firmware is non-GPL, provided by Intel and packed by Ubuntu. I don’t believe it has any actual tie to a particular kernel version. cmp shows that the versions in the various directories are identical. So how does the kernel know where to look for firmware? Update I found this solution to the exact problem I’m having, however it no longer works as Ubuntu has eliminated /etc/hotplug.d and no longer stores its firmware in /usr/lib/hotplug/firmware . Update2 Some more research turned up some more answers. Up until version 92 of udev , the program firmware_helper was the way firmware got loaded. Starting with udev 93 this program was replaced with a script named firmware.sh providing identical functionality as far as I can tell. Both of these hardcode the firmware path to /lib/firmware . Ubuntu still seems to be using the /lib/udev/firmware_helper binary. The name of the firmware file is passed to firmware_helper in the environment variable $FIRMWARE which is concatenated to the path /lib/firmware and used to load the firmware. The actual request to load the firmware is made by the driver (ipw2200 in my case) via the system call:
request_firmware(. "ipw2200-bss.fw", . );
Now somewhere in between the driver calling request_firmware and firmware_helper looking at the $FIRMWARE environment variable, the kernel package name is getting prepended to the firmware name. So who’s doing it?
Firmware Guidelines¶
Users switching to a newer kernel should not have to install newer firmware files to keep their hardware working. At the same time updated firmware files must not cause any regressions for users of older kernel releases.
Drivers that use firmware from linux-firmware should follow the rules in this guide. (Where there is limited control of the firmware, i.e. company doesn’t support Linux, firmwares sourced from misc places, then of course these rules will not apply strictly.)
- Firmware files shall be designed in a way that it allows checking for firmware ABI version changes. It is recommended that firmware files be versioned with at least a major/minor version. It is suggested that the firmware files in linux-firmware be named with some device specific name, and just the major version. The firmware version should be stored in the firmware header, or as an exception, as part of the firmware file name, in order to let the driver detact any non-ABI fixes/changes. The firmware files in linux-firmware should be overwritten with the newest compatible major version. Newer major version firmware shall remain compatible with all kernels that load that major number.
- If the kernel support for the hardware is normally inactive, or the hardware isn’t available for public consumption, this can be ignored, until the first kernel release that enables that hardware. This means no major version bumps without the kernel retaining backwards compatibility for the older major versions. Minor version bumps should not introduce new features that newer kernels depend on non-optionally.
- If a security fix needs lockstep firmware and kernel fixes in order to be successful, then all supported major versions in the linux-firmware repo that are required by currently supported stable/LTS kernels, should be updated with the security fix. The kernel patches should detect if the firmware is new enough to declare if the security issue is fixed. All communications around security fixes should point at both the firmware and kernel fixes. If a security fix requires deprecating old major versions, then this should only be done as a last option, and be stated clearly in all communications.
Firmware Upload API¶
A device driver that registers with the firmware loader will expose persistent sysfs nodes to enable users to initiate firmware updates for that device. It is the responsibility of the device driver and/or the device itself to perform any validation on the data received. Firmware upload uses the same loading and data sysfs files described in the documentation for firmware fallback. It also adds additional sysfs files to provide status on the transfer of the firmware image to the device.
Register for firmware upload¶
A device driver registers for firmware upload by calling firmware_upload_register() . Among the parameter list is a name to identify the device under /sys/class/firmware. A user may initiate a firmware upload by echoing a 1 to the loading sysfs file for the target device. Next, the user writes the firmware image to the data sysfs file. After writing the firmware data, the user echos 0 to the loading sysfs file to signal completion. Echoing 0 to loading also triggers the transfer of the firmware to the lower-lever device driver in the context of a kernel worker thread.
To use the firmware upload API, write a driver that implements a set of ops. The probe function calls firmware_upload_register() and the remove function calls firmware_upload_unregister() such as:
static const struct fw_upload_ops m10bmc_ops = < .prepare = m10bmc_sec_prepare, .write = m10bmc_sec_write, .poll_complete = m10bmc_sec_poll_complete, .cancel = m10bmc_sec_cancel, .cleanup = m10bmc_sec_cleanup, >; static int m10bmc_sec_probe(struct platform_device *pdev) < const char *fw_name, *truncate; struct m10bmc_sec *sec; struct fw_upload *fwl; unsigned int len; sec = devm_kzalloc(&pdev->dev, sizeof(*sec), GFP_KERNEL); if (!sec) return -ENOMEM; sec->dev = &pdev->dev; sec->m10bmc = dev_get_drvdata(pdev->dev.parent); dev_set_drvdata(&pdev->dev, sec); fw_name = dev_name(sec->dev); truncate = strstr(fw_name, ".auto"); len = (truncate) ? truncate - fw_name : strlen(fw_name); sec->fw_name = kmemdup_nul(fw_name, len, GFP_KERNEL); fwl = firmware_upload_register(THIS_MODULE, sec->dev, sec->fw_name, &m10bmc_ops, sec); if (IS_ERR(fwl)) < dev_err(sec->dev, "Firmware Upload driver failed to start\n"); kfree(sec->fw_name); return PTR_ERR(fwl); > sec->fwl = fwl; return 0; > static int m10bmc_sec_remove(struct platform_device *pdev) < struct m10bmc_sec *sec = dev_get_drvdata(&pdev->dev); firmware_upload_unregister(sec->fwl); kfree(sec->fw_name); return 0; >
firmware_upload_register¶
struct fw_upload * firmware_upload_register ( struct module * module , struct device * parent , const char * name , const struct fw_upload_ops * ops , void * dd_handle ) ¶
register for the firmware upload sysfs API
kernel module of this device
parent device instantiating firmware upload
firmware name to be associated with this device
const struct fw_upload_ops *ops
pointer to structure of firmware upload ops
pointer to parent driver private data
name must be unique among all users of firmware upload. The firmware sysfs files for this device will be found at /sys/class/firmware/name.
struct fw_upload pointer or ERR_PTR()
firmware_upload_unregister¶
Unregister firmware upload interface
struct fw_upload *fw_upload
pointer to struct fw_upload
Firmware Upload Ops¶
device specific operations to support firmware upload
Required: Prepare secure update
Required: The write() op receives the remaining size to be written and must return the actual size written or a negative error code. The write() op will be called repeatedly until all data is written.
Required: Check for the completion of the HW authentication/programming process.
Required: Request cancellation of update. This op is called from the context of a different kernel thread, so race conditions need to be considered.
Optional: Complements the prepare() function and is called at the completion of the update, on success or failure, if the prepare function succeeded.
Firmware Upload Progress Codes¶
The following progress codes are used internally by the firmware loader. Corresponding strings are reported through the status sysfs node that is described below and are documented in the ABI documentation.
firmware upload progress codes
there is no firmware upload in progress
worker thread is receiving firmware data
target device is preparing for firmware upload
data is being copied to the device
device is performing the firmware update
Maximum progress code marker
Firmware Upload Error Codes¶
The following error codes may be returned by the driver ops in case of failure:
firmware upload error codes
returned to indicate success
error signalled by hardware, see kernel log
SW timed out on handshake with HW/firmware
upload was cancelled by the user
there is an upload operation already in progress
invalid firmware image size
read or write to HW failed, see kernel log
FLASH device is approaching wear-out, wait & retry
Maximum error code marker
Sysfs Attributes¶
In addition to the loading and data sysfs files, there are additional sysfs files to monitor the status of the data transfer to the target device and to determine the final pass/fail status of the transfer. Depending on the device and the size of the firmware image, a firmware update could take milliseconds or minutes.
The additional sysfs files are:
- status — provides an indication of the progress of a firmware update
- error — provides error information for a failed firmware update
- remaining_size — tracks the data transfer portion of an update
- cancel — echo 1 to this file to cancel the update