qemu-devel
[Top][All Lists]
Advanced

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

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


From: Gabriel L. Somlo
Subject: [Qemu-devel] RFC: guest-side retrieval of fw_cfg file
Date: Mon, 13 Jul 2015 16:09:37 -0400
User-agent: Mutt/1.5.23 (2014-03-12)

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.

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]