Re: [PATCH v3] drivers/virt: vmgenid: add vm generation id driver

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

 



Hi Adrian,

Usually each version of a patch is a separate e-mail thread

On Fri, Nov 27, 2020 at 08:26:02PM +0200, Catangiu, Adrian Costin wrote:
> - Background
> 
> The VM Generation ID is a feature defined by Microsoft (paper:
> http://go.microsoft.com/fwlink/?LinkId=260709) and supported by
> multiple hypervisor vendors.
> 
> The feature is required in virtualized environments by apps that work
> with local copies/caches of world-unique data such as random values,
> uuids, monotonically increasing counters, etc.
> Such apps can be negatively affected by VM snapshotting when the VM
> is either cloned or returned to an earlier point in time.
> 
> The VM Generation ID is a simple concept meant to alleviate the issue
> by providing a unique ID that changes each time the VM is restored
> from a snapshot. The hw provided UUID value can be used to
> differentiate between VMs or different generations of the same VM.
> 
> - Problem
> 
> The VM Generation ID is exposed through an ACPI device by multiple
> hypervisor vendors but neither the vendors or upstream Linux have no
> default driver for it leaving users to fend for themselves.
> 
> Furthermore, simply finding out about a VM generation change is only
> the starting point of a process to renew internal states of possibly
> multiple applications across the system. This process could benefit
> from a driver that provides an interface through which orchestration
> can be easily done.
> 
> - Solution
> 
> This patch is a driver that exposes a monotonic incremental Virtual
> Machine Generation u32 counter via a char-dev FS interface. The FS
> interface provides sync and async VmGen counter updates notifications.
> It also provides VmGen counter retrieval and confirmation mechanisms.
> 
> The generation counter and the interface through which it is exposed
> are available even when there is no acpi device present.
> 
> When the device is present, the hw provided UUID is not exposed to
> userspace, it is internally used by the driver to keep accounting for
> the exposed VmGen counter. The counter starts from zero when the
> driver is initialized and monotonically increments every time the hw
> UUID changes (the VM generation changes).
> On each hw UUID change, the new hypervisor-provided UUID is also fed
> to the kernel RNG.
> 
> If there is no acpi vmgenid device present, the generation changes are
> not driven by hw vmgenid events but can be driven by software through
> a dedicated driver ioctl.
> 
> This patch builds on top of Or Idgar <oridgar@xxxxxxxxx>'s proposal
> https://lkml.org/lkml/2018/3/1/498
> 
> - Future improvements
> 
> Ideally we would want the driver to register itself based on devices'
> _CID and not _HID, but unfortunately I couldn't find a way to do that.
> The problem is that ACPI device matching is done by
> '__acpi_match_device()' which exclusively looks at
> 'acpi_hardware_id *hwid'.
> 
> There is a path for platform devices to match on _CID when _HID is
> 'PRP0001' - but this is not the case for the Qemu vmgenid device.
> 
> Guidance and help here would be greatly appreciated.
> 
> Signed-off-by: Adrian Catangiu <acatan@xxxxxxxxxx>
> 
> ---
 
Please put the history in the descending order next time

v2 -> v3:
...

v1 -> v2:
...

> v1 -> v2:
> 
>   - expose to userspace a monotonically increasing u32 Vm Gen Counter
>     instead of the hw VmGen UUID
>   - since the hw/hypervisor-provided 128-bit UUID is not public
>     anymore, add it to the kernel RNG as device randomness
>   - insert driver page containing Vm Gen Counter in the user vma in
>     the driver's mmap handler instead of using a fault handler
>   - turn driver into a misc device driver to auto-create /dev/vmgenid
>   - change ioctl arg to avoid leaking kernel structs to userspace
>   - update documentation
>   - various nits
>   - rebase on top of linus latest
> 
> v2 -> v3:
> 
>   - separate the core driver logic and interface, from the ACPI device.
>     The ACPI vmgenid device is now one possible backend.
>   - fix issue when timeout=0 in VMGENID_WAIT_WATCHERS
>   - add locking to avoid races between fs ops handlers and hw irq
>     driven generation updates
>   - change VMGENID_WAIT_WATCHERS ioctl so if the current caller is
>     outdated or a generation change happens while waiting (thus making
>     current caller outdated), the ioctl returns -EINTR to signal the
>     user to handle event and retry. Fixes blocking on oneself.
>   - add VMGENID_FORCE_GEN_UPDATE ioctl conditioned by
>     CAP_CHECKPOINT_RESTORE capability, through which software can force
>     generation bump.
> ---
>  Documentation/virt/vmgenid.rst | 240 +++++++++++++++++++++++
>  drivers/virt/Kconfig           |  17 ++
>  drivers/virt/Makefile          |   1 +
>  drivers/virt/vmgenid.c         | 435 +++++++++++++++++++++++++++++++++++++++++
>  include/uapi/linux/vmgenid.h   |  14 ++
>  5 files changed, 707 insertions(+)
>  create mode 100644 Documentation/virt/vmgenid.rst
>  create mode 100644 drivers/virt/vmgenid.c
>  create mode 100644 include/uapi/linux/vmgenid.h
> 
> diff --git a/Documentation/virt/vmgenid.rst b/Documentation/virt/vmgenid.rst
> new file mode 100644
> index 0000000..b6a9f8d
> --- /dev/null
> +++ b/Documentation/virt/vmgenid.rst
> @@ -0,0 +1,240 @@
> +.. SPDX-License-Identifier: GPL-2.0
> +
> +============
> +VMGENID
> +============

The "==" line should be the same length as the title, I think.

> +
> +The VM Generation ID is a feature defined by Microsoft (paper:
> +http://go.microsoft.com/fwlink/?LinkId=260709) and supported by
> +multiple hypervisor vendors.
> +
> +The feature is required in virtualized environments by apps that work

Please spell 'applications' fully

> +with local copies/caches of world-unique data such as random values,
> +uuids, monotonically increasing counters, etc.

UUIDs

> +Such apps can be negatively affected by VM snapshotting when the VM

       ^applications

> +is either cloned or returned to an earlier point in time.
> +
> +The VM Generation ID is a simple concept meant to alleviate the issue
> +by providing a unique ID that changes each time the VM is restored
> +from a snapshot. The hw provided UUID value can be used to

                       ^hardware (and below as well)

> +differentiate between VMs or different generations of the same VM.
> +
> +The VM Generation ID is exposed through an ACPI device by multiple
> +hypervisor vendors. The driver for it lives at
> +``drivers/virt/vmgenid.c``
> +
> +The ``vmgenid`` driver exposes a monotonic incremental Virtual
> +Machine Generation u32 counter via a char-dev FS interface that
> +provides sync and async VmGen counter updates notifications. It also
> +provides VmGen counter retrieval and confirmation mechanisms.

It would be nice to memntion here the name of the chardev :)

> +This counter and the interface through which it is exposed are
> +available even when there is no acpi device present.
> +
> +When the device is present, the hw provided UUID is not exposed to
> +userspace, it is internally used by the driver to keep accounting for
> +the exposed VmGen counter. The counter starts from zero when the
> +driver is initialized and monotonically increments every time the hw
> +UUID changes (the VM generation changes).
> +On each hw UUID change, the new UUID is also fed to the kernel RNG.
> +
> +If there is no acpi vmgenid device present, the generation changes are
> +not driven by hw vmgenid events and thus should be driven by software
> +through a dedicated driver ioctl.
> +
> +Driver interface:
> +
> +``open()``:
> +  When the device is opened, a copy of the current Vm-Gen-Id (counter)
> +  is associated with the open file descriptor. The driver now tracks
> +  this file as an independent *watcher*. The driver tracks how many
> +  watchers are aware of the latest Vm-Gen-Id counter and how many of
> +  them are *outdated*; outdated being those that have lived through
> +  a Vm-Gen-Id change but not yet confirmed the new generation counter.
> +
> +``read()``:
> +  Read is meant to provide the *new* VM generation counter when a
> +  generation change takes place. The read operation blocks until the
> +  associated counter is no longer up to date - until HW vm gen id
> +  changes - at which point the new counter is provided/returned.
> +  Nonblocking ``read()`` uses ``EAGAIN`` to signal that there is no
> +  *new* counter value available. The generation counter is considered
> +  *new* for each open file descriptor that hasn't confirmed the new
> +  value, following a generation change. Therefore, once a generation
> +  change takes place, all ``read()`` calls will immediately return the
> +  new generation counter and will continue to do so until the
> +  new value is confirmed back to the driver through ``write()``.
> +  Partial reads are not allowed - read buffer needs to be at least
> +  ``sizeof(unsigned)`` in size.
> +
> +``write()``:
> +  Write is used to confirm the up-to-date Vm Gen counter back to the
> +  driver.
> +  Following a VM generation change, all existing watchers are marked
> +  as *outdated*. Each file descriptor will maintain the *outdated*
> +  status until a ``write()`` confirms the up-to-date counter back to
> +  the driver.
> +  Partial writes are not allowed - write buffer should be exactly
> +  ``sizeof(unsigned)`` in size.
> +
> +``poll()``:
> +  Poll is implemented to allow polling for generation counter updates.
> +  Such updates result in ``EPOLLIN`` polling status until the new
> +  up-to-date counter is confirmed back to the driver through a
> +  ``write()``.
> +
> +``ioctl()``:
> +  The driver also adds support for tracking count of open file
> +  descriptors that haven't acknowledged a generation counter update.
> +  This is exposed through two IOCTLs:
> +
> +  - VMGENID_GET_OUTDATED_WATCHERS: immediately returns the number of
> +    *outdated* watchers - number of file descriptors that were open
> +    during a VM generation change, and which have not yet confirmed the
> +    new generation counter.
> +  - VMGENID_WAIT_WATCHERS: blocks until there are no more *outdated*
> +    watchers, or if a ``timeout`` argument is provided, until the
> +    timeout expires.
> +    If the current caller is *outdated* or a generation change happens
> +    while waiting (thus making current caller *outdated*), the ioctl
> +    returns ``-EINTR`` to signal the user to handle event and retry.
> +  - VMGENID_FORCE_GEN_UPDATE: forces a generation counter bump. Can only
> +    be used by processes with CAP_CHECKPOINT_RESTORE or CAP_SYS_ADMIN
> +    capabilities.
> +
> +``mmap()``:
> +  The driver supports ``PROT_READ, MAP_SHARED`` mmaps of a single page
> +  in size. The first 4 bytes of the mapped page will contain an
> +  up-to-date copy of the VM generation counter.
> +  The mapped memory can be used as a low-latency generation counter
> +  probe mechanism in critical sections - see examples.
> +
> +``close()``:
> +  Removes the file descriptor as a Vm generation counter watcher.
> +
> +Example application workflows
> +-----------------------------
> +
> +1) Watchdog thread simplified example::
> +
> +    void watchdog_thread_handler(int *thread_active)
> +    {
> +        unsigned genid;
> +        int fd = open("/dev/vmgenid", O_RDWR | O_CLOEXEC, S_IRUSR |
> S_IWUSR);
> +
> +        do {
> +            // read new gen ID - blocks until VM generation changes
> +            read(fd, &genid, sizeof(genid));
> +
> +            // because of VM generation change, we need to rebuild world
> +            reseed_app_env();
> +
> +            // confirm we're done handling gen ID update
> +            write(fd, &genid, sizeof(genid));
> +        } while (atomic_read(thread_active));
> +
> +        close(fd);
> +    }
> +
> +2) ASYNC simplified example::
> +
> +    void handle_io_on_vmgenfd(int vmgenfd)
> +    {
> +        unsigned genid;
> +
> +        // read new gen ID - we need it to confirm we've handled update
> +        read(fd, &genid, sizeof(genid));
> +
> +        // because of VM generation change, we need to rebuild world
> +        reseed_app_env();
> +
> +        // confirm we're done handling the gen ID update
> +        write(fd, &genid, sizeof(genid));
> +    }
> +
> +    int main() {
> +        int epfd, vmgenfd;
> +        struct epoll_event ev;
> +
> +        epfd = epoll_create(EPOLL_QUEUE_LEN);
> +
> +        vmgenfd = open("/dev/vmgenid",
> +                       O_RDWR | O_CLOEXEC | O_NONBLOCK,
> +                       S_IRUSR | S_IWUSR);
> +
> +        // register vmgenid for polling
> +        ev.events = EPOLLIN;
> +        ev.data.fd = vmgenfd;
> +        epoll_ctl(epfd, EPOLL_CTL_ADD, vmgenfd, &ev);
> +
> +        // register other parts of your app for polling
> +        // ...
> +
> +        while (1) {
> +            // wait for something to do...
> +            int nfds = epoll_wait(epfd, events,
> +                MAX_EPOLL_EVENTS_PER_RUN,
> +                EPOLL_RUN_TIMEOUT);
> +            if (nfds < 0) die("Error in epoll_wait!");
> +
> +            // for each ready fd
> +            for(int i = 0; i < nfds; i++) {
> +                int fd = events[i].data.fd;
> +
> +                if (fd == vmgenfd)
> +                    handle_io_on_vmgenfd(vmgenfd);
> +                else
> +                    handle_some_other_part_of_the_app(fd);
> +            }
> +        }
> +
> +        return 0;
> +    }
> +
> +3) Mapped memory polling simplified example::
> +
> +    /*
> +     * app/library function that provides cached secrets
> +     */
> +    char * safe_cached_secret(app_data_t *app)
> +    {
> +        char *secret;
> +        volatile unsigned *const genid_ptr = get_vmgenid_mapping(app);
> +    again:
> +        secret = __cached_secret(app);
> +
> +        if (unlikely(*genid_ptr != app->cached_genid)) {
> +            // rebuild world then confirm the genid update (thru write)
> +            rebuild_caches(app);
> +
> +            app->cached_genid = *genid_ptr;
> +            ack_vmgenid_update(app);
> +
> +            goto again;
> +        }
> +
> +        return secret;
> +    }
> +
> +4) Orchestrator simplified example::
> +
> +    /*
> +     * orchestrator - manages multiple apps and libraries used by a service
> +     * and tries to make sure all sensitive components gracefully handle
> +     * VM generation changes.
> +     * Following function is called on detection of a VM generation change.
> +     */
> +    int handle_vmgen_update(int vmgen_fd, unsigned new_gen_id)
> +    {
> +        // pause until all components have handled event
> +        pause_service();
> +
> +        // confirm *this* watcher as up-to-date
> +        write(vmgen_fd, &new_gen_id, sizeof(unsigned));
> +
> +        // wait for all *others* for at most 5 seconds.
> +        ioctl(vmgen_fd, VMGENID_WAIT_WATCHERS, 5000);
> +
> +        // all apps on the system have rebuilt worlds
> +        resume_service();
> +    }
> diff --git a/drivers/virt/Kconfig b/drivers/virt/Kconfig
> index 80c5f9c1..5d5f37b 100644
> --- a/drivers/virt/Kconfig
> +++ b/drivers/virt/Kconfig
> @@ -13,6 +13,23 @@ menuconfig VIRT_DRIVERS
>  
>  if VIRT_DRIVERS
>  
> +config VMGENID
> +    tristate "Virtual Machine Generation ID driver"
> +    depends on ACPI

I think this is not needed. We have /dev/vmgenid regardless of ACPI
device for container usecase and we may have a different HW emulation
for s390 and PowerPC.

> +    default N
> +    help
> +      This is a Virtual Machine Generation ID driver which provides
> +      a virtual machine generation counter. The driver exposes FS ops
> +      on /dev/vmgenid through which it can provide information and
> +      notifications on VM generation changes that happen on snapshots
> +      or cloning.
> +      This enables applications and libraries that store or cache
> +      sensitive information, to know that they need to regenerate it
> +      after process memory has been exposed to potential copying.
> +
> +      To compile this driver as a module, choose M here: the
> +      module will be called vmgenid.
> +
>  config FSL_HV_MANAGER
>      tristate "Freescale hypervisor management driver"
>      depends on FSL_SOC
> diff --git a/drivers/virt/Makefile b/drivers/virt/Makefile
> index f28425c..889be01 100644
> --- a/drivers/virt/Makefile
> +++ b/drivers/virt/Makefile
> @@ -4,6 +4,7 @@
>  #
>  
>  obj-$(CONFIG_FSL_HV_MANAGER)    += fsl_hypervisor.o
> +obj-$(CONFIG_VMGENID)        += vmgenid.o
>  obj-y                += vboxguest/
>  
>  obj-$(CONFIG_NITRO_ENCLAVES)    += nitro_enclaves/
> diff --git a/drivers/virt/vmgenid.c b/drivers/virt/vmgenid.c
> new file mode 100644
> index 0000000..c4d4683
> --- /dev/null
> +++ b/drivers/virt/vmgenid.c
> @@ -0,0 +1,435 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * Virtual Machine Generation ID driver
> + *
> + * Copyright (C) 2018 Red Hat Inc. All rights reserved.
> + *
> + * Copyright (C) 2020 Amazon. All rights reserved.
> + *
> + *    Authors:
> + *      Adrian Catangiu <acatan@xxxxxxxxxx>
> + *      Or Idgar <oridgar@xxxxxxxxx>
> + *      Gal Hammer <ghammer@xxxxxxxxxx>
> + *
> + */
> +#include <linux/acpi.h>
> +#include <linux/kernel.h>
> +#include <linux/miscdevice.h>
> +#include <linux/mm.h>
> +#include <linux/module.h>
> +#include <linux/poll.h>
> +#include <linux/random.h>
> +#include <linux/uuid.h>
> +#include <linux/vmgenid.h>
> +
> +#define DEV_NAME "vmgenid"
> +ACPI_MODULE_NAME(DEV_NAME);
> +
> +struct acpi_data {
> +    uuid_t uuid;
> +    void   *uuid_iomap;
> +};
> +
> +struct driver_data {

I'd suggest vmgenid_data

> +    unsigned long     map_buf;

We use tab=8 for indentation. Please run your patch though
scripts/checkpatch.pl to make sure it conforms the coding style.

> +    wait_queue_head_t read_waitq;
> +    atomic_t          generation_counter;
> +
> +    unsigned int      watchers;
> +    atomic_t          outdated_watchers;
> +    wait_queue_head_t outdated_waitq;
> +    spinlock_t        lock;
> +
> +    struct acpi_data  *acpi_data;
> +};
> +struct driver_data driver_data;

static 

> +
> +struct file_data {
> +    unsigned int acked_gen_counter;
> +};
> +
> +static int equals_gen_counter(unsigned int counter)
> +{
> +    return counter == atomic_read(&driver_data.generation_counter);
> +}
> +
> +static void vmgenid_bump_generation(void)
> +{
> +    unsigned long flags;
> +    int counter;
> +
> +    spin_lock_irqsave(&driver_data.lock, flags);
> +    counter = atomic_inc_return(&driver_data.generation_counter);
> +    *((int *) driver_data.map_buf) = counter;
> +    atomic_set(&driver_data.outdated_watchers, driver_data.watchers);
> +
> +    wake_up_interruptible(&driver_data.read_waitq);
> +    wake_up_interruptible(&driver_data.outdated_waitq);
> +    spin_unlock_irqrestore(&driver_data.lock, flags);
> +}
> +
> +static void vmgenid_put_outdated_watchers(void)
> +{
> +    if (atomic_dec_and_test(&driver_data.outdated_watchers))
> +        wake_up_interruptible(&driver_data.outdated_waitq);
> +}
> +
> +static int vmgenid_open(struct inode *inode, struct file *file)
> +{
> +    struct file_data *fdata = kzalloc(sizeof(struct file_data),
> GFP_KERNEL);
> +    unsigned long flags;
> +
> +    if (!fdata)
> +        return -ENOMEM;
> +
> +    spin_lock_irqsave(&driver_data.lock, flags);
> +    fdata->acked_gen_counter =
> atomic_read(&driver_data.generation_counter);
> +    ++driver_data.watchers;
> +    spin_unlock_irqrestore(&driver_data.lock, flags);
> +
> +    file->private_data = fdata;
> +
> +    return 0;
> +}
> +
> +static int vmgenid_close(struct inode *inode, struct file *file)
> +{
> +    struct file_data *fdata = file->private_data;
> +    unsigned long flags;
> +
> +    spin_lock_irqsave(&driver_data.lock, flags);
> +    if (!equals_gen_counter(fdata->acked_gen_counter))
> +        vmgenid_put_outdated_watchers();
> +    --driver_data.watchers;
> +    spin_unlock_irqrestore(&driver_data.lock, flags);
> +
> +    kfree(fdata);
> +
> +    return 0;
> +}
> +
> +static ssize_t
> +vmgenid_read(struct file *file, char __user *ubuf, size_t nbytes,

Please keep the function name at the same line as return type and wrap
parameters to the next line.

> loff_t *ppos)
> +{
> +    struct file_data *fdata = file->private_data;
> +    ssize_t ret;
> +    int gen_counter;
> +
> +    if (nbytes == 0)
> +        return 0;
> +    /* disallow partial reads */
> +    if (nbytes < sizeof(gen_counter))
> +        return -EINVAL;
> +
> +    if (equals_gen_counter(fdata->acked_gen_counter)) {
> +        if (file->f_flags & O_NONBLOCK)
> +            return -EAGAIN;
> +        ret = wait_event_interruptible(
> +            driver_data.read_waitq,
> +            !equals_gen_counter(fdata->acked_gen_counter)
> +        );
> +        if (ret)
> +            return ret;
> +    }
> +
> +    gen_counter = atomic_read(&driver_data.generation_counter);
> +    ret = copy_to_user(ubuf, &gen_counter, sizeof(gen_counter));
> +    if (ret)
> +        return -EFAULT;
> +
> +    return sizeof(gen_counter);
> +}
> +
> +static ssize_t vmgenid_write(struct file *file, const char __user *ubuf,
> +                size_t count, loff_t *ppos)
> +{
> +    struct file_data *fdata = file->private_data;
> +    unsigned int new_acked_gen;
> +    unsigned long flags;
> +
> +    /* disallow partial writes */
> +    if (count != sizeof(new_acked_gen))
> +        return -EINVAL;
> +    if (copy_from_user(&new_acked_gen, ubuf, count))
> +        return -EFAULT;
> +
> +    spin_lock_irqsave(&driver_data.lock, flags);
> +    /* wrong gen-counter acknowledged */
> +    if (!equals_gen_counter(new_acked_gen)) {
> +        spin_unlock_irqrestore(&driver_data.lock, flags);
> +        return -EINVAL;
> +    }
> +    if (!equals_gen_counter(fdata->acked_gen_counter)) {
> +        fdata->acked_gen_counter = new_acked_gen;
> +        vmgenid_put_outdated_watchers();
> +    }
> +    spin_unlock_irqrestore(&driver_data.lock, flags);
> +
> +    return (ssize_t)count;
> +}
> +
> +static __poll_t
> +vmgenid_poll(struct file *file, poll_table *wait)
> +{
> +    __poll_t mask = 0;
> +    struct file_data *fdata = file->private_data;
> +
> +    if (!equals_gen_counter(fdata->acked_gen_counter))
> +        return EPOLLIN | EPOLLRDNORM;
> +
> +    poll_wait(file, &driver_data.read_waitq, wait);
> +
> +    if (!equals_gen_counter(fdata->acked_gen_counter))
> +        mask = EPOLLIN | EPOLLRDNORM;
> +
> +    return mask;
> +}
> +
> +static long vmgenid_ioctl(struct file *file,
> +        unsigned int cmd, unsigned long arg)
> +{
> +    struct file_data *fdata = file->private_data;
> +    unsigned long timeout_ns;
> +    ktime_t until;
> +    int ret = 0;
> +
> +    switch (cmd) {
> +    case VMGENID_GET_OUTDATED_WATCHERS:
> +        ret = atomic_read(&driver_data.outdated_watchers);
> +        break;
> +    case VMGENID_WAIT_WATCHERS:
> +        timeout_ns = arg * NSEC_PER_MSEC;
> +        until = timeout_ns ? ktime_set(0, timeout_ns) : KTIME_MAX;
> +
> +        ret = wait_event_interruptible_hrtimeout(
> +            driver_data.outdated_waitq,
> +            (!atomic_read(&driver_data.outdated_watchers) ||
> +                    !equals_gen_counter(fdata->acked_gen_counter)),
> +            until
> +        );
> +        if (atomic_read(&driver_data.outdated_watchers))
> +            ret = -EINTR;
> +        else
> +            ret = 0;
> +        break;
> +    case VMGENID_FORCE_GEN_UPDATE:
> +        if (!checkpoint_restore_ns_capable(current_user_ns()))
> +            return -EACCES;
> +        vmgenid_bump_generation();
> +        break;
> +    default:
> +        ret = -EINVAL;
> +        break;
> +    }
> +    return ret;
> +}
> +
> +static int vmgenid_mmap(struct file *file, struct vm_area_struct *vma)
> +{
> +    struct file_data *fdata = file->private_data;
> +
> +    if (vma->vm_pgoff != 0 || vma_pages(vma) > 1)
> +        return -EINVAL;
> +
> +    if ((vma->vm_flags & VM_WRITE) != 0)
> +        return -EPERM;
> +
> +    vma->vm_flags |= VM_DONTEXPAND | VM_DONTDUMP;
> +    vma->vm_flags &= ~VM_MAYWRITE;
> +    vma->vm_private_data = fdata;
> +
> +    return vm_insert_page(vma, vma->vm_start,
> +                          virt_to_page(driver_data.map_buf));
> +}
> +
> +static const struct file_operations fops = {
> +    .owner          = THIS_MODULE,
> +    .mmap           = vmgenid_mmap,
> +    .open           = vmgenid_open,
> +    .release        = vmgenid_close,
> +    .read           = vmgenid_read,
> +    .write          = vmgenid_write,
> +    .poll           = vmgenid_poll,
> +    .unlocked_ioctl = vmgenid_ioctl,
> +};
> +
> +struct miscdevice vmgenid_misc = {

static

> +    .minor = MISC_DYNAMIC_MINOR,
> +    .name = "vmgenid",
> +    .fops = &fops,
> +};
> +
> +static int vmgenid_acpi_map(struct acpi_data *priv, acpi_handle handle)
> +{
> +    int i;
> +    phys_addr_t phys_addr;
> +    struct acpi_buffer buffer = { ACPI_ALLOCATE_BUFFER, NULL };
> +    acpi_status status;
> +    union acpi_object *pss;
> +    union acpi_object *element;
> +
> +    status = acpi_evaluate_object(handle, "ADDR", NULL, &buffer);
> +    if (ACPI_FAILURE(status)) {
> +        ACPI_EXCEPTION((AE_INFO, status, "Evaluating ADDR"));
> +        return -ENODEV;
> +    }
> +    pss = buffer.pointer;
> +    if (!pss || pss->type != ACPI_TYPE_PACKAGE || pss->package.count != 2)
> +        return -EINVAL;
> +
> +    phys_addr = 0;
> +    for (i = 0; i < pss->package.count; i++) {
> +        element = &(pss->package.elements[i]);
> +        if (element->type != ACPI_TYPE_INTEGER)
> +            return -EINVAL;
> +        phys_addr |= element->integer.value << i * 32;
> +    }
> +
> +    priv->uuid_iomap = acpi_os_map_memory(phys_addr, sizeof(uuid_t));
> +    if (!priv->uuid_iomap) {
> +        pr_err("Could not map memory at 0x%llx, size %u\n",
> +               phys_addr,
> +               (u32) sizeof(uuid_t));
> +        return -ENOMEM;
> +    }
> +
> +    memcpy_fromio(&priv->uuid, priv->uuid_iomap, sizeof(uuid_t));
> +
> +    return 0;
> +}
> +
> +static int vmgenid_acpi_add(struct acpi_device *device)
> +{
> +    int ret;
> +
> +    if (!device)
> +        return -EINVAL;
> +
> +    driver_data.acpi_data = kzalloc(sizeof(struct acpi_data), GFP_KERNEL);
> +    if (!driver_data.acpi_data) {
> +        pr_err("vmgenid: failed to allocate acpi_data\n");
> +        return -ENOMEM;
> +    }
> +    device->driver_data = &driver_data;
> +
> +    ret = vmgenid_acpi_map(driver_data.acpi_data, device->handle);
> +    if (ret < 0) {
> +        pr_err("vmgenid: failed to map acpi device\n");
> +        goto err;
> +    }
> +
> +    return 0;
> +
> +err:
> +    kfree(driver_data.acpi_data);
> +    driver_data.acpi_data = NULL;
> +
> +    return ret;
> +}
> +
> +static int vmgenid_acpi_remove(struct acpi_device *device)
> +{
> +    struct acpi_data *priv;
> +
> +    if (!device || !acpi_driver_data(device))
> +        return -EINVAL;
> +
> +    device->driver_data = NULL;
> +    priv = driver_data.acpi_data;
> +    driver_data.acpi_data = NULL;
> +
> +    if (priv && priv->uuid_iomap)
> +        acpi_os_unmap_memory(priv->uuid_iomap, sizeof(uuid_t));
> +    kfree(priv);
> +
> +    return 0;
> +}
> +
> +static void vmgenid_acpi_notify(struct acpi_device *device, u32 event)
> +{
> +    struct acpi_data *priv;
> +    uuid_t old_uuid;
> +
> +    if (!device || !acpi_driver_data(device)) {
> +        pr_err("VMGENID notify with NULL private data\n");
> +        return;
> +    }
> +    priv = driver_data.acpi_data;
> +
> +    /* update VM Generation UUID */
> +    old_uuid = priv->uuid;
> +    memcpy_fromio(&priv->uuid, priv->uuid_iomap, sizeof(uuid_t));
> +
> +    if (memcmp(&old_uuid, &priv->uuid, sizeof(uuid_t))) {
> +        /* HW uuid updated */
> +        vmgenid_bump_generation();
> +        add_device_randomness(&priv->uuid, sizeof(uuid_t));
> +    }
> +}
> +
> +static const struct acpi_device_id vmgenid_ids[] = {
> +    {"QEMUVGID", 0},
> +    {"", 0},
> +};
> +
> +static struct acpi_driver acpi_vmgenid_driver = {
> +    .name = "vm_generation_id",
> +    .ids = vmgenid_ids,
> +    .owner = THIS_MODULE,
> +    .ops = {
> +        .add = vmgenid_acpi_add,
> +        .remove = vmgenid_acpi_remove,
> +        .notify = vmgenid_acpi_notify,
> +    }
> +};
> +
> +static int __init vmgenid_init(void)
> +{
> +    int ret;
> +
> +    driver_data.map_buf = get_zeroed_page(GFP_KERNEL);
> +    if (!driver_data.map_buf)
> +        return -ENOMEM;
> +
> +    atomic_set(&driver_data.generation_counter, 0);
> +    atomic_set(&driver_data.outdated_watchers, 0);
> +    init_waitqueue_head(&driver_data.read_waitq);
> +    init_waitqueue_head(&driver_data.outdated_waitq);
> +    spin_lock_init(&driver_data.lock);
> +    driver_data.acpi_data = NULL;
> +
> +    ret = misc_register(&vmgenid_misc);
> +    if (ret < 0) {
> +        pr_err("misc_register() failed for vmgenid\n");
> +        goto err;
> +    }
> +
> +    ret = acpi_bus_register_driver(&acpi_vmgenid_driver);
> +    if (ret < 0)
> +        pr_warn("No vmgenid acpi device found\n");

I think this needs to be reworked to support no-ACPI version. For
instance we can call here something like

	ret = vmgenid_hw_register();

and have 

#ifdef CONFIG_ACPI
static int vmgenid_hw_register(void)
{
	return acpi_bus_register_driver(&acpi_vmgenid_driver);
}
#else
static int vmgenid_hw_register(void)
{
	return 0;
}
#endif

> +
> +    return 0;
> +
> +err:
> +    free_pages(driver_data.map_buf, 0);
> +    driver_data.map_buf = 0;
> +
> +    return ret;
> +}
> +
> +static void __exit vmgenid_exit(void)
> +{
> +    acpi_bus_unregister_driver(&acpi_vmgenid_driver);
> +
> +    misc_deregister(&vmgenid_misc);
> +    free_pages(driver_data.map_buf, 0);
> +    driver_data.map_buf = 0;
> +}
> +
> +module_init(vmgenid_init);
> +module_exit(vmgenid_exit);
> +
> +MODULE_AUTHOR("Adrian Catangiu");
> +MODULE_DESCRIPTION("Virtual Machine Generation ID");
> +MODULE_LICENSE("GPL");
> +MODULE_VERSION("0.1");
> diff --git a/include/uapi/linux/vmgenid.h b/include/uapi/linux/vmgenid.h
> new file mode 100644
> index 0000000..9316b00
> --- /dev/null
> +++ b/include/uapi/linux/vmgenid.h
> @@ -0,0 +1,14 @@
> +/* SPDX-License-Identifier: GPL-2.0+ WITH Linux-syscall-note */
> +
> +#ifndef _UAPI_LINUX_VMGENID_H
> +#define _UAPI_LINUX_VMGENID_H
> +
> +#include <linux/ioctl.h>
> +
> +#define VMGENID_IOCTL 0x2d
> +#define VMGENID_GET_OUTDATED_WATCHERS _IO(VMGENID_IOCTL, 1)
> +#define VMGENID_WAIT_WATCHERS         _IO(VMGENID_IOCTL, 2)
> +#define VMGENID_FORCE_GEN_UPDATE      _IO(VMGENID_IOCTL, 3)
> +
> +#endif /* _UAPI_LINUX_VMGENID_H */
> +
> -- 
> 2.7.4
> 

-- 
Sincerely yours,
Mike.




[Index of Archives]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux