qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file


From: Laszlo Ersek
Subject: Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file
Date: Tue, 14 Jul 2015 13:38:32 +0200
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Thunderbird/31.7.0

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
>    address@hidden 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 <address@hidden>");
> MODULE_DESCRIPTION("QEMU fw_cfg sysfs support");
> MODULE_LICENSE("GPL");
> MODULE_DEVICE_TABLE(dmi, fw_cfg_whitelist);
> 




reply via email to

[Prev in Thread] Current Thread [Next in Thread]