On 07/13/15 22:09, Gabriel L. Somlo wrote: > Hi, > > A while ago I was pondering on the options available for retrieving > a fw_cfg blob from the guest-side (now that we can insert fw_cfg > files on the host-side command line, see commit 81b2b8106). > > So over the last couple of weekends I cooked up the sysfs kernel module > below, which lists all fw_cfg files under /sys/firmware/fw_cfg/<filename>. > > I'm building it against the current Fedora (22) running kernel (after > installing the kernel-devel rpm) using the following Makefile: > > obj-m := fw_cfg.o > KDIR := /lib/modules/$(shell uname -r)/build > PWD := $(shell pwd) > default: > $(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules > > I'm looking for early feedback before trying to push this into the > kernel: > > 1. Right now, only fw_cfg *files* are listed (not sure it's worth > trying for the pre-FW_CFG_FILE_FIRST blobs, since AFAICT there's > no good way of figuring out their size, not to mention even > knowing which ones are present in the first place. > > 2. File names are listed in /sys/fs/fw_cfg/... with slashes replaced > exclamation marks, e.g.: > > # ls /sys/firmware/fw_cfg/ > bootorder etc!e820 etc!table-loader > etc!acpi!rsdp etc!smbios!smbios-anchor genroms!kvmvapic.bin > etc!acpi!tables etc!smbios!smbios-tables > etc!boot-fail-wait etc!system-states > > That's done automatically by kobject_init_and_add(), and I'm hoping > it's acceptable, since the alternative involves parsing file names > and building some sort of hierarchy of ksets representing subfolders > like "etc", "etc/smbios/", "etc/acpi/", "opt/whatever/", etc.
In general I hope Matt will respond to you; I have just one note (although I have no horse in this race :)): I think it would be really useful if you could somehow translate the fw_cfg "pathname components" to a real directory structure. Utilities like "find", "dirname", "basename" etc are generally great, and it would be nice to preserve that flexibility. Anyway, this is just an idea (because I like "find" so much :)); feel free to ignore it. Thanks! Laszlo > 3. I'm currently only handling x86 and I/O ports. I could drop the > fw_cfg_dmi_whitelist and just check the signature, using mmio where > appropriate, but I don't have a handy-dandy set of VMs for those > architectures on which I could test. Wondering if that's something > we should have before I officially try to submit this to the kernel, > or whether it could wait for a second iteration. > > Speaking of the kernel: My default plan is to subscribe to > kernelnewb...@kernelnewbies.org and submit it there (this is after > all baby's first kernel module :) but if there's a better route for > pushing it upstream, please feel free to suggest it to me. > > Thanks much, > --Gabriel > > PS. This still leaves me with the open question of how to do something > similar on Windows, but I'm less bothered by the concept of compiling > an in-house, ad-hoc, binary-from-hell program to simply read/write from > the fw_cfg I/O ports on Windows :) Although in principle it'd be > nice to have a standard, cross-platform way of doing things, likely > involving the guest agent... > > > /* fw_cfg.c > * > * Expose entries from QEMU's firmware configuration (fw_cfg) device in > * sysfs (read-only, under "/sys/firmware/fw_cfg/<fw_cfg_filename>"). > * > * NOTE: '/' chars in fw_cfg file names automatically converted to '!' by > * the kobject_init_and_add() call. > */ > > #include <linux/module.h> > #include <linux/capability.h> > #include <linux/slab.h> > #include <linux/dmi.h> > > > /* fw_cfg i/o ports */ > #define FW_CFG_PORT_CTL 0x510 > #define FW_CFG_PORT_DATA 0x511 > > /* 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]; > }; > > > /* provide atomic read access to hardware fw_cfg device > * (critical section involves potentially lengthy i/o, using mutex) */ > static DEFINE_MUTEX(fw_cfg_dev_lock); > > /* 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); > outw(select, FW_CFG_PORT_CTL); > while (pos-- > 0) > inb(FW_CFG_PORT_DATA); > insb(FW_CFG_PORT_DATA, buf, count); > mutex_unlock(&fw_cfg_dev_lock); > } > > > /* 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 the kernel, so we need mutual exclusion; > * (critical section is super-short, using spinlock) */ > 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, > }; > > > /* whitelist only hardware likely to have a fw_cfg device */ > static int fw_cfg_dmi_match(const struct dmi_system_id *id) > { > return 1; > } > > static __initdata struct dmi_system_id fw_cfg_whitelist[] = { > { fw_cfg_dmi_match, "QEMU Standard PC", { > DMI_MATCH(DMI_SYS_VENDOR, "QEMU"), > DMI_MATCH(DMI_PRODUCT_NAME, "Standard PC") }, > }, > { .ident = NULL } > }; > > > /* object set to represent fw_cfg sysfs subfolder */ > static struct kset *fw_cfg_kset; > > static int __init fw_cfg_sysfs_init(void) > { > int ret = 0; > uint32_t count, i; > char sig[FW_CFG_SIG_SIZE]; > struct fw_cfg_sysfs_entry *entry; > > /* only install on matching "hardware" */ > if (!dmi_check_system(fw_cfg_whitelist)) { > pr_warn("Supported QEMU Standard PC hardware not found!\n"); > return -ENODEV; > } > > /* 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) { > pr_warn("QEMU fw_cfg signature not found!\n"); > return -ENODEV; > } > > /* create fw_cfg folder in sysfs */ > fw_cfg_kset = kset_create_and_add("fw_cfg", NULL, firmware_kobj); > if (!fw_cfg_kset) > return -ENOMEM; > > /* process fw_cfg file directory entry */ > mutex_lock(&fw_cfg_dev_lock); > outw(FW_CFG_FILE_DIR, FW_CFG_PORT_CTL); /* select fw_cfg directory */ > insb(FW_CFG_PORT_DATA, &count, sizeof(count)); /* get file count */ > count = be32_to_cpu(count); > /* create & register a sysfs node for each file in the directory */ > for (i = 0; i < count; i++) { > /* allocate new entry */ > entry = kzalloc(sizeof(*entry), GFP_KERNEL); > if (!entry) { > ret = -ENOMEM; > break; > } > > /* acquire file information from directory */ > insb(FW_CFG_PORT_DATA, &entry->f, sizeof(struct fw_cfg_file)); > entry->f.size = be32_to_cpu(entry->f.size); > entry->f.select = be16_to_cpu(entry->f.select); > > /* register sysfs entry for this file */ > entry->kobj.kset = fw_cfg_kset; > ret = kobject_init_and_add(&entry->kobj, > &fw_cfg_sysfs_entry_ktype, NULL, > /* NOTE: '/' represented as '!' */ > "%s", entry->f.name); > if (ret) > break; > > /* add raw binary content access */ > ret = sysfs_create_bin_file(&entry->kobj, > &fw_cfg_sysfs_attr_raw); > if (ret) > break; > > /* add entry to global cache */ > fw_cfg_sysfs_cache_enlist(entry); > } > mutex_unlock(&fw_cfg_dev_lock); > > if (ret) { > fw_cfg_sysfs_cache_cleanup(); > kset_unregister(fw_cfg_kset); > } else > pr_debug("fw_cfg: loaded.\n"); > > return ret; > } > > static void __exit fw_cfg_sysfs_exit(void) > { > pr_debug("fw_cfg: unloading.\n"); > fw_cfg_sysfs_cache_cleanup(); > kset_unregister(fw_cfg_kset); > } > > 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"); > MODULE_DEVICE_TABLE(dmi, fw_cfg_whitelist); >