[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);
>
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, (continued)
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Gabriel L. Somlo, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Gabriel L. Somlo, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Richard W.M. Jones, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Richard W.M. Jones, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Peter Maydell, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Gabriel L. Somlo, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Gerd Hoffmann, 2015/07/14
- Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Gabriel L. Somlo, 2015/07/15
Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file,
Laszlo Ersek <=
Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Matt Fleming, 2015/07/15
Re: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file, Michael S. Tsirkin, 2015/07/15