On 07/26/15 01:21, Gabriel L. Somlo wrote: > On Tue, Jul 21, 2015 at 12:07:08AM +0200, Laszlo Ersek wrote: >> On 07/20/15 23:19, Gabriel L. Somlo wrote: >> >> ... I just love "find", sorry. :) Anyway, I don't envision myself as a >> user of this feature any time soon, so please feel free to ignore me. > > How about this: I create /sys/firmware/fw_cfg/by_select with *all* > entries, named after their selector key, in a flat folder.
I think this is nice. The fw_cfg directory blob itself is available at a fixed key (FW_CFG_FILE_DIR, 0x0019, and it is even documented :)), so for a fool-proof, non-interactive lookup, a C language program can search the 0x0019 blob, grab the relevant key value, and then open /sys/firmware/fw_cfg/by_select/<THAT_KEY>. For interactive use or scripting, "by_name" can be used with "find" etc. I think this is really nice. Thanks Laszlo > Then, I also create /sys/firmware/fw_cfg/by_name/... where I try to > build the folder hierarchy given by whatever the file names end up > being, *on a best effort basis*, i.e. I may fail to create *some* of > them, if they have brain-damaged names :) > > The kernel will issue big scary warnings if a symlink already exists > where I'm trying to add a new subdirectory of the same name, or the > other way around. > > The whole thing is below, tested and working on x86 and arm (so one > example of IOPort and one example of MMIO). > > I'm out all of next week, but will submit this to the kernel (and cc > Greg-K-H as advised by Matt) once I get back. > > In the mean time, any final words of wisdom, advice, testing, etc. > much much appreciated ! > > Cheers, > --Gabriel > > > /* > * drivers/firmware/fw_cfg.c > * > * Expose entries from QEMU's firmware configuration (fw_cfg) device in > * sysfs (read-only, under "/sys/firmware/fw_cfg/..."). > * > * Copyright 2015 Gabriel L. Somlo <so...@cmu.edu> > * > * This program is free software; you can redistribute it and/or modify > * it under the terms of the GNU General Public License v2 as published > * by the Free Software Foundation. > * > * This program is distributed in the hope that it will be useful, but > * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY > * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License > * for more details. > * > * You should have received a copy of the GNU General Public License along > * with this program; if not, write to the Free Software Foundation, Inc., > * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA > */ > > #include <linux/module.h> > #include <linux/capability.h> > #include <linux/slab.h> > #include <linux/io.h> > #include <linux/ioport.h> > #include <linux/ctype.h> > > /* selector values for "well-known" fw_cfg entries */ > #define FW_CFG_SIGNATURE 0x00 > #define FW_CFG_FILE_DIR 0x19 > > /* size in bytes of fw_cfg signature */ > #define FW_CFG_SIG_SIZE 4 > > /* fw_cfg "file name" is up to 56 characters (including terminating nul) */ > #define FW_CFG_MAX_FILE_PATH 56 > > /* fw_cfg file directory entry type */ > struct fw_cfg_file { > uint32_t size; > uint16_t select; > uint16_t reserved; > char name[FW_CFG_MAX_FILE_PATH]; > }; > > /* fw_cfg device i/o access options type */ > struct fw_cfg_access { > phys_addr_t start; > uint8_t size; > uint8_t ctrl_offset; > uint8_t data_offset; > bool is_mmio; > const char *name; > }; > > /* fw_cfg device i/o access available options for known architectures */ > static struct fw_cfg_access fw_cfg_modes[] = { > { 0x510, 2, 0, 1, false, "fw_cfg on i386, sun4u" }, > { 0x9020000, 10, 8, 0, true, "fw_cfg on arm" }, > { 0xd00000510, 3, 0, 2, true, "fw_cfg on sun4m" }, > { 0xf0000510, 3, 0, 2, true, "fw_cfg on ppc/mac" }, > { } > }; > > /* fw_cfg device i/o currently selected option set */ > static struct fw_cfg_access *fw_cfg_mode; > > /* fw_cfg device i/o register addresses */ > static void __iomem *fw_cfg_dev_base; > static void __iomem *fw_cfg_dev_ctrl; > static void __iomem *fw_cfg_dev_data; > > /* atomic access to fw_cfg device (potentially slow i/o, so using mutex) */ > static DEFINE_MUTEX(fw_cfg_dev_lock); > > /* pick apropriate endianness for selector key */ > static inline uint16_t fw_cfg_sel_endianness(uint16_t select) > { > return fw_cfg_mode->is_mmio ? cpu_to_be16(select) : cpu_to_le16(select); > } > > /* type for fw_cfg "directory scan" visitor/callback function */ > typedef int (*fw_cfg_file_callback)(const struct fw_cfg_file *f); > > /* run a given callback on each fw_cfg directory entry */ > static int fw_cfg_scan_dir(fw_cfg_file_callback callback) > { > int ret = 0; > uint32_t count, i; > struct fw_cfg_file f; > > mutex_lock(&fw_cfg_dev_lock); > iowrite16(fw_cfg_sel_endianness(FW_CFG_FILE_DIR), fw_cfg_dev_ctrl); > ioread8_rep(fw_cfg_dev_data, &count, sizeof(count)); > for (i = 0; i < be32_to_cpu(count); i++) { > ioread8_rep(fw_cfg_dev_data, &f, sizeof(f)); > ret = callback(&f); > if (ret) > break; > } > mutex_unlock(&fw_cfg_dev_lock); > return ret; > } > > /* read chunk of given fw_cfg blob (caller responsible for sanity-check) */ > static inline void fw_cfg_read_blob(uint16_t select, > void *buf, loff_t pos, size_t count) > { > mutex_lock(&fw_cfg_dev_lock); > iowrite16(fw_cfg_sel_endianness(select), fw_cfg_dev_ctrl); > while (pos-- > 0) > ioread8(fw_cfg_dev_data); > ioread8_rep(fw_cfg_dev_data, buf, count); > mutex_unlock(&fw_cfg_dev_lock); > } > > /* clean up fw_cfg device i/o setup */ > static void fw_cfg_io_cleanup(void) > { > if (fw_cfg_mode->is_mmio) { > iounmap(fw_cfg_dev_base); > release_mem_region(fw_cfg_mode->start, fw_cfg_mode->size); > } else { > ioport_unmap(fw_cfg_dev_base); > release_region(fw_cfg_mode->start, fw_cfg_mode->size); > } > } > > /* probe and map fw_cfg device */ > static int __init fw_cfg_io_probe(void) > { > char sig[FW_CFG_SIG_SIZE]; > > for (fw_cfg_mode = &fw_cfg_modes[0]; > fw_cfg_mode->start; fw_cfg_mode++) { > > phys_addr_t start = fw_cfg_mode->start; > uint8_t size = fw_cfg_mode->size; > > /* reserve and map mmio or ioport region */ > if (fw_cfg_mode->is_mmio) { > if (!request_mem_region(start, size, fw_cfg_mode->name)) > continue; > fw_cfg_dev_base = ioremap(start, size); > if (!fw_cfg_dev_base) { > release_mem_region(start, size); > continue; > } > } else { > if (!request_region(start, size, fw_cfg_mode->name)) > continue; > fw_cfg_dev_base = ioport_map(start, size); > if (!fw_cfg_dev_base) { > release_region(start, size); > continue; > } > } > > /* set control and data register addresses */ > fw_cfg_dev_ctrl = fw_cfg_dev_base + fw_cfg_mode->ctrl_offset; > fw_cfg_dev_data = fw_cfg_dev_base + fw_cfg_mode->data_offset; > > /* verify fw_cfg device signature */ > fw_cfg_read_blob(FW_CFG_SIGNATURE, sig, 0, FW_CFG_SIG_SIZE); > if (memcmp(sig, "QEMU", FW_CFG_SIG_SIZE) == 0) > /* success, we're done */ > return 0; > > /* clean up before probing next access mode */ > fw_cfg_io_cleanup(); > } > > return -ENODEV; > } > > /* fw_cfg_sysfs_entry type */ > struct fw_cfg_sysfs_entry { > struct kobject kobj; > struct fw_cfg_file f; > struct list_head list; > }; > > /* get fw_cfg_sysfs_entry from kobject member */ > static inline struct fw_cfg_sysfs_entry *to_entry(struct kobject *kobj) > { > return container_of(kobj, struct fw_cfg_sysfs_entry, kobj); > } > > /* fw_cfg_sysfs_attribute type */ > struct fw_cfg_sysfs_attribute { > struct attribute attr; > ssize_t (*show)(struct fw_cfg_sysfs_entry *entry, char *buf); > }; > > /* get fw_cfg_sysfs_attribute from attribute member */ > static inline struct fw_cfg_sysfs_attribute *to_attr(struct attribute *attr) > { > return container_of(attr, struct fw_cfg_sysfs_attribute, attr); > } > > /* global cache of fw_cfg_sysfs_entry objects */ > static LIST_HEAD(fw_cfg_entry_cache); > > /* kobjects removed lazily by kernel, mutual exclusion needed */ > static DEFINE_SPINLOCK(fw_cfg_cache_lock); > > static inline void fw_cfg_sysfs_cache_enlist(struct fw_cfg_sysfs_entry *entry) > { > spin_lock(&fw_cfg_cache_lock); > list_add_tail(&entry->list, &fw_cfg_entry_cache); > spin_unlock(&fw_cfg_cache_lock); > } > > static inline void fw_cfg_sysfs_cache_delist(struct fw_cfg_sysfs_entry *entry) > { > spin_lock(&fw_cfg_cache_lock); > list_del(&entry->list); > spin_unlock(&fw_cfg_cache_lock); > } > > static void fw_cfg_sysfs_cache_cleanup(void) > { > struct fw_cfg_sysfs_entry *entry, *next; > > list_for_each_entry_safe(entry, next, &fw_cfg_entry_cache, list) { > /* will end up invoking fw_cfg_sysfs_cache_delist() > * via each object's release() method (i.e. destructor) */ > kobject_put(&entry->kobj); > } > } > > /* default_attrs: per-entry attributes and show methods */ > > #define FW_CFG_SYSFS_ATTR(_attr) \ > struct fw_cfg_sysfs_attribute fw_cfg_sysfs_attr_##_attr = { \ > .attr = { .name = __stringify(_attr), .mode = 0400 }, \ > .show = fw_cfg_sysfs_show_##_attr, \ > } > > static ssize_t fw_cfg_sysfs_show_size(struct fw_cfg_sysfs_entry *e, char *buf) > { > return sprintf(buf, "%d\n", e->f.size); > } > > static ssize_t fw_cfg_sysfs_show_select(struct fw_cfg_sysfs_entry *e, char > *buf) > { > return sprintf(buf, "%d\n", e->f.select); > } > > static ssize_t fw_cfg_sysfs_show_name(struct fw_cfg_sysfs_entry *e, char *buf) > { > return sprintf(buf, "%s\n", e->f.name); > } > > static FW_CFG_SYSFS_ATTR(size); > static FW_CFG_SYSFS_ATTR(select); > static FW_CFG_SYSFS_ATTR(name); > > static struct attribute *fw_cfg_sysfs_entry_attrs[] = { > &fw_cfg_sysfs_attr_size.attr, > &fw_cfg_sysfs_attr_select.attr, > &fw_cfg_sysfs_attr_name.attr, > NULL, > }; > > /* sysfs_ops: find fw_cfg_[entry, attribute] and call appropriate show method > */ > static ssize_t fw_cfg_sysfs_attr_show(struct kobject *kobj, struct attribute > *a, > char *buf) > { > struct fw_cfg_sysfs_entry *entry = to_entry(kobj); > struct fw_cfg_sysfs_attribute *attr = to_attr(a); > > if (!capable(CAP_SYS_ADMIN)) > return -EACCES; > > return attr->show(entry, buf); > } > > static const struct sysfs_ops fw_cfg_sysfs_attr_ops = { > .show = fw_cfg_sysfs_attr_show, > }; > > /* release: destructor, to be called via kobject_put() */ > static void fw_cfg_sysfs_release_entry(struct kobject *kobj) > { > struct fw_cfg_sysfs_entry *entry = to_entry(kobj); > > fw_cfg_sysfs_cache_delist(entry); > kfree(entry); > } > > /* kobj_type: ties together all properties required to register an entry */ > static struct kobj_type fw_cfg_sysfs_entry_ktype = { > .default_attrs = fw_cfg_sysfs_entry_attrs, > .sysfs_ops = &fw_cfg_sysfs_attr_ops, > .release = fw_cfg_sysfs_release_entry, > }; > > /* raw-read method and attribute */ > static ssize_t fw_cfg_sysfs_read_raw(struct file *filp, struct kobject *kobj, > struct bin_attribute *bin_attr, > char *buf, loff_t pos, size_t count) > { > struct fw_cfg_sysfs_entry *entry = to_entry(kobj); > > if (!capable(CAP_SYS_ADMIN)) > return -EACCES; > > if (pos > entry->f.size) > return -EINVAL; > > if (count > entry->f.size - pos) > count = entry->f.size - pos; > > fw_cfg_read_blob(entry->f.select, buf, pos, count); > return count; > } > > static struct bin_attribute fw_cfg_sysfs_attr_raw = { > .attr = { .name = "raw", .mode = 0400 }, > .read = fw_cfg_sysfs_read_raw, > }; > > /* > * kset_find_obj() is not EXPORTed from lib/kobject.c, clone it here for now. > * FIXME: patch adding EXPORT in lib/kobject.c tested working, but then > * building this module for older kernels wouldn't work anymore... > */ > struct kobject *my_kset_find_obj(struct kset *kset, const char *name) > { > struct kobject *k; > struct kobject *ret = NULL; > > spin_lock(&kset->list_lock); > > list_for_each_entry(k, &kset->list, entry) { > if (kobject_name(k) && !strcmp(kobject_name(k), name)) { > ret = kobject_get(k); > break; > } > } > > spin_unlock(&kset->list_lock); > return ret; > } > > /* > * Create a kset subdirectory matching each '/' delimited dirname token > * in 'name', starting with sysfs kset/folder 'dir'; At the end, create > * a symlink directed at the given 'target'. > * NOTE: We do this on a best-effort basis, since 'name' is not guaranteed > * to be a well-behaved path name. Whenever a symlink vs. kset directory > * name collision occurs, the kernel will issue big scary warnings while > * refusing to add the offending link or directory. We follow up with our > * own, slightly less scary error messages explaining the situation :) > */ > static void __init fw_cfg_build_symlink(struct kset *dir, > struct kobject *target, > const char *name) > { > struct kset *subdir; > struct kobject *ko; > char *name_copy, *p, *tok; > > if (!dir || !target || !name || !*name) { > pr_err("fw_cfg: invalid argument to fw_cfg_build_symlink\n"); > return; > } > > /* clone a copy of name for parsing */ > name_copy = p = kstrdup(name, GFP_KERNEL); > if (!name_copy) { > pr_err("fw_cfg: kstrdup failed while " > "creating symlink %s\n", name); > return; > } > > /* create folders for each dirname token, then symlink for basename */ > while ((tok = strsep(&p, "/")) && *tok) { > > /* last (basename) token? If so, add symlink here */ > if (!p || !*p) { > if (sysfs_create_link(&dir->kobj, target, tok)) { > pr_err("fw_cfg: can't create symlink %s " > "of %s\n", tok, name); > } > break; > } > > /* does the current dir contain an item named after tok ? */ > ko = my_kset_find_obj(dir, tok); > if (ko) { > /* drop reference added by kset_find_obj */ > kobject_put(ko); > > /* ko MUST be a kset - we're about to use it as one ! */ > if (ko->ktype != dir->kobj.ktype) { > pr_err("fw_cfg: non-folder token %s already " > "exists while creating %s\n", tok, name); > break; > } > > /* descend into already existing subdirectory */ > dir = to_kset(ko); > } else { > /* create new subdirectory kset */ > subdir = kzalloc(sizeof(struct kset), GFP_KERNEL); > if (!subdir) { > pr_err("fw_cfg: alloc error creating subdir %s " > "of %s\n", tok, name); > break; > } > subdir->kobj.kset = dir; > subdir->kobj.ktype = dir->kobj.ktype; > if (kobject_set_name(&subdir->kobj, "%s", tok)) { > kfree(subdir); > pr_err("fw_cfg: set_name failed on subdir %s " > "of %s\n", tok, name); > break; > } > if (kset_register(subdir)) { > kfree(subdir); > pr_err("fw_cfg: register failed on subdir %s " > "of %s\n", tok, name); > break; > } > > /* descend into newly created subdirectory */ > dir = subdir; > } > } > > /* we're done with cloned copy of name */ > kfree(name_copy); > } > > /* recursively unregister fw_cfg/by_name/ kset directory tree */ > static void fw_cfg_kset_unregister_recursive(struct kset *kset) > { > struct kobject *k, *next; > > list_for_each_entry_safe(k, next, &kset->list, entry) > /* all set members are ksets too, but check just in case... */ > if (k->ktype == kset->kobj.ktype) > fw_cfg_kset_unregister_recursive(to_kset(k)); > > /* symlinks are cleanly and automatically removed with the directory */ > kset_unregister(kset); > } > > /* kobjects & kset representing top-level, by_select, and by_name folders */ > static struct kobject *fw_cfg_top_ko; > static struct kobject *fw_cfg_sel_ko; > static struct kset *fw_cfg_fname_kset; > > /* callback function to register an individual fw_cfg file */ > static int __init fw_cfg_register_file(const struct fw_cfg_file *f) > { > int err; > struct fw_cfg_sysfs_entry *entry; > > /* allocate new entry */ > entry = kzalloc(sizeof(*entry), GFP_KERNEL); > if (!entry) > return -ENOMEM; > > /* set file entry information */ > entry->f.size = be32_to_cpu(f->size); > entry->f.select = be16_to_cpu(f->select); > strcpy(entry->f.name, f->name); > > /* register entry under "/sys/firmware/fw_cfg/by_select/" */ > err = kobject_init_and_add(&entry->kobj, &fw_cfg_sysfs_entry_ktype, > fw_cfg_sel_ko, "%d", entry->f.select); > if (err) > goto err_register; > > /* add raw binary content access */ > err = sysfs_create_bin_file(&entry->kobj, &fw_cfg_sysfs_attr_raw); > if (err) > goto err_add_raw; > > /* try adding "/sys/firmware/fw_cfg/by_name/" symlink (best-effort) */ > fw_cfg_build_symlink(fw_cfg_fname_kset, &entry->kobj, entry->f.name); > > /* success, add entry to global cache */ > fw_cfg_sysfs_cache_enlist(entry); > return 0; > > err_add_raw: > kobject_del(&entry->kobj); > err_register: > kfree(entry); > return err; > } > > /* unregister top-level or by_select folder */ > static inline void fw_cfg_kobj_cleanup(struct kobject *kobj) > { > kobject_del(kobj); > kobject_put(kobj); > } > > static int __init fw_cfg_sysfs_init(void) > { > int err; > > /* probe for the fw_cfg "hardware" */ > err = fw_cfg_io_probe(); > if (err) > return err; > > /* create sysfs toplevel, by_select, and by_name folders */ > err = -ENOMEM; > fw_cfg_top_ko = kobject_create_and_add("fw_cfg", firmware_kobj); > if (!fw_cfg_top_ko) > goto err_sysfs_top; > fw_cfg_sel_ko = kobject_create_and_add("by_select", fw_cfg_top_ko); > if (!fw_cfg_sel_ko) > goto err_sysfs_sel; > fw_cfg_fname_kset = kset_create_and_add("by_name", NULL, fw_cfg_top_ko); > if (!fw_cfg_fname_kset) > goto err_sysfs_name; > > /* process fw_cfg file directory entry, registering each file */ > err = fw_cfg_scan_dir(fw_cfg_register_file); > if (err) > goto err_scan; > > /* success */ > pr_debug("fw_cfg: loaded.\n"); > return 0; > > err_scan: > fw_cfg_sysfs_cache_cleanup(); > fw_cfg_kset_unregister_recursive(fw_cfg_fname_kset); > err_sysfs_name: > fw_cfg_kobj_cleanup(fw_cfg_sel_ko); > err_sysfs_sel: > fw_cfg_kobj_cleanup(fw_cfg_top_ko); > err_sysfs_top: > fw_cfg_io_cleanup(); > return err; > } > > static void __exit fw_cfg_sysfs_exit(void) > { > pr_debug("fw_cfg: unloading.\n"); > fw_cfg_sysfs_cache_cleanup(); > fw_cfg_kset_unregister_recursive(fw_cfg_fname_kset); > fw_cfg_kobj_cleanup(fw_cfg_sel_ko); > fw_cfg_kobj_cleanup(fw_cfg_top_ko); > fw_cfg_io_cleanup(); > } > > module_init(fw_cfg_sysfs_init); > module_exit(fw_cfg_sysfs_exit); > MODULE_AUTHOR("Gabriel L. Somlo <so...@cmu.edu>"); > MODULE_DESCRIPTION("QEMU fw_cfg sysfs support"); > MODULE_LICENSE("GPL"); >