Something went wrong. Try again.
(I'm so sorry!) Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux
Something went wrong. Try again.
10 kB · 425 lines
C
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426// SPDX-License-Identifier: GPL-2.0
#include <linux/highmem.h>#include <linux/module.h>#include <linux/security.h>#include <linux/slab.h>#include <linux/types.h>
#include "sysfs.h"
/* * sysfs support for firmware loader */
void __fw_load_abort(struct fw_priv *fw_priv){ /* * There is a small window in which user can write to 'loading' * between loading done/aborted and disappearance of 'loading' */ if (fw_state_is_aborted(fw_priv) || fw_state_is_done(fw_priv)) return;
fw_state_aborted(fw_priv);}
#ifdef CONFIG_FW_LOADER_USER_HELPERstatic ssize_t timeout_show(const struct class *class, const struct class_attribute *attr, char *buf){ return sysfs_emit(buf, "%d\n", __firmware_loading_timeout());}
/** * timeout_store() - set number of seconds to wait for firmware * @class: device class pointer * @attr: device attribute pointer * @buf: buffer to scan for timeout value * @count: number of bytes in @buf * * Sets the number of seconds to wait for the firmware. Once * this expires an error will be returned to the driver and no * firmware will be provided. * * Note: zero means 'wait forever'. **/static ssize_t timeout_store(const struct class *class, const struct class_attribute *attr, const char *buf, size_t count){ int tmp_loading_timeout;
if (kstrtoint(buf, 10, &tmp_loading_timeout)) return -EINVAL;
if (tmp_loading_timeout < 0) tmp_loading_timeout = 0;
__fw_fallback_set_timeout(tmp_loading_timeout);
return count;}static CLASS_ATTR_RW(timeout);
static struct attribute *firmware_class_attrs[] = { &class_attr_timeout.attr, NULL,};ATTRIBUTE_GROUPS(firmware_class);
static int do_firmware_uevent(const struct fw_sysfs *fw_sysfs, struct kobj_uevent_env *env){ if (add_uevent_var(env, "FIRMWARE=%s", fw_sysfs->fw_priv->fw_name)) return -ENOMEM; if (add_uevent_var(env, "TIMEOUT=%i", __firmware_loading_timeout())) return -ENOMEM; if (add_uevent_var(env, "ASYNC=%d", fw_sysfs->nowait)) return -ENOMEM;
return 0;}
static int firmware_uevent(const struct device *dev, struct kobj_uevent_env *env){ const struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); int err = 0;
mutex_lock(&fw_lock); if (fw_sysfs->fw_priv) err = do_firmware_uevent(fw_sysfs, env); mutex_unlock(&fw_lock); return err;}#endif /* CONFIG_FW_LOADER_USER_HELPER */
static void fw_dev_release(struct device *dev){ struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev);
if (fw_sysfs->fw_upload_priv) fw_upload_free(fw_sysfs);
kfree(fw_sysfs);}
static struct class firmware_class = { .name = "firmware",#ifdef CONFIG_FW_LOADER_USER_HELPER .class_groups = firmware_class_groups, .dev_uevent = firmware_uevent,#endif .dev_release = fw_dev_release,};
int register_sysfs_loader(void){ int ret = class_register(&firmware_class);
if (ret != 0) return ret; return register_firmware_config_sysctl();}
void unregister_sysfs_loader(void){ unregister_firmware_config_sysctl(); class_unregister(&firmware_class);}
static ssize_t firmware_loading_show(struct device *dev, struct device_attribute *attr, char *buf){ struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); int loading = 0;
mutex_lock(&fw_lock); if (fw_sysfs->fw_priv) loading = fw_state_is_loading(fw_sysfs->fw_priv); mutex_unlock(&fw_lock);
return sysfs_emit(buf, "%d\n", loading);}
/** * firmware_loading_store() - set value in the 'loading' control file * @dev: device pointer * @attr: device attribute pointer * @buf: buffer to scan for loading control value * @count: number of bytes in @buf * * The relevant values are: * * 1: Start a load, discarding any previous partial load. * 0: Conclude the load and hand the data to the driver code. * -1: Conclude the load with an error and discard any written data. **/static ssize_t firmware_loading_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count){ struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); struct fw_priv *fw_priv; ssize_t written = count; int loading;
if (kstrtoint(buf, 10, &loading)) return -EINVAL;
mutex_lock(&fw_lock); fw_priv = fw_sysfs->fw_priv; if (fw_state_is_aborted(fw_priv) || fw_state_is_done(fw_priv)) goto out;
switch (loading) { case 1: /* discarding any previous partial load */ fw_free_paged_buf(fw_priv); fw_state_start(fw_priv); break; case 0: if (fw_state_is_loading(fw_priv)) { int rc;
/* * Several loading requests may be pending on * one same firmware buf, so let all requests * see the mapped 'buf->data' once the loading * is completed. */ rc = fw_map_paged_buf(fw_priv); if (rc) dev_err(dev, "%s: map pages failed\n", __func__); else rc = security_kernel_post_load_data(fw_priv->data, fw_priv->size, LOADING_FIRMWARE, "blob");
/* * Same logic as fw_load_abort, only the DONE bit * is ignored and we set ABORT only on failure. */ if (rc) { fw_state_aborted(fw_priv); written = rc; } else { fw_state_done(fw_priv);
/* * If this is a user-initiated firmware upload * then start the upload in a worker thread now. */ rc = fw_upload_start(fw_sysfs); if (rc) written = rc; } break; } fallthrough; default: dev_err(dev, "%s: unexpected value (%d)\n", __func__, loading); fallthrough; case -1: fw_load_abort(fw_sysfs); if (fw_sysfs->fw_upload_priv) fw_state_init(fw_sysfs->fw_priv);
break; }out: mutex_unlock(&fw_lock); return written;}
DEVICE_ATTR(loading, 0644, firmware_loading_show, firmware_loading_store);
static void firmware_rw_data(struct fw_priv *fw_priv, char *buffer, loff_t offset, size_t count, bool read){ if (read) memcpy(buffer, fw_priv->data + offset, count); else memcpy(fw_priv->data + offset, buffer, count);}
static void firmware_rw(struct fw_priv *fw_priv, char *buffer, loff_t offset, size_t count, bool read){ while (count) { int page_nr = offset >> PAGE_SHIFT; int page_ofs = offset & (PAGE_SIZE - 1); int page_cnt = min_t(size_t, PAGE_SIZE - page_ofs, count);
if (read) memcpy_from_page(buffer, fw_priv->pages[page_nr], page_ofs, page_cnt); else memcpy_to_page(fw_priv->pages[page_nr], page_ofs, buffer, page_cnt);
buffer += page_cnt; offset += page_cnt; count -= page_cnt; }}
static ssize_t firmware_data_read(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t offset, size_t count){ struct device *dev = kobj_to_dev(kobj); struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); struct fw_priv *fw_priv; ssize_t ret_count;
mutex_lock(&fw_lock); fw_priv = fw_sysfs->fw_priv; if (!fw_priv || fw_state_is_done(fw_priv)) { ret_count = -ENODEV; goto out; } if (offset > fw_priv->size) { ret_count = 0; goto out; } if (count > fw_priv->size - offset) count = fw_priv->size - offset;
ret_count = count;
if (fw_priv->data) firmware_rw_data(fw_priv, buffer, offset, count, true); else firmware_rw(fw_priv, buffer, offset, count, true);
out: mutex_unlock(&fw_lock); return ret_count;}
static int fw_realloc_pages(struct fw_sysfs *fw_sysfs, int min_size){ int err;
err = fw_grow_paged_buf(fw_sysfs->fw_priv, PAGE_ALIGN(min_size) >> PAGE_SHIFT); if (err) fw_load_abort(fw_sysfs); return err;}
/** * firmware_data_write() - write method for firmware * @filp: open sysfs file * @kobj: kobject for the device * @bin_attr: bin_attr structure * @buffer: buffer being written * @offset: buffer offset for write in total data store area * @count: buffer size * * Data written to the 'data' attribute will be later handed to * the driver as a firmware image. **/static ssize_t firmware_data_write(struct file *filp, struct kobject *kobj, const struct bin_attribute *bin_attr, char *buffer, loff_t offset, size_t count){ struct device *dev = kobj_to_dev(kobj); struct fw_sysfs *fw_sysfs = to_fw_sysfs(dev); struct fw_priv *fw_priv; ssize_t retval;
if (!capable(CAP_SYS_RAWIO)) return -EPERM;
mutex_lock(&fw_lock); fw_priv = fw_sysfs->fw_priv; if (!fw_priv || fw_state_is_done(fw_priv)) { retval = -ENODEV; goto out; }
if (fw_priv->data) { if (offset + count > fw_priv->allocated_size) { retval = -ENOMEM; goto out; } firmware_rw_data(fw_priv, buffer, offset, count, false); retval = count; } else { retval = fw_realloc_pages(fw_sysfs, offset + count); if (retval) goto out;
retval = count; firmware_rw(fw_priv, buffer, offset, count, false); }
fw_priv->size = max_t(size_t, offset + count, fw_priv->size);out: mutex_unlock(&fw_lock); return retval;}
static const struct bin_attribute firmware_attr_data = { .attr = { .name = "data", .mode = 0644 }, .size = 0, .read = firmware_data_read, .write = firmware_data_write,};
static struct attribute *fw_dev_attrs[] = { &dev_attr_loading.attr,#ifdef CONFIG_FW_UPLOAD &dev_attr_cancel.attr, &dev_attr_status.attr, &dev_attr_error.attr, &dev_attr_remaining_size.attr,#endif NULL};
static const struct bin_attribute *const fw_dev_bin_attrs[] = { &firmware_attr_data, NULL};
static const struct attribute_group fw_dev_attr_group = { .attrs = fw_dev_attrs, .bin_attrs = fw_dev_bin_attrs,#ifdef CONFIG_FW_UPLOAD .is_visible = fw_upload_is_visible,#endif};
static const struct attribute_group *fw_dev_attr_groups[] = { &fw_dev_attr_group, NULL};
struct fw_sysfs *fw_create_instance(struct firmware *firmware, const char *fw_name, struct device *device, u32 opt_flags){ struct fw_sysfs *fw_sysfs; struct device *f_dev;
fw_sysfs = kzalloc_obj(*fw_sysfs); if (!fw_sysfs) { fw_sysfs = ERR_PTR(-ENOMEM); goto exit; }
fw_sysfs->nowait = !!(opt_flags & FW_OPT_NOWAIT); fw_sysfs->fw = firmware; f_dev = &fw_sysfs->dev;
device_initialize(f_dev); dev_set_name(f_dev, "%s", fw_name); f_dev->parent = device; f_dev->class = &firmware_class; f_dev->groups = fw_dev_attr_groups;exit: return fw_sysfs;}