Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

firmware: Add request_partial_firmware_into_buf()

Add request_partial_firmware_into_buf() to allow for portions of a
firmware file to be read into a buffer. This is needed when large firmware
must be loaded in portions from a file on memory constrained systems.

Signed-off-by: Scott Branden <scott.branden@broadcom.com>
Co-developed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: Kees Cook <keescook@chromium.org>
Link: https://lore.kernel.org/r/20201002173828.2099543-16-keescook@chromium.org
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>

authored by

Scott Branden and committed by
Greg Kroah-Hartman
59cdb23c 89287c16

+99 -18
+4
drivers/base/firmware_loader/firmware.h
··· 32 32 * @FW_OPT_FALLBACK_PLATFORM: Enable fallback to device fw copy embedded in 33 33 * the platform's main firmware. If both this fallback and the sysfs 34 34 * fallback are enabled, then this fallback will be tried first. 35 + * @FW_OPT_PARTIAL: Allow partial read of firmware instead of needing to read 36 + * entire file. 35 37 */ 36 38 enum fw_opt { 37 39 FW_OPT_UEVENT = BIT(0), ··· 43 41 FW_OPT_NOCACHE = BIT(4), 44 42 FW_OPT_NOFALLBACK_SYSFS = BIT(5), 45 43 FW_OPT_FALLBACK_PLATFORM = BIT(6), 44 + FW_OPT_PARTIAL = BIT(7), 46 45 }; 47 46 48 47 enum fw_status { ··· 71 68 void *data; 72 69 size_t size; 73 70 size_t allocated_size; 71 + size_t offset; 74 72 u32 opt_flags; 75 73 #ifdef CONFIG_FW_LOADER_PAGED_BUF 76 74 bool is_paged_buf;
+83 -18
drivers/base/firmware_loader/main.c
··· 170 170 struct firmware_cache *fwc, 171 171 void *dbuf, 172 172 size_t size, 173 + size_t offset, 173 174 u32 opt_flags) 174 175 { 175 176 struct fw_priv *fw_priv; 177 + 178 + /* For a partial read, the buffer must be preallocated. */ 179 + if ((opt_flags & FW_OPT_PARTIAL) && !dbuf) 180 + return NULL; 181 + 182 + /* Only partial reads are allowed to use an offset. */ 183 + if (offset != 0 && !(opt_flags & FW_OPT_PARTIAL)) 184 + return NULL; 176 185 177 186 fw_priv = kzalloc(sizeof(*fw_priv), GFP_ATOMIC); 178 187 if (!fw_priv) ··· 197 188 fw_priv->fwc = fwc; 198 189 fw_priv->data = dbuf; 199 190 fw_priv->allocated_size = size; 191 + fw_priv->offset = offset; 200 192 fw_priv->opt_flags = opt_flags; 201 193 fw_state_init(fw_priv); 202 194 #ifdef CONFIG_FW_LOADER_USER_HELPER ··· 226 216 struct fw_priv **fw_priv, 227 217 void *dbuf, 228 218 size_t size, 219 + size_t offset, 229 220 u32 opt_flags) 230 221 { 231 222 struct fw_priv *tmp; 232 223 233 224 spin_lock(&fwc->lock); 234 - if (!(opt_flags & FW_OPT_NOCACHE)) { 225 + /* 226 + * Do not merge requests that are marked to be non-cached or 227 + * are performing partial reads. 228 + */ 229 + if (!(opt_flags & (FW_OPT_NOCACHE | FW_OPT_PARTIAL))) { 235 230 tmp = __lookup_fw_priv(fw_name); 236 231 if (tmp) { 237 232 kref_get(&tmp->ref); ··· 247 232 } 248 233 } 249 234 250 - tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, opt_flags); 235 + tmp = __allocate_fw_priv(fw_name, fwc, dbuf, size, offset, opt_flags); 251 236 if (tmp) { 252 237 INIT_LIST_HEAD(&tmp->list); 253 238 if (!(opt_flags & FW_OPT_NOCACHE)) ··· 505 490 return -ENOMEM; 506 491 507 492 for (i = 0; i < ARRAY_SIZE(fw_path); i++) { 493 + size_t file_size = 0; 494 + size_t *file_size_ptr = NULL; 495 + 508 496 /* skip the unset customized path */ 509 497 if (!fw_path[i][0]) 510 498 continue; ··· 521 503 522 504 fw_priv->size = 0; 523 505 506 + /* 507 + * The total file size is only examined when doing a partial 508 + * read; the "full read" case needs to fail if the whole 509 + * firmware was not completely loaded. 510 + */ 511 + if ((fw_priv->opt_flags & FW_OPT_PARTIAL) && buffer) 512 + file_size_ptr = &file_size; 513 + 524 514 /* load firmware files from the mount namespace of init */ 525 - rc = kernel_read_file_from_path_initns(path, 0, &buffer, msize, 526 - NULL, 515 + rc = kernel_read_file_from_path_initns(path, fw_priv->offset, 516 + &buffer, msize, 517 + file_size_ptr, 527 518 READING_FIRMWARE); 528 519 if (rc < 0) { 529 520 if (rc != -ENOENT) ··· 723 696 static int 724 697 _request_firmware_prepare(struct firmware **firmware_p, const char *name, 725 698 struct device *device, void *dbuf, size_t size, 726 - u32 opt_flags) 699 + size_t offset, u32 opt_flags) 727 700 { 728 701 struct firmware *firmware; 729 702 struct fw_priv *fw_priv; ··· 742 715 } 743 716 744 717 ret = alloc_lookup_fw_priv(name, &fw_cache, &fw_priv, dbuf, size, 745 - opt_flags); 718 + offset, opt_flags); 746 719 747 720 /* 748 721 * bind with 'priv' now to avoid warning in failure path ··· 789 762 static int 790 763 _request_firmware(const struct firmware **firmware_p, const char *name, 791 764 struct device *device, void *buf, size_t size, 792 - u32 opt_flags) 765 + size_t offset, u32 opt_flags) 793 766 { 794 767 struct firmware *fw = NULL; 768 + bool nondirect = false; 795 769 int ret; 796 770 797 771 if (!firmware_p) ··· 804 776 } 805 777 806 778 ret = _request_firmware_prepare(&fw, name, device, buf, size, 807 - opt_flags); 779 + offset, opt_flags); 808 780 if (ret <= 0) /* error or already assigned */ 809 781 goto out; 810 782 811 783 ret = fw_get_filesystem_firmware(device, fw->priv, "", NULL); 784 + 785 + /* Only full reads can support decompression, platform, and sysfs. */ 786 + if (!(opt_flags & FW_OPT_PARTIAL)) 787 + nondirect = true; 788 + 812 789 #ifdef CONFIG_FW_LOADER_COMPRESS 813 - if (ret == -ENOENT) 790 + if (ret == -ENOENT && nondirect) 814 791 ret = fw_get_filesystem_firmware(device, fw->priv, ".xz", 815 792 fw_decompress_xz); 816 793 #endif 817 - 818 - if (ret == -ENOENT) 794 + if (ret == -ENOENT && nondirect) 819 795 ret = firmware_fallback_platform(fw->priv); 820 796 821 797 if (ret) { ··· 827 795 dev_warn(device, 828 796 "Direct firmware load for %s failed with error %d\n", 829 797 name, ret); 830 - ret = firmware_fallback_sysfs(fw, name, device, opt_flags, ret); 798 + if (nondirect) 799 + ret = firmware_fallback_sysfs(fw, name, device, 800 + opt_flags, ret); 831 801 } else 832 802 ret = assign_fw(fw, device); 833 803 ··· 872 838 873 839 /* Need to pin this module until return */ 874 840 __module_get(THIS_MODULE); 875 - ret = _request_firmware(firmware_p, name, device, NULL, 0, 841 + ret = _request_firmware(firmware_p, name, device, NULL, 0, 0, 876 842 FW_OPT_UEVENT); 877 843 module_put(THIS_MODULE); 878 844 return ret; ··· 899 865 900 866 /* Need to pin this module until return */ 901 867 __module_get(THIS_MODULE); 902 - ret = _request_firmware(firmware, name, device, NULL, 0, 868 + ret = _request_firmware(firmware, name, device, NULL, 0, 0, 903 869 FW_OPT_UEVENT | FW_OPT_NO_WARN); 904 870 module_put(THIS_MODULE); 905 871 return ret; ··· 923 889 int ret; 924 890 925 891 __module_get(THIS_MODULE); 926 - ret = _request_firmware(firmware_p, name, device, NULL, 0, 892 + ret = _request_firmware(firmware_p, name, device, NULL, 0, 0, 927 893 FW_OPT_UEVENT | FW_OPT_NO_WARN | 928 894 FW_OPT_NOFALLBACK_SYSFS); 929 895 module_put(THIS_MODULE); ··· 948 914 949 915 /* Need to pin this module until return */ 950 916 __module_get(THIS_MODULE); 951 - ret = _request_firmware(firmware, name, device, NULL, 0, 917 + ret = _request_firmware(firmware, name, device, NULL, 0, 0, 952 918 FW_OPT_UEVENT | FW_OPT_FALLBACK_PLATFORM); 953 919 module_put(THIS_MODULE); 954 920 return ret; ··· 1004 970 return -EOPNOTSUPP; 1005 971 1006 972 __module_get(THIS_MODULE); 1007 - ret = _request_firmware(firmware_p, name, device, buf, size, 973 + ret = _request_firmware(firmware_p, name, device, buf, size, 0, 1008 974 FW_OPT_UEVENT | FW_OPT_NOCACHE); 1009 975 module_put(THIS_MODULE); 1010 976 return ret; 1011 977 } 1012 978 EXPORT_SYMBOL(request_firmware_into_buf); 979 + 980 + /** 981 + * request_partial_firmware_into_buf() - load partial firmware into a previously allocated buffer 982 + * @firmware_p: pointer to firmware image 983 + * @name: name of firmware file 984 + * @device: device for which firmware is being loaded and DMA region allocated 985 + * @buf: address of buffer to load firmware into 986 + * @size: size of buffer 987 + * @offset: offset into file to read 988 + * 989 + * This function works pretty much like request_firmware_into_buf except 990 + * it allows a partial read of the file. 991 + */ 992 + int 993 + request_partial_firmware_into_buf(const struct firmware **firmware_p, 994 + const char *name, struct device *device, 995 + void *buf, size_t size, size_t offset) 996 + { 997 + int ret; 998 + 999 + if (fw_cache_is_setup(device, name)) 1000 + return -EOPNOTSUPP; 1001 + 1002 + __module_get(THIS_MODULE); 1003 + ret = _request_firmware(firmware_p, name, device, buf, size, offset, 1004 + FW_OPT_UEVENT | FW_OPT_NOCACHE | 1005 + FW_OPT_PARTIAL); 1006 + module_put(THIS_MODULE); 1007 + return ret; 1008 + } 1009 + EXPORT_SYMBOL(request_partial_firmware_into_buf); 1013 1010 1014 1011 /** 1015 1012 * release_firmware() - release the resource associated with a firmware image ··· 1074 1009 1075 1010 fw_work = container_of(work, struct firmware_work, work); 1076 1011 1077 - _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 1012 + _request_firmware(&fw, fw_work->name, fw_work->device, NULL, 0, 0, 1078 1013 fw_work->opt_flags); 1079 1014 fw_work->cont(fw, fw_work->context); 1080 1015 put_device(fw_work->device); /* taken in request_firmware_nowait() */
+12
include/linux/firmware.h
··· 53 53 struct device *device); 54 54 int request_firmware_into_buf(const struct firmware **firmware_p, 55 55 const char *name, struct device *device, void *buf, size_t size); 56 + int request_partial_firmware_into_buf(const struct firmware **firmware_p, 57 + const char *name, struct device *device, 58 + void *buf, size_t size, size_t offset); 56 59 57 60 void release_firmware(const struct firmware *fw); 58 61 #else ··· 101 98 102 99 static inline int request_firmware_into_buf(const struct firmware **firmware_p, 103 100 const char *name, struct device *device, void *buf, size_t size) 101 + { 102 + return -EINVAL; 103 + } 104 + 105 + static inline int request_partial_firmware_into_buf 106 + (const struct firmware **firmware_p, 107 + const char *name, 108 + struct device *device, 109 + void *buf, size_t size, size_t offset) 104 110 { 105 111 return -EINVAL; 106 112 }