diff options
| author | Niklas Cassel <cassel@kernel.org> | 2026-09-16 15:00:32 +0200 |
|---|---|---|
| committer | Niklas Cassel <cassel@kernel.org> | 2026-09-16 16:18:00 +0200 |
| commit | 8d836581f9b1f57ceaa2b47654754ef1260b410b (patch) | |
| tree | 1dbd4d0dfb99c2e238356cbe947e5b0be3e3a571 /scripts/mod | |
| download | linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.tar.gz linux-stable-8d836581f9b1f57ceaa2b47654754ef1260b410b.zip | |
ata: libata-scsi: fix ata_dsm_trim_pages() kernel-docgrafted
make htmldocs fails with:
Documentation/driver-api/libata:604: ./drivers/ata/libata-scsi.c:2301:
ERROR: Unexpected indentation. [docutils]
The Return: section of ata_dsm_trim_pages() ends its first sentence with
a colon and continues with an indented bullet list. reStructuredText
requires a blank line before an indented block, so docutils chokes on
the list.
Simply adding the missing blank line does not work either: Return: is a
kernel-doc "special section", which is terminated by the first blank
line, so the bullet list would end up in the Description section,
detached from the sentence introducing it.
Spell the two bounds out as prose instead, so that the Return: section
stays self-contained. Documentation-only change.
Fixes: e64e6b5dc867 ("ata: libata-scsi: scale DSM TRIM payload by MAX PAGES PER DSM COMMAND")
Reported-by: Thomas Huth <thuth@redhat.com>
Closes: https://lore.kernel.org/linux-ide/b0a0b8e8-cc4f-4c7b-8bc5-fee0712d405a@redhat.com/
Reviewed-by: Damien Le Moal <dlemoal@kernel.org>
Reviewed-by: Thomas Huth <thuth@redhat.com>
Link: https://lore.kernel.org/r/20260916130031.29990-2-cassel@kernel.org
Signed-off-by: Niklas Cassel <cassel@kernel.org>
Diffstat (limited to 'scripts/mod')
| -rw-r--r-- | scripts/mod/.gitignore | 5 | ||||
| -rw-r--r-- | scripts/mod/Makefile | 27 | ||||
| -rw-r--r-- | scripts/mod/devicetable-offsets.c | 288 | ||||
| -rw-r--r-- | scripts/mod/empty.c | 1 | ||||
| -rw-r--r-- | scripts/mod/file2alias.c | 1610 | ||||
| -rw-r--r-- | scripts/mod/mk_elfconfig.c | 32 | ||||
| -rw-r--r-- | scripts/mod/modpost.c | 2420 | ||||
| -rw-r--r-- | scripts/mod/modpost.h | 244 | ||||
| -rw-r--r-- | scripts/mod/sumversion.c | 413 | ||||
| -rw-r--r-- | scripts/mod/symsearch.c | 199 |
10 files changed, 5239 insertions, 0 deletions
diff --git a/scripts/mod/.gitignore b/scripts/mod/.gitignore new file mode 100644 index 000000000..0465ec33c --- /dev/null +++ b/scripts/mod/.gitignore @@ -0,0 +1,5 @@ +# SPDX-License-Identifier: GPL-2.0-only +/devicetable-offsets.h +/elfconfig.h +/mk_elfconfig +/modpost diff --git a/scripts/mod/Makefile b/scripts/mod/Makefile new file mode 100644 index 000000000..c729bc936 --- /dev/null +++ b/scripts/mod/Makefile @@ -0,0 +1,27 @@ +# SPDX-License-Identifier: GPL-2.0 +CFLAGS_REMOVE_empty.o += $(CC_FLAGS_LTO) + +hostprogs-always-y += modpost mk_elfconfig +always-y += empty.o + +modpost-objs := modpost.o file2alias.o sumversion.o symsearch.o + +devicetable-offsets-file := devicetable-offsets.h + +$(obj)/$(devicetable-offsets-file): $(obj)/devicetable-offsets.s FORCE + $(call filechk,offsets,__DEVICETABLE_OFFSETS_H__) + +targets += $(devicetable-offsets-file) devicetable-offsets.s + +# dependencies on generated files need to be listed explicitly + +$(obj)/modpost.o $(obj)/file2alias.o $(obj)/sumversion.o $(obj)/symsearch.o: $(obj)/elfconfig.h +$(obj)/file2alias.o: $(obj)/$(devicetable-offsets-file) + +quiet_cmd_elfconfig = MKELF $@ + cmd_elfconfig = $(obj)/mk_elfconfig < $< > $@ + +$(obj)/elfconfig.h: $(obj)/empty.o $(obj)/mk_elfconfig FORCE + $(call if_changed,elfconfig) + +targets += elfconfig.h diff --git a/scripts/mod/devicetable-offsets.c b/scripts/mod/devicetable-offsets.c new file mode 100644 index 000000000..b4178c42d --- /dev/null +++ b/scripts/mod/devicetable-offsets.c @@ -0,0 +1,288 @@ +// SPDX-License-Identifier: GPL-2.0 +#define COMPILE_OFFSETS +#include <linux/kbuild.h> +#include <linux/mod_devicetable.h> + +#define DEVID(devid) DEFINE(SIZE_##devid, sizeof(struct devid)) +#define DEVID_FIELD(devid, field) \ + DEFINE(OFF_##devid##_##field, offsetof(struct devid, field)) + +int main(void) +{ + DEVID(usb_device_id); + DEVID_FIELD(usb_device_id, match_flags); + DEVID_FIELD(usb_device_id, idVendor); + DEVID_FIELD(usb_device_id, idProduct); + DEVID_FIELD(usb_device_id, bcdDevice_lo); + DEVID_FIELD(usb_device_id, bcdDevice_hi); + DEVID_FIELD(usb_device_id, bDeviceClass); + DEVID_FIELD(usb_device_id, bDeviceSubClass); + DEVID_FIELD(usb_device_id, bDeviceProtocol); + DEVID_FIELD(usb_device_id, bInterfaceClass); + DEVID_FIELD(usb_device_id, bInterfaceSubClass); + DEVID_FIELD(usb_device_id, bInterfaceProtocol); + DEVID_FIELD(usb_device_id, bInterfaceNumber); + + DEVID(hid_device_id); + DEVID_FIELD(hid_device_id, bus); + DEVID_FIELD(hid_device_id, group); + DEVID_FIELD(hid_device_id, vendor); + DEVID_FIELD(hid_device_id, product); + + DEVID(ieee1394_device_id); + DEVID_FIELD(ieee1394_device_id, match_flags); + DEVID_FIELD(ieee1394_device_id, vendor_id); + DEVID_FIELD(ieee1394_device_id, model_id); + DEVID_FIELD(ieee1394_device_id, specifier_id); + DEVID_FIELD(ieee1394_device_id, version); + + DEVID(pci_device_id); + DEVID_FIELD(pci_device_id, vendor); + DEVID_FIELD(pci_device_id, device); + DEVID_FIELD(pci_device_id, subvendor); + DEVID_FIELD(pci_device_id, subdevice); + DEVID_FIELD(pci_device_id, class); + DEVID_FIELD(pci_device_id, class_mask); + DEVID_FIELD(pci_device_id, override_only); + + DEVID(ccw_device_id); + DEVID_FIELD(ccw_device_id, match_flags); + DEVID_FIELD(ccw_device_id, cu_type); + DEVID_FIELD(ccw_device_id, cu_model); + DEVID_FIELD(ccw_device_id, dev_type); + DEVID_FIELD(ccw_device_id, dev_model); + + DEVID(ap_device_id); + DEVID_FIELD(ap_device_id, dev_type); + + DEVID(css_device_id); + DEVID_FIELD(css_device_id, type); + + DEVID(serio_device_id); + DEVID_FIELD(serio_device_id, type); + DEVID_FIELD(serio_device_id, proto); + DEVID_FIELD(serio_device_id, id); + DEVID_FIELD(serio_device_id, extra); + + DEVID(acpi_device_id); + DEVID_FIELD(acpi_device_id, id); + DEVID_FIELD(acpi_device_id, cls); + DEVID_FIELD(acpi_device_id, cls_msk); + + DEVID(pnp_device_id); + DEVID_FIELD(pnp_device_id, id); + + DEVID(pnp_card_device_id); + DEVID_FIELD(pnp_card_device_id, devs); + + DEVID(pcmcia_device_id); + DEVID_FIELD(pcmcia_device_id, match_flags); + DEVID_FIELD(pcmcia_device_id, manf_id); + DEVID_FIELD(pcmcia_device_id, card_id); + DEVID_FIELD(pcmcia_device_id, func_id); + DEVID_FIELD(pcmcia_device_id, function); + DEVID_FIELD(pcmcia_device_id, device_no); + DEVID_FIELD(pcmcia_device_id, prod_id_hash); + + DEVID(of_device_id); + DEVID_FIELD(of_device_id, name); + DEVID_FIELD(of_device_id, type); + DEVID_FIELD(of_device_id, compatible); + + DEVID(vio_device_id); + DEVID_FIELD(vio_device_id, type); + DEVID_FIELD(vio_device_id, compat); + + DEVID(input_device_id); + DEVID_FIELD(input_device_id, flags); + DEVID_FIELD(input_device_id, bustype); + DEVID_FIELD(input_device_id, vendor); + DEVID_FIELD(input_device_id, product); + DEVID_FIELD(input_device_id, version); + DEVID_FIELD(input_device_id, evbit); + DEVID_FIELD(input_device_id, keybit); + DEVID_FIELD(input_device_id, relbit); + DEVID_FIELD(input_device_id, absbit); + DEVID_FIELD(input_device_id, mscbit); + DEVID_FIELD(input_device_id, ledbit); + DEVID_FIELD(input_device_id, sndbit); + DEVID_FIELD(input_device_id, ffbit); + DEVID_FIELD(input_device_id, swbit); + + DEVID(eisa_device_id); + DEVID_FIELD(eisa_device_id, sig); + + DEVID(parisc_device_id); + DEVID_FIELD(parisc_device_id, hw_type); + DEVID_FIELD(parisc_device_id, hversion); + DEVID_FIELD(parisc_device_id, hversion_rev); + DEVID_FIELD(parisc_device_id, sversion); + + DEVID(sdio_device_id); + DEVID_FIELD(sdio_device_id, class); + DEVID_FIELD(sdio_device_id, vendor); + DEVID_FIELD(sdio_device_id, device); + + DEVID(ssb_device_id); + DEVID_FIELD(ssb_device_id, vendor); + DEVID_FIELD(ssb_device_id, coreid); + DEVID_FIELD(ssb_device_id, revision); + + DEVID(bcma_device_id); + DEVID_FIELD(bcma_device_id, manuf); + DEVID_FIELD(bcma_device_id, id); + DEVID_FIELD(bcma_device_id, rev); + DEVID_FIELD(bcma_device_id, class); + + DEVID(virtio_device_id); + DEVID_FIELD(virtio_device_id, device); + DEVID_FIELD(virtio_device_id, vendor); + + DEVID(hv_vmbus_device_id); + DEVID_FIELD(hv_vmbus_device_id, guid); + + DEVID(rpmsg_device_id); + DEVID_FIELD(rpmsg_device_id, name); + + DEVID(i2c_device_id); + DEVID_FIELD(i2c_device_id, name); + + DEVID(i3c_device_id); + DEVID_FIELD(i3c_device_id, match_flags); + DEVID_FIELD(i3c_device_id, dcr); + DEVID_FIELD(i3c_device_id, manuf_id); + DEVID_FIELD(i3c_device_id, part_id); + DEVID_FIELD(i3c_device_id, extra_info); + + DEVID(slim_device_id); + DEVID_FIELD(slim_device_id, manf_id); + DEVID_FIELD(slim_device_id, prod_code); + + DEVID(spi_device_id); + DEVID_FIELD(spi_device_id, name); + + DEVID(dmi_system_id); + DEVID_FIELD(dmi_system_id, matches); + + DEVID(platform_device_id); + DEVID_FIELD(platform_device_id, name); + + DEVID(mdio_device_id); + DEVID_FIELD(mdio_device_id, phy_id); + DEVID_FIELD(mdio_device_id, phy_id_mask); + + DEVID(zorro_device_id); + DEVID_FIELD(zorro_device_id, id); + + DEVID(isapnp_device_id); + DEVID_FIELD(isapnp_device_id, vendor); + DEVID_FIELD(isapnp_device_id, function); + + DEVID(ipack_device_id); + DEVID_FIELD(ipack_device_id, format); + DEVID_FIELD(ipack_device_id, vendor); + DEVID_FIELD(ipack_device_id, device); + + DEVID(amba_id); + DEVID_FIELD(amba_id, id); + DEVID_FIELD(amba_id, mask); + + DEVID(mips_cdmm_device_id); + DEVID_FIELD(mips_cdmm_device_id, type); + + DEVID(x86_cpu_id); + DEVID_FIELD(x86_cpu_id, feature); + DEVID_FIELD(x86_cpu_id, family); + DEVID_FIELD(x86_cpu_id, model); + DEVID_FIELD(x86_cpu_id, vendor); + + DEVID(cpu_feature); + DEVID_FIELD(cpu_feature, feature); + + DEVID(mcb_device_id); + DEVID_FIELD(mcb_device_id, device); + + DEVID(mei_cl_device_id); + DEVID_FIELD(mei_cl_device_id, name); + DEVID_FIELD(mei_cl_device_id, uuid); + DEVID_FIELD(mei_cl_device_id, version); + + DEVID(rio_device_id); + DEVID_FIELD(rio_device_id, did); + DEVID_FIELD(rio_device_id, vid); + DEVID_FIELD(rio_device_id, asm_did); + DEVID_FIELD(rio_device_id, asm_vid); + + DEVID(ulpi_device_id); + DEVID_FIELD(ulpi_device_id, vendor); + DEVID_FIELD(ulpi_device_id, product); + + DEVID(hda_device_id); + DEVID_FIELD(hda_device_id, vendor_id); + DEVID_FIELD(hda_device_id, rev_id); + DEVID_FIELD(hda_device_id, api_version); + + DEVID(sdw_device_id); + DEVID_FIELD(sdw_device_id, mfg_id); + DEVID_FIELD(sdw_device_id, part_id); + DEVID_FIELD(sdw_device_id, sdw_version); + DEVID_FIELD(sdw_device_id, class_id); + + DEVID(fsl_mc_device_id); + DEVID_FIELD(fsl_mc_device_id, vendor); + DEVID_FIELD(fsl_mc_device_id, obj_type); + + DEVID(tb_service_id); + DEVID_FIELD(tb_service_id, match_flags); + DEVID_FIELD(tb_service_id, protocol_key); + DEVID_FIELD(tb_service_id, protocol_id); + DEVID_FIELD(tb_service_id, protocol_version); + DEVID_FIELD(tb_service_id, protocol_revision); + + DEVID(typec_device_id); + DEVID_FIELD(typec_device_id, svid); + + DEVID(tee_client_device_id); + DEVID_FIELD(tee_client_device_id, uuid); + + DEVID(wmi_device_id); + DEVID_FIELD(wmi_device_id, guid_string); + + DEVID(mhi_device_id); + DEVID_FIELD(mhi_device_id, chan); + + DEVID(auxiliary_device_id); + DEVID_FIELD(auxiliary_device_id, name); + + DEVID(ssam_device_id); + DEVID_FIELD(ssam_device_id, match_flags); + DEVID_FIELD(ssam_device_id, domain); + DEVID_FIELD(ssam_device_id, category); + DEVID_FIELD(ssam_device_id, target); + DEVID_FIELD(ssam_device_id, instance); + DEVID_FIELD(ssam_device_id, function); + + DEVID(dfl_device_id); + DEVID_FIELD(dfl_device_id, type); + DEVID_FIELD(dfl_device_id, feature_id); + + DEVID(ishtp_device_id); + DEVID_FIELD(ishtp_device_id, guid); + + DEVID(cdx_device_id); + DEVID_FIELD(cdx_device_id, vendor); + DEVID_FIELD(cdx_device_id, device); + DEVID_FIELD(cdx_device_id, subvendor); + DEVID_FIELD(cdx_device_id, subdevice); + DEVID_FIELD(cdx_device_id, class); + DEVID_FIELD(cdx_device_id, class_mask); + DEVID_FIELD(cdx_device_id, override_only); + + DEVID(vchiq_device_id); + DEVID_FIELD(vchiq_device_id, name); + + DEVID(coreboot_device_id); + DEVID_FIELD(coreboot_device_id, tag); + + return 0; +} diff --git a/scripts/mod/empty.c b/scripts/mod/empty.c new file mode 100644 index 000000000..49839cc4f --- /dev/null +++ b/scripts/mod/empty.c @@ -0,0 +1 @@ +/* empty file to figure out endianness / word size */ diff --git a/scripts/mod/file2alias.c b/scripts/mod/file2alias.c new file mode 100644 index 000000000..8d36c74de --- /dev/null +++ b/scripts/mod/file2alias.c @@ -0,0 +1,1610 @@ +/* Simple code to turn various tables in an ELF file into alias definitions. + * This deals with kernel datastructures where they should be + * dealt with: in the kernel source. + * + * Copyright 2002-2003 Rusty Russell, IBM Corporation + * 2003 Kai Germaschewski + * + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + */ + +#include <stdarg.h> +#include <stdio.h> + +#include "list.h" +#include "xalloc.h" + +#include "modpost.h" +#include "devicetable-offsets.h" + +/* We use the ELF typedefs for kernel_ulong_t but bite the bullet and + * use either stdint.h or inttypes.h for the rest. */ +#if KERNEL_ELFCLASS == ELFCLASS32 +typedef Elf32_Addr kernel_ulong_t; +#define BITS_PER_LONG 32 +#else +typedef Elf64_Addr kernel_ulong_t; +#define BITS_PER_LONG 64 +#endif +#ifdef __sun__ +#include <inttypes.h> +#else +#include <stdint.h> +#endif + +#include <ctype.h> +#include <stdbool.h> + +/** + * module_alias_printf - add auto-generated MODULE_ALIAS() + * + * @mod: module + * @append_wildcard: append '*' for future extension if not exist yet + * @fmt: printf(3)-like format + */ +static void __attribute__((format (printf, 3, 4))) +module_alias_printf(struct module *mod, bool append_wildcard, + const char *fmt, ...) +{ + struct module_alias *new, *als; + size_t len; + int n; + va_list ap; + + /* Determine required size. */ + va_start(ap, fmt); + n = vsnprintf(NULL, 0, fmt, ap); + va_end(ap); + + if (n < 0) { + error("vsnprintf failed\n"); + return; + } + + len = n + 1; /* extra byte for '\0' */ + + if (append_wildcard) + len++; /* extra byte for '*' */ + + new = xmalloc(sizeof(*new) + len); + + /* Now, really print it to the allocated buffer */ + va_start(ap, fmt); + n = vsnprintf(new->str, len, fmt, ap); + va_end(ap); + + if (n < 0) { + error("vsnprintf failed\n"); + free(new); + return; + } + + if (append_wildcard && (n == 0 || new->str[n - 1] != '*')) { + new->str[n] = '*'; + new->str[n + 1] = '\0'; + } + + /* avoid duplication */ + list_for_each_entry(als, &mod->aliases, node) { + if (!strcmp(als->str, new->str)) { + free(new); + return; + } + } + + new->builtin_modname = NULL; + list_add_tail(&new->node, &mod->aliases); +} + +typedef uint32_t __u32; +typedef uint16_t __u16; +typedef unsigned char __u8; + +/* UUID types for backward compatibility, don't use in new code */ +typedef struct { + __u8 b[16]; +} guid_t; + +typedef struct { + __u8 b[16]; +} uuid_t; + +#define UUID_STRING_LEN 36 + +/* MEI UUID type, don't use anywhere else */ +typedef struct { + __u8 b[16]; +} uuid_le; + +/* Big exception to the "don't include kernel headers into userspace, which + * even potentially has different endianness and word sizes, since + * we handle those differences explicitly below */ +#include "../../include/linux/mod_devicetable.h" + +struct devtable { + const char *device_id; + unsigned long id_size; + void (*do_entry)(struct module *mod, void *symval); +}; + +/* Define a variable f that holds the value of field f of struct devid + * based at address m. + */ +#define DEF_FIELD(m, devid, f) \ + typeof(((struct devid *)0)->f) f = \ + get_unaligned_native((typeof(f) *)((m) + OFF_##devid##_##f)) + +/* Define a variable f that holds the address of field f of struct devid + * based at address m. Due to the way typeof works, for a field of type + * T[N] the variable has type T(*)[N], _not_ T*. + */ +#define DEF_FIELD_ADDR(m, devid, f) \ + typeof(((struct devid *)0)->f) *f = ((m) + OFF_##devid##_##f) + +#define ADD(str, sep, cond, field) \ +do { \ + strcat(str, sep); \ + if (cond) \ + sprintf(str + strlen(str), \ + sizeof(field) == 1 ? "%02X" : \ + sizeof(field) == 2 ? "%04X" : \ + sizeof(field) == 4 ? "%08X" : "", \ + field); \ + else \ + sprintf(str + strlen(str), "*"); \ +} while(0) + +static inline void add_uuid(char *str, uuid_le uuid) +{ + int len = strlen(str); + + sprintf(str + len, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid.b[3], uuid.b[2], uuid.b[1], uuid.b[0], + uuid.b[5], uuid.b[4], uuid.b[7], uuid.b[6], + uuid.b[8], uuid.b[9], uuid.b[10], uuid.b[11], + uuid.b[12], uuid.b[13], uuid.b[14], uuid.b[15]); +} + +static inline void add_guid(char *str, guid_t guid) +{ + int len = strlen(str); + + sprintf(str + len, "%02X%02X%02X%02X-%02X%02X-%02X%02X-%02X%02X-%02X%02X%02X%02X%02X%02X", + guid.b[3], guid.b[2], guid.b[1], guid.b[0], + guid.b[5], guid.b[4], guid.b[7], guid.b[6], + guid.b[8], guid.b[9], guid.b[10], guid.b[11], + guid.b[12], guid.b[13], guid.b[14], guid.b[15]); +} + +/* USB is special because the bcdDevice can be matched against a numeric range */ +/* Looks like "usb:vNpNdNdcNdscNdpNicNiscNipNinN" */ +static void do_usb_entry(void *symval, + unsigned int bcdDevice_initial, int bcdDevice_initial_digits, + unsigned char range_lo, unsigned char range_hi, + unsigned char max, struct module *mod) +{ + char alias[500]; + DEF_FIELD(symval, usb_device_id, match_flags); + DEF_FIELD(symval, usb_device_id, idVendor); + DEF_FIELD(symval, usb_device_id, idProduct); + DEF_FIELD(symval, usb_device_id, bcdDevice_lo); + DEF_FIELD(symval, usb_device_id, bDeviceClass); + DEF_FIELD(symval, usb_device_id, bDeviceSubClass); + DEF_FIELD(symval, usb_device_id, bDeviceProtocol); + DEF_FIELD(symval, usb_device_id, bInterfaceClass); + DEF_FIELD(symval, usb_device_id, bInterfaceSubClass); + DEF_FIELD(symval, usb_device_id, bInterfaceProtocol); + DEF_FIELD(symval, usb_device_id, bInterfaceNumber); + + strcpy(alias, "usb:"); + ADD(alias, "v", match_flags&USB_DEVICE_ID_MATCH_VENDOR, + idVendor); + ADD(alias, "p", match_flags&USB_DEVICE_ID_MATCH_PRODUCT, + idProduct); + + strcat(alias, "d"); + if (bcdDevice_initial_digits) + sprintf(alias + strlen(alias), "%0*X", + bcdDevice_initial_digits, bcdDevice_initial); + if (range_lo == range_hi) + sprintf(alias + strlen(alias), "%X", range_lo); + else if (range_lo > 0 || range_hi < max) { + if (range_lo > 0x9 || range_hi < 0xA) + sprintf(alias + strlen(alias), + "[%X-%X]", + range_lo, + range_hi); + else { + sprintf(alias + strlen(alias), + range_lo < 0x9 ? "[%X-9" : "[%X", + range_lo); + sprintf(alias + strlen(alias), + range_hi > 0xA ? "A-%X]" : "%X]", + range_hi); + } + } + if (bcdDevice_initial_digits < (sizeof(bcdDevice_lo) * 2 - 1)) + strcat(alias, "*"); + + ADD(alias, "dc", match_flags&USB_DEVICE_ID_MATCH_DEV_CLASS, + bDeviceClass); + ADD(alias, "dsc", match_flags&USB_DEVICE_ID_MATCH_DEV_SUBCLASS, + bDeviceSubClass); + ADD(alias, "dp", match_flags&USB_DEVICE_ID_MATCH_DEV_PROTOCOL, + bDeviceProtocol); + ADD(alias, "ic", match_flags&USB_DEVICE_ID_MATCH_INT_CLASS, + bInterfaceClass); + ADD(alias, "isc", match_flags&USB_DEVICE_ID_MATCH_INT_SUBCLASS, + bInterfaceSubClass); + ADD(alias, "ip", match_flags&USB_DEVICE_ID_MATCH_INT_PROTOCOL, + bInterfaceProtocol); + ADD(alias, "in", match_flags&USB_DEVICE_ID_MATCH_INT_NUMBER, + bInterfaceNumber); + + module_alias_printf(mod, true, "%s", alias); +} + +/* Handles increment/decrement of BCD formatted integers */ +/* Returns the previous value, so it works like i++ or i-- */ +static unsigned int incbcd(unsigned int *bcd, + int inc, + unsigned char max, + size_t chars) +{ + unsigned int init = *bcd, i, j; + unsigned long long c, dec = 0; + + /* If bcd is not in BCD format, just increment */ + if (max > 0x9) { + *bcd += inc; + return init; + } + + /* Convert BCD to Decimal */ + for (i=0 ; i < chars ; i++) { + c = (*bcd >> (i << 2)) & 0xf; + c = c > 9 ? 9 : c; /* force to bcd just in case */ + for (j=0 ; j < i ; j++) + c = c * 10; + dec += c; + } + + /* Do our increment/decrement */ + dec += inc; + *bcd = 0; + + /* Convert back to BCD */ + for (i=0 ; i < chars ; i++) { + for (c=1,j=0 ; j < i ; j++) + c = c * 10; + c = (dec / c) % 10; + *bcd += c << (i << 2); + } + return init; +} + +static void do_usb_entry_multi(struct module *mod, void *symval) +{ + unsigned int devlo, devhi; + unsigned char chi, clo, max; + int ndigits; + + DEF_FIELD(symval, usb_device_id, match_flags); + DEF_FIELD(symval, usb_device_id, idVendor); + DEF_FIELD(symval, usb_device_id, idProduct); + DEF_FIELD(symval, usb_device_id, bcdDevice_lo); + DEF_FIELD(symval, usb_device_id, bcdDevice_hi); + DEF_FIELD(symval, usb_device_id, bDeviceClass); + DEF_FIELD(symval, usb_device_id, bInterfaceClass); + + devlo = match_flags & USB_DEVICE_ID_MATCH_DEV_LO ? + bcdDevice_lo : 0x0U; + devhi = match_flags & USB_DEVICE_ID_MATCH_DEV_HI ? + bcdDevice_hi : ~0x0U; + + /* Figure out if this entry is in bcd or hex format */ + max = 0x9; /* Default to decimal format */ + for (ndigits = 0 ; ndigits < sizeof(bcdDevice_lo) * 2 ; ndigits++) { + clo = (devlo >> (ndigits << 2)) & 0xf; + chi = ((devhi > 0x9999 ? 0x9999 : devhi) >> (ndigits << 2)) & 0xf; + if (clo > max || chi > max) { + max = 0xf; + break; + } + } + + /* + * Some modules (visor) have empty slots as placeholder for + * run-time specification that results in catch-all alias + */ + if (!(idVendor | idProduct | bDeviceClass | bInterfaceClass)) + return; + + /* Convert numeric bcdDevice range into fnmatch-able pattern(s) */ + for (ndigits = sizeof(bcdDevice_lo) * 2 - 1; devlo <= devhi; ndigits--) { + clo = devlo & 0xf; + chi = devhi & 0xf; + if (chi > max) /* If we are in bcd mode, truncate if necessary */ + chi = max; + devlo >>= 4; + devhi >>= 4; + + if (devlo == devhi || !ndigits) { + do_usb_entry(symval, devlo, ndigits, clo, chi, max, mod); + break; + } + + if (clo > 0x0) + do_usb_entry(symval, + incbcd(&devlo, 1, max, + sizeof(bcdDevice_lo) * 2), + ndigits, clo, max, max, mod); + + if (chi < max) + do_usb_entry(symval, + incbcd(&devhi, -1, max, + sizeof(bcdDevice_lo) * 2), + ndigits, 0x0, chi, max, mod); + } +} + +static void do_of_entry(struct module *mod, void *symval) +{ + char alias[500]; + int len; + char *tmp; + + DEF_FIELD_ADDR(symval, of_device_id, name); + DEF_FIELD_ADDR(symval, of_device_id, type); + DEF_FIELD_ADDR(symval, of_device_id, compatible); + + len = sprintf(alias, "of:N%sT%s", (*name)[0] ? *name : "*", + (*type)[0] ? *type : "*"); + + if ((*compatible)[0]) + sprintf(&alias[len], "%sC%s", (*type)[0] ? "*" : "", + *compatible); + + /* Replace all whitespace with underscores */ + for (tmp = alias; tmp && *tmp; tmp++) + if (isspace(*tmp)) + *tmp = '_'; + + module_alias_printf(mod, false, "%s", alias); + module_alias_printf(mod, false, "%sC*", alias); +} + +/* Looks like: hid:bNvNpN */ +static void do_hid_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, hid_device_id, bus); + DEF_FIELD(symval, hid_device_id, group); + DEF_FIELD(symval, hid_device_id, vendor); + DEF_FIELD(symval, hid_device_id, product); + + ADD(alias, "b", bus != HID_BUS_ANY, bus); + ADD(alias, "g", group != HID_GROUP_ANY, group); + ADD(alias, "v", vendor != HID_ANY_ID, vendor); + ADD(alias, "p", product != HID_ANY_ID, product); + + module_alias_printf(mod, false, "hid:%s", alias); +} + +/* Looks like: ieee1394:venNmoNspNverN */ +static void do_ieee1394_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, ieee1394_device_id, match_flags); + DEF_FIELD(symval, ieee1394_device_id, vendor_id); + DEF_FIELD(symval, ieee1394_device_id, model_id); + DEF_FIELD(symval, ieee1394_device_id, specifier_id); + DEF_FIELD(symval, ieee1394_device_id, version); + + ADD(alias, "ven", match_flags & IEEE1394_MATCH_VENDOR_ID, + vendor_id); + ADD(alias, "mo", match_flags & IEEE1394_MATCH_MODEL_ID, + model_id); + ADD(alias, "sp", match_flags & IEEE1394_MATCH_SPECIFIER_ID, + specifier_id); + ADD(alias, "ver", match_flags & IEEE1394_MATCH_VERSION, + version); + + module_alias_printf(mod, true, "ieee1394:%s", alias); +} + +/* Looks like: pci:vNdNsvNsdNbcNscNiN or <prefix>_pci:vNdNsvNsdNbcNscNiN. */ +static void do_pci_entry(struct module *mod, void *symval) +{ + char alias[256]; + /* Class field can be divided into these three. */ + unsigned char baseclass, subclass, interface, + baseclass_mask, subclass_mask, interface_mask; + + DEF_FIELD(symval, pci_device_id, vendor); + DEF_FIELD(symval, pci_device_id, device); + DEF_FIELD(symval, pci_device_id, subvendor); + DEF_FIELD(symval, pci_device_id, subdevice); + DEF_FIELD(symval, pci_device_id, class); + DEF_FIELD(symval, pci_device_id, class_mask); + DEF_FIELD(symval, pci_device_id, override_only); + + switch (override_only) { + case 0: + strcpy(alias, "pci:"); + break; + case PCI_ID_F_VFIO_DRIVER_OVERRIDE: + strcpy(alias, "vfio_pci:"); + break; + default: + warn("Unknown PCI driver_override alias %08X\n", + override_only); + } + + ADD(alias, "v", vendor != PCI_ANY_ID, vendor); + ADD(alias, "d", device != PCI_ANY_ID, device); + ADD(alias, "sv", subvendor != PCI_ANY_ID, subvendor); + ADD(alias, "sd", subdevice != PCI_ANY_ID, subdevice); + + baseclass = (class) >> 16; + baseclass_mask = (class_mask) >> 16; + subclass = (class) >> 8; + subclass_mask = (class_mask) >> 8; + interface = class; + interface_mask = class_mask; + + if ((baseclass_mask != 0 && baseclass_mask != 0xFF) + || (subclass_mask != 0 && subclass_mask != 0xFF) + || (interface_mask != 0 && interface_mask != 0xFF)) { + warn("Can't handle masks in %s:%04X\n", + mod->name, class_mask); + return; + } + + ADD(alias, "bc", baseclass_mask == 0xFF, baseclass); + ADD(alias, "sc", subclass_mask == 0xFF, subclass); + ADD(alias, "i", interface_mask == 0xFF, interface); + + module_alias_printf(mod, true, "%s", alias); +} + +/* looks like: "ccw:tNmNdtNdmN" */ +static void do_ccw_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, ccw_device_id, match_flags); + DEF_FIELD(symval, ccw_device_id, cu_type); + DEF_FIELD(symval, ccw_device_id, cu_model); + DEF_FIELD(symval, ccw_device_id, dev_type); + DEF_FIELD(symval, ccw_device_id, dev_model); + + ADD(alias, "t", match_flags&CCW_DEVICE_ID_MATCH_CU_TYPE, + cu_type); + ADD(alias, "m", match_flags&CCW_DEVICE_ID_MATCH_CU_MODEL, + cu_model); + ADD(alias, "dt", match_flags&CCW_DEVICE_ID_MATCH_DEVICE_TYPE, + dev_type); + ADD(alias, "dm", match_flags&CCW_DEVICE_ID_MATCH_DEVICE_MODEL, + dev_model); + + module_alias_printf(mod, true, "ccw:%s", alias); +} + +/* looks like: "ap:tN" */ +static void do_ap_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, ap_device_id, dev_type); + + module_alias_printf(mod, false, "ap:t%02X*", dev_type); +} + +/* looks like: "css:tN" */ +static void do_css_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, css_device_id, type); + + module_alias_printf(mod, false, "css:t%01X", type); +} + +/* Looks like: "serio:tyNprNidNexN" */ +static void do_serio_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, serio_device_id, type); + DEF_FIELD(symval, serio_device_id, proto); + DEF_FIELD(symval, serio_device_id, id); + DEF_FIELD(symval, serio_device_id, extra); + + ADD(alias, "ty", type != SERIO_ANY, type); + ADD(alias, "pr", proto != SERIO_ANY, proto); + ADD(alias, "id", id != SERIO_ANY, id); + ADD(alias, "ex", extra != SERIO_ANY, extra); + + module_alias_printf(mod, true, "serio:%s", alias); +} + +/* looks like: "acpi:ACPI0003" or "acpi:PNP0C0B" or "acpi:LNXVIDEO" or + * "acpi:bbsspp" (bb=base-class, ss=sub-class, pp=prog-if) + * + * NOTE: Each driver should use one of the following : _HID, _CIDs + * or _CLS. Also, bb, ss, and pp can be substituted with ?? + * as don't care byte. + */ +static void do_acpi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, acpi_device_id, id); + DEF_FIELD(symval, acpi_device_id, cls); + DEF_FIELD(symval, acpi_device_id, cls_msk); + + if ((*id)[0]) + module_alias_printf(mod, false, "acpi*:%s:*", *id); + else { + char alias[256]; + int i, byte_shift, cnt = 0; + unsigned int msk; + + for (i = 1; i <= 3; i++) { + byte_shift = 8 * (3-i); + msk = (cls_msk >> byte_shift) & 0xFF; + if (msk) + sprintf(&alias[cnt], "%02x", + (cls >> byte_shift) & 0xFF); + else + sprintf(&alias[cnt], "??"); + cnt += 2; + } + module_alias_printf(mod, false, "acpi*:%s:*", alias); + } +} + +/* looks like: "pnp:dD" */ +static void do_pnp_device_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, pnp_device_id, id); + char acpi_id[sizeof(*id)]; + + /* fix broken pnp bus lowercasing */ + for (unsigned int i = 0; i < sizeof(acpi_id); i++) + acpi_id[i] = toupper((*id)[i]); + module_alias_printf(mod, false, "pnp:d%s*", *id); + module_alias_printf(mod, false, "acpi*:%s:*", acpi_id); +} + +/* looks like: "pnp:dD" for every device of the card */ +static void do_pnp_card_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, pnp_card_device_id, devs); + + for (unsigned int i = 0; i < PNP_MAX_DEVICES; i++) { + const char *id = (char *)(*devs)[i].id; + char acpi_id[PNP_ID_LEN]; + + if (!id[0]) + break; + + /* fix broken pnp bus lowercasing */ + for (unsigned int j = 0; j < sizeof(acpi_id); j++) + acpi_id[j] = toupper(id[j]); + + /* add an individual alias for every device entry */ + module_alias_printf(mod, false, "pnp:d%s*", id); + module_alias_printf(mod, false, "acpi*:%s:*", acpi_id); + } +} + +/* Looks like: pcmcia:mNcNfNfnNpfnNvaNvbNvcNvdN. */ +static void do_pcmcia_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, pcmcia_device_id, match_flags); + DEF_FIELD(symval, pcmcia_device_id, manf_id); + DEF_FIELD(symval, pcmcia_device_id, card_id); + DEF_FIELD(symval, pcmcia_device_id, func_id); + DEF_FIELD(symval, pcmcia_device_id, function); + DEF_FIELD(symval, pcmcia_device_id, device_no); + DEF_FIELD_ADDR(symval, pcmcia_device_id, prod_id_hash); + + ADD(alias, "m", match_flags & PCMCIA_DEV_ID_MATCH_MANF_ID, + manf_id); + ADD(alias, "c", match_flags & PCMCIA_DEV_ID_MATCH_CARD_ID, + card_id); + ADD(alias, "f", match_flags & PCMCIA_DEV_ID_MATCH_FUNC_ID, + func_id); + ADD(alias, "fn", match_flags & PCMCIA_DEV_ID_MATCH_FUNCTION, + function); + ADD(alias, "pfn", match_flags & PCMCIA_DEV_ID_MATCH_DEVICE_NO, + device_no); + ADD(alias, "pa", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID1, + get_unaligned_native(*prod_id_hash + 0)); + ADD(alias, "pb", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID2, + get_unaligned_native(*prod_id_hash + 1)); + ADD(alias, "pc", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID3, + get_unaligned_native(*prod_id_hash + 2)); + ADD(alias, "pd", match_flags & PCMCIA_DEV_ID_MATCH_PROD_ID4, + get_unaligned_native(*prod_id_hash + 3)); + + module_alias_printf(mod, true, "pcmcia:%s", alias); +} + +static void do_vio_entry(struct module *mod, void *symval) +{ + char alias[256]; + char *tmp; + DEF_FIELD_ADDR(symval, vio_device_id, type); + DEF_FIELD_ADDR(symval, vio_device_id, compat); + + sprintf(alias, "vio:T%sS%s", (*type)[0] ? *type : "*", + (*compat)[0] ? *compat : "*"); + + /* Replace all whitespace with underscores */ + for (tmp = alias; tmp && *tmp; tmp++) + if (isspace (*tmp)) + *tmp = '_'; + + module_alias_printf(mod, true, "%s", alias); +} + +static void __attribute__((format(printf, 3, 4))) +alias_append(char *alias, size_t size, const char *fmt, ...) +{ + size_t len = strlen(alias); + va_list args; + int n; + + if (len >= size) + fatal("alias buffer (%zu) overflow before append\n", size); + + va_start(args, fmt); + n = vsnprintf(alias + len, size - len, fmt, args); + va_end(args); + + if (n < 0 || (size_t)n >= size - len) + fatal("alias buffer (%zu) overflow on append (need %d, have %zu)\n", + size, n, size - len); +} + +static void do_input(char *alias, size_t size, + kernel_ulong_t *arr, unsigned int min, unsigned int max) +{ + unsigned int i; + + for (i = min; i <= max; i++) + if (get_unaligned_native(arr + i / BITS_PER_LONG) & + (1ULL << (i % BITS_PER_LONG))) + alias_append(alias, size, "%X,*", i); +} + +/* input:b0v0p0e0-eXkXrXaXmXlXsXfXwX where X is comma-separated %02X. */ +static void do_input_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + const size_t sizeof_alias = sizeof(alias); + + DEF_FIELD(symval, input_device_id, flags); + DEF_FIELD(symval, input_device_id, bustype); + DEF_FIELD(symval, input_device_id, vendor); + DEF_FIELD(symval, input_device_id, product); + DEF_FIELD(symval, input_device_id, version); + DEF_FIELD_ADDR(symval, input_device_id, evbit); + DEF_FIELD_ADDR(symval, input_device_id, keybit); + DEF_FIELD_ADDR(symval, input_device_id, relbit); + DEF_FIELD_ADDR(symval, input_device_id, absbit); + DEF_FIELD_ADDR(symval, input_device_id, mscbit); + DEF_FIELD_ADDR(symval, input_device_id, ledbit); + DEF_FIELD_ADDR(symval, input_device_id, sndbit); + DEF_FIELD_ADDR(symval, input_device_id, ffbit); + DEF_FIELD_ADDR(symval, input_device_id, swbit); + + ADD(alias, "b", flags & INPUT_DEVICE_ID_MATCH_BUS, bustype); + ADD(alias, "v", flags & INPUT_DEVICE_ID_MATCH_VENDOR, vendor); + ADD(alias, "p", flags & INPUT_DEVICE_ID_MATCH_PRODUCT, product); + ADD(alias, "e", flags & INPUT_DEVICE_ID_MATCH_VERSION, version); + + alias_append(alias, sizeof_alias, "-e*"); + if (flags & INPUT_DEVICE_ID_MATCH_EVBIT) + do_input(alias, sizeof_alias, *evbit, 0, INPUT_DEVICE_ID_EV_MAX); + alias_append(alias, sizeof_alias, "k*"); + if (flags & INPUT_DEVICE_ID_MATCH_KEYBIT) + do_input(alias, sizeof_alias, *keybit, + INPUT_DEVICE_ID_KEY_MIN_INTERESTING, + INPUT_DEVICE_ID_KEY_MAX); + alias_append(alias, sizeof_alias, "r*"); + if (flags & INPUT_DEVICE_ID_MATCH_RELBIT) + do_input(alias, sizeof_alias, *relbit, 0, INPUT_DEVICE_ID_REL_MAX); + alias_append(alias, sizeof_alias, "a*"); + if (flags & INPUT_DEVICE_ID_MATCH_ABSBIT) + do_input(alias, sizeof_alias, *absbit, 0, INPUT_DEVICE_ID_ABS_MAX); + alias_append(alias, sizeof_alias, "m*"); + if (flags & INPUT_DEVICE_ID_MATCH_MSCIT) + do_input(alias, sizeof_alias, *mscbit, 0, INPUT_DEVICE_ID_MSC_MAX); + alias_append(alias, sizeof_alias, "l*"); + if (flags & INPUT_DEVICE_ID_MATCH_LEDBIT) + do_input(alias, sizeof_alias, *ledbit, 0, INPUT_DEVICE_ID_LED_MAX); + alias_append(alias, sizeof_alias, "s*"); + if (flags & INPUT_DEVICE_ID_MATCH_SNDBIT) + do_input(alias, sizeof_alias, *sndbit, 0, INPUT_DEVICE_ID_SND_MAX); + alias_append(alias, sizeof_alias, "f*"); + if (flags & INPUT_DEVICE_ID_MATCH_FFBIT) + do_input(alias, sizeof_alias, *ffbit, 0, INPUT_DEVICE_ID_FF_MAX); + alias_append(alias, sizeof_alias, "w*"); + if (flags & INPUT_DEVICE_ID_MATCH_SWBIT) + do_input(alias, sizeof_alias, *swbit, 0, INPUT_DEVICE_ID_SW_MAX); + + module_alias_printf(mod, false, "input:%s", alias); +} + +static void do_eisa_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, eisa_device_id, sig); + module_alias_printf(mod, false, EISA_DEVICE_MODALIAS_FMT "*", *sig); +} + +/* Looks like: parisc:tNhvNrevNsvN */ +static void do_parisc_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, parisc_device_id, hw_type); + DEF_FIELD(symval, parisc_device_id, hversion); + DEF_FIELD(symval, parisc_device_id, hversion_rev); + DEF_FIELD(symval, parisc_device_id, sversion); + + ADD(alias, "t", hw_type != PA_HWTYPE_ANY_ID, hw_type); + ADD(alias, "hv", hversion != PA_HVERSION_ANY_ID, hversion); + ADD(alias, "rev", hversion_rev != PA_HVERSION_REV_ANY_ID, hversion_rev); + ADD(alias, "sv", sversion != PA_SVERSION_ANY_ID, sversion); + + module_alias_printf(mod, true, "parisc:%s", alias); +} + +/* Looks like: sdio:cNvNdN. */ +static void do_sdio_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, sdio_device_id, class); + DEF_FIELD(symval, sdio_device_id, vendor); + DEF_FIELD(symval, sdio_device_id, device); + + ADD(alias, "c", class != (__u8)SDIO_ANY_ID, class); + ADD(alias, "v", vendor != (__u16)SDIO_ANY_ID, vendor); + ADD(alias, "d", device != (__u16)SDIO_ANY_ID, device); + + module_alias_printf(mod, true, "sdio:%s", alias); +} + +/* Looks like: ssb:vNidNrevN. */ +static void do_ssb_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, ssb_device_id, vendor); + DEF_FIELD(symval, ssb_device_id, coreid); + DEF_FIELD(symval, ssb_device_id, revision); + + ADD(alias, "v", vendor != SSB_ANY_VENDOR, vendor); + ADD(alias, "id", coreid != SSB_ANY_ID, coreid); + ADD(alias, "rev", revision != SSB_ANY_REV, revision); + + module_alias_printf(mod, true, "ssb:%s", alias); +} + +/* Looks like: bcma:mNidNrevNclN. */ +static void do_bcma_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, bcma_device_id, manuf); + DEF_FIELD(symval, bcma_device_id, id); + DEF_FIELD(symval, bcma_device_id, rev); + DEF_FIELD(symval, bcma_device_id, class); + + ADD(alias, "m", manuf != BCMA_ANY_MANUF, manuf); + ADD(alias, "id", id != BCMA_ANY_ID, id); + ADD(alias, "rev", rev != BCMA_ANY_REV, rev); + ADD(alias, "cl", class != BCMA_ANY_CLASS, class); + + module_alias_printf(mod, true, "bcma:%s", alias); +} + +/* Looks like: virtio:dNvN */ +static void do_virtio_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, virtio_device_id, device); + DEF_FIELD(symval, virtio_device_id, vendor); + + ADD(alias, "d", device != VIRTIO_DEV_ANY_ID, device); + ADD(alias, "v", vendor != VIRTIO_DEV_ANY_ID, vendor); + + module_alias_printf(mod, true, "virtio:%s", alias); +} + +/* + * Looks like: vmbus:guid + * Each byte of the guid will be represented by two hex characters + * in the name. + */ +static void do_vmbus_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, hv_vmbus_device_id, guid); + char guid_name[sizeof(*guid) * 2 + 1]; + + for (int i = 0; i < sizeof(*guid); i++) + sprintf(&guid_name[i * 2], "%02x", guid->b[i]); + + module_alias_printf(mod, false, "vmbus:%s", guid_name); +} + +/* Looks like: rpmsg:S */ +static void do_rpmsg_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, rpmsg_device_id, name); + + module_alias_printf(mod, false, RPMSG_DEVICE_MODALIAS_FMT, *name); +} + +/* Looks like: i2c:S */ +static void do_i2c_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, i2c_device_id, name); + + module_alias_printf(mod, false, I2C_MODULE_PREFIX "%s", *name); +} + +static void do_i3c_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, i3c_device_id, match_flags); + DEF_FIELD(symval, i3c_device_id, dcr); + DEF_FIELD(symval, i3c_device_id, manuf_id); + DEF_FIELD(symval, i3c_device_id, part_id); + DEF_FIELD(symval, i3c_device_id, extra_info); + + ADD(alias, "dcr", match_flags & I3C_MATCH_DCR, dcr); + ADD(alias, "manuf", match_flags & I3C_MATCH_MANUF, manuf_id); + ADD(alias, "part", match_flags & I3C_MATCH_PART, part_id); + ADD(alias, "ext", match_flags & I3C_MATCH_EXTRA_INFO, extra_info); + + module_alias_printf(mod, false, "i3c:%s", alias); +} + +static void do_slim_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, slim_device_id, manf_id); + DEF_FIELD(symval, slim_device_id, prod_code); + + module_alias_printf(mod, false, "slim:%x:%x:*", manf_id, prod_code); +} + +/* Looks like: spi:S */ +static void do_spi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, spi_device_id, name); + + module_alias_printf(mod, false, SPI_MODULE_PREFIX "%s", *name); +} + +static const struct dmifield { + const char *prefix; + int field; +} dmi_fields[] = { + { "bvn", DMI_BIOS_VENDOR }, + { "bvr", DMI_BIOS_VERSION }, + { "bd", DMI_BIOS_DATE }, + { "br", DMI_BIOS_RELEASE }, + { "efr", DMI_EC_FIRMWARE_RELEASE }, + { "svn", DMI_SYS_VENDOR }, + { "pn", DMI_PRODUCT_NAME }, + { "pvr", DMI_PRODUCT_VERSION }, + { "rvn", DMI_BOARD_VENDOR }, + { "rn", DMI_BOARD_NAME }, + { "rvr", DMI_BOARD_VERSION }, + { "cvn", DMI_CHASSIS_VENDOR }, + { "ct", DMI_CHASSIS_TYPE }, + { "cvr", DMI_CHASSIS_VERSION }, + { NULL, DMI_NONE } +}; + +static void dmi_ascii_filter(char *d, size_t avail, const char *s) +{ + /* Filter out characters we don't want to see in the modalias string */ + for (; *s; s++) + if (*s > ' ' && *s < 127 && *s != ':') { + if (avail <= 1) + fatal("%s: alias buffer overflow\n", __func__); + *(d++) = *s; + avail--; + } + + *d = 0; +} + + +static void do_dmi_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + const size_t sizeof_alias = sizeof(alias); + size_t len; + int i, j; + DEF_FIELD_ADDR(symval, dmi_system_id, matches); + + for (i = 0; i < ARRAY_SIZE(dmi_fields); i++) { + for (j = 0; j < 4; j++) { + if ((*matches)[j].slot && + (*matches)[j].slot == dmi_fields[i].field) { + alias_append(alias, sizeof_alias, ":%s*", + dmi_fields[i].prefix); + len = strlen(alias); + dmi_ascii_filter(alias + len, sizeof_alias - len, + (*matches)[j].substr); + alias_append(alias, sizeof_alias, "*"); + } + } + } + + module_alias_printf(mod, false, "dmi*%s:", alias); +} + +static void do_platform_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, platform_device_id, name); + + module_alias_printf(mod, false, PLATFORM_MODULE_PREFIX "%s", *name); +} + +static void do_mdio_entry(struct module *mod, void *symval) +{ + char id[33]; + int i; + DEF_FIELD(symval, mdio_device_id, phy_id); + DEF_FIELD(symval, mdio_device_id, phy_id_mask); + + for (i = 0; i < 32; i++) { + if (!((phy_id_mask >> (31-i)) & 1)) + id[i] = '?'; + else if ((phy_id >> (31-i)) & 1) + id[i] = '1'; + else + id[i] = '0'; + } + + /* Terminate the string */ + id[32] = '\0'; + + module_alias_printf(mod, false, MDIO_MODULE_PREFIX "%s", id); +} + +/* Looks like: zorro:iN. */ +static void do_zorro_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + DEF_FIELD(symval, zorro_device_id, id); + + ADD(alias, "i", id != ZORRO_WILDCARD, id); + + module_alias_printf(mod, false, "zorro:%s", alias); +} + +/* looks like: "pnp:dD" */ +static void do_isapnp_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, isapnp_device_id, vendor); + DEF_FIELD(symval, isapnp_device_id, function); + module_alias_printf(mod, false, "pnp:d%c%c%c%x%x%x%x*", + 'A' + ((vendor >> 2) & 0x3f) - 1, + 'A' + (((vendor & 3) << 3) | ((vendor >> 13) & 7)) - 1, + 'A' + ((vendor >> 8) & 0x1f) - 1, + (function >> 4) & 0x0f, function & 0x0f, + (function >> 12) & 0x0f, (function >> 8) & 0x0f); +} + +/* Looks like: "ipack:fNvNdN". */ +static void do_ipack_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + DEF_FIELD(symval, ipack_device_id, format); + DEF_FIELD(symval, ipack_device_id, vendor); + DEF_FIELD(symval, ipack_device_id, device); + + ADD(alias, "f", format != IPACK_ANY_FORMAT, format); + ADD(alias, "v", vendor != IPACK_ANY_ID, vendor); + ADD(alias, "d", device != IPACK_ANY_ID, device); + + module_alias_printf(mod, true, "ipack:%s", alias); +} + +/* + * Append a match expression for a single masked hex digit. + * outp points to a pointer to the character at which to append. + * *outp is updated on return to point just after the appended text, + * to facilitate further appending. + */ +static void append_nibble_mask(char **outp, + unsigned int nibble, unsigned int mask) +{ + char *p = *outp; + unsigned int i; + + switch (mask) { + case 0: + *p++ = '?'; + break; + + case 0xf: + p += sprintf(p, "%X", nibble); + break; + + default: + /* + * Dumbly emit a match pattern for all possible matching + * digits. This could be improved in some cases using ranges, + * but it has the advantage of being trivially correct, and is + * often optimal. + */ + *p++ = '['; + for (i = 0; i < 0x10; i++) + if ((i & mask) == nibble) + p += sprintf(p, "%X", i); + *p++ = ']'; + } + + /* Ensure that the string remains NUL-terminated: */ + *p = '\0'; + + /* Advance the caller's end-of-string pointer: */ + *outp = p; +} + +/* + * looks like: "amba:dN" + * + * N is exactly 8 digits, where each is an upper-case hex digit, or + * a ? or [] pattern matching exactly one digit. + */ +static void do_amba_entry(struct module *mod, void *symval) +{ + char alias[256]; + unsigned int digit; + char *p = alias; + DEF_FIELD(symval, amba_id, id); + DEF_FIELD(symval, amba_id, mask); + + if ((id & mask) != id) + fatal("%s: Masked-off bit(s) of AMBA device ID are non-zero: id=0x%08X, mask=0x%08X. Please fix this driver.\n", + mod->name, id, mask); + + for (digit = 0; digit < 8; digit++) + append_nibble_mask(&p, + (id >> (4 * (7 - digit))) & 0xf, + (mask >> (4 * (7 - digit))) & 0xf); + + module_alias_printf(mod, false, "amba:d%s", alias); +} + +/* + * looks like: "mipscdmm:tN" + * + * N is exactly 2 digits, where each is an upper-case hex digit, or + * a ? or [] pattern matching exactly one digit. + */ +static void do_mips_cdmm_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, mips_cdmm_device_id, type); + + module_alias_printf(mod, false, "mipscdmm:t%02X*", type); +} + +/* LOOKS like cpu:type:x86,venVVVVfamFFFFmodMMMM:feature:*,FEAT,* + * All fields are numbers. It would be nicer to use strings for vendor + * and feature, but getting those out of the build system here is too + * complicated. + */ + +static void do_x86cpu_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, x86_cpu_id, feature); + DEF_FIELD(symval, x86_cpu_id, family); + DEF_FIELD(symval, x86_cpu_id, model); + DEF_FIELD(symval, x86_cpu_id, vendor); + + ADD(alias, "ven", vendor != X86_VENDOR_ANY, vendor); + ADD(alias, "fam", family != X86_FAMILY_ANY, family); + ADD(alias, "mod", model != X86_MODEL_ANY, model); + strcat(alias, ":feature:*"); + if (feature != X86_FEATURE_ANY) + sprintf(alias + strlen(alias), "%04X*", feature); + + module_alias_printf(mod, false, "cpu:type:x86,%s", alias); +} + +/* LOOKS like cpu:type:*:feature:*FEAT* */ +static void do_cpu_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, cpu_feature, feature); + + module_alias_printf(mod, false, "cpu:type:*:feature:*%04X*", feature); +} + +/* Looks like: mcb:16zN */ +static void do_mcb_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, mcb_device_id, device); + + module_alias_printf(mod, false, "mcb:16z%03d", device); +} + +/* Looks like: mei:S:uuid:N:* */ +static void do_mei_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD_ADDR(symval, mei_cl_device_id, name); + DEF_FIELD_ADDR(symval, mei_cl_device_id, uuid); + DEF_FIELD(symval, mei_cl_device_id, version); + + add_uuid(alias, *uuid); + ADD(alias, ":", version != MEI_CL_VERSION_ANY, version); + + module_alias_printf(mod, false, MEI_CL_MODULE_PREFIX "%s:%s:*", + (*name)[0] ? *name : "*", alias); +} + +/* Looks like: rapidio:vNdNavNadN */ +static void do_rio_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, rio_device_id, did); + DEF_FIELD(symval, rio_device_id, vid); + DEF_FIELD(symval, rio_device_id, asm_did); + DEF_FIELD(symval, rio_device_id, asm_vid); + + ADD(alias, "v", vid != RIO_ANY_ID, vid); + ADD(alias, "d", did != RIO_ANY_ID, did); + ADD(alias, "av", asm_vid != RIO_ANY_ID, asm_vid); + ADD(alias, "ad", asm_did != RIO_ANY_ID, asm_did); + + module_alias_printf(mod, true, "rapidio:%s", alias); +} + +/* Looks like: ulpi:vNpN */ +static void do_ulpi_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, ulpi_device_id, vendor); + DEF_FIELD(symval, ulpi_device_id, product); + + module_alias_printf(mod, false, "ulpi:v%04xp%04x", vendor, product); +} + +/* Looks like: hdaudio:vNrNaN */ +static void do_hda_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, hda_device_id, vendor_id); + DEF_FIELD(symval, hda_device_id, rev_id); + DEF_FIELD(symval, hda_device_id, api_version); + + ADD(alias, "v", vendor_id != 0, vendor_id); + ADD(alias, "r", rev_id != 0, rev_id); + ADD(alias, "a", api_version != 0, api_version); + + module_alias_printf(mod, true, "hdaudio:%s", alias); +} + +/* Looks like: sdw:mNpNvNcN */ +static void do_sdw_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, sdw_device_id, mfg_id); + DEF_FIELD(symval, sdw_device_id, part_id); + DEF_FIELD(symval, sdw_device_id, sdw_version); + DEF_FIELD(symval, sdw_device_id, class_id); + + ADD(alias, "m", mfg_id != 0, mfg_id); + ADD(alias, "p", part_id != 0, part_id); + ADD(alias, "v", sdw_version != 0, sdw_version); + ADD(alias, "c", class_id != 0, class_id); + + module_alias_printf(mod, true, "sdw:%s", alias); +} + +/* Looks like: fsl-mc:vNdN */ +static void do_fsl_mc_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, fsl_mc_device_id, vendor); + DEF_FIELD_ADDR(symval, fsl_mc_device_id, obj_type); + + module_alias_printf(mod, false, "fsl-mc:v%08Xd%s", vendor, *obj_type); +} + +/* Looks like: tbsvc:kSpNvNrN */ +static void do_tbsvc_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, tb_service_id, match_flags); + DEF_FIELD_ADDR(symval, tb_service_id, protocol_key); + DEF_FIELD(symval, tb_service_id, protocol_id); + DEF_FIELD(symval, tb_service_id, protocol_version); + DEF_FIELD(symval, tb_service_id, protocol_revision); + + if (match_flags & TBSVC_MATCH_PROTOCOL_KEY) + sprintf(alias + strlen(alias), "k%s", *protocol_key); + else + strcat(alias + strlen(alias), "k*"); + ADD(alias, "p", match_flags & TBSVC_MATCH_PROTOCOL_ID, protocol_id); + ADD(alias, "v", match_flags & TBSVC_MATCH_PROTOCOL_VERSION, + protocol_version); + ADD(alias, "r", match_flags & TBSVC_MATCH_PROTOCOL_REVISION, + protocol_revision); + + module_alias_printf(mod, true, "tbsvc:%s", alias); +} + +/* Looks like: typec:idN */ +static void do_typec_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, typec_device_id, svid); + + module_alias_printf(mod, false, "typec:id%04X", svid); +} + +/* Looks like: tee:uuid */ +static void do_tee_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, tee_client_device_id, uuid); + + module_alias_printf(mod, true, + "tee:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + uuid->b[0], uuid->b[1], uuid->b[2], uuid->b[3], uuid->b[4], + uuid->b[5], uuid->b[6], uuid->b[7], uuid->b[8], uuid->b[9], + uuid->b[10], uuid->b[11], uuid->b[12], uuid->b[13], uuid->b[14], + uuid->b[15]); +} + +/* Looks like: wmi:guid */ +static void do_wmi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, wmi_device_id, guid_string); + char result[sizeof(*guid_string)]; + int i; + + if (strlen(*guid_string) != UUID_STRING_LEN) { + warn("Invalid WMI device id 'wmi:%s' in '%s'\n", + *guid_string, mod->name); + return; + } + + for (i = 0; i < UUID_STRING_LEN; i++) { + char value = (*guid_string)[i]; + bool valid = false; + + if (i == 8 || i == 13 || i == 18 || i == 23) { + if (value == '-') + valid = true; + } else { + if (isxdigit(value)) + valid = true; + } + + if (!valid) { + warn("Invalid character %c inside WMI GUID string '%s' in '%s'\n", + value, *guid_string, mod->name); + return; + } + + /* Some GUIDs from BMOF definitions contain lowercase characters */ + result[i] = toupper(value); + } + + result[i] = '\0'; + + module_alias_printf(mod, false, WMI_MODULE_PREFIX "%s", result); +} + +/* Looks like: mhi:S */ +static void do_mhi_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, mhi_device_id, chan); + module_alias_printf(mod, false, MHI_DEVICE_MODALIAS_FMT, *chan); +} + +/* Looks like: mhi_ep:S */ +static void do_mhi_ep_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, mhi_device_id, chan); + + module_alias_printf(mod, false, MHI_EP_DEVICE_MODALIAS_FMT, *chan); +} + +/* Looks like: ishtp:{guid} */ +static void do_ishtp_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + DEF_FIELD_ADDR(symval, ishtp_device_id, guid); + + add_guid(alias, *guid); + + module_alias_printf(mod, false, ISHTP_MODULE_PREFIX "{%s}", alias); +} + +static void do_auxiliary_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, auxiliary_device_id, name); + + module_alias_printf(mod, false, AUXILIARY_MODULE_PREFIX "%s", *name); +} + +/* + * Looks like: ssam:dNcNtNiNfN + * + * N is exactly 2 digits, where each is an upper-case hex digit. + */ +static void do_ssam_entry(struct module *mod, void *symval) +{ + char alias[256] = {}; + + DEF_FIELD(symval, ssam_device_id, match_flags); + DEF_FIELD(symval, ssam_device_id, domain); + DEF_FIELD(symval, ssam_device_id, category); + DEF_FIELD(symval, ssam_device_id, target); + DEF_FIELD(symval, ssam_device_id, instance); + DEF_FIELD(symval, ssam_device_id, function); + + ADD(alias, "t", match_flags & SSAM_MATCH_TARGET, target); + ADD(alias, "i", match_flags & SSAM_MATCH_INSTANCE, instance); + ADD(alias, "f", match_flags & SSAM_MATCH_FUNCTION, function); + + module_alias_printf(mod, false, "ssam:d%02Xc%02X%s", + domain, category, alias); +} + +/* Looks like: dfl:tNfN */ +static void do_dfl_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, dfl_device_id, type); + DEF_FIELD(symval, dfl_device_id, feature_id); + + module_alias_printf(mod, true, "dfl:t%04Xf%04X", type, feature_id); +} + +/* Looks like: cdx:vNdN */ +static void do_cdx_entry(struct module *mod, void *symval) +{ + char alias[256]; + + DEF_FIELD(symval, cdx_device_id, vendor); + DEF_FIELD(symval, cdx_device_id, device); + DEF_FIELD(symval, cdx_device_id, subvendor); + DEF_FIELD(symval, cdx_device_id, subdevice); + DEF_FIELD(symval, cdx_device_id, class); + DEF_FIELD(symval, cdx_device_id, class_mask); + DEF_FIELD(symval, cdx_device_id, override_only); + + switch (override_only) { + case 0: + strcpy(alias, "cdx:"); + break; + case CDX_ID_F_VFIO_DRIVER_OVERRIDE: + strcpy(alias, "vfio_cdx:"); + break; + default: + warn("Unknown CDX driver_override alias %08X\n", + override_only); + return; + } + + ADD(alias, "v", vendor != CDX_ANY_ID, vendor); + ADD(alias, "d", device != CDX_ANY_ID, device); + ADD(alias, "sv", subvendor != CDX_ANY_ID, subvendor); + ADD(alias, "sd", subdevice != CDX_ANY_ID, subdevice); + ADD(alias, "c", class_mask == 0xFFFFFF, class); + + module_alias_printf(mod, false, "%s", alias); +} + +static void do_vchiq_entry(struct module *mod, void *symval) +{ + DEF_FIELD_ADDR(symval, vchiq_device_id, name); + + module_alias_printf(mod, false, "vchiq:%s", *name); +} + +/* Looks like: coreboot:tN */ +static void do_coreboot_entry(struct module *mod, void *symval) +{ + DEF_FIELD(symval, coreboot_device_id, tag); + + module_alias_printf(mod, false, "coreboot:t%08X", tag); +} + +/* Does namelen bytes of name exactly match the symbol? */ +static bool sym_is(const char *name, unsigned namelen, const char *symbol) +{ + if (namelen != strlen(symbol)) + return false; + + return memcmp(name, symbol, namelen) == 0; +} + +static void do_table(const char *name, void *symval, unsigned long size, + unsigned long id_size, + const char *device_id, + void (*do_entry)(struct module *mod, void *symval), + struct module *mod) +{ + unsigned int i; + + if (size % id_size || size < id_size) { + error("%s: type mismatch between %s[] and MODULE_DEVICE_TABLE(%s, ...)\n", + mod->name, name, device_id); + return; + } + + /* Verify the last entry is a terminator */ + for (i = size - id_size; i < size; i++) { + if (*(uint8_t *)(symval + i)) { + error("%s: %s[] is not terminated with a NULL entry\n", + mod->name, name); + return; + } + } + + /* Leave last one: it's the terminator. */ + size -= id_size; + + for (i = 0; i < size; i += id_size) + do_entry(mod, symval + i); +} + +static const struct devtable devtable[] = { + {"hid", SIZE_hid_device_id, do_hid_entry}, + {"ieee1394", SIZE_ieee1394_device_id, do_ieee1394_entry}, + {"pci", SIZE_pci_device_id, do_pci_entry}, + {"ccw", SIZE_ccw_device_id, do_ccw_entry}, + {"ap", SIZE_ap_device_id, do_ap_entry}, + {"css", SIZE_css_device_id, do_css_entry}, + {"serio", SIZE_serio_device_id, do_serio_entry}, + {"acpi", SIZE_acpi_device_id, do_acpi_entry}, + {"pcmcia", SIZE_pcmcia_device_id, do_pcmcia_entry}, + {"vio", SIZE_vio_device_id, do_vio_entry}, + {"input", SIZE_input_device_id, do_input_entry}, + {"eisa", SIZE_eisa_device_id, do_eisa_entry}, + {"parisc", SIZE_parisc_device_id, do_parisc_entry}, + {"sdio", SIZE_sdio_device_id, do_sdio_entry}, + {"ssb", SIZE_ssb_device_id, do_ssb_entry}, + {"bcma", SIZE_bcma_device_id, do_bcma_entry}, + {"virtio", SIZE_virtio_device_id, do_virtio_entry}, + {"vmbus", SIZE_hv_vmbus_device_id, do_vmbus_entry}, + {"rpmsg", SIZE_rpmsg_device_id, do_rpmsg_entry}, + {"i2c", SIZE_i2c_device_id, do_i2c_entry}, + {"i3c", SIZE_i3c_device_id, do_i3c_entry}, + {"slim", SIZE_slim_device_id, do_slim_entry}, + {"spi", SIZE_spi_device_id, do_spi_entry}, + {"dmi", SIZE_dmi_system_id, do_dmi_entry}, + {"platform", SIZE_platform_device_id, do_platform_entry}, + {"mdio", SIZE_mdio_device_id, do_mdio_entry}, + {"zorro", SIZE_zorro_device_id, do_zorro_entry}, + {"isapnp", SIZE_isapnp_device_id, do_isapnp_entry}, + {"ipack", SIZE_ipack_device_id, do_ipack_entry}, + {"amba", SIZE_amba_id, do_amba_entry}, + {"mipscdmm", SIZE_mips_cdmm_device_id, do_mips_cdmm_entry}, + {"x86cpu", SIZE_x86_cpu_id, do_x86cpu_entry}, + {"cpu", SIZE_cpu_feature, do_cpu_entry}, + {"mcb", SIZE_mcb_device_id, do_mcb_entry}, + {"mei", SIZE_mei_cl_device_id, do_mei_entry}, + {"rapidio", SIZE_rio_device_id, do_rio_entry}, + {"ulpi", SIZE_ulpi_device_id, do_ulpi_entry}, + {"hdaudio", SIZE_hda_device_id, do_hda_entry}, + {"sdw", SIZE_sdw_device_id, do_sdw_entry}, + {"fslmc", SIZE_fsl_mc_device_id, do_fsl_mc_entry}, + {"tbsvc", SIZE_tb_service_id, do_tbsvc_entry}, + {"typec", SIZE_typec_device_id, do_typec_entry}, + {"tee", SIZE_tee_client_device_id, do_tee_entry}, + {"wmi", SIZE_wmi_device_id, do_wmi_entry}, + {"mhi", SIZE_mhi_device_id, do_mhi_entry}, + {"mhi_ep", SIZE_mhi_device_id, do_mhi_ep_entry}, + {"auxiliary", SIZE_auxiliary_device_id, do_auxiliary_entry}, + {"ssam", SIZE_ssam_device_id, do_ssam_entry}, + {"dfl", SIZE_dfl_device_id, do_dfl_entry}, + {"ishtp", SIZE_ishtp_device_id, do_ishtp_entry}, + {"cdx", SIZE_cdx_device_id, do_cdx_entry}, + {"vchiq", SIZE_vchiq_device_id, do_vchiq_entry}, + {"coreboot", SIZE_coreboot_device_id, do_coreboot_entry}, + {"of", SIZE_of_device_id, do_of_entry}, + {"usb", SIZE_usb_device_id, do_usb_entry_multi}, + {"pnp", SIZE_pnp_device_id, do_pnp_device_entry}, + {"pnp_card", SIZE_pnp_card_device_id, do_pnp_card_entry}, +}; + +/* Create MODULE_ALIAS() statements. + * At this time, we cannot write the actual output C source yet, + * so we write into the mod->dev_table_buf buffer. */ +void handle_moddevtable(struct module *mod, struct elf_info *info, + Elf_Sym *sym, const char *symname) +{ + void *symval; + char *zeros = NULL; + const char *type, *name, *modname; + size_t typelen, modnamelen; + static const char *prefix = "__mod_device_table__"; + + /* We're looking for a section relative symbol */ + if (!sym->st_shndx || get_secindex(info, sym) >= info->num_sections) + return; + + /* We're looking for an object */ + if (ELF_ST_TYPE(sym->st_info) != STT_OBJECT) + return; + + /* All our symbols are of form __mod_device_table__kmod_<modname>__<type>__<name>. */ + if (!strstarts(symname, prefix)) + return; + + modname = strstr(symname, "__kmod_"); + if (!modname) + return; + modname += strlen("__kmod_"); + + type = strstr(modname, "__"); + if (!type) + return; + modnamelen = type - modname; + type += strlen("__"); + + name = strstr(type, "__"); + if (!name) + return; + typelen = name - type; + name += strlen("__"); + + /* Handle all-NULL symbols allocated into .bss */ + if (info->sechdrs[get_secindex(info, sym)].sh_type & SHT_NOBITS) { + zeros = calloc(1, sym->st_size); + symval = zeros; + } else { + symval = sym_get_data(info, sym); + } + + for (int i = 0; i < ARRAY_SIZE(devtable); i++) { + const struct devtable *p = &devtable[i]; + + if (sym_is(type, typelen, p->device_id)) { + do_table(name, symval, sym->st_size, p->id_size, + p->device_id, p->do_entry, mod); + break; + } + } + + if (mod->is_vmlinux) { + struct module_alias *alias; + + /* + * If this is vmlinux, record the name of the builtin module. + * Traverse the linked list in the reverse order, and set the + * builtin_modname unless it has already been set in the + * previous call. + */ + list_for_each_entry_reverse(alias, &mod->aliases, node) { + if (alias->builtin_modname) + break; + alias->builtin_modname = xstrndup(modname, modnamelen); + } + } + + free(zeros); +} diff --git a/scripts/mod/mk_elfconfig.c b/scripts/mod/mk_elfconfig.c new file mode 100644 index 000000000..e8cee4e4b --- /dev/null +++ b/scripts/mod/mk_elfconfig.c @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <elf.h> + +int +main(int argc, char **argv) +{ + unsigned char ei[EI_NIDENT]; + + if (fread(ei, 1, EI_NIDENT, stdin) != EI_NIDENT) { + fprintf(stderr, "Error: input truncated\n"); + return 1; + } + if (memcmp(ei, ELFMAG, SELFMAG) != 0) { + fprintf(stderr, "Error: not ELF\n"); + return 1; + } + switch (ei[EI_CLASS]) { + case ELFCLASS32: + printf("#define KERNEL_ELFCLASS ELFCLASS32\n"); + break; + case ELFCLASS64: + printf("#define KERNEL_ELFCLASS ELFCLASS64\n"); + break; + default: + exit(1); + } + + return 0; +} diff --git a/scripts/mod/modpost.c b/scripts/mod/modpost.c new file mode 100644 index 000000000..75374c64b --- /dev/null +++ b/scripts/mod/modpost.c @@ -0,0 +1,2420 @@ +/* Postprocess module symbol versions + * + * Copyright 2003 Kai Germaschewski + * Copyright 2002-2004 Rusty Russell, IBM Corporation + * Copyright 2006-2008 Sam Ravnborg + * Based in part on module-init-tools/depmod.c,file2alias + * + * This software may be used and distributed according to the terms + * of the GNU General Public License, incorporated herein by reference. + * + * Usage: modpost vmlinux module1.o module2.o ... + */ + +#define _GNU_SOURCE +#include <elf.h> +#include <fnmatch.h> +#include <stdio.h> +#include <ctype.h> +#include <string.h> +#include <limits.h> +#include <stdbool.h> +#include <errno.h> + +#include <hash.h> +#include <hashtable.h> +#include <list.h> +#include <xalloc.h> +#include "modpost.h" +#include "../../include/linux/license.h" + +#define MODULE_NS_PREFIX "module:" + +static bool module_enabled; +/* Are we using CONFIG_MODVERSIONS? */ +static bool modversions; +/* Is CONFIG_MODULE_SRCVERSION_ALL set? */ +static bool all_versions; +/* Is CONFIG_BASIC_MODVERSIONS set? */ +static bool basic_modversions; +/* Is CONFIG_EXTENDED_MODVERSIONS set? */ +static bool extended_modversions; +/* If we are modposting external module set to 1 */ +static bool external_module; +/* Only warn about unresolved symbols */ +static bool warn_unresolved; + +static int sec_mismatch_count; +static bool sec_mismatch_warn_only = true; +/* Trim EXPORT_SYMBOLs that are unused by in-tree modules */ +static bool trim_unused_exports; + +/* ignore missing files */ +static bool ignore_missing_files; +/* If set to 1, only warn (instead of error) about missing ns imports */ +static bool allow_missing_ns_imports; + +static bool error_occurred; + +static bool extra_warn __attribute__((unused)); + +bool target_is_big_endian; +bool host_is_big_endian; + +/* + * Cut off the warnings when there are too many. This typically occurs when + * vmlinux is missing. ('make modules' without building vmlinux.) + */ +#define MAX_UNRESOLVED_REPORTS 10 +static unsigned int nr_unresolved; + +/* In kernel, this size is defined in linux/module.h; + * here we use Elf_Addr instead of long for covering cross-compile + */ + +#define MODULE_NAME_LEN (64 - sizeof(Elf_Addr)) + +void modpost_log(bool is_error, struct module *mod, const char *fmt, ...) +{ + va_list arglist; + + if (is_error) { + fprintf(stderr, "ERROR: "); + error_occurred = true; + } else { + fprintf(stderr, "WARNING: "); + } + + fprintf(stderr, "modpost: "); + + if (mod) + fprintf(stderr, "%s%s: ", mod->name, mod->is_vmlinux ? "" : ".ko"); + + va_start(arglist, fmt); + vfprintf(stderr, fmt, arglist); + va_end(arglist); +} + +#define mod_warn(mod, fmt, args...) modpost_log(false, mod, fmt, ##args) +#define mod_error(mod, fmt, args...) modpost_log(true, mod, fmt, ##args) + +static inline bool strends(const char *str, const char *postfix) +{ + if (strlen(str) < strlen(postfix)) + return false; + + return strcmp(str + strlen(str) - strlen(postfix), postfix) == 0; +} + +/** + * get_basename - return the last part of a pathname. + * + * @path: path to extract the filename from. + */ +const char *get_basename(const char *path) +{ + const char *tail = strrchr(path, '/'); + + return tail ? tail + 1 : path; +} + +char *read_text_file(const char *filename) +{ + struct stat st; + size_t nbytes; + int fd; + char *buf; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + perror(filename); + exit(1); + } + + if (fstat(fd, &st) < 0) { + perror(filename); + exit(1); + } + + buf = xmalloc(st.st_size + 1); + + nbytes = st.st_size; + + while (nbytes) { + ssize_t bytes_read; + + bytes_read = read(fd, buf, nbytes); + if (bytes_read < 0) { + perror(filename); + exit(1); + } + + nbytes -= bytes_read; + } + buf[st.st_size] = '\0'; + + close(fd); + + return buf; +} + +char *get_line(char **stringp) +{ + char *orig = *stringp, *next; + + /* do not return the unwanted extra line at EOF */ + if (!orig || *orig == '\0') + return NULL; + + /* don't use strsep here, it is not available everywhere */ + next = strchr(orig, '\n'); + if (next) + *next++ = '\0'; + + *stringp = next; + + return orig; +} + +/* A list of all modules we processed */ +LIST_HEAD(modules); + +static struct module *find_module(const char *filename, const char *modname) +{ + struct module *mod; + + list_for_each_entry(mod, &modules, list) { + if (!strcmp(mod->dump_file, filename) && + !strcmp(mod->name, modname)) + return mod; + } + return NULL; +} + +static struct module *new_module(const char *name, size_t namelen) +{ + struct module *mod; + + mod = xmalloc(sizeof(*mod) + namelen + 1); + memset(mod, 0, sizeof(*mod)); + + INIT_LIST_HEAD(&mod->exported_symbols); + INIT_LIST_HEAD(&mod->unresolved_symbols); + INIT_LIST_HEAD(&mod->missing_namespaces); + INIT_LIST_HEAD(&mod->imported_namespaces); + INIT_LIST_HEAD(&mod->aliases); + + memcpy(mod->name, name, namelen); + mod->name[namelen] = '\0'; + mod->is_vmlinux = (strcmp(mod->name, "vmlinux") == 0); + + /* + * Set mod->is_gpl_compatible to true by default. If MODULE_LICENSE() + * is missing, do not check the use for EXPORT_SYMBOL_GPL() because + * modpost will exit with an error anyway. + */ + mod->is_gpl_compatible = true; + + list_add_tail(&mod->list, &modules); + + return mod; +} + +struct symbol { + struct hlist_node hnode;/* link to hash table */ + struct list_head list; /* link to module::exported_symbols or module::unresolved_symbols */ + struct module *module; + char *namespace; + unsigned int crc; + bool crc_valid; + bool weak; + bool is_func; + bool is_gpl_only; /* exported by EXPORT_SYMBOL_GPL */ + bool used; /* there exists a user of this symbol */ + char name[]; +}; + +static HASHTABLE_DEFINE(symbol_hashtable, 1U << 10); + +/** + * Allocate a new symbols for use in the hash of exported symbols or + * the list of unresolved symbols per module + **/ +static struct symbol *alloc_symbol(const char *name) +{ + struct symbol *s = xmalloc(sizeof(*s) + strlen(name) + 1); + + memset(s, 0, sizeof(*s)); + strcpy(s->name, name); + + return s; +} + +static uint8_t get_symbol_flags(const struct symbol *sym) +{ + return sym->is_gpl_only ? KSYM_FLAG_GPL_ONLY : 0; +} + +/* For the hash of exported symbols */ +static void hash_add_symbol(struct symbol *sym) +{ + hash_add(symbol_hashtable, &sym->hnode, hash_str(sym->name)); +} + +static void sym_add_unresolved(const char *name, struct module *mod, bool weak) +{ + struct symbol *sym; + + sym = alloc_symbol(name); + sym->weak = weak; + + list_add_tail(&sym->list, &mod->unresolved_symbols); +} + +static struct symbol *sym_find_with_module(const char *name, struct module *mod) +{ + struct symbol *s; + + /* For our purposes, .foo matches foo. PPC64 needs this. */ + if (name[0] == '.') + name++; + + hash_for_each_possible(symbol_hashtable, s, hnode, hash_str(name)) { + if (strcmp(s->name, name) == 0 && (!mod || s->module == mod)) + return s; + } + return NULL; +} + +static struct symbol *find_symbol(const char *name) +{ + return sym_find_with_module(name, NULL); +} + +struct namespace_list { + struct list_head list; + char namespace[]; +}; + +static bool contains_namespace(struct list_head *head, const char *namespace) +{ + struct namespace_list *list; + + /* + * The default namespace is null string "", which is always implicitly + * contained. + */ + if (!namespace[0]) + return true; + + list_for_each_entry(list, head, list) { + if (!strcmp(list->namespace, namespace)) + return true; + } + + return false; +} + +static void add_namespace(struct list_head *head, const char *namespace) +{ + struct namespace_list *ns_entry; + + if (!contains_namespace(head, namespace)) { + ns_entry = xmalloc(sizeof(*ns_entry) + strlen(namespace) + 1); + strcpy(ns_entry->namespace, namespace); + list_add_tail(&ns_entry->list, head); + } +} + +static void *sym_get_data_by_offset(const struct elf_info *info, + unsigned int secindex, unsigned long offset) +{ + Elf_Shdr *sechdr = &info->sechdrs[secindex]; + + return (void *)info->hdr + sechdr->sh_offset + offset; +} + +void *sym_get_data(const struct elf_info *info, const Elf_Sym *sym) +{ + return sym_get_data_by_offset(info, get_secindex(info, sym), + sym->st_value); +} + +static const char *sech_name(const struct elf_info *info, Elf_Shdr *sechdr) +{ + return sym_get_data_by_offset(info, info->secindex_strings, + sechdr->sh_name); +} + +static const char *sec_name(const struct elf_info *info, unsigned int secindex) +{ + /* + * If sym->st_shndx is a special section index, there is no + * corresponding section header. + * Return "" if the index is out of range of info->sechdrs[] array. + */ + if (secindex >= info->num_sections) + return ""; + + return sech_name(info, &info->sechdrs[secindex]); +} + +static struct symbol *sym_add_exported(const char *name, struct module *mod, + bool gpl_only, const char *namespace) +{ + struct symbol *s = find_symbol(name); + + if (s && (!external_module || s->module->is_vmlinux || s->module == mod)) { + mod_error(mod, "symbol '%s' exported twice. Previous export was in %s%s\n", + name, s->module->name, s->module->is_vmlinux ? "" : ".ko"); + } + + s = alloc_symbol(name); + s->module = mod; + s->is_gpl_only = gpl_only; + s->namespace = xstrdup(namespace); + list_add_tail(&s->list, &mod->exported_symbols); + hash_add_symbol(s); + + return s; +} + +static void sym_set_crc(struct symbol *sym, unsigned int crc) +{ + sym->crc = crc; + sym->crc_valid = true; +} + +static void *grab_file(const char *filename, size_t *size) +{ + struct stat st; + void *map = MAP_FAILED; + int fd; + + fd = open(filename, O_RDONLY); + if (fd < 0) + return NULL; + if (fstat(fd, &st)) + goto failed; + + *size = st.st_size; + map = mmap(NULL, *size, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + +failed: + close(fd); + if (map == MAP_FAILED) + return NULL; + return map; +} + +static void release_file(void *file, size_t size) +{ + munmap(file, size); +} + +static int parse_elf(struct elf_info *info, const char *filename) +{ + unsigned int i; + Elf_Ehdr *hdr; + Elf_Shdr *sechdrs; + Elf_Sym *sym; + const char *secstrings; + unsigned int symtab_idx = ~0U, symtab_shndx_idx = ~0U; + + hdr = grab_file(filename, &info->size); + if (!hdr) { + if (ignore_missing_files) { + fprintf(stderr, "%s: %s (ignored)\n", filename, + strerror(errno)); + return 0; + } + perror(filename); + exit(1); + } + info->hdr = hdr; + if (info->size < sizeof(*hdr)) { + /* file too small, assume this is an empty .o file */ + return 0; + } + /* Is this a valid ELF file? */ + if ((hdr->e_ident[EI_MAG0] != ELFMAG0) || + (hdr->e_ident[EI_MAG1] != ELFMAG1) || + (hdr->e_ident[EI_MAG2] != ELFMAG2) || + (hdr->e_ident[EI_MAG3] != ELFMAG3)) { + /* Not an ELF file - silently ignore it */ + return 0; + } + + switch (hdr->e_ident[EI_DATA]) { + case ELFDATA2LSB: + target_is_big_endian = false; + break; + case ELFDATA2MSB: + target_is_big_endian = true; + break; + default: + fatal("target endian is unknown\n"); + } + + /* Fix endianness in ELF header */ + hdr->e_type = TO_NATIVE(hdr->e_type); + hdr->e_machine = TO_NATIVE(hdr->e_machine); + hdr->e_version = TO_NATIVE(hdr->e_version); + hdr->e_entry = TO_NATIVE(hdr->e_entry); + hdr->e_phoff = TO_NATIVE(hdr->e_phoff); + hdr->e_shoff = TO_NATIVE(hdr->e_shoff); + hdr->e_flags = TO_NATIVE(hdr->e_flags); + hdr->e_ehsize = TO_NATIVE(hdr->e_ehsize); + hdr->e_phentsize = TO_NATIVE(hdr->e_phentsize); + hdr->e_phnum = TO_NATIVE(hdr->e_phnum); + hdr->e_shentsize = TO_NATIVE(hdr->e_shentsize); + hdr->e_shnum = TO_NATIVE(hdr->e_shnum); + hdr->e_shstrndx = TO_NATIVE(hdr->e_shstrndx); + sechdrs = (void *)hdr + hdr->e_shoff; + info->sechdrs = sechdrs; + + /* modpost only works for relocatable objects */ + if (hdr->e_type != ET_REL) + fatal("%s: not relocatable object.", filename); + + /* Check if file offset is correct */ + if (hdr->e_shoff > info->size) + fatal("section header offset=%lu in file '%s' is bigger than filesize=%zu\n", + (unsigned long)hdr->e_shoff, filename, info->size); + + if (hdr->e_shnum == SHN_UNDEF) { + /* + * There are more than 64k sections, + * read count from .sh_size. + */ + info->num_sections = TO_NATIVE(sechdrs[0].sh_size); + } + else { + info->num_sections = hdr->e_shnum; + } + if (hdr->e_shstrndx == SHN_XINDEX) { + info->secindex_strings = TO_NATIVE(sechdrs[0].sh_link); + } + else { + info->secindex_strings = hdr->e_shstrndx; + } + + /* Fix endianness in section headers */ + for (i = 0; i < info->num_sections; i++) { + sechdrs[i].sh_name = TO_NATIVE(sechdrs[i].sh_name); + sechdrs[i].sh_type = TO_NATIVE(sechdrs[i].sh_type); + sechdrs[i].sh_flags = TO_NATIVE(sechdrs[i].sh_flags); + sechdrs[i].sh_addr = TO_NATIVE(sechdrs[i].sh_addr); + sechdrs[i].sh_offset = TO_NATIVE(sechdrs[i].sh_offset); + sechdrs[i].sh_size = TO_NATIVE(sechdrs[i].sh_size); + sechdrs[i].sh_link = TO_NATIVE(sechdrs[i].sh_link); + sechdrs[i].sh_info = TO_NATIVE(sechdrs[i].sh_info); + sechdrs[i].sh_addralign = TO_NATIVE(sechdrs[i].sh_addralign); + sechdrs[i].sh_entsize = TO_NATIVE(sechdrs[i].sh_entsize); + } + /* Find symbol table. */ + secstrings = (void *)hdr + sechdrs[info->secindex_strings].sh_offset; + for (i = 1; i < info->num_sections; i++) { + const char *secname; + int nobits = sechdrs[i].sh_type == SHT_NOBITS; + + if (!nobits && sechdrs[i].sh_offset > info->size) + fatal("%s is truncated. sechdrs[i].sh_offset=%lu > sizeof(*hrd)=%zu\n", + filename, (unsigned long)sechdrs[i].sh_offset, + sizeof(*hdr)); + + secname = secstrings + sechdrs[i].sh_name; + if (strcmp(secname, ".modinfo") == 0) { + if (nobits) + fatal("%s has NOBITS .modinfo\n", filename); + info->modinfo = (void *)hdr + sechdrs[i].sh_offset; + info->modinfo_len = sechdrs[i].sh_size; + } else if (!strcmp(secname, ".export_symbol")) { + info->export_symbol_secndx = i; + } else if (!strcmp(secname, ".no_trim_symbol")) { + info->no_trim_symbol = (void *)hdr + sechdrs[i].sh_offset; + info->no_trim_symbol_len = sechdrs[i].sh_size; + } + + if (sechdrs[i].sh_type == SHT_SYMTAB) { + unsigned int sh_link_idx; + symtab_idx = i; + info->symtab_start = (void *)hdr + + sechdrs[i].sh_offset; + info->symtab_stop = (void *)hdr + + sechdrs[i].sh_offset + sechdrs[i].sh_size; + sh_link_idx = sechdrs[i].sh_link; + info->strtab = (void *)hdr + + sechdrs[sh_link_idx].sh_offset; + } + + /* 32bit section no. table? ("more than 64k sections") */ + if (sechdrs[i].sh_type == SHT_SYMTAB_SHNDX) { + symtab_shndx_idx = i; + info->symtab_shndx_start = (void *)hdr + + sechdrs[i].sh_offset; + info->symtab_shndx_stop = (void *)hdr + + sechdrs[i].sh_offset + sechdrs[i].sh_size; + } + } + if (!info->symtab_start) + fatal("%s has no symtab?\n", filename); + + /* Fix endianness in symbols */ + for (sym = info->symtab_start; sym < info->symtab_stop; sym++) { + sym->st_shndx = TO_NATIVE(sym->st_shndx); + sym->st_name = TO_NATIVE(sym->st_name); + sym->st_value = TO_NATIVE(sym->st_value); + sym->st_size = TO_NATIVE(sym->st_size); + } + + if (symtab_shndx_idx != ~0U) { + Elf32_Word *p; + if (symtab_idx != sechdrs[symtab_shndx_idx].sh_link) + fatal("%s: SYMTAB_SHNDX has bad sh_link: %u!=%u\n", + filename, sechdrs[symtab_shndx_idx].sh_link, + symtab_idx); + /* Fix endianness */ + for (p = info->symtab_shndx_start; p < info->symtab_shndx_stop; + p++) + *p = TO_NATIVE(*p); + } + + symsearch_init(info); + + return 1; +} + +static void parse_elf_finish(struct elf_info *info) +{ + symsearch_finish(info); + release_file(info->hdr, info->size); +} + +static int ignore_undef_symbol(struct elf_info *info, const char *symname) +{ + /* ignore __this_module, it will be resolved shortly */ + if (strcmp(symname, "__this_module") == 0) + return 1; + /* ignore global offset table */ + if (strcmp(symname, "_GLOBAL_OFFSET_TABLE_") == 0) + return 1; + if (info->hdr->e_machine == EM_PPC) + /* Special register function linked on all modules during final link of .ko */ + if (strstarts(symname, "_restgpr_") || + strstarts(symname, "_savegpr_") || + strstarts(symname, "_rest32gpr_") || + strstarts(symname, "_save32gpr_") || + strstarts(symname, "_restvr_") || + strstarts(symname, "_savevr_")) + return 1; + if (info->hdr->e_machine == EM_PPC64) + /* Special register function linked on all modules during final link of .ko */ + if (strstarts(symname, "_restgpr0_") || + strstarts(symname, "_savegpr0_") || + strstarts(symname, "_restgpr1_") || + strstarts(symname, "_savegpr1_") || + strstarts(symname, "_restfpr_") || + strstarts(symname, "_savefpr_") || + strstarts(symname, "_restvr_") || + strstarts(symname, "_savevr_") || + strcmp(symname, ".TOC.") == 0) + return 1; + + /* ignore linker-created section bounds variables */ + if (strstarts(symname, "__start_") || strstarts(symname, "__stop_")) + return 1; + + /* Do not ignore this symbol */ + return 0; +} + +static void handle_symbol(struct module *mod, struct elf_info *info, + const Elf_Sym *sym, const char *symname) +{ + switch (sym->st_shndx) { + case SHN_COMMON: + if (strstarts(symname, "__gnu_lto_")) { + /* Should warn here, but modpost runs before the linker */ + } else + mod_warn(mod, "'%s' is COMMON symbol\n", symname); + break; + case SHN_UNDEF: + /* undefined symbol */ + if (ELF_ST_BIND(sym->st_info) != STB_GLOBAL && + ELF_ST_BIND(sym->st_info) != STB_WEAK) + break; + if (ignore_undef_symbol(info, symname)) + break; + if (info->hdr->e_machine == EM_SPARC || + info->hdr->e_machine == EM_SPARCV9) { + /* Ignore register directives. */ + if (ELF_ST_TYPE(sym->st_info) == STT_SPARC_REGISTER) + break; + if (symname[0] == '.') { + char *munged = xstrdup(symname); + munged[0] = '_'; + munged[1] = toupper(munged[1]); + symname = munged; + } + } + + sym_add_unresolved(symname, mod, + ELF_ST_BIND(sym->st_info) == STB_WEAK); + break; + default: + if (strcmp(symname, "init_module") == 0) + mod->has_init = true; + if (strcmp(symname, "cleanup_module") == 0) + mod->has_cleanup = true; + break; + } +} + +/** + * Parse tag=value strings from .modinfo section + **/ +static char *next_string(char *string, unsigned long *secsize) +{ + /* Skip non-zero chars */ + while (string[0]) { + string++; + if ((*secsize)-- <= 1) + return NULL; + } + + /* Skip any zero padding. */ + while (!string[0]) { + string++; + if ((*secsize)-- <= 1) + return NULL; + } + return string; +} + +static char *get_next_modinfo(struct elf_info *info, const char *tag, + char *prev) +{ + char *p; + unsigned int taglen = strlen(tag); + char *modinfo = info->modinfo; + unsigned long size = info->modinfo_len; + + if (prev) { + size -= prev - modinfo; + modinfo = next_string(prev, &size); + } + + for (p = modinfo; p; p = next_string(p, &size)) { + if (strncmp(p, tag, taglen) == 0 && p[taglen] == '=') + return p + taglen + 1; + } + return NULL; +} + +static char *get_modinfo(struct elf_info *info, const char *tag) + +{ + return get_next_modinfo(info, tag, NULL); +} + +static const char *sym_name(struct elf_info *elf, Elf_Sym *sym) +{ + return sym ? elf->strtab + sym->st_name : ""; +} + +/* + * Check whether the 'string' argument matches one of the 'patterns', + * an array of shell wildcard patterns (glob). + * + * Return true is there is a match. + */ +static bool match(const char *string, const char *const patterns[]) +{ + const char *pattern; + + while ((pattern = *patterns++)) { + if (!fnmatch(pattern, string, 0)) + return true; + } + + return false; +} + +/* useful to pass patterns to match() directly */ +#define PATTERNS(...) \ + ({ \ + static const char *const patterns[] = {__VA_ARGS__, NULL}; \ + patterns; \ + }) + +/* sections that we do not want to do full section mismatch check on */ +static const char *const section_white_list[] = +{ + ".comment*", + ".debug*", + ".zdebug*", /* Compressed debug sections. */ + ".GCC.command.line", /* record-gcc-switches */ + ".mdebug*", /* alpha, score, mips etc. */ + ".pdr", /* alpha, score, mips etc. */ + ".stab*", + ".note*", + ".got*", + ".toc*", + ".xt.prop", /* xtensa */ + ".xt.lit", /* xtensa */ + ".arcextmap*", /* arc */ + ".gnu.linkonce.arcext*", /* arc : modules */ + ".cmem*", /* EZchip */ + ".fmt_slot*", /* EZchip */ + ".gnu.lto*", + ".discard.*", + ".llvm.call-graph-profile", /* call graph */ + "__llvm_covfun", + "__llvm_covmap", + ".klp.symid", /* objtool --klp-symids */ + NULL +}; + +/* + * This is used to find sections missing the SHF_ALLOC flag. + * The cause of this is often a section specified in assembler + * without "ax" / "aw". + */ +static void check_section(struct module *mod, struct elf_info *elf, + Elf_Shdr *sechdr) +{ + const char *sec = sech_name(elf, sechdr); + + if (sechdr->sh_type == SHT_PROGBITS && + !(sechdr->sh_flags & SHF_ALLOC) && + !match(sec, section_white_list)) { + mod_warn(mod, "unexpected non-allocatable section '%s'.\n" + "Did you forget to use \"ax\"/\"aw\" in a .S file?\n" + "Note that for example <linux/init.h> contains\n" + "section definitions for use in .S files.\n\n", + sec); + } +} + + + +#define ALL_INIT_DATA_SECTIONS \ + ".init.setup", ".init.rodata", ".init.data" + +#define ALL_PCI_INIT_SECTIONS \ + ".pci_fixup_early", ".pci_fixup_header", ".pci_fixup_final", \ + ".pci_fixup_enable", ".pci_fixup_resume", \ + ".pci_fixup_resume_early", ".pci_fixup_suspend" + +#define ALL_INIT_SECTIONS ".init.*" +#define ALL_EXIT_SECTIONS ".exit.*" + +#define DATA_SECTIONS ".data", ".data.rel" +#define TEXT_SECTIONS ".text", ".text.*", ".sched.text", \ + ".kprobes.text", ".cpuidle.text", ".noinstr.text", \ + ".ltext", ".ltext.*" +#define OTHER_TEXT_SECTIONS ".ref.text", ".head.text", ".spinlock.text", \ + ".fixup", ".entry.text", ".exception.text", \ + ".coldtext", ".softirqentry.text", ".irqentry.text" + +#define ALL_TEXT_SECTIONS ".init.text", ".exit.text", \ + TEXT_SECTIONS, OTHER_TEXT_SECTIONS + +enum mismatch { + TEXTDATA_TO_ANY_INIT_EXIT, + XXXINIT_TO_SOME_INIT, + ANY_INIT_TO_ANY_EXIT, + ANY_EXIT_TO_ANY_INIT, + EXTABLE_TO_NON_TEXT, +}; + +/** + * Describe how to match sections on different criteria: + * + * @fromsec: Array of sections to be matched. + * + * @bad_tosec: Relocations applied to a section in @fromsec to a section in + * this array is forbidden (black-list). Can be empty. + * + * @good_tosec: Relocations applied to a section in @fromsec must be + * targeting sections in this array (white-list). Can be empty. + * + * @mismatch: Type of mismatch. + */ +struct sectioncheck { + const char *fromsec[20]; + const char *bad_tosec[20]; + const char *good_tosec[20]; + enum mismatch mismatch; +}; + +static const struct sectioncheck sectioncheck[] = { +/* Do not reference init/exit code/data from + * normal code and data + */ +{ + .fromsec = { TEXT_SECTIONS, DATA_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS, NULL }, + .mismatch = TEXTDATA_TO_ANY_INIT_EXIT, +}, +/* Do not use exit code/data from init code */ +{ + .fromsec = { ALL_INIT_SECTIONS, NULL }, + .bad_tosec = { ALL_EXIT_SECTIONS, NULL }, + .mismatch = ANY_INIT_TO_ANY_EXIT, +}, +/* Do not use init code/data from exit code */ +{ + .fromsec = { ALL_EXIT_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, NULL }, + .mismatch = ANY_EXIT_TO_ANY_INIT, +}, +{ + .fromsec = { ALL_PCI_INIT_SECTIONS, NULL }, + .bad_tosec = { ALL_INIT_SECTIONS, NULL }, + .mismatch = ANY_INIT_TO_ANY_EXIT, +}, +{ + .fromsec = { "__ex_table", NULL }, + /* If you're adding any new black-listed sections in here, consider + * adding a special 'printer' for them in scripts/check_extable. + */ + .bad_tosec = { ".altinstr_replacement", NULL }, + .good_tosec = {ALL_TEXT_SECTIONS , NULL}, + .mismatch = EXTABLE_TO_NON_TEXT, +} +}; + +static const struct sectioncheck *section_mismatch( + const char *fromsec, const char *tosec) +{ + int i; + + /* + * The target section could be the SHT_NUL section when we're + * handling relocations to un-resolved symbols, trying to match it + * doesn't make much sense and causes build failures on parisc + * architectures. + */ + if (*tosec == '\0') + return NULL; + + for (i = 0; i < ARRAY_SIZE(sectioncheck); i++) { + const struct sectioncheck *check = §ioncheck[i]; + + if (match(fromsec, check->fromsec)) { + if (check->bad_tosec[0] && match(tosec, check->bad_tosec)) + return check; + if (check->good_tosec[0] && !match(tosec, check->good_tosec)) + return check; + } + } + return NULL; +} + +/** + * Whitelist to allow certain references to pass with no warning. + * + * Pattern 1: + * If a module parameter is declared __initdata and permissions=0 + * then this is legal despite the warning generated. + * We cannot see value of permissions here, so just ignore + * this pattern. + * The pattern is identified by: + * tosec = .init.data + * fromsec = .data* + * atsym =__param* + * + * Pattern 1a: + * module_param_call() ops can refer to __init set function if permissions=0 + * The pattern is identified by: + * tosec = .init.text + * fromsec = .data* + * atsym = __param_ops_* + * + * Pattern 3: + * Whitelist all references from .head.text to any init section + * + * Pattern 4: + * Some symbols belong to init section but still it is ok to reference + * these from non-init sections as these symbols don't have any memory + * allocated for them and symbol address and value are same. So even + * if init section is freed, its ok to reference those symbols. + * For ex. symbols marking the init section boundaries. + * This pattern is identified by + * refsymname = __init_begin, _sinittext, _einittext + * + * Pattern 5: + * GCC may optimize static inlines when fed constant arg(s) resulting + * in functions like cpumask_empty() -- generating an associated symbol + * cpumask_empty.constprop.3 that appears in the audit. If the const that + * is passed in comes from __init, like say nmi_ipi_mask, we get a + * meaningless section warning. May need to add isra symbols too... + * This pattern is identified by + * tosec = init section + * fromsec = text section + * refsymname = *.constprop.* + * + **/ +static int secref_whitelist(const char *fromsec, const char *fromsym, + const char *tosec, const char *tosym) +{ + /* Check for pattern 1 */ + if (match(tosec, PATTERNS(ALL_INIT_DATA_SECTIONS)) && + match(fromsec, PATTERNS(DATA_SECTIONS)) && + strstarts(fromsym, "__param")) + return 0; + + /* Check for pattern 1a */ + if (strcmp(tosec, ".init.text") == 0 && + match(fromsec, PATTERNS(DATA_SECTIONS)) && + strstarts(fromsym, "__param_ops_")) + return 0; + + /* symbols in data sections that may refer to any init/exit sections */ + if (match(fromsec, PATTERNS(DATA_SECTIONS)) && + match(tosec, PATTERNS(ALL_INIT_SECTIONS, ALL_EXIT_SECTIONS)) && + match(fromsym, PATTERNS("*_ops", "*_ops.llvm.*", "*_console"))) + return 0; + + /* Check for pattern 3 */ + if (strstarts(fromsec, ".head.text") && + match(tosec, PATTERNS(ALL_INIT_SECTIONS))) + return 0; + + /* Check for pattern 4 */ + if (match(tosym, PATTERNS("__init_begin", "_sinittext", "_einittext"))) + return 0; + + /* Check for pattern 5 */ + if (match(fromsec, PATTERNS(ALL_TEXT_SECTIONS)) && + match(tosec, PATTERNS(ALL_INIT_SECTIONS)) && + match(fromsym, PATTERNS("*.constprop.*"))) + return 0; + + return 1; +} + +static Elf_Sym *find_fromsym(struct elf_info *elf, Elf_Addr addr, + unsigned int secndx) +{ + return symsearch_find_nearest(elf, addr, secndx, false, ~0); +} + +static Elf_Sym *find_tosym(struct elf_info *elf, Elf_Addr addr, Elf_Sym *sym) +{ + Elf_Sym *new_sym; + + /* If the supplied symbol has a valid name, return it */ + if (is_valid_name(elf, sym)) + return sym; + + /* + * Strive to find a better symbol name, but the resulting name may not + * match the symbol referenced in the original code. + */ + new_sym = symsearch_find_nearest(elf, addr, get_secindex(elf, sym), + true, 20); + return new_sym ? new_sym : sym; +} + +static bool is_executable_section(struct elf_info *elf, unsigned int secndx) +{ + if (secndx >= elf->num_sections) + return false; + + return (elf->sechdrs[secndx].sh_flags & SHF_EXECINSTR) != 0; +} + +static void default_mismatch_handler(struct module *mod, struct elf_info *elf, + const struct sectioncheck* const mismatch, + Elf_Sym *tsym, + unsigned int fsecndx, const char *fromsec, Elf_Addr faddr, + const char *tosec, Elf_Addr taddr) +{ + Elf_Sym *from; + const char *tosym; + const char *fromsym; + char taddr_str[16]; + + from = find_fromsym(elf, faddr, fsecndx); + fromsym = sym_name(elf, from); + + tsym = find_tosym(elf, taddr, tsym); + tosym = sym_name(elf, tsym); + + /* check whitelist - we may ignore it */ + if (!secref_whitelist(fromsec, fromsym, tosec, tosym)) + return; + + sec_mismatch_count++; + + if (!tosym[0]) + snprintf(taddr_str, sizeof(taddr_str), "0x%x", (unsigned int)taddr); + + /* + * The format for the reference source: <symbol_name>+<offset> or <address> + * The format for the reference destination: <symbol_name> or <address> + */ + mod_warn(mod, "section mismatch in reference: %s%s0x%x (section: %s) -> %s (section: %s)\n", + fromsym, fromsym[0] ? "+" : "", + (unsigned int)(faddr - (fromsym[0] ? from->st_value : 0)), + fromsec, tosym[0] ? tosym : taddr_str, tosec); + + if (mismatch->mismatch == EXTABLE_TO_NON_TEXT) { + if (match(tosec, mismatch->bad_tosec)) + fatal("The relocation at %s+0x%lx references\n" + "section \"%s\" which is black-listed.\n" + "Something is seriously wrong and should be fixed.\n" + "You might get more information about where this is\n" + "coming from by using scripts/check_extable.sh %s\n", + fromsec, (long)faddr, tosec, mod->name); + else if (is_executable_section(elf, get_secindex(elf, tsym))) + warn("The relocation at %s+0x%lx references\n" + "section \"%s\" which is not in the list of\n" + "authorized sections. If you're adding a new section\n" + "and/or if this reference is valid, add \"%s\" to the\n" + "list of authorized sections to jump to on fault.\n" + "This can be achieved by adding \"%s\" to\n" + "OTHER_TEXT_SECTIONS in scripts/mod/modpost.c.\n", + fromsec, (long)faddr, tosec, tosec, tosec); + else + error("%s+0x%lx references non-executable section '%s'\n", + fromsec, (long)faddr, tosec); + } +} + +static void check_export_symbol(struct module *mod, struct elf_info *elf, + Elf_Addr faddr, const char *secname, + Elf_Sym *sym) +{ + static const char *prefix = "__export_symbol_"; + const char *label_name, *name, *data; + Elf_Sym *label; + struct symbol *s; + bool is_gpl; + + label = find_fromsym(elf, faddr, elf->export_symbol_secndx); + label_name = sym_name(elf, label); + + if (!strstarts(label_name, prefix)) { + mod_error(mod, ".export_symbol section contains strange symbol '%s'\n", + label_name); + return; + } + + if (ELF_ST_BIND(sym->st_info) != STB_GLOBAL && + ELF_ST_BIND(sym->st_info) != STB_WEAK) { + mod_error(mod, "local symbol '%s' was exported\n", + label_name + strlen(prefix)); + return; + } + + name = sym_name(elf, sym); + if (strcmp(label_name + strlen(prefix), name)) { + mod_error(mod, ".export_symbol section references '%s', but it does not seem to be an export symbol\n", + name); + return; + } + + data = sym_get_data(elf, label); /* license */ + if (!strcmp(data, "GPL")) { + is_gpl = true; + } else if (!strcmp(data, "")) { + is_gpl = false; + } else { + mod_error(mod, "unknown license '%s' was specified for '%s'\n", + data, name); + return; + } + + data += strlen(data) + 1; /* namespace */ + s = sym_add_exported(name, mod, is_gpl, data); + + /* + * We need to be aware whether we are exporting a function or + * a data on some architectures. + */ + s->is_func = (ELF_ST_TYPE(sym->st_info) == STT_FUNC); + + /* + * For parisc64, symbols prefixed $$ from the library have the symbol type + * STT_LOPROC. They should be handled as functions too. + */ + if (elf->hdr->e_ident[EI_CLASS] == ELFCLASS64 && + elf->hdr->e_machine == EM_PARISC && + ELF_ST_TYPE(sym->st_info) == STT_LOPROC) + s->is_func = true; + + if (match(secname, PATTERNS(ALL_INIT_SECTIONS))) + mod_warn(mod, "EXPORT_SYMBOL used for init symbol '%s'. Remove __init or EXPORT_SYMBOL.\n", + name); + else if (match(secname, PATTERNS(ALL_EXIT_SECTIONS))) + mod_warn(mod, "EXPORT_SYMBOL used for exit symbol '%s'. Remove __exit or EXPORT_SYMBOL.\n", + name); +} + +static void check_section_mismatch(struct module *mod, struct elf_info *elf, + Elf_Sym *sym, + unsigned int fsecndx, const char *fromsec, + Elf_Addr faddr, Elf_Addr taddr) +{ + const char *tosec = sec_name(elf, get_secindex(elf, sym)); + const struct sectioncheck *mismatch; + + if (module_enabled && elf->export_symbol_secndx == fsecndx) { + check_export_symbol(mod, elf, faddr, tosec, sym); + return; + } + + mismatch = section_mismatch(fromsec, tosec); + if (!mismatch) + return; + + default_mismatch_handler(mod, elf, mismatch, sym, + fsecndx, fromsec, faddr, + tosec, taddr); +} + +static Elf_Addr addend_386_rel(uint32_t *location, unsigned int r_type) +{ + switch (r_type) { + case R_386_32: + return get_unaligned_native(location); + case R_386_PC32: + return get_unaligned_native(location) + 4; + } + + return (Elf_Addr)(-1); +} + +static int32_t sign_extend32(int32_t value, int index) +{ + uint8_t shift = 31 - index; + + return (int32_t)(value << shift) >> shift; +} + +static Elf_Addr addend_arm_rel(void *loc, Elf_Sym *sym, unsigned int r_type) +{ + uint32_t inst, upper, lower, sign, j1, j2; + int32_t offset; + + switch (r_type) { + case R_ARM_ABS32: + case R_ARM_REL32: + inst = get_unaligned_native((uint32_t *)loc); + return inst + sym->st_value; + case R_ARM_MOVW_ABS_NC: + case R_ARM_MOVT_ABS: + inst = get_unaligned_native((uint32_t *)loc); + offset = sign_extend32(((inst & 0xf0000) >> 4) | (inst & 0xfff), + 15); + return offset + sym->st_value; + case R_ARM_PC24: + case R_ARM_CALL: + case R_ARM_JUMP24: + inst = get_unaligned_native((uint32_t *)loc); + offset = sign_extend32((inst & 0x00ffffff) << 2, 25); + return offset + sym->st_value + 8; + case R_ARM_THM_MOVW_ABS_NC: + case R_ARM_THM_MOVT_ABS: + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); + offset = sign_extend32(((upper & 0x000f) << 12) | + ((upper & 0x0400) << 1) | + ((lower & 0x7000) >> 4) | + (lower & 0x00ff), + 15); + return offset + sym->st_value; + case R_ARM_THM_JUMP19: + /* + * Encoding T3: + * S = upper[10] + * imm6 = upper[5:0] + * J1 = lower[13] + * J2 = lower[11] + * imm11 = lower[10:0] + * imm32 = SignExtend(S:J2:J1:imm6:imm11:'0') + */ + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); + + sign = (upper >> 10) & 1; + j1 = (lower >> 13) & 1; + j2 = (lower >> 11) & 1; + offset = sign_extend32((sign << 20) | (j2 << 19) | (j1 << 18) | + ((upper & 0x03f) << 12) | + ((lower & 0x07ff) << 1), + 20); + return offset + sym->st_value + 4; + case R_ARM_THM_PC22: + case R_ARM_THM_JUMP24: + /* + * Encoding T4: + * S = upper[10] + * imm10 = upper[9:0] + * J1 = lower[13] + * J2 = lower[11] + * imm11 = lower[10:0] + * I1 = NOT(J1 XOR S) + * I2 = NOT(J2 XOR S) + * imm32 = SignExtend(S:I1:I2:imm10:imm11:'0') + */ + upper = get_unaligned_native((uint16_t *)loc); + lower = get_unaligned_native((uint16_t *)loc + 1); + + sign = (upper >> 10) & 1; + j1 = (lower >> 13) & 1; + j2 = (lower >> 11) & 1; + offset = sign_extend32((sign << 24) | + ((~(j1 ^ sign) & 1) << 23) | + ((~(j2 ^ sign) & 1) << 22) | + ((upper & 0x03ff) << 12) | + ((lower & 0x07ff) << 1), + 24); + return offset + sym->st_value + 4; + } + + return (Elf_Addr)(-1); +} + +static Elf_Addr addend_mips_rel(uint32_t *location, unsigned int r_type) +{ + uint32_t inst; + + inst = get_unaligned_native(location); + switch (r_type) { + case R_MIPS_LO16: + return inst & 0xffff; + case R_MIPS_26: + return (inst & 0x03ffffff) << 2; + case R_MIPS_32: + return inst; + } + return (Elf_Addr)(-1); +} + +#ifndef EM_RISCV +#define EM_RISCV 243 +#endif + +#ifndef R_RISCV_SUB32 +#define R_RISCV_SUB32 39 +#endif + +#ifndef EM_LOONGARCH +#define EM_LOONGARCH 258 +#endif + +#ifndef R_LARCH_SUB32 +#define R_LARCH_SUB32 55 +#endif + +#ifndef R_LARCH_RELAX +#define R_LARCH_RELAX 100 +#endif + +#ifndef R_LARCH_ALIGN +#define R_LARCH_ALIGN 102 +#endif + +static void get_rel_type_and_sym(struct elf_info *elf, uint64_t r_info, + unsigned int *r_type, unsigned int *r_sym) +{ + typedef struct { + Elf64_Word r_sym; /* Symbol index */ + unsigned char r_ssym; /* Special symbol for 2nd relocation */ + unsigned char r_type3; /* 3rd relocation type */ + unsigned char r_type2; /* 2nd relocation type */ + unsigned char r_type; /* 1st relocation type */ + } Elf64_Mips_R_Info; + + bool is_64bit = (elf->hdr->e_ident[EI_CLASS] == ELFCLASS64); + + if (elf->hdr->e_machine == EM_MIPS && is_64bit) { + Elf64_Mips_R_Info *mips64_r_info = (void *)&r_info; + + *r_type = mips64_r_info->r_type; + *r_sym = TO_NATIVE(mips64_r_info->r_sym); + return; + } + + if (is_64bit) + r_info = TO_NATIVE((Elf64_Xword)r_info); + else + r_info = TO_NATIVE((Elf32_Word)r_info); + + *r_type = ELF_R_TYPE(r_info); + *r_sym = ELF_R_SYM(r_info); +} + +static void section_rela(struct module *mod, struct elf_info *elf, + unsigned int fsecndx, const char *fromsec, + const Elf_Rela *start, const Elf_Rela *stop) +{ + const Elf_Rela *rela; + + for (rela = start; rela < stop; rela++) { + Elf_Sym *tsym; + Elf_Addr taddr, r_offset; + unsigned int r_type, r_sym; + + r_offset = TO_NATIVE(rela->r_offset); + get_rel_type_and_sym(elf, rela->r_info, &r_type, &r_sym); + + tsym = elf->symtab_start + r_sym; + taddr = tsym->st_value + TO_NATIVE(rela->r_addend); + + switch (elf->hdr->e_machine) { + case EM_RISCV: + if (!strcmp("__ex_table", fromsec) && + r_type == R_RISCV_SUB32) + continue; + break; + case EM_LOONGARCH: + switch (r_type) { + case R_LARCH_SUB32: + if (!strcmp("__ex_table", fromsec)) + continue; + break; + case R_LARCH_RELAX: + case R_LARCH_ALIGN: + /* These relocs do not refer to symbols */ + continue; + } + break; + } + + check_section_mismatch(mod, elf, tsym, + fsecndx, fromsec, r_offset, taddr); + } +} + +static void section_rel(struct module *mod, struct elf_info *elf, + unsigned int fsecndx, const char *fromsec, + const Elf_Rel *start, const Elf_Rel *stop) +{ + const Elf_Rel *rel; + + for (rel = start; rel < stop; rel++) { + Elf_Sym *tsym; + Elf_Addr taddr, r_offset; + unsigned int r_type, r_sym; + void *loc; + + r_offset = TO_NATIVE(rel->r_offset); + get_rel_type_and_sym(elf, rel->r_info, &r_type, &r_sym); + + loc = sym_get_data_by_offset(elf, fsecndx, r_offset); + tsym = elf->symtab_start + r_sym; + + switch (elf->hdr->e_machine) { + case EM_386: + taddr = addend_386_rel(loc, r_type); + break; + case EM_ARM: + taddr = addend_arm_rel(loc, tsym, r_type); + break; + case EM_MIPS: + taddr = addend_mips_rel(loc, r_type); + break; + default: + fatal("Please add code to calculate addend for this architecture\n"); + } + + check_section_mismatch(mod, elf, tsym, + fsecndx, fromsec, r_offset, taddr); + } +} + +/** + * A module includes a number of sections that are discarded + * either when loaded or when used as built-in. + * For loaded modules all functions marked __init and all data + * marked __initdata will be discarded when the module has been initialized. + * Likewise for modules used built-in the sections marked __exit + * are discarded because __exit marked function are supposed to be called + * only when a module is unloaded which never happens for built-in modules. + * The check_sec_ref() function traverses all relocation records + * to find all references to a section that reference a section that will + * be discarded and warns about it. + **/ +static void check_sec_ref(struct module *mod, struct elf_info *elf) +{ + int i; + + /* Walk through all sections */ + for (i = 0; i < elf->num_sections; i++) { + Elf_Shdr *sechdr = &elf->sechdrs[i]; + + check_section(mod, elf, sechdr); + /* We want to process only relocation sections and not .init */ + if (sechdr->sh_type == SHT_REL || sechdr->sh_type == SHT_RELA) { + /* section to which the relocation applies */ + unsigned int secndx = sechdr->sh_info; + const char *secname = sec_name(elf, secndx); + const void *start, *stop; + + /* If the section is known good, skip it */ + if (match(secname, section_white_list)) + continue; + + start = sym_get_data_by_offset(elf, i, 0); + stop = start + sechdr->sh_size; + + if (sechdr->sh_type == SHT_RELA) + section_rela(mod, elf, secndx, secname, + start, stop); + else + section_rel(mod, elf, secndx, secname, + start, stop); + } + } +} + +static char *remove_dot(char *s) +{ + size_t n = strcspn(s, "."); + + if (n && s[n]) { + size_t m = strspn(s + n + 1, "0123456789"); + if (m && (s[n + m + 1] == '.' || s[n + m + 1] == 0)) + s[n] = 0; + } + return s; +} + +/* + * The CRCs are recorded in .*.cmd files in the form of: + * #SYMVER <name> <crc> + */ +static void extract_crcs_for_object(const char *object, struct module *mod) +{ + char cmd_file[PATH_MAX]; + char *buf, *p; + const char *base; + int dirlen, baselen_without_suffix, ret; + + base = get_basename(object); + dirlen = base - object; + + baselen_without_suffix = strlen(object) - dirlen - strlen(".o"); + + /* + * When CONFIG_LTO_CLANG_THIN_DIST=y, the ELF is *.thinlto-native.o + * but the symbol CRCs are recorded in *.o.cmd file. + */ + if (strends(object, ".thinlto-native.o")) + baselen_without_suffix -= strlen(".thinlto-native"); + + ret = snprintf(cmd_file, sizeof(cmd_file), "%.*s.%.*s.o.cmd", + dirlen, object, baselen_without_suffix, base); + if (ret >= sizeof(cmd_file)) { + error("%s: too long path was truncated\n", cmd_file); + return; + } + + buf = read_text_file(cmd_file); + p = buf; + + while ((p = strstr(p, "\n#SYMVER "))) { + char *name; + size_t namelen; + unsigned int crc; + struct symbol *sym; + + name = p + strlen("\n#SYMVER "); + + p = strchr(name, ' '); + if (!p) + break; + + namelen = p - name; + p++; + + if (!isdigit(*p)) + continue; /* skip this line */ + + crc = strtoul(p, &p, 0); + if (*p != '\n') + continue; /* skip this line */ + + name[namelen] = '\0'; + + /* + * sym_find_with_module() may return NULL here. + * It typically occurs when CONFIG_TRIM_UNUSED_KSYMS=y. + * Since commit e1327a127703, genksyms calculates CRCs of all + * symbols, including trimmed ones. Ignore orphan CRCs. + */ + sym = sym_find_with_module(name, mod); + if (sym) + sym_set_crc(sym, crc); + } + + free(buf); +} + +/* + * The symbol versions (CRC) are recorded in the .*.cmd files. + * Parse them to retrieve CRCs for the current module. + */ +static void mod_set_crcs(struct module *mod) +{ + char objlist[PATH_MAX]; + char *buf, *p, *obj; + int ret; + + if (mod->is_vmlinux) { + strcpy(objlist, ".vmlinux.objs"); + } else { + /* objects for a module are listed in the *.mod file. */ + ret = snprintf(objlist, sizeof(objlist), "%s.mod", mod->name); + if (ret >= sizeof(objlist)) { + error("%s: too long path was truncated\n", objlist); + return; + } + } + + buf = read_text_file(objlist); + p = buf; + + while ((obj = strsep(&p, "\n")) && obj[0]) + extract_crcs_for_object(obj, mod); + + free(buf); +} + +static void read_symbols(const char *modname) +{ + const char *symname; + char *version; + char *license; + char *namespace; + struct module *mod; + struct elf_info info = { }; + Elf_Sym *sym; + + if (!strends(modname, ".o")) { + error("%s: filename must be suffixed with .o\n", modname); + return; + } + + if (!parse_elf(&info, modname)) + return; + + /* strip trailing .o */ + mod = new_module(modname, strlen(modname) - strlen(".o")); + + /* save .no_trim_symbol section for later use */ + if (info.no_trim_symbol_len) { + mod->no_trim_symbol = xmalloc(info.no_trim_symbol_len); + memcpy(mod->no_trim_symbol, info.no_trim_symbol, + info.no_trim_symbol_len); + mod->no_trim_symbol_len = info.no_trim_symbol_len; + } + + if (!mod->is_vmlinux) { + license = get_modinfo(&info, "license"); + if (!license) + mod_error(mod, "missing MODULE_LICENSE()\n"); + while (license) { + if (!license_is_gpl_compatible(license)) { + mod->is_gpl_compatible = false; + break; + } + license = get_next_modinfo(&info, "license", license); + } + + for (namespace = get_modinfo(&info, "import_ns"); + namespace; + namespace = get_next_modinfo(&info, "import_ns", namespace)) { + if (strstarts(namespace, MODULE_NS_PREFIX)) + mod_error(mod, "explicitly importing namespace '%s' is not allowed.\n", + namespace); + + add_namespace(&mod->imported_namespaces, namespace); + } + + if (!get_modinfo(&info, "description")) + mod_warn(mod, "missing MODULE_DESCRIPTION()\n"); + } + + for (sym = info.symtab_start; sym < info.symtab_stop; sym++) { + symname = remove_dot(info.strtab + sym->st_name); + + handle_symbol(mod, &info, sym, symname); + handle_moddevtable(mod, &info, sym, symname); + } + + check_sec_ref(mod, &info); + + if (!mod->is_vmlinux) { + version = get_modinfo(&info, "version"); + if (version || all_versions) + get_src_version(mod->name, mod->srcversion, + sizeof(mod->srcversion) - 1); + } + + parse_elf_finish(&info); + + if (modversions) { + /* + * Our trick to get versioning for module struct etc. - it's + * never passed as an argument to an exported function, so + * the automatic versioning doesn't pick it up, but it's really + * important anyhow. + */ + sym_add_unresolved("module_layout", mod, false); + + mod_set_crcs(mod); + } +} + +static void read_symbols_from_files(const char *filename) +{ + FILE *in = stdin; + char fname[PATH_MAX]; + + in = fopen(filename, "r"); + if (!in) + fatal("Can't open filenames file %s: %m", filename); + + while (fgets(fname, PATH_MAX, in) != NULL) { + if (strends(fname, "\n")) + fname[strlen(fname)-1] = '\0'; + read_symbols(fname); + } + + fclose(in); +} + +#define SZ 500 + +/* We first write the generated file into memory using the + * following helper, then compare to the file on disk and + * only update the later if anything changed */ + +void __attribute__((format(printf, 2, 3))) buf_printf(struct buffer *buf, + const char *fmt, ...) +{ + char tmp[SZ]; + int len; + va_list ap; + + va_start(ap, fmt); + len = vsnprintf(tmp, SZ, fmt, ap); + va_end(ap); + + if (len < 0) { + perror("vsnprintf failed"); + exit(1); + } + if (len >= SZ) + fatal("buf_printf output truncated for string %s: %d bytes needed, %d available\n", + tmp, len + 1, SZ); + + buf_write(buf, tmp, len); +} + +void buf_write(struct buffer *buf, const char *s, int len) +{ + if (buf->size - buf->pos < len) { + buf->size += len + SZ; + buf->p = xrealloc(buf->p, buf->size); + } + strncpy(buf->p + buf->pos, s, len); + buf->pos += len; +} + +/** + * verify_module_namespace() - does @modname have access to this symbol's @namespace + * @namespace: export symbol namespace + * @modname: module name + * + * If @namespace is prefixed with "module:" to indicate it is a module namespace + * then test if @modname matches any of the comma separated patterns. + * + * The patterns only support tail-glob. + */ +static bool verify_module_namespace(const char *namespace, const char *modname) +{ + size_t len, modlen = strlen(modname); + const char *prefix = "module:"; + const char *sep; + bool glob; + + if (!strstarts(namespace, prefix)) + return false; + + for (namespace += strlen(prefix); *namespace; namespace = sep) { + sep = strchrnul(namespace, ','); + len = sep - namespace; + + glob = false; + if (sep[-1] == '*') { + len--; + glob = true; + } + + if (*sep) + sep++; + + if (strncmp(namespace, modname, len) == 0 && (glob || len == modlen)) + return true; + } + + return false; +} + +static void check_exports(struct module *mod) +{ + struct symbol *s, *exp; + + list_for_each_entry(s, &mod->unresolved_symbols, list) { + const char *basename; + exp = find_symbol(s->name); + if (!exp) { + if (!s->weak && nr_unresolved++ < MAX_UNRESOLVED_REPORTS) + modpost_log(!warn_unresolved, mod, + "symbol '%s' undefined!\n", + s->name); + continue; + } + if (exp->module == mod) { + mod_error(mod, "symbol '%s' was exported without definition\n", s->name); + continue; + } + + exp->used = true; + s->module = exp->module; + s->crc_valid = exp->crc_valid; + s->crc = exp->crc; + + basename = get_basename(mod->name); + + if (!verify_module_namespace(exp->namespace, basename) && + !contains_namespace(&mod->imported_namespaces, exp->namespace)) { + modpost_log(!allow_missing_ns_imports, mod, + "module uses symbol '%s' from namespace '%s', but does not import it.\n", + exp->name, exp->namespace); + add_namespace(&mod->missing_namespaces, exp->namespace); + } + + if (!mod->is_gpl_compatible && exp->is_gpl_only) + mod_error(mod, "GPL-incompatible module uses GPL-only symbol '%s'\n", + exp->name); + } +} + +static void handle_white_list_exports(const char *white_list) +{ + char *buf, *p, *name; + + buf = read_text_file(white_list); + p = buf; + + while ((name = strsep(&p, "\n"))) { + struct symbol *sym = find_symbol(name); + + if (sym) + sym->used = true; + } + + free(buf); +} + +/* + * Keep symbols recorded in the .no_trim_symbol section. This is necessary to + * prevent CONFIG_TRIM_UNUSED_KSYMS from dropping EXPORT_SYMBOL because + * symbol_get() relies on the symbol being present in the ksymtab for lookups. + */ +static void keep_no_trim_symbols(struct module *mod) +{ + unsigned long size = mod->no_trim_symbol_len; + + for (char *s = mod->no_trim_symbol; s; s = next_string(s , &size)) { + struct symbol *sym; + + /* + * If find_symbol() returns NULL, this symbol is not provided + * by any module, and symbol_get() will fail. + */ + sym = find_symbol(s); + if (sym) + sym->used = true; + } +} + +static void check_modname_len(struct module *mod) +{ + const char *mod_name; + + mod_name = get_basename(mod->name); + + if (strlen(mod_name) >= MODULE_NAME_LEN) + mod_error(mod, "module name is too long\n"); +} + +/** + * Header for the generated file + **/ +static void add_header(struct buffer *b, struct module *mod) +{ + buf_printf(b, "#include <linux/module.h>\n"); + buf_printf(b, "#include <linux/export-internal.h>\n"); + buf_printf(b, "#include <linux/compiler.h>\n"); + buf_printf(b, "\n"); + buf_printf(b, "MODULE_INFO(name, KBUILD_MODNAME);\n"); + buf_printf(b, "\n"); + buf_printf(b, "__visible struct module __this_module\n"); + buf_printf(b, "__section(\".gnu.linkonce.this_module\") = {\n"); + buf_printf(b, "\t.name = KBUILD_MODNAME,\n"); + if (mod->has_init) + buf_printf(b, "\t.init = init_module,\n"); + if (mod->has_cleanup) + buf_printf(b, "#ifdef CONFIG_MODULE_UNLOAD\n" + "\t.exit = cleanup_module,\n" + "#endif\n"); + buf_printf(b, "\t.arch = MODULE_ARCH_INIT,\n"); + buf_printf(b, "};\n"); + + if (!external_module) + buf_printf(b, "\nMODULE_INFO(intree, \"Y\");\n"); + + if (strstarts(mod->name, "drivers/staging")) + buf_printf(b, "\nMODULE_INFO(staging, \"Y\");\n"); + + if (strstarts(mod->name, "tools/testing")) + buf_printf(b, "\nMODULE_INFO(test, \"Y\");\n"); +} + +static void add_exported_symbols(struct buffer *buf, struct module *mod) +{ + struct symbol *sym; + + /* generate struct for exported symbols */ + buf_printf(buf, "\n"); + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + + buf_printf(buf, "KSYMTAB_%s(%s, \"%s\");\n", + sym->is_func ? "FUNC" : "DATA", sym->name, + sym->namespace); + + buf_printf(buf, "SYMBOL_FLAGS(%s, 0x%02x);\n", + sym->name, get_symbol_flags(sym)); + } + + if (!modversions) + return; + + /* record CRCs for exported symbols */ + buf_printf(buf, "\n"); + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + + if (!sym->crc_valid) + mod_warn(mod, "EXPORT symbol '%s' version generation failed, symbol will not be versioned.\n" + "Is '%s' prototyped in <asm/asm-prototypes.h>?\n", + sym->name, sym->name); + + buf_printf(buf, "SYMBOL_CRC(%s, 0x%08x);\n", + sym->name, sym->crc); + } +} + +/** + * Record CRCs for unresolved symbols, supporting long names + */ +static void add_extended_versions(struct buffer *b, struct module *mod) +{ + struct symbol *s; + + if (!extended_modversions) + return; + + buf_printf(b, "\n"); + buf_printf(b, "static const u32 ____version_ext_crcs[]\n"); + buf_printf(b, "__used __section(\"__version_ext_crcs\") = {\n"); + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (!s->module) + continue; + if (!s->crc_valid) { + mod_warn(mod, "symbol '%s' has no CRC!\n", s->name); + continue; + } + buf_printf(b, "\t0x%08x,\n", s->crc); + } + buf_printf(b, "};\n"); + + buf_printf(b, "static const char ____version_ext_names[]\n"); + buf_printf(b, "__used __section(\"__version_ext_names\") =\n"); + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (!s->module) + continue; + if (!s->crc_valid) + /* + * We already warned on this when producing the crc + * table. + * We need to skip its name too, as the indexes in + * both tables need to align. + */ + continue; + buf_printf(b, "\t\"%s\\0\"\n", s->name); + } + buf_printf(b, ";\n"); +} + +/** + * Record CRCs for unresolved symbols + **/ +static void add_versions(struct buffer *b, struct module *mod) +{ + struct symbol *s; + + if (!basic_modversions) + return; + + buf_printf(b, "\n"); + buf_printf(b, "static const struct modversion_info ____versions[]\n"); + buf_printf(b, "__used __section(\"__versions\") = {\n"); + + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (!s->module) + continue; + if (!s->crc_valid) { + mod_warn(mod, "symbol '%s' has no CRC!\n", s->name); + continue; + } + if (strlen(s->name) >= MODULE_NAME_LEN) { + if (extended_modversions) { + /* this symbol will only be in the extended info */ + continue; + } else { + mod_error(mod, "too long symbol '%s'\n", s->name); + break; + } + } + buf_printf(b, "\t{ 0x%08x, \"%s\" },\n", + s->crc, s->name); + } + + buf_printf(b, "};\n"); +} + +static void add_depends(struct buffer *b, struct module *mod) +{ + struct symbol *s; + int first = 1; + + /* Clear ->seen flag of modules that own symbols needed by this. */ + list_for_each_entry(s, &mod->unresolved_symbols, list) { + if (s->module) + s->module->seen = s->module->is_vmlinux; + } + + buf_printf(b, "\n"); + buf_printf(b, "MODULE_INFO(depends, \""); + list_for_each_entry(s, &mod->unresolved_symbols, list) { + const char *p; + if (!s->module) + continue; + + if (s->module->seen) + continue; + + s->module->seen = true; + p = get_basename(s->module->name); + buf_printf(b, "%s%s", first ? "" : ",", p); + first = 0; + } + buf_printf(b, "\");\n"); +} + +static void add_srcversion(struct buffer *b, struct module *mod) +{ + if (mod->srcversion[0]) { + buf_printf(b, "\n"); + buf_printf(b, "MODULE_INFO(srcversion, \"%s\");\n", + mod->srcversion); + } +} + +static void write_buf(struct buffer *b, const char *fname) +{ + FILE *file; + + if (error_occurred) + return; + + file = fopen(fname, "w"); + if (!file) { + perror(fname); + exit(1); + } + if (fwrite(b->p, 1, b->pos, file) != b->pos) { + perror(fname); + exit(1); + } + if (fclose(file) != 0) { + perror(fname); + exit(1); + } +} + +static void write_if_changed(struct buffer *b, const char *fname) +{ + char *tmp; + FILE *file; + struct stat st; + + file = fopen(fname, "r"); + if (!file) + goto write; + + if (fstat(fileno(file), &st) < 0) + goto close_write; + + if (st.st_size != b->pos) + goto close_write; + + tmp = xmalloc(b->pos); + if (fread(tmp, 1, b->pos, file) != b->pos) + goto free_write; + + if (memcmp(tmp, b->p, b->pos) != 0) + goto free_write; + + free(tmp); + fclose(file); + return; + + free_write: + free(tmp); + close_write: + fclose(file); + write: + write_buf(b, fname); +} + +static void write_vmlinux_export_c_file(struct module *mod) +{ + struct buffer buf = { }; + struct module_alias *alias, *next; + + buf_printf(&buf, + "#include <linux/export-internal.h>\n"); + + add_exported_symbols(&buf, mod); + + buf_printf(&buf, + "#include <linux/module.h>\n" + "#undef __MODULE_INFO_PREFIX\n" + "#define __MODULE_INFO_PREFIX\n"); + + list_for_each_entry_safe(alias, next, &mod->aliases, node) { + buf_printf(&buf, "MODULE_INFO(%s.alias, \"%s\");\n", + alias->builtin_modname, alias->str); + list_del(&alias->node); + free(alias->builtin_modname); + free(alias); + } + + write_if_changed(&buf, ".vmlinux.export.c"); + free(buf.p); +} + +/* do sanity checks, and generate *.mod.c file */ +static void write_mod_c_file(struct module *mod) +{ + struct buffer buf = { }; + struct module_alias *alias, *next; + char fname[PATH_MAX]; + int ret; + + add_header(&buf, mod); + add_exported_symbols(&buf, mod); + add_versions(&buf, mod); + add_extended_versions(&buf, mod); + add_depends(&buf, mod); + + buf_printf(&buf, "\n"); + list_for_each_entry_safe(alias, next, &mod->aliases, node) { + buf_printf(&buf, "MODULE_ALIAS(\"%s\");\n", alias->str); + list_del(&alias->node); + free(alias); + } + + add_srcversion(&buf, mod); + + ret = snprintf(fname, sizeof(fname), "%s.mod.c", mod->name); + if (ret >= sizeof(fname)) { + error("%s: too long path was truncated\n", fname); + goto free; + } + + write_if_changed(&buf, fname); + +free: + free(buf.p); +} + +/* parse Module.symvers file. line format: + * 0x12345678<tab>symbol<tab>module<tab>export<tab>namespace + **/ +static void read_dump(const char *fname) +{ + char *buf, *pos, *line; + + buf = read_text_file(fname); + if (!buf) + /* No symbol versions, silently ignore */ + return; + + pos = buf; + + while ((line = get_line(&pos))) { + char *symname, *namespace, *modname, *d, *export; + unsigned int crc; + struct module *mod; + struct symbol *s; + bool gpl_only; + + if (!(symname = strchr(line, '\t'))) + goto fail; + *symname++ = '\0'; + if (!(modname = strchr(symname, '\t'))) + goto fail; + *modname++ = '\0'; + if (!(export = strchr(modname, '\t'))) + goto fail; + *export++ = '\0'; + if (!(namespace = strchr(export, '\t'))) + goto fail; + *namespace++ = '\0'; + + crc = strtoul(line, &d, 16); + if (*symname == '\0' || *modname == '\0' || *d != '\0') + goto fail; + + if (!strcmp(export, "EXPORT_SYMBOL_GPL")) { + gpl_only = true; + } else if (!strcmp(export, "EXPORT_SYMBOL")) { + gpl_only = false; + } else { + error("%s: unknown license %s. skip", symname, export); + continue; + } + + mod = find_module(fname, modname); + if (!mod) { + mod = new_module(modname, strlen(modname)); + mod->dump_file = fname; + } + s = sym_add_exported(symname, mod, gpl_only, namespace); + sym_set_crc(s, crc); + } + free(buf); + return; +fail: + free(buf); + fatal("parse error in symbol dump file\n"); +} + +static void write_dump(const char *fname) +{ + struct buffer buf = { }; + struct module *mod; + struct symbol *sym; + + list_for_each_entry(mod, &modules, list) { + if (mod->dump_file) + continue; + list_for_each_entry(sym, &mod->exported_symbols, list) { + if (trim_unused_exports && !sym->used) + continue; + + buf_printf(&buf, "0x%08x\t%s\t%s\tEXPORT_SYMBOL%s\t%s\n", + sym->crc, sym->name, mod->name, + sym->is_gpl_only ? "_GPL" : "", + sym->namespace); + } + } + write_buf(&buf, fname); + free(buf.p); +} + +static void write_namespace_deps_files(const char *fname) +{ + struct module *mod; + struct namespace_list *ns; + struct buffer ns_deps_buf = {}; + + list_for_each_entry(mod, &modules, list) { + + if (mod->dump_file || list_empty(&mod->missing_namespaces)) + continue; + + buf_printf(&ns_deps_buf, "%s.ko:", mod->name); + + list_for_each_entry(ns, &mod->missing_namespaces, list) + buf_printf(&ns_deps_buf, " %s", ns->namespace); + + buf_printf(&ns_deps_buf, "\n"); + } + + write_if_changed(&ns_deps_buf, fname); + free(ns_deps_buf.p); +} + +struct dump_list { + struct list_head list; + const char *file; +}; + +static void check_host_endian(void) +{ + static const union { + short s; + char c[2]; + } endian_test = { .c = {0x01, 0x02} }; + + switch (endian_test.s) { + case 0x0102: + host_is_big_endian = true; + break; + case 0x0201: + host_is_big_endian = false; + break; + default: + fatal("Unknown host endian\n"); + } +} + +int main(int argc, char **argv) +{ + struct module *mod; + char *missing_namespace_deps = NULL; + char *unused_exports_white_list = NULL; + char *dump_write = NULL, *files_source = NULL; + int opt; + LIST_HEAD(dump_lists); + struct dump_list *dl, *dl2; + + while ((opt = getopt(argc, argv, "ei:MmnT:to:au:WwENd:xb")) != -1) { + switch (opt) { + case 'e': + external_module = true; + break; + case 'i': + dl = xmalloc(sizeof(*dl)); + dl->file = optarg; + list_add_tail(&dl->list, &dump_lists); + break; + case 'M': + module_enabled = true; + break; + case 'm': + modversions = true; + break; + case 'n': + ignore_missing_files = true; + break; + case 'o': + dump_write = optarg; + break; + case 'a': + all_versions = true; + break; + case 'T': + files_source = optarg; + break; + case 't': + trim_unused_exports = true; + break; + case 'u': + unused_exports_white_list = optarg; + break; + case 'W': + extra_warn = true; + break; + case 'w': + warn_unresolved = true; + break; + case 'E': + sec_mismatch_warn_only = false; + break; + case 'N': + allow_missing_ns_imports = true; + break; + case 'd': + missing_namespace_deps = optarg; + break; + case 'b': + basic_modversions = true; + break; + case 'x': + extended_modversions = true; + break; + default: + exit(1); + } + } + + check_host_endian(); + + list_for_each_entry_safe(dl, dl2, &dump_lists, list) { + read_dump(dl->file); + list_del(&dl->list); + free(dl); + } + + while (optind < argc) + read_symbols(argv[optind++]); + + if (files_source) + read_symbols_from_files(files_source); + + list_for_each_entry(mod, &modules, list) { + keep_no_trim_symbols(mod); + + if (mod->dump_file || mod->is_vmlinux) + continue; + + check_modname_len(mod); + check_exports(mod); + } + + if (unused_exports_white_list) + handle_white_list_exports(unused_exports_white_list); + + list_for_each_entry(mod, &modules, list) { + if (mod->dump_file) + continue; + + if (mod->is_vmlinux) + write_vmlinux_export_c_file(mod); + else + write_mod_c_file(mod); + } + + if (missing_namespace_deps) + write_namespace_deps_files(missing_namespace_deps); + + if (dump_write) + write_dump(dump_write); + if (sec_mismatch_count && !sec_mismatch_warn_only) + error("Section mismatches detected.\n" + "Set CONFIG_SECTION_MISMATCH_WARN_ONLY=y to allow them.\n"); + + if (nr_unresolved > MAX_UNRESOLVED_REPORTS) + warn("suppressed %u unresolved symbol warnings because there were too many)\n", + nr_unresolved - MAX_UNRESOLVED_REPORTS); + + return error_occurred ? 1 : 0; +} diff --git a/scripts/mod/modpost.h b/scripts/mod/modpost.h new file mode 100644 index 000000000..d5f6d8283 --- /dev/null +++ b/scripts/mod/modpost.h @@ -0,0 +1,244 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#include <byteswap.h> +#include <stdbool.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/mman.h> +#include <fcntl.h> +#include <unistd.h> +#include <elf.h> +#include "../../include/linux/module_symbol.h" + +#include <list_types.h> +#include "elfconfig.h" + +/* On BSD-alike OSes elf.h defines these according to host's word size */ +#undef ELF_ST_BIND +#undef ELF_ST_TYPE +#undef ELF_R_SYM +#undef ELF_R_TYPE + +#if KERNEL_ELFCLASS == ELFCLASS32 + +#define Elf_Ehdr Elf32_Ehdr +#define Elf_Shdr Elf32_Shdr +#define Elf_Sym Elf32_Sym +#define Elf_Addr Elf32_Addr +#define Elf_Section Elf32_Half +#define ELF_ST_BIND ELF32_ST_BIND +#define ELF_ST_TYPE ELF32_ST_TYPE + +#define Elf_Rel Elf32_Rel +#define Elf_Rela Elf32_Rela +#define ELF_R_SYM ELF32_R_SYM +#define ELF_R_TYPE ELF32_R_TYPE +#else + +#define Elf_Ehdr Elf64_Ehdr +#define Elf_Shdr Elf64_Shdr +#define Elf_Sym Elf64_Sym +#define Elf_Addr Elf64_Addr +#define Elf_Section Elf64_Half +#define ELF_ST_BIND ELF64_ST_BIND +#define ELF_ST_TYPE ELF64_ST_TYPE + +#define Elf_Rel Elf64_Rel +#define Elf_Rela Elf64_Rela +#define ELF_R_SYM ELF64_R_SYM +#define ELF_R_TYPE ELF64_R_TYPE +#endif + +#define bswap(x) \ +({ \ + _Static_assert(sizeof(x) == 1 || sizeof(x) == 2 || \ + sizeof(x) == 4 || sizeof(x) == 8, "bug"); \ + (typeof(x))(sizeof(x) == 2 ? bswap_16(x) : \ + sizeof(x) == 4 ? bswap_32(x) : \ + sizeof(x) == 8 ? bswap_64(x) : \ + x); \ +}) + +#define TO_NATIVE(x) \ + (target_is_big_endian == host_is_big_endian ? x : bswap(x)) + +#define __get_unaligned_t(type, ptr) ({ \ + const struct { type x; } __attribute__((__packed__)) *__pptr = \ + (typeof(__pptr))(ptr); \ + __pptr->x; \ +}) + +#define get_unaligned(ptr) __get_unaligned_t(typeof(*(ptr)), (ptr)) + +#define get_unaligned_native(ptr) \ +({ \ + typeof(*(ptr)) _val = get_unaligned(ptr); \ + TO_NATIVE(_val); \ +}) + +#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) + +#define strstarts(str, prefix) (strncmp(str, prefix, strlen(prefix)) == 0) + +struct buffer { + char *p; + int pos; + int size; +}; + +void __attribute__((format(printf, 2, 3))) +buf_printf(struct buffer *buf, const char *fmt, ...); + +void +buf_write(struct buffer *buf, const char *s, int len); + +/** + * struct module_alias - auto-generated MODULE_ALIAS() + * + * @node: linked to module::aliases + * @modname: name of the builtin module (only for vmlinux) + * @str: a string for MODULE_ALIAS() + */ +struct module_alias { + struct list_head node; + char *builtin_modname; + char str[]; +}; + +/** + * struct module - represent a module (vmlinux or *.ko) + * + * @dump_file: path to the .symvers file if loaded from a file + * @aliases: list head for module_aliases + * @no_trim_symbol: .no_trim_symbol section data + * @no_trim_symbol_len: length of the .no_trim_symbol section + */ +struct module { + struct list_head list; + struct list_head exported_symbols; + struct list_head unresolved_symbols; + const char *dump_file; + bool is_gpl_compatible; + bool is_vmlinux; + bool seen; + bool has_init; + bool has_cleanup; + char srcversion[25]; + // Missing namespace dependencies + struct list_head missing_namespaces; + // Actual imported namespaces + struct list_head imported_namespaces; + struct list_head aliases; + char *no_trim_symbol; + unsigned int no_trim_symbol_len; + char name[]; +}; + +struct elf_info { + size_t size; + Elf_Ehdr *hdr; + Elf_Shdr *sechdrs; + Elf_Sym *symtab_start; + Elf_Sym *symtab_stop; + unsigned int export_symbol_secndx; /* .export_symbol section */ + char *strtab; + char *modinfo; + unsigned int modinfo_len; + char *no_trim_symbol; + unsigned int no_trim_symbol_len; + + /* support for 32bit section numbers */ + + unsigned int num_sections; /* max_secindex + 1 */ + unsigned int secindex_strings; + /* if Nth symbol table entry has .st_shndx = SHN_XINDEX, + * take shndx from symtab_shndx_start[N] instead */ + Elf32_Word *symtab_shndx_start; + Elf32_Word *symtab_shndx_stop; + + struct symsearch *symsearch; +}; + +/* Accessor for sym->st_shndx, hides ugliness of "64k sections" */ +static inline unsigned int get_secindex(const struct elf_info *info, + const Elf_Sym *sym) +{ + unsigned int index = sym->st_shndx; + + /* + * Elf{32,64}_Sym::st_shndx is 2 byte. Big section numbers are available + * in the .symtab_shndx section. + */ + if (index == SHN_XINDEX) + return info->symtab_shndx_start[sym - info->symtab_start]; + + /* + * Move reserved section indices SHN_LORESERVE..SHN_HIRESERVE out of + * the way to UINT_MAX-255..UINT_MAX, to avoid conflicting with real + * section indices. + */ + if (index >= SHN_LORESERVE && index <= SHN_HIRESERVE) + return index - SHN_HIRESERVE - 1; + + return index; +} + +/* + * If there's no name there, ignore it; likewise, ignore it if it's + * one of the magic symbols emitted used by current tools. + * + * Internal symbols created by tools should be ignored by modpost. + */ +static inline bool is_valid_name(struct elf_info *elf, Elf_Sym *sym) +{ + const char *name = elf->strtab + sym->st_name; + + if (!name || !strlen(name)) + return false; + return !is_mapping_symbol(name); +} + +/* symsearch.c */ +void symsearch_init(struct elf_info *elf); +void symsearch_finish(struct elf_info *elf); +Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr, + unsigned int secndx, bool allow_negative, + Elf_Addr min_distance); + +/* file2alias.c */ +void handle_moddevtable(struct module *mod, struct elf_info *info, + Elf_Sym *sym, const char *symname); + +/* sumversion.c */ +void get_src_version(const char *modname, char sum[], unsigned sumlen); + +/* from modpost.c */ +extern bool target_is_big_endian; +extern bool host_is_big_endian; +const char *get_basename(const char *path); +char *read_text_file(const char *filename); +char *get_line(char **stringp); +void *sym_get_data(const struct elf_info *info, const Elf_Sym *sym); + +void __attribute__((format(printf, 3, 4))) +modpost_log(bool is_error, struct module *mod, const char *fmt, ...); + +/* + * warn - show the given message, then let modpost continue running, still + * allowing modpost to exit successfully. This should be used when + * we still allow to generate vmlinux and modules. + * + * error - show the given message, then let modpost continue running, but fail + * in the end. This should be used when we should stop building vmlinux + * or modules, but we can continue running modpost to catch as many + * issues as possible. + * + * fatal - show the given message, and bail out immediately. This should be + * used when there is no point to continue running modpost. + */ +#define warn(fmt, args...) modpost_log(false, NULL, fmt, ##args) +#define error(fmt, args...) modpost_log(true, NULL, fmt, ##args) +#define fatal(fmt, args...) do { error(fmt, ##args); exit(1); } while (1) diff --git a/scripts/mod/sumversion.c b/scripts/mod/sumversion.c new file mode 100644 index 000000000..3dd28b4d0 --- /dev/null +++ b/scripts/mod/sumversion.c @@ -0,0 +1,413 @@ +#include <netinet/in.h> +#ifdef __sun__ +#include <inttypes.h> +#else +#include <stdint.h> +#endif +#include <ctype.h> +#include <errno.h> +#include <string.h> +#include <limits.h> + +#include <xalloc.h> +#include "modpost.h" + +/* + * Stolen form Cryptographic API. + * + * MD4 Message Digest Algorithm (RFC1320). + * + * Implementation derived from Andrew Tridgell and Steve French's + * CIFS MD4 implementation, and the cryptoapi implementation + * originally based on the public domain implementation written + * by Colin Plumb in 1993. + * + * Copyright (c) Andrew Tridgell 1997-1998. + * Modified by Steve French (sfrench@us.ibm.com) 2002 + * Copyright (c) Cryptoapi developers. + * Copyright (c) 2002 David S. Miller (davem@redhat.com) + * Copyright (c) 2002 James Morris <jmorris@intercode.com.au> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ +#define MD4_DIGEST_SIZE 16 +#define MD4_HMAC_BLOCK_SIZE 64 +#define MD4_BLOCK_WORDS 16 +#define MD4_HASH_WORDS 4 + +struct md4_ctx { + uint32_t hash[MD4_HASH_WORDS]; + uint32_t block[MD4_BLOCK_WORDS]; + uint64_t byte_count; +}; + +static inline uint32_t lshift(uint32_t x, unsigned int s) +{ + x &= 0xFFFFFFFF; + return ((x << s) & 0xFFFFFFFF) | (x >> (32 - s)); +} + +static inline uint32_t F(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | ((~x) & z); +} + +static inline uint32_t G(uint32_t x, uint32_t y, uint32_t z) +{ + return (x & y) | (x & z) | (y & z); +} + +static inline uint32_t H(uint32_t x, uint32_t y, uint32_t z) +{ + return x ^ y ^ z; +} + +#define ROUND1(a,b,c,d,k,s) (a = lshift(a + F(b,c,d) + k, s)) +#define ROUND2(a,b,c,d,k,s) (a = lshift(a + G(b,c,d) + k + (uint32_t)0x5A827999,s)) +#define ROUND3(a,b,c,d,k,s) (a = lshift(a + H(b,c,d) + k + (uint32_t)0x6ED9EBA1,s)) + +/* XXX: this stuff can be optimized */ +static inline void le32_to_cpu_array(uint32_t *buf, unsigned int words) +{ + while (words--) { + *buf = ntohl(*buf); + buf++; + } +} + +static inline void cpu_to_le32_array(uint32_t *buf, unsigned int words) +{ + while (words--) { + *buf = htonl(*buf); + buf++; + } +} + +static void md4_transform(uint32_t *hash, uint32_t const *in) +{ + uint32_t a, b, c, d; + + a = hash[0]; + b = hash[1]; + c = hash[2]; + d = hash[3]; + + ROUND1(a, b, c, d, in[0], 3); + ROUND1(d, a, b, c, in[1], 7); + ROUND1(c, d, a, b, in[2], 11); + ROUND1(b, c, d, a, in[3], 19); + ROUND1(a, b, c, d, in[4], 3); + ROUND1(d, a, b, c, in[5], 7); + ROUND1(c, d, a, b, in[6], 11); + ROUND1(b, c, d, a, in[7], 19); + ROUND1(a, b, c, d, in[8], 3); + ROUND1(d, a, b, c, in[9], 7); + ROUND1(c, d, a, b, in[10], 11); + ROUND1(b, c, d, a, in[11], 19); + ROUND1(a, b, c, d, in[12], 3); + ROUND1(d, a, b, c, in[13], 7); + ROUND1(c, d, a, b, in[14], 11); + ROUND1(b, c, d, a, in[15], 19); + + ROUND2(a, b, c, d,in[ 0], 3); + ROUND2(d, a, b, c, in[4], 5); + ROUND2(c, d, a, b, in[8], 9); + ROUND2(b, c, d, a, in[12], 13); + ROUND2(a, b, c, d, in[1], 3); + ROUND2(d, a, b, c, in[5], 5); + ROUND2(c, d, a, b, in[9], 9); + ROUND2(b, c, d, a, in[13], 13); + ROUND2(a, b, c, d, in[2], 3); + ROUND2(d, a, b, c, in[6], 5); + ROUND2(c, d, a, b, in[10], 9); + ROUND2(b, c, d, a, in[14], 13); + ROUND2(a, b, c, d, in[3], 3); + ROUND2(d, a, b, c, in[7], 5); + ROUND2(c, d, a, b, in[11], 9); + ROUND2(b, c, d, a, in[15], 13); + + ROUND3(a, b, c, d,in[ 0], 3); + ROUND3(d, a, b, c, in[8], 9); + ROUND3(c, d, a, b, in[4], 11); + ROUND3(b, c, d, a, in[12], 15); + ROUND3(a, b, c, d, in[2], 3); + ROUND3(d, a, b, c, in[10], 9); + ROUND3(c, d, a, b, in[6], 11); + ROUND3(b, c, d, a, in[14], 15); + ROUND3(a, b, c, d, in[1], 3); + ROUND3(d, a, b, c, in[9], 9); + ROUND3(c, d, a, b, in[5], 11); + ROUND3(b, c, d, a, in[13], 15); + ROUND3(a, b, c, d, in[3], 3); + ROUND3(d, a, b, c, in[11], 9); + ROUND3(c, d, a, b, in[7], 11); + ROUND3(b, c, d, a, in[15], 15); + + hash[0] += a; + hash[1] += b; + hash[2] += c; + hash[3] += d; +} + +static inline void md4_transform_helper(struct md4_ctx *ctx) +{ + le32_to_cpu_array(ctx->block, ARRAY_SIZE(ctx->block)); + md4_transform(ctx->hash, ctx->block); +} + +static void md4_init(struct md4_ctx *mctx) +{ + mctx->hash[0] = 0x67452301; + mctx->hash[1] = 0xefcdab89; + mctx->hash[2] = 0x98badcfe; + mctx->hash[3] = 0x10325476; + mctx->byte_count = 0; +} + +static void md4_update(struct md4_ctx *mctx, + const unsigned char *data, unsigned int len) +{ + const uint32_t avail = sizeof(mctx->block) - (mctx->byte_count & 0x3f); + + mctx->byte_count += len; + + if (avail > len) { + memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), + data, len); + return; + } + + memcpy((char *)mctx->block + (sizeof(mctx->block) - avail), + data, avail); + + md4_transform_helper(mctx); + data += avail; + len -= avail; + + while (len >= sizeof(mctx->block)) { + memcpy(mctx->block, data, sizeof(mctx->block)); + md4_transform_helper(mctx); + data += sizeof(mctx->block); + len -= sizeof(mctx->block); + } + + memcpy(mctx->block, data, len); +} + +static void md4_final_ascii(struct md4_ctx *mctx, char *out, unsigned int len) +{ + const unsigned int offset = mctx->byte_count & 0x3f; + char *p = (char *)mctx->block + offset; + int padding = 56 - (offset + 1); + + *p++ = 0x80; + if (padding < 0) { + memset(p, 0x00, padding + sizeof (uint64_t)); + md4_transform_helper(mctx); + p = (char *)mctx->block; + padding = 56; + } + + memset(p, 0, padding); + mctx->block[14] = mctx->byte_count << 3; + mctx->block[15] = mctx->byte_count >> 29; + le32_to_cpu_array(mctx->block, (sizeof(mctx->block) - + sizeof(uint64_t)) / sizeof(uint32_t)); + md4_transform(mctx->hash, mctx->block); + cpu_to_le32_array(mctx->hash, ARRAY_SIZE(mctx->hash)); + + snprintf(out, len, "%08X%08X%08X%08X", + mctx->hash[0], mctx->hash[1], mctx->hash[2], mctx->hash[3]); +} + +static inline void add_char(unsigned char c, struct md4_ctx *md) +{ + md4_update(md, &c, 1); +} + +static int parse_string(const char *file, unsigned long len, + struct md4_ctx *md) +{ + unsigned long i; + + add_char(file[0], md); + for (i = 1; i < len; i++) { + add_char(file[i], md); + if (file[i] == '"' && file[i-1] != '\\') + break; + } + return i; +} + +static int parse_comment(const char *file, unsigned long len) +{ + unsigned long i; + + for (i = 2; i < len; i++) { + if (file[i-1] == '*' && file[i] == '/') + break; + } + return i; +} + +/* FIXME: Handle .s files differently (eg. # starts comments) --RR */ +static int parse_file(const char *fname, struct md4_ctx *md) +{ + char *file; + unsigned long i, len; + + file = read_text_file(fname); + len = strlen(file); + + for (i = 0; i < len; i++) { + /* Collapse and ignore \ and CR. */ + if (file[i] == '\\' && (i+1 < len) && file[i+1] == '\n') { + i++; + continue; + } + + /* Ignore whitespace */ + if (isspace(file[i])) + continue; + + /* Handle strings as whole units */ + if (file[i] == '"') { + i += parse_string(file+i, len - i, md); + continue; + } + + /* Comments: ignore */ + if (file[i] == '/' && file[i+1] == '*') { + i += parse_comment(file+i, len - i); + continue; + } + + add_char(file[i], md); + } + free(file); + return 1; +} +/* Check whether the file is a static library or not */ +static bool is_static_library(const char *objfile) +{ + int len = strlen(objfile); + + return objfile[len - 2] == '.' && objfile[len - 1] == 'a'; +} + +/* We have dir/file.o. Open dir/.file.o.cmd, look for source_ and deps_ line + * to figure out source files. */ +static int parse_source_files(const char *objfile, struct md4_ctx *md) +{ + char *cmd, *file, *line, *dir, *pos; + const char *base; + int dirlen, ret = 0, check_files = 0; + + cmd = xmalloc(strlen(objfile) + sizeof("..cmd")); + + base = get_basename(objfile); + dirlen = base - objfile; + sprintf(cmd, "%.*s.%s.cmd", dirlen, objfile, base); + + dir = xmalloc(dirlen + 1); + strncpy(dir, objfile, dirlen); + dir[dirlen] = '\0'; + + file = read_text_file(cmd); + + pos = file; + + /* Sum all files in the same dir or subdirs. */ + while ((line = get_line(&pos))) { + char* p; + + /* trim the leading spaces away */ + while (isspace(*line)) + line++; + p = line; + + if (strstarts(line, "source_")) { + p = strrchr(line, ' '); + if (!p) { + warn("malformed line: %s\n", line); + goto out_file; + } + p++; + if (!parse_file(p, md)) { + warn("could not open %s: %s\n", + p, strerror(errno)); + goto out_file; + } + continue; + } + if (strstarts(line, "deps_")) { + check_files = 1; + continue; + } + if (!check_files) + continue; + + /* Continue until line does not end with '\' */ + if ( *(p + strlen(p)-1) != '\\') + break; + /* Terminate line at first space, to get rid of final ' \' */ + while (*p) { + if (isspace(*p)) { + *p = '\0'; + break; + } + p++; + } + + /* Check if this file is in same dir as objfile */ + if ((strstr(line, dir)+strlen(dir)-1) == strrchr(line, '/')) { + if (!parse_file(line, md)) { + warn("could not open %s: %s\n", + line, strerror(errno)); + goto out_file; + } + + } + + } + + /* Everyone parsed OK */ + ret = 1; +out_file: + free(file); + free(dir); + free(cmd); + return ret; +} + +/* Calc and record src checksum. */ +void get_src_version(const char *modname, char sum[], unsigned sumlen) +{ + char *buf, *pos; + struct md4_ctx md; + char *fname; + char filelist[PATH_MAX + 1]; + + /* objects for a module are listed in the first line of *.mod file. */ + snprintf(filelist, sizeof(filelist), "%s.mod", modname); + + buf = read_text_file(filelist); + pos = buf; + + md4_init(&md); + while ((fname = strsep(&pos, "\n"))) { + if (!*fname) + continue; + if (!(is_static_library(fname)) && + !parse_source_files(fname, &md)) + goto free; + } + + md4_final_ascii(&md, sum, sumlen); +free: + free(buf); +} diff --git a/scripts/mod/symsearch.c b/scripts/mod/symsearch.c new file mode 100644 index 000000000..b9737b92f --- /dev/null +++ b/scripts/mod/symsearch.c @@ -0,0 +1,199 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * Helper functions for finding the symbol in an ELF which is "nearest" + * to a given address. + */ +#include <xalloc.h> +#include "modpost.h" + +struct syminfo { + unsigned int symbol_index; + unsigned int section_index; + Elf_Addr addr; +}; + +/* + * Container used to hold an entire binary search table. + * Entries in table are ascending, sorted first by section_index, + * then by addr, and last by symbol_index. The sorting by + * symbol_index is used to ensure predictable behavior when + * multiple symbols are present with the same address; all + * symbols past the first are effectively ignored, by eliding + * them in symsearch_fixup(). + */ +struct symsearch { + unsigned int table_size; + struct syminfo table[]; +}; + +static int syminfo_compare(const void *s1, const void *s2) +{ + const struct syminfo *sym1 = s1; + const struct syminfo *sym2 = s2; + + if (sym1->section_index > sym2->section_index) + return 1; + if (sym1->section_index < sym2->section_index) + return -1; + if (sym1->addr > sym2->addr) + return 1; + if (sym1->addr < sym2->addr) + return -1; + if (sym1->symbol_index > sym2->symbol_index) + return 1; + if (sym1->symbol_index < sym2->symbol_index) + return -1; + return 0; +} + +static unsigned int symbol_count(struct elf_info *elf) +{ + unsigned int result = 0; + + for (Elf_Sym *sym = elf->symtab_start; sym < elf->symtab_stop; sym++) { + if (is_valid_name(elf, sym)) + result++; + } + return result; +} + +/* + * Populate the search array that we just allocated. + * Be slightly paranoid here. The ELF file is mmap'd and could + * conceivably change between symbol_count() and symsearch_populate(). + * If we notice any difference, bail out rather than potentially + * propagating errors or crashing. + */ +static void symsearch_populate(struct elf_info *elf, + struct syminfo *table, + unsigned int table_size) +{ + bool is_arm = (elf->hdr->e_machine == EM_ARM); + + for (Elf_Sym *sym = elf->symtab_start; sym < elf->symtab_stop; sym++) { + if (is_valid_name(elf, sym)) { + if (table_size-- == 0) + fatal("%s: size mismatch\n", __func__); + table->symbol_index = sym - elf->symtab_start; + table->section_index = get_secindex(elf, sym); + table->addr = sym->st_value; + + /* + * For ARM Thumb instruction, the bit 0 of st_value is + * set if the symbol is STT_FUNC type. Mask it to get + * the address. + */ + if (is_arm && ELF_ST_TYPE(sym->st_info) == STT_FUNC) + table->addr &= ~1; + + table++; + } + } + + if (table_size != 0) + fatal("%s: size mismatch\n", __func__); +} + +/* + * Do any fixups on the table after sorting. + * For now, this just finds adjacent entries which have + * the same section_index and addr, and it propagates + * the first symbol_index over the subsequent entries, + * so that only one symbol_index is seen for any given + * section_index and addr. This ensures that whether + * we're looking at an address from "above" or "below" + * that we see the same symbol_index. + * This does leave some duplicate entries in the table; + * in practice, these are a small fraction of the + * total number of entries, and they are harmless to + * the binary search algorithm other than a few occasional + * unnecessary comparisons. + */ +static void symsearch_fixup(struct syminfo *table, unsigned int table_size) +{ + /* Don't look at index 0, it will never change. */ + for (unsigned int i = 1; i < table_size; i++) { + if (table[i].addr == table[i - 1].addr && + table[i].section_index == table[i - 1].section_index) { + table[i].symbol_index = table[i - 1].symbol_index; + } + } +} + +void symsearch_init(struct elf_info *elf) +{ + unsigned int table_size = symbol_count(elf); + + elf->symsearch = xmalloc(sizeof(struct symsearch) + + sizeof(struct syminfo) * table_size); + elf->symsearch->table_size = table_size; + + symsearch_populate(elf, elf->symsearch->table, table_size); + qsort(elf->symsearch->table, table_size, + sizeof(struct syminfo), syminfo_compare); + + symsearch_fixup(elf->symsearch->table, table_size); +} + +void symsearch_finish(struct elf_info *elf) +{ + free(elf->symsearch); + elf->symsearch = NULL; +} + +/* + * Find the syminfo which is in secndx and "nearest" to addr. + * allow_negative: allow returning a symbol whose address is > addr. + * min_distance: ignore symbols which are further away than this. + * + * Returns a pointer into the symbol table for success. + * Returns NULL if no legal symbol is found within the requested range. + */ +Elf_Sym *symsearch_find_nearest(struct elf_info *elf, Elf_Addr addr, + unsigned int secndx, bool allow_negative, + Elf_Addr min_distance) +{ + unsigned int hi = elf->symsearch->table_size; + unsigned int lo = 0; + struct syminfo *table = elf->symsearch->table; + struct syminfo target; + + target.addr = addr; + target.section_index = secndx; + target.symbol_index = ~0; /* compares greater than any actual index */ + while (hi > lo) { + unsigned int mid = lo + (hi - lo) / 2; /* Avoids overflow */ + + if (syminfo_compare(&table[mid], &target) > 0) + hi = mid; + else + lo = mid + 1; + } + + /* + * table[hi], if it exists, is the first entry in the array which + * lies beyond target. table[hi - 1], if it exists, is the last + * entry in the array which comes before target, including the + * case where it perfectly matches the section and the address. + * + * Note -- if the address we're looking up falls perfectly + * in the middle of two symbols, this is written to always + * prefer the symbol with the lower address. + */ + Elf_Sym *result = NULL; + + if (allow_negative && + hi < elf->symsearch->table_size && + table[hi].section_index == secndx && + table[hi].addr - addr <= min_distance) { + min_distance = table[hi].addr - addr; + result = &elf->symtab_start[table[hi].symbol_index]; + } + if (hi > 0 && + table[hi - 1].section_index == secndx && + addr - table[hi - 1].addr <= min_distance) { + result = &elf->symtab_start[table[hi - 1].symbol_index]; + } + return result; +} |
