Skip to content

Commit e4c2c0f

Browse files
jwrdegoedegregkh
authored andcommitted
firmware: Add new platform fallback mechanism and firmware_request_platform()
In some cases the platform's main firmware (e.g. the UEFI fw) may contain an embedded copy of device firmware which needs to be (re)loaded into the peripheral. Normally such firmware would be part of linux-firmware, but in some cases this is not feasible, for 2 reasons: 1) The firmware is customized for a specific use-case of the chipset / use with a specific hardware model, so we cannot have a single firmware file for the chipset. E.g. touchscreen controller firmwares are compiled specifically for the hardware model they are used with, as they are calibrated for a specific model digitizer. 2) Despite repeated attempts we have failed to get permission to redistribute the firmware. This is especially a problem with customized firmwares, these get created by the chip vendor for a specific ODM and the copyright may partially belong with the ODM, so the chip vendor cannot give a blanket permission to distribute these. This commit adds a new platform fallback mechanism to the firmware loader which will try to lookup a device fw copy embedded in the platform's main firmware if direct filesystem lookup fails. Drivers which need such embedded fw copies can enable this fallback mechanism by using the new firmware_request_platform() function. Note that for now this is only supported on EFI platforms and even on these platforms firmware_fallback_platform() only works if CONFIG_EFI_EMBEDDED_FIRMWARE is enabled (this gets selected by drivers which need this), in all other cases firmware_fallback_platform() simply always returns -ENOENT. Reported-by: Dave Olsthoorn <[email protected]> Suggested-by: Peter Jones <[email protected]> Acked-by: Luis Chamberlain <[email protected]> Signed-off-by: Hans de Goede <[email protected]> Link: https://lore.kernel.org/r/[email protected] Signed-off-by: Greg Kroah-Hartman <[email protected]>
1 parent 4445eb6 commit e4c2c0f

File tree

10 files changed

+198
-0
lines changed

10 files changed

+198
-0
lines changed

Documentation/driver-api/firmware/fallback-mechanisms.rst

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,3 +202,106 @@ the following file:
202202

203203
If you echo 0 into it means MAX_JIFFY_OFFSET will be used. The data type
204204
for the timeout is an int.
205+
206+
EFI embedded firmware fallback mechanism
207+
========================================
208+
209+
On some devices the system's EFI code / ROM may contain an embedded copy
210+
of firmware for some of the system's integrated peripheral devices and
211+
the peripheral's Linux device-driver needs to access this firmware.
212+
213+
Device drivers which need such firmware can use the
214+
firmware_request_platform() function for this, note that this is a
215+
separate fallback mechanism from the other fallback mechanisms and
216+
this does not use the sysfs interface.
217+
218+
A device driver which needs this can describe the firmware it needs
219+
using an efi_embedded_fw_desc struct:
220+
221+
.. kernel-doc:: include/linux/efi_embedded_fw.h
222+
:functions: efi_embedded_fw_desc
223+
224+
The EFI embedded-fw code works by scanning all EFI_BOOT_SERVICES_CODE memory
225+
segments for an eight byte sequence matching prefix; if the prefix is found it
226+
then does a sha256 over length bytes and if that matches makes a copy of length
227+
bytes and adds that to its list with found firmwares.
228+
229+
To avoid doing this somewhat expensive scan on all systems, dmi matching is
230+
used. Drivers are expected to export a dmi_system_id array, with each entries'
231+
driver_data pointing to an efi_embedded_fw_desc.
232+
233+
To register this array with the efi-embedded-fw code, a driver needs to:
234+
235+
1. Always be builtin to the kernel or store the dmi_system_id array in a
236+
separate object file which always gets builtin.
237+
238+
2. Add an extern declaration for the dmi_system_id array to
239+
include/linux/efi_embedded_fw.h.
240+
241+
3. Add the dmi_system_id array to the embedded_fw_table in
242+
drivers/firmware/efi/embedded-firmware.c wrapped in a #ifdef testing that
243+
the driver is being builtin.
244+
245+
4. Add "select EFI_EMBEDDED_FIRMWARE if EFI_STUB" to its Kconfig entry.
246+
247+
The firmware_request_platform() function will always first try to load firmware
248+
with the specified name directly from the disk, so the EFI embedded-fw can
249+
always be overridden by placing a file under /lib/firmware.
250+
251+
Note that:
252+
253+
1. The code scanning for EFI embedded-firmware runs near the end
254+
of start_kernel(), just before calling rest_init(). For normal drivers and
255+
subsystems using subsys_initcall() to register themselves this does not
256+
matter. This means that code running earlier cannot use EFI
257+
embedded-firmware.
258+
259+
2. At the moment the EFI embedded-fw code assumes that firmwares always start at
260+
an offset which is a multiple of 8 bytes, if this is not true for your case
261+
send in a patch to fix this.
262+
263+
3. At the moment the EFI embedded-fw code only works on x86 because other archs
264+
free EFI_BOOT_SERVICES_CODE before the EFI embedded-fw code gets a chance to
265+
scan it.
266+
267+
4. The current brute-force scanning of EFI_BOOT_SERVICES_CODE is an ad-hoc
268+
brute-force solution. There has been discussion to use the UEFI Platform
269+
Initialization (PI) spec's Firmware Volume protocol. This has been rejected
270+
because the FV Protocol relies on *internal* interfaces of the PI spec, and:
271+
1. The PI spec does not define peripheral firmware at all
272+
2. The internal interfaces of the PI spec do not guarantee any backward
273+
compatibility. Any implementation details in FV may be subject to change,
274+
and may vary system to system. Supporting the FV Protocol would be
275+
difficult as it is purposely ambiguous.
276+
277+
Example how to check for and extract embedded firmware
278+
------------------------------------------------------
279+
280+
To check for, for example Silead touchscreen controller embedded firmware,
281+
do the following:
282+
283+
1. Boot the system with efi=debug on the kernel commandline
284+
285+
2. cp /sys/kernel/debug/efi/boot_services_code? to your home dir
286+
287+
3. Open the boot_services_code? files in a hex-editor, search for the
288+
magic prefix for Silead firmware: F0 00 00 00 02 00 00 00, this gives you
289+
the beginning address of the firmware inside the boot_services_code? file.
290+
291+
4. The firmware has a specific pattern, it starts with a 8 byte page-address,
292+
typically F0 00 00 00 02 00 00 00 for the first page followed by 32-bit
293+
word-address + 32-bit value pairs. With the word-address incrementing 4
294+
bytes (1 word) for each pair until a page is complete. A complete page is
295+
followed by a new page-address, followed by more word + value pairs. This
296+
leads to a very distinct pattern. Scroll down until this pattern stops,
297+
this gives you the end of the firmware inside the boot_services_code? file.
298+
299+
5. "dd if=boot_services_code? of=firmware bs=1 skip=<begin-addr> count=<len>"
300+
will extract the firmware for you. Inspect the firmware file in a
301+
hexeditor to make sure you got the dd parameters correct.
302+
303+
6. Copy it to /lib/firmware under the expected name to test it.
304+
305+
7. If the extracted firmware works, you can use the found info to fill an
306+
efi_embedded_fw_desc struct to describe it, run "sha256sum firmware"
307+
to get the sha256sum to put in the sha256 field.

Documentation/driver-api/firmware/lookup-order.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ a driver issues a firmware API call.
1212
return it immediately
1313
* The ''Direct filesystem lookup'' is performed next, if found we
1414
return it immediately
15+
* The ''Platform firmware fallback'' is performed next, but only when
16+
firmware_request_platform() is used, if found we return it immediately
1517
* If no firmware has been found and the fallback mechanism was enabled
1618
the sysfs interface is created. After this either a kobject uevent
1719
is issued or the custom firmware loading is relied upon for firmware

Documentation/driver-api/firmware/request_firmware.rst

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,11 @@ firmware_request_nowarn
2525
.. kernel-doc:: drivers/base/firmware_loader/main.c
2626
:functions: firmware_request_nowarn
2727

28+
firmware_request_platform
29+
-------------------------
30+
.. kernel-doc:: drivers/base/firmware_loader/main.c
31+
:functions: firmware_request_platform
32+
2833
request_firmware_direct
2934
-----------------------
3035
.. kernel-doc:: drivers/base/firmware_loader/main.c

drivers/base/firmware_loader/Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,6 @@ obj-$(CONFIG_FW_LOADER_USER_HELPER) += fallback_table.o
55
obj-$(CONFIG_FW_LOADER) += firmware_class.o
66
firmware_class-objs := main.o
77
firmware_class-$(CONFIG_FW_LOADER_USER_HELPER) += fallback.o
8+
firmware_class-$(CONFIG_EFI_EMBEDDED_FIRMWARE) += fallback_platform.o
89

910
obj-y += builtin/

drivers/base/firmware_loader/fallback.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,4 +66,14 @@ static inline void unregister_sysfs_loader(void)
6666
}
6767
#endif /* CONFIG_FW_LOADER_USER_HELPER */
6868

69+
#ifdef CONFIG_EFI_EMBEDDED_FIRMWARE
70+
int firmware_fallback_platform(struct fw_priv *fw_priv, enum fw_opt opt_flags);
71+
#else
72+
static inline int firmware_fallback_platform(struct fw_priv *fw_priv,
73+
enum fw_opt opt_flags)
74+
{
75+
return -ENOENT;
76+
}
77+
#endif
78+
6979
#endif /* __FIRMWARE_FALLBACK_H */
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// SPDX-License-Identifier: GPL-2.0
2+
3+
#include <linux/efi_embedded_fw.h>
4+
#include <linux/property.h>
5+
#include <linux/security.h>
6+
#include <linux/vmalloc.h>
7+
8+
#include "fallback.h"
9+
#include "firmware.h"
10+
11+
int firmware_fallback_platform(struct fw_priv *fw_priv, enum fw_opt opt_flags)
12+
{
13+
const u8 *data;
14+
size_t size;
15+
int rc;
16+
17+
if (!(opt_flags & FW_OPT_FALLBACK_PLATFORM))
18+
return -ENOENT;
19+
20+
rc = security_kernel_load_data(LOADING_FIRMWARE_EFI_EMBEDDED);
21+
if (rc)
22+
return rc;
23+
24+
rc = efi_get_embedded_fw(fw_priv->fw_name, &data, &size);
25+
if (rc)
26+
return rc; /* rc == -ENOENT when the fw was not found */
27+
28+
fw_priv->data = vmalloc(size);
29+
if (!fw_priv->data)
30+
return -ENOMEM;
31+
32+
memcpy(fw_priv->data, data, size);
33+
fw_priv->size = size;
34+
fw_state_done(fw_priv);
35+
return 0;
36+
}

drivers/base/firmware_loader/firmware.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@
2929
* firmware caching mechanism.
3030
* @FW_OPT_NOFALLBACK_SYSFS: Disable the sysfs fallback mechanism. Takes
3131
* precedence over &FW_OPT_UEVENT and &FW_OPT_USERHELPER.
32+
* @FW_OPT_FALLBACK_PLATFORM: Enable fallback to device fw copy embedded in
33+
* the platform's main firmware. If both this fallback and the sysfs
34+
* fallback are enabled, then this fallback will be tried first.
3235
*/
3336
enum fw_opt {
3437
FW_OPT_UEVENT = BIT(0),
@@ -37,6 +40,7 @@ enum fw_opt {
3740
FW_OPT_NO_WARN = BIT(3),
3841
FW_OPT_NOCACHE = BIT(4),
3942
FW_OPT_NOFALLBACK_SYSFS = BIT(5),
43+
FW_OPT_FALLBACK_PLATFORM = BIT(6),
4044
};
4145

4246
enum fw_status {

drivers/base/firmware_loader/main.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,9 @@ _request_firmware(const struct firmware **firmware_p, const char *name,
778778
fw_decompress_xz);
779779
#endif
780780

781+
if (ret == -ENOENT)
782+
ret = firmware_fallback_platform(fw->priv, opt_flags);
783+
781784
if (ret) {
782785
if (!(opt_flags & FW_OPT_NO_WARN))
783786
dev_warn(device,
@@ -885,6 +888,30 @@ int request_firmware_direct(const struct firmware **firmware_p,
885888
}
886889
EXPORT_SYMBOL_GPL(request_firmware_direct);
887890

891+
/**
892+
* firmware_request_platform() - request firmware with platform-fw fallback
893+
* @firmware: pointer to firmware image
894+
* @name: name of firmware file
895+
* @device: device for which firmware is being loaded
896+
*
897+
* This function is similar in behaviour to request_firmware, except that if
898+
* direct filesystem lookup fails, it will fallback to looking for a copy of the
899+
* requested firmware embedded in the platform's main (e.g. UEFI) firmware.
900+
**/
901+
int firmware_request_platform(const struct firmware **firmware,
902+
const char *name, struct device *device)
903+
{
904+
int ret;
905+
906+
/* Need to pin this module until return */
907+
__module_get(THIS_MODULE);
908+
ret = _request_firmware(firmware, name, device, NULL, 0,
909+
FW_OPT_UEVENT | FW_OPT_FALLBACK_PLATFORM);
910+
module_put(THIS_MODULE);
911+
return ret;
912+
}
913+
EXPORT_SYMBOL_GPL(firmware_request_platform);
914+
888915
/**
889916
* firmware_request_cache() - cache firmware for suspend so resume can use it
890917
* @name: name of firmware file

include/linux/firmware.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@ int request_firmware(const struct firmware **fw, const char *name,
4444
struct device *device);
4545
int firmware_request_nowarn(const struct firmware **fw, const char *name,
4646
struct device *device);
47+
int firmware_request_platform(const struct firmware **fw, const char *name,
48+
struct device *device);
4749
int request_firmware_nowait(
4850
struct module *module, bool uevent,
4951
const char *name, struct device *device, gfp_t gfp, void *context,
@@ -69,6 +71,13 @@ static inline int firmware_request_nowarn(const struct firmware **fw,
6971
return -EINVAL;
7072
}
7173

74+
static inline int firmware_request_platform(const struct firmware **fw,
75+
const char *name,
76+
struct device *device)
77+
{
78+
return -EINVAL;
79+
}
80+
7281
static inline int request_firmware_nowait(
7382
struct module *module, bool uevent,
7483
const char *name, struct device *device, gfp_t gfp, void *context,

include/linux/fs.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2982,6 +2982,7 @@ extern int do_pipe_flags(int *, int);
29822982
id(UNKNOWN, unknown) \
29832983
id(FIRMWARE, firmware) \
29842984
id(FIRMWARE_PREALLOC_BUFFER, firmware) \
2985+
id(FIRMWARE_EFI_EMBEDDED, firmware) \
29852986
id(MODULE, kernel-module) \
29862987
id(KEXEC_IMAGE, kexec-image) \
29872988
id(KEXEC_INITRAMFS, kexec-initramfs) \

0 commit comments

Comments
 (0)