Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Virtualization support drivers
#
menuconfig VIRT_DRIVERS
bool "Virtualization drivers"
help
Say Y here to get to see options for device drivers that support
virtualization environments.
If you say N, all options in this submenu will be skipped and disabled.
if VIRT_DRIVERS
config VMGENID
tristate "Virtual Machine Generation ID driver"
default y
help
Say Y here to use the hypervisor-provided Virtual Machine Generation ID
to reseed the RNG when the VM is cloned. This is highly recommended if
you intend to do any rollback / cloning / snapshotting of VMs.
Prefer Y to M so that this protection is activated very early.
config FSL_HV_MANAGER
tristate "Freescale hypervisor management driver"
depends on FSL_SOC
select EPAPR_PARAVIRT
help
The Freescale hypervisor management driver provides several services
to drivers and applications related to the Freescale hypervisor:
1) An ioctl interface for querying and managing partitions.
2) A file interface to reading incoming doorbells.
3) An interrupt handler for shutting down the partition upon
receiving the shutdown doorbell from a manager partition.
4) A kernel interface for receiving callbacks when a managed
partition shuts down.
source "drivers/virt/vboxguest/Kconfig"
source "drivers/virt/nitro_enclaves/Kconfig"
source "drivers/virt/acrn/Kconfig"
endif
source "drivers/virt/coco/Kconfig"
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Makefile for drivers that support virtualization
#
obj-$(CONFIG_FSL_HV_MANAGER) += fsl_hypervisor.o
obj-$(CONFIG_VMGENID) += vmgenid.o
obj-y += vboxguest/
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves/
obj-$(CONFIG_ACRN_HSM) += acrn/
obj-y += coco/
+15
View File
@@ -0,0 +1,15 @@
# SPDX-License-Identifier: GPL-2.0
config ACRN_HSM
tristate "ACRN Hypervisor Service Module"
depends on ACRN_GUEST
select EVENTFD
help
ACRN Hypervisor Service Module (HSM) is a kernel module which
communicates with ACRN userspace through ioctls and talks to
the ACRN Hypervisor through hypercalls. HSM will only run in
a privileged management VM, called Service VM, to manage User
VMs and do I/O emulation. Not required for simply running
under ACRN as a User VM.
To compile as a module, choose M, the module will be called
acrn. If unsure, say N.
+3
View File
@@ -0,0 +1,3 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_ACRN_HSM) := acrn.o
acrn-y := hsm.o vm.o mm.o ioreq.o ioeventfd.o irqfd.o
+229
View File
@@ -0,0 +1,229 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __ACRN_HSM_DRV_H
#define __ACRN_HSM_DRV_H
#include <linux/acrn.h>
#include <linux/dev_printk.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include "hypercall.h"
extern struct miscdevice acrn_dev;
#define ACRN_NAME_LEN 16
#define ACRN_MEM_MAPPING_MAX 256
#define ACRN_MEM_REGION_ADD 0
#define ACRN_MEM_REGION_DEL 2
struct acrn_vm;
struct acrn_ioreq_client;
/**
* struct vm_memory_region_op - Hypervisor memory operation
* @type: Operation type (ACRN_MEM_REGION_*)
* @attr: Memory attribute (ACRN_MEM_TYPE_* | ACRN_MEM_ACCESS_*)
* @user_vm_pa: Physical address of User VM to be mapped.
* @service_vm_pa: Physical address of Service VM to be mapped.
* @size: Size of this region.
*
* Structure containing needed information that is provided to ACRN Hypervisor
* to manage the EPT mappings of a single memory region of the User VM. Several
* &struct vm_memory_region_op can be batched to ACRN Hypervisor, see &struct
* vm_memory_region_batch.
*/
struct vm_memory_region_op {
u32 type;
u32 attr;
u64 user_vm_pa;
u64 service_vm_pa;
u64 size;
};
/**
* struct vm_memory_region_batch - A batch of vm_memory_region_op.
* @vmid: A User VM ID.
* @reserved: Reserved.
* @regions_num: The number of vm_memory_region_op.
* @regions_gpa: Physical address of a vm_memory_region_op array.
* @regions_op: Flexible array of vm_memory_region_op.
*
* HC_VM_SET_MEMORY_REGIONS uses this structure to manage EPT mappings of
* multiple memory regions of a User VM. A &struct vm_memory_region_batch
* contains multiple &struct vm_memory_region_op for batch processing in the
* ACRN Hypervisor.
*/
struct vm_memory_region_batch {
u16 vmid;
u16 reserved[3];
u32 regions_num;
u64 regions_gpa;
struct vm_memory_region_op regions_op[] __counted_by(regions_num);
};
/**
* struct vm_memory_mapping - Memory map between a User VM and the Service VM
* @pages: Pages in Service VM kernel.
* @npages: Number of pages.
* @service_vm_va: Virtual address in Service VM kernel.
* @user_vm_pa: Physical address in User VM.
* @size: Size of this memory region.
*
* HSM maintains memory mappings between a User VM GPA and the Service VM
* kernel VA for accelerating the User VM GPA translation.
*/
struct vm_memory_mapping {
struct page **pages;
int npages;
void *service_vm_va;
u64 user_vm_pa;
size_t size;
};
/**
* struct acrn_ioreq_buffer - Data for setting the ioreq buffer of User VM
* @ioreq_buf: The GPA of the IO request shared buffer of a VM
*
* The parameter for the HC_SET_IOREQ_BUFFER hypercall used to set up
* the shared I/O request buffer between Service VM and ACRN hypervisor.
*/
struct acrn_ioreq_buffer {
u64 ioreq_buf;
};
struct acrn_ioreq_range {
struct list_head list;
u32 type;
u64 start;
u64 end;
};
#define ACRN_IOREQ_CLIENT_DESTROYING 0U
typedef int (*ioreq_handler_t)(struct acrn_ioreq_client *client,
struct acrn_io_request *req);
/**
* struct acrn_ioreq_client - Structure of I/O client.
* @name: Client name
* @vm: The VM that the client belongs to
* @list: List node for this acrn_ioreq_client
* @is_default: If this client is the default one
* @flags: Flags (ACRN_IOREQ_CLIENT_*)
* @range_list: I/O ranges
* @range_lock: Lock to protect range_list
* @ioreqs_map: The pending I/O requests bitmap.
* @handler: I/O requests handler of this client
* @thread: The thread which executes the handler
* @wq: The wait queue for the handler thread parking
* @priv: Data for the thread
*/
struct acrn_ioreq_client {
char name[ACRN_NAME_LEN];
struct acrn_vm *vm;
struct list_head list;
bool is_default;
unsigned long flags;
struct list_head range_list;
rwlock_t range_lock;
DECLARE_BITMAP(ioreqs_map, ACRN_IO_REQUEST_MAX);
ioreq_handler_t handler;
struct task_struct *thread;
wait_queue_head_t wq;
void *priv;
};
#define ACRN_INVALID_VMID (0xffffU)
#define ACRN_VM_FLAG_DESTROYED 0U
#define ACRN_VM_FLAG_CLEARING_IOREQ 1U
extern struct list_head acrn_vm_list;
extern rwlock_t acrn_vm_list_lock;
/**
* struct acrn_vm - Properties of ACRN User VM.
* @list: Entry within global list of all VMs.
* @vmid: User VM ID.
* @vcpu_num: Number of virtual CPUs in the VM.
* @flags: Flags (ACRN_VM_FLAG_*) of the VM. This is VM
* flag management in HSM which is different
* from the &acrn_vm_creation.vm_flag.
* @regions_mapping_lock: Lock to protect &acrn_vm.regions_mapping and
* &acrn_vm.regions_mapping_count.
* @regions_mapping: Memory mappings of this VM.
* @regions_mapping_count: Number of memory mapping of this VM.
* @ioreq_clients_lock: Lock to protect ioreq_clients and default_client
* @ioreq_clients: The I/O request clients list of this VM
* @default_client: The default I/O request client
* @ioreq_buf: I/O request shared buffer
* @ioreq_page: The page of the I/O request shared buffer
* @pci_conf_addr: Address of a PCI configuration access emulation
* @monitor_page: Page of interrupt statistics of User VM
* @ioeventfds_lock: Lock to protect ioeventfds list
* @ioeventfds: List to link all hsm_ioeventfd
* @ioeventfd_client: I/O client for ioeventfds of the VM
* @irqfds_lock: Lock to protect irqfds list
* @irqfds: List to link all hsm_irqfd
* @irqfd_wq: Workqueue for irqfd async shutdown
*/
struct acrn_vm {
struct list_head list;
u16 vmid;
int vcpu_num;
unsigned long flags;
struct mutex regions_mapping_lock;
struct vm_memory_mapping regions_mapping[ACRN_MEM_MAPPING_MAX];
int regions_mapping_count;
spinlock_t ioreq_clients_lock;
struct list_head ioreq_clients;
struct acrn_ioreq_client *default_client;
struct acrn_io_request_buffer *ioreq_buf;
struct page *ioreq_page;
u32 pci_conf_addr;
struct page *monitor_page;
struct mutex ioeventfds_lock;
struct list_head ioeventfds;
struct acrn_ioreq_client *ioeventfd_client;
struct mutex irqfds_lock;
struct list_head irqfds;
struct workqueue_struct *irqfd_wq;
};
struct acrn_vm *acrn_vm_create(struct acrn_vm *vm,
struct acrn_vm_creation *vm_param);
int acrn_vm_destroy(struct acrn_vm *vm);
int acrn_mm_region_add(struct acrn_vm *vm, u64 user_gpa, u64 service_gpa,
u64 size, u32 mem_type, u32 mem_access_right);
int acrn_mm_region_del(struct acrn_vm *vm, u64 user_gpa, u64 size);
int acrn_vm_memseg_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap);
int acrn_vm_memseg_unmap(struct acrn_vm *vm, struct acrn_vm_memmap *memmap);
int acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap);
void acrn_vm_all_ram_unmap(struct acrn_vm *vm);
int acrn_ioreq_init(struct acrn_vm *vm, u64 buf_vma);
void acrn_ioreq_deinit(struct acrn_vm *vm);
int acrn_ioreq_intr_setup(void);
void acrn_ioreq_intr_remove(void);
void acrn_ioreq_request_clear(struct acrn_vm *vm);
int acrn_ioreq_client_wait(struct acrn_ioreq_client *client);
int acrn_ioreq_request_default_complete(struct acrn_vm *vm, u16 vcpu);
struct acrn_ioreq_client *acrn_ioreq_client_create(struct acrn_vm *vm,
ioreq_handler_t handler,
void *data, bool is_default,
const char *name);
void acrn_ioreq_client_destroy(struct acrn_ioreq_client *client);
int acrn_ioreq_range_add(struct acrn_ioreq_client *client,
u32 type, u64 start, u64 end);
void acrn_ioreq_range_del(struct acrn_ioreq_client *client,
u32 type, u64 start, u64 end);
int acrn_msi_inject(struct acrn_vm *vm, u64 msi_addr, u64 msi_data);
int acrn_ioeventfd_init(struct acrn_vm *vm);
int acrn_ioeventfd_config(struct acrn_vm *vm, struct acrn_ioeventfd *args);
void acrn_ioeventfd_deinit(struct acrn_vm *vm);
int acrn_irqfd_init(struct acrn_vm *vm);
int acrn_irqfd_config(struct acrn_vm *vm, struct acrn_irqfd *args);
void acrn_irqfd_deinit(struct acrn_vm *vm);
#endif /* __ACRN_HSM_DRV_H */
+539
View File
@@ -0,0 +1,539 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACRN Hypervisor Service Module (HSM)
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
*
* Authors:
* Fengwei Yin <fengwei.yin@intel.com>
* Yakui Zhao <yakui.zhao@intel.com>
*/
#include <linux/cpu.h>
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <asm/acrn.h>
#include <asm/cpuid/api.h>
#include <asm/hypervisor.h>
#include "acrn_drv.h"
/*
* When /dev/acrn_hsm is opened, a 'struct acrn_vm' object is created to
* represent a VM instance and continues to be associated with the opened file
* descriptor. All ioctl operations on this file descriptor will be targeted to
* the VM instance. Release of this file descriptor will destroy the object.
*/
static int acrn_dev_open(struct inode *inode, struct file *filp)
{
struct acrn_vm *vm;
vm = kzalloc_obj(*vm);
if (!vm)
return -ENOMEM;
vm->vmid = ACRN_INVALID_VMID;
filp->private_data = vm;
return 0;
}
static int pmcmd_ioctl(u64 cmd, void __user *uptr)
{
struct acrn_pstate_data *px_data;
struct acrn_cstate_data *cx_data;
u64 *pm_info;
int ret = 0;
switch (cmd & PMCMD_TYPE_MASK) {
case ACRN_PMCMD_GET_PX_CNT:
case ACRN_PMCMD_GET_CX_CNT:
pm_info = kzalloc(sizeof(u64), GFP_KERNEL);
if (!pm_info)
return -ENOMEM;
ret = hcall_get_cpu_state(cmd, virt_to_phys(pm_info));
if (ret < 0) {
kfree(pm_info);
break;
}
if (copy_to_user(uptr, pm_info, sizeof(u64)))
ret = -EFAULT;
kfree(pm_info);
break;
case ACRN_PMCMD_GET_PX_DATA:
px_data = kzalloc_obj(*px_data);
if (!px_data)
return -ENOMEM;
ret = hcall_get_cpu_state(cmd, virt_to_phys(px_data));
if (ret < 0) {
kfree(px_data);
break;
}
if (copy_to_user(uptr, px_data, sizeof(*px_data)))
ret = -EFAULT;
kfree(px_data);
break;
case ACRN_PMCMD_GET_CX_DATA:
cx_data = kzalloc_obj(*cx_data);
if (!cx_data)
return -ENOMEM;
ret = hcall_get_cpu_state(cmd, virt_to_phys(cx_data));
if (ret < 0) {
kfree(cx_data);
break;
}
if (copy_to_user(uptr, cx_data, sizeof(*cx_data)))
ret = -EFAULT;
kfree(cx_data);
break;
default:
break;
}
return ret;
}
/*
* HSM relies on hypercall layer of the ACRN hypervisor to do the
* sanity check against the input parameters.
*/
static long acrn_dev_ioctl(struct file *filp, unsigned int cmd,
unsigned long ioctl_param)
{
struct acrn_vm *vm = filp->private_data;
struct acrn_vm_creation *vm_param;
struct acrn_vcpu_regs *cpu_regs;
struct acrn_ioreq_notify notify;
struct acrn_ptdev_irq *irq_info;
struct acrn_ioeventfd ioeventfd;
struct acrn_vm_memmap memmap;
struct acrn_mmiodev *mmiodev;
struct acrn_msi_entry *msi;
struct acrn_pcidev *pcidev;
struct acrn_irqfd irqfd;
struct acrn_vdev *vdev;
struct page *page;
u64 cstate_cmd;
int i, ret = 0;
if (vm->vmid == ACRN_INVALID_VMID && cmd != ACRN_IOCTL_CREATE_VM) {
dev_dbg(acrn_dev.this_device,
"ioctl 0x%x: Invalid VM state!\n", cmd);
return -EINVAL;
}
switch (cmd) {
case ACRN_IOCTL_CREATE_VM:
vm_param = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_vm_creation));
if (IS_ERR(vm_param))
return PTR_ERR(vm_param);
if ((vm_param->reserved0 | vm_param->reserved1) != 0) {
kfree(vm_param);
return -EINVAL;
}
vm = acrn_vm_create(vm, vm_param);
if (!vm) {
ret = -EINVAL;
kfree(vm_param);
break;
}
if (copy_to_user((void __user *)ioctl_param, vm_param,
sizeof(struct acrn_vm_creation))) {
acrn_vm_destroy(vm);
ret = -EFAULT;
}
kfree(vm_param);
break;
case ACRN_IOCTL_START_VM:
ret = hcall_start_vm(vm->vmid);
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to start VM %u!\n", vm->vmid);
break;
case ACRN_IOCTL_PAUSE_VM:
ret = hcall_pause_vm(vm->vmid);
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to pause VM %u!\n", vm->vmid);
break;
case ACRN_IOCTL_RESET_VM:
ret = hcall_reset_vm(vm->vmid);
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to restart VM %u!\n", vm->vmid);
break;
case ACRN_IOCTL_DESTROY_VM:
ret = acrn_vm_destroy(vm);
break;
case ACRN_IOCTL_SET_VCPU_REGS:
cpu_regs = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_vcpu_regs));
if (IS_ERR(cpu_regs))
return PTR_ERR(cpu_regs);
for (i = 0; i < ARRAY_SIZE(cpu_regs->reserved); i++)
if (cpu_regs->reserved[i]) {
kfree(cpu_regs);
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(cpu_regs->vcpu_regs.reserved_32); i++)
if (cpu_regs->vcpu_regs.reserved_32[i]) {
kfree(cpu_regs);
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(cpu_regs->vcpu_regs.reserved_64); i++)
if (cpu_regs->vcpu_regs.reserved_64[i]) {
kfree(cpu_regs);
return -EINVAL;
}
for (i = 0; i < ARRAY_SIZE(cpu_regs->vcpu_regs.gdt.reserved); i++)
if (cpu_regs->vcpu_regs.gdt.reserved[i] |
cpu_regs->vcpu_regs.idt.reserved[i]) {
kfree(cpu_regs);
return -EINVAL;
}
ret = hcall_set_vcpu_regs(vm->vmid, virt_to_phys(cpu_regs));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to set regs state of VM%u!\n",
vm->vmid);
kfree(cpu_regs);
break;
case ACRN_IOCTL_SET_MEMSEG:
if (copy_from_user(&memmap, (void __user *)ioctl_param,
sizeof(memmap)))
return -EFAULT;
ret = acrn_vm_memseg_map(vm, &memmap);
break;
case ACRN_IOCTL_UNSET_MEMSEG:
if (copy_from_user(&memmap, (void __user *)ioctl_param,
sizeof(memmap)))
return -EFAULT;
ret = acrn_vm_memseg_unmap(vm, &memmap);
break;
case ACRN_IOCTL_ASSIGN_MMIODEV:
mmiodev = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_mmiodev));
if (IS_ERR(mmiodev))
return PTR_ERR(mmiodev);
ret = hcall_assign_mmiodev(vm->vmid, virt_to_phys(mmiodev));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to assign MMIO device!\n");
kfree(mmiodev);
break;
case ACRN_IOCTL_DEASSIGN_MMIODEV:
mmiodev = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_mmiodev));
if (IS_ERR(mmiodev))
return PTR_ERR(mmiodev);
ret = hcall_deassign_mmiodev(vm->vmid, virt_to_phys(mmiodev));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to deassign MMIO device!\n");
kfree(mmiodev);
break;
case ACRN_IOCTL_ASSIGN_PCIDEV:
pcidev = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_pcidev));
if (IS_ERR(pcidev))
return PTR_ERR(pcidev);
ret = hcall_assign_pcidev(vm->vmid, virt_to_phys(pcidev));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to assign pci device!\n");
kfree(pcidev);
break;
case ACRN_IOCTL_DEASSIGN_PCIDEV:
pcidev = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_pcidev));
if (IS_ERR(pcidev))
return PTR_ERR(pcidev);
ret = hcall_deassign_pcidev(vm->vmid, virt_to_phys(pcidev));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to deassign pci device!\n");
kfree(pcidev);
break;
case ACRN_IOCTL_CREATE_VDEV:
vdev = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_vdev));
if (IS_ERR(vdev))
return PTR_ERR(vdev);
ret = hcall_create_vdev(vm->vmid, virt_to_phys(vdev));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to create virtual device!\n");
kfree(vdev);
break;
case ACRN_IOCTL_DESTROY_VDEV:
vdev = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_vdev));
if (IS_ERR(vdev))
return PTR_ERR(vdev);
ret = hcall_destroy_vdev(vm->vmid, virt_to_phys(vdev));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to destroy virtual device!\n");
kfree(vdev);
break;
case ACRN_IOCTL_SET_PTDEV_INTR:
irq_info = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_ptdev_irq));
if (IS_ERR(irq_info))
return PTR_ERR(irq_info);
ret = hcall_set_ptdev_intr(vm->vmid, virt_to_phys(irq_info));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to configure intr for ptdev!\n");
kfree(irq_info);
break;
case ACRN_IOCTL_RESET_PTDEV_INTR:
irq_info = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_ptdev_irq));
if (IS_ERR(irq_info))
return PTR_ERR(irq_info);
ret = hcall_reset_ptdev_intr(vm->vmid, virt_to_phys(irq_info));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to reset intr for ptdev!\n");
kfree(irq_info);
break;
case ACRN_IOCTL_SET_IRQLINE:
ret = hcall_set_irqline(vm->vmid, ioctl_param);
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to set interrupt line!\n");
break;
case ACRN_IOCTL_INJECT_MSI:
msi = memdup_user((void __user *)ioctl_param,
sizeof(struct acrn_msi_entry));
if (IS_ERR(msi))
return PTR_ERR(msi);
ret = hcall_inject_msi(vm->vmid, virt_to_phys(msi));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to inject MSI!\n");
kfree(msi);
break;
case ACRN_IOCTL_VM_INTR_MONITOR:
ret = pin_user_pages_fast(ioctl_param, 1,
FOLL_WRITE | FOLL_LONGTERM, &page);
if (unlikely(ret != 1)) {
dev_dbg(acrn_dev.this_device,
"Failed to pin intr hdr buffer!\n");
return -EFAULT;
}
ret = hcall_vm_intr_monitor(vm->vmid, page_to_phys(page));
if (ret < 0) {
unpin_user_page(page);
dev_dbg(acrn_dev.this_device,
"Failed to monitor intr data!\n");
return ret;
}
if (vm->monitor_page)
unpin_user_page(vm->monitor_page);
vm->monitor_page = page;
break;
case ACRN_IOCTL_CREATE_IOREQ_CLIENT:
if (vm->default_client)
return -EEXIST;
if (!acrn_ioreq_client_create(vm, NULL, NULL, true, "acrndm"))
ret = -EINVAL;
break;
case ACRN_IOCTL_DESTROY_IOREQ_CLIENT:
if (vm->default_client)
acrn_ioreq_client_destroy(vm->default_client);
break;
case ACRN_IOCTL_ATTACH_IOREQ_CLIENT:
if (vm->default_client)
ret = acrn_ioreq_client_wait(vm->default_client);
else
ret = -ENODEV;
break;
case ACRN_IOCTL_NOTIFY_REQUEST_FINISH:
if (copy_from_user(&notify, (void __user *)ioctl_param,
sizeof(struct acrn_ioreq_notify)))
return -EFAULT;
if (notify.reserved != 0)
return -EINVAL;
ret = acrn_ioreq_request_default_complete(vm, notify.vcpu);
break;
case ACRN_IOCTL_CLEAR_VM_IOREQ:
acrn_ioreq_request_clear(vm);
break;
case ACRN_IOCTL_PM_GET_CPU_STATE:
if (copy_from_user(&cstate_cmd, (void __user *)ioctl_param,
sizeof(cstate_cmd)))
return -EFAULT;
ret = pmcmd_ioctl(cstate_cmd, (void __user *)ioctl_param);
break;
case ACRN_IOCTL_IOEVENTFD:
if (copy_from_user(&ioeventfd, (void __user *)ioctl_param,
sizeof(ioeventfd)))
return -EFAULT;
if (ioeventfd.reserved != 0)
return -EINVAL;
ret = acrn_ioeventfd_config(vm, &ioeventfd);
break;
case ACRN_IOCTL_IRQFD:
if (copy_from_user(&irqfd, (void __user *)ioctl_param,
sizeof(irqfd)))
return -EFAULT;
ret = acrn_irqfd_config(vm, &irqfd);
break;
default:
dev_dbg(acrn_dev.this_device, "Unknown IOCTL 0x%x!\n", cmd);
ret = -ENOTTY;
}
return ret;
}
static int acrn_dev_release(struct inode *inode, struct file *filp)
{
struct acrn_vm *vm = filp->private_data;
acrn_vm_destroy(vm);
kfree(vm);
return 0;
}
static ssize_t remove_cpu_store(struct device *dev,
struct device_attribute *attr,
const char *buf, size_t count)
{
u64 cpu, lapicid;
int ret;
if (kstrtoull(buf, 0, &cpu) < 0)
return -EINVAL;
if (cpu >= num_possible_cpus() || cpu == 0 || !cpu_is_hotpluggable(cpu))
return -EINVAL;
if (cpu_online(cpu))
remove_cpu(cpu);
lapicid = cpu_data(cpu).topo.apicid;
dev_dbg(dev, "Try to remove cpu %lld with lapicid %lld\n", cpu, lapicid);
ret = hcall_sos_remove_cpu(lapicid);
if (ret < 0) {
dev_err(dev, "Failed to remove cpu %lld!\n", cpu);
goto fail_remove;
}
return count;
fail_remove:
add_cpu(cpu);
return ret;
}
static DEVICE_ATTR_WO(remove_cpu);
static umode_t acrn_attr_visible(struct kobject *kobj, struct attribute *a, int n)
{
if (a == &dev_attr_remove_cpu.attr)
return IS_ENABLED(CONFIG_HOTPLUG_CPU) ? a->mode : 0;
return a->mode;
}
static struct attribute *acrn_attrs[] = {
&dev_attr_remove_cpu.attr,
NULL
};
static struct attribute_group acrn_attr_group = {
.attrs = acrn_attrs,
.is_visible = acrn_attr_visible,
};
static const struct attribute_group *acrn_attr_groups[] = {
&acrn_attr_group,
NULL
};
static const struct file_operations acrn_fops = {
.owner = THIS_MODULE,
.open = acrn_dev_open,
.release = acrn_dev_release,
.unlocked_ioctl = acrn_dev_ioctl,
};
struct miscdevice acrn_dev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "acrn_hsm",
.fops = &acrn_fops,
.groups = acrn_attr_groups,
};
static int __init hsm_init(void)
{
int ret;
if (x86_hyper_type != X86_HYPER_ACRN)
return -ENODEV;
if (!(cpuid_eax(ACRN_CPUID_FEATURES) & ACRN_FEATURE_PRIVILEGED_VM))
return -EPERM;
ret = misc_register(&acrn_dev);
if (ret) {
pr_err("Create misc dev failed!\n");
return ret;
}
ret = acrn_ioreq_intr_setup();
if (ret) {
pr_err("Setup I/O request handler failed!\n");
misc_deregister(&acrn_dev);
return ret;
}
return 0;
}
static void __exit hsm_exit(void)
{
acrn_ioreq_intr_remove();
misc_deregister(&acrn_dev);
}
module_init(hsm_init);
module_exit(hsm_exit);
MODULE_AUTHOR("Intel Corporation");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("ACRN Hypervisor Service Module (HSM)");
+306
View File
@@ -0,0 +1,306 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* ACRN HSM: hypercalls of ACRN Hypervisor
*/
#ifndef __ACRN_HSM_HYPERCALL_H
#define __ACRN_HSM_HYPERCALL_H
#include <asm/acrn.h>
/*
* Hypercall IDs of the ACRN Hypervisor
*/
#define _HC_ID(x, y) (((x) << 24) | (y))
#define HC_ID 0x80UL
#define HC_ID_GEN_BASE 0x0UL
#define HC_SOS_REMOVE_CPU _HC_ID(HC_ID, HC_ID_GEN_BASE + 0x01)
#define HC_ID_VM_BASE 0x10UL
#define HC_CREATE_VM _HC_ID(HC_ID, HC_ID_VM_BASE + 0x00)
#define HC_DESTROY_VM _HC_ID(HC_ID, HC_ID_VM_BASE + 0x01)
#define HC_START_VM _HC_ID(HC_ID, HC_ID_VM_BASE + 0x02)
#define HC_PAUSE_VM _HC_ID(HC_ID, HC_ID_VM_BASE + 0x03)
#define HC_RESET_VM _HC_ID(HC_ID, HC_ID_VM_BASE + 0x05)
#define HC_SET_VCPU_REGS _HC_ID(HC_ID, HC_ID_VM_BASE + 0x06)
#define HC_ID_IRQ_BASE 0x20UL
#define HC_INJECT_MSI _HC_ID(HC_ID, HC_ID_IRQ_BASE + 0x03)
#define HC_VM_INTR_MONITOR _HC_ID(HC_ID, HC_ID_IRQ_BASE + 0x04)
#define HC_SET_IRQLINE _HC_ID(HC_ID, HC_ID_IRQ_BASE + 0x05)
#define HC_ID_IOREQ_BASE 0x30UL
#define HC_SET_IOREQ_BUFFER _HC_ID(HC_ID, HC_ID_IOREQ_BASE + 0x00)
#define HC_NOTIFY_REQUEST_FINISH _HC_ID(HC_ID, HC_ID_IOREQ_BASE + 0x01)
#define HC_ID_MEM_BASE 0x40UL
#define HC_VM_SET_MEMORY_REGIONS _HC_ID(HC_ID, HC_ID_MEM_BASE + 0x02)
#define HC_ID_PCI_BASE 0x50UL
#define HC_SET_PTDEV_INTR _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x03)
#define HC_RESET_PTDEV_INTR _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x04)
#define HC_ASSIGN_PCIDEV _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x05)
#define HC_DEASSIGN_PCIDEV _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x06)
#define HC_ASSIGN_MMIODEV _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x07)
#define HC_DEASSIGN_MMIODEV _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x08)
#define HC_CREATE_VDEV _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x09)
#define HC_DESTROY_VDEV _HC_ID(HC_ID, HC_ID_PCI_BASE + 0x0A)
#define HC_ID_PM_BASE 0x80UL
#define HC_PM_GET_CPU_STATE _HC_ID(HC_ID, HC_ID_PM_BASE + 0x00)
/**
* hcall_sos_remove_cpu() - Remove a vCPU of Service VM
* @cpu: The vCPU to be removed
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_sos_remove_cpu(u64 cpu)
{
return acrn_hypercall1(HC_SOS_REMOVE_CPU, cpu);
}
/**
* hcall_create_vm() - Create a User VM
* @vminfo: Service VM GPA of info of User VM creation
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_create_vm(u64 vminfo)
{
return acrn_hypercall1(HC_CREATE_VM, vminfo);
}
/**
* hcall_start_vm() - Start a User VM
* @vmid: User VM ID
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_start_vm(u64 vmid)
{
return acrn_hypercall1(HC_START_VM, vmid);
}
/**
* hcall_pause_vm() - Pause a User VM
* @vmid: User VM ID
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_pause_vm(u64 vmid)
{
return acrn_hypercall1(HC_PAUSE_VM, vmid);
}
/**
* hcall_destroy_vm() - Destroy a User VM
* @vmid: User VM ID
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_destroy_vm(u64 vmid)
{
return acrn_hypercall1(HC_DESTROY_VM, vmid);
}
/**
* hcall_reset_vm() - Reset a User VM
* @vmid: User VM ID
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_reset_vm(u64 vmid)
{
return acrn_hypercall1(HC_RESET_VM, vmid);
}
/**
* hcall_set_vcpu_regs() - Set up registers of virtual CPU of a User VM
* @vmid: User VM ID
* @regs_state: Service VM GPA of registers state
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_set_vcpu_regs(u64 vmid, u64 regs_state)
{
return acrn_hypercall2(HC_SET_VCPU_REGS, vmid, regs_state);
}
/**
* hcall_inject_msi() - Deliver a MSI interrupt to a User VM
* @vmid: User VM ID
* @msi: Service VM GPA of MSI message
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_inject_msi(u64 vmid, u64 msi)
{
return acrn_hypercall2(HC_INJECT_MSI, vmid, msi);
}
/**
* hcall_vm_intr_monitor() - Set a shared page for User VM interrupt statistics
* @vmid: User VM ID
* @addr: Service VM GPA of the shared page
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_vm_intr_monitor(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_VM_INTR_MONITOR, vmid, addr);
}
/**
* hcall_set_irqline() - Set or clear an interrupt line
* @vmid: User VM ID
* @op: Service VM GPA of interrupt line operations
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_set_irqline(u64 vmid, u64 op)
{
return acrn_hypercall2(HC_SET_IRQLINE, vmid, op);
}
/**
* hcall_set_ioreq_buffer() - Set up the shared buffer for I/O Requests.
* @vmid: User VM ID
* @buffer: Service VM GPA of the shared buffer
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_set_ioreq_buffer(u64 vmid, u64 buffer)
{
return acrn_hypercall2(HC_SET_IOREQ_BUFFER, vmid, buffer);
}
/**
* hcall_notify_req_finish() - Notify ACRN Hypervisor of I/O request completion.
* @vmid: User VM ID
* @vcpu: The vCPU which initiated the I/O request
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_notify_req_finish(u64 vmid, u64 vcpu)
{
return acrn_hypercall2(HC_NOTIFY_REQUEST_FINISH, vmid, vcpu);
}
/**
* hcall_set_memory_regions() - Inform the hypervisor to set up EPT mappings
* @regions_pa: Service VM GPA of &struct vm_memory_region_batch
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_set_memory_regions(u64 regions_pa)
{
return acrn_hypercall1(HC_VM_SET_MEMORY_REGIONS, regions_pa);
}
/**
* hcall_create_vdev() - Create a virtual device for a User VM
* @vmid: User VM ID
* @addr: Service VM GPA of the &struct acrn_vdev
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_create_vdev(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_CREATE_VDEV, vmid, addr);
}
/**
* hcall_destroy_vdev() - Destroy a virtual device of a User VM
* @vmid: User VM ID
* @addr: Service VM GPA of the &struct acrn_vdev
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_destroy_vdev(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_DESTROY_VDEV, vmid, addr);
}
/**
* hcall_assign_mmiodev() - Assign a MMIO device to a User VM
* @vmid: User VM ID
* @addr: Service VM GPA of the &struct acrn_mmiodev
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_assign_mmiodev(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_ASSIGN_MMIODEV, vmid, addr);
}
/**
* hcall_deassign_mmiodev() - De-assign a PCI device from a User VM
* @vmid: User VM ID
* @addr: Service VM GPA of the &struct acrn_mmiodev
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_deassign_mmiodev(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_DEASSIGN_MMIODEV, vmid, addr);
}
/**
* hcall_assign_pcidev() - Assign a PCI device to a User VM
* @vmid: User VM ID
* @addr: Service VM GPA of the &struct acrn_pcidev
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_assign_pcidev(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_ASSIGN_PCIDEV, vmid, addr);
}
/**
* hcall_deassign_pcidev() - De-assign a PCI device from a User VM
* @vmid: User VM ID
* @addr: Service VM GPA of the &struct acrn_pcidev
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_deassign_pcidev(u64 vmid, u64 addr)
{
return acrn_hypercall2(HC_DEASSIGN_PCIDEV, vmid, addr);
}
/**
* hcall_set_ptdev_intr() - Configure an interrupt for an assigned PCI device.
* @vmid: User VM ID
* @irq: Service VM GPA of the &struct acrn_ptdev_irq
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_set_ptdev_intr(u64 vmid, u64 irq)
{
return acrn_hypercall2(HC_SET_PTDEV_INTR, vmid, irq);
}
/**
* hcall_reset_ptdev_intr() - Reset an interrupt for an assigned PCI device.
* @vmid: User VM ID
* @irq: Service VM GPA of the &struct acrn_ptdev_irq
*
* Return: 0 on success, <0 on failure
*/
static inline long hcall_reset_ptdev_intr(u64 vmid, u64 irq)
{
return acrn_hypercall2(HC_RESET_PTDEV_INTR, vmid, irq);
}
/*
* hcall_get_cpu_state() - Get P-states and C-states info from the hypervisor
* @state: Service VM GPA of buffer of P-states and C-states
*/
static inline long hcall_get_cpu_state(u64 cmd, u64 state)
{
return acrn_hypercall2(HC_PM_GET_CPU_STATE, cmd, state);
}
#endif /* __ACRN_HSM_HYPERCALL_H */
+273
View File
@@ -0,0 +1,273 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACRN HSM eventfd - use eventfd objects to signal expected I/O requests
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
*
* Authors:
* Shuo Liu <shuo.a.liu@intel.com>
* Yakui Zhao <yakui.zhao@intel.com>
*/
#include <linux/eventfd.h>
#include <linux/slab.h>
#include "acrn_drv.h"
/**
* struct hsm_ioeventfd - Properties of HSM ioeventfd
* @list: Entry within &acrn_vm.ioeventfds of ioeventfds of a VM
* @eventfd: Eventfd of the HSM ioeventfd
* @addr: Address of I/O range
* @data: Data for matching
* @length: Length of I/O range
* @type: Type of I/O range (ACRN_IOREQ_TYPE_MMIO/ACRN_IOREQ_TYPE_PORTIO)
* @wildcard: Data matching or not
*/
struct hsm_ioeventfd {
struct list_head list;
struct eventfd_ctx *eventfd;
u64 addr;
u64 data;
int length;
int type;
bool wildcard;
};
static inline int ioreq_type_from_flags(int flags)
{
return flags & ACRN_IOEVENTFD_FLAG_PIO ?
ACRN_IOREQ_TYPE_PORTIO : ACRN_IOREQ_TYPE_MMIO;
}
static void acrn_ioeventfd_shutdown(struct acrn_vm *vm, struct hsm_ioeventfd *p)
{
lockdep_assert_held(&vm->ioeventfds_lock);
eventfd_ctx_put(p->eventfd);
list_del(&p->list);
kfree(p);
}
static bool hsm_ioeventfd_is_conflict(struct acrn_vm *vm,
struct hsm_ioeventfd *ioeventfd)
{
struct hsm_ioeventfd *p;
lockdep_assert_held(&vm->ioeventfds_lock);
/* Either one is wildcard, the data matching will be skipped. */
list_for_each_entry(p, &vm->ioeventfds, list)
if (p->eventfd == ioeventfd->eventfd &&
p->addr == ioeventfd->addr &&
p->type == ioeventfd->type &&
(p->wildcard || ioeventfd->wildcard ||
p->data == ioeventfd->data))
return true;
return false;
}
/*
* Assign an eventfd to a VM and create a HSM ioeventfd associated with the
* eventfd. The properties of the HSM ioeventfd are built from a &struct
* acrn_ioeventfd.
*/
static int acrn_ioeventfd_assign(struct acrn_vm *vm,
struct acrn_ioeventfd *args)
{
struct eventfd_ctx *eventfd;
struct hsm_ioeventfd *p;
int ret;
/* Check for range overflow */
if (args->addr + args->len < args->addr)
return -EINVAL;
/*
* Currently, acrn_ioeventfd is used to support vhost. 1,2,4,8 width
* accesses can cover vhost's requirements.
*/
if (!(args->len == 1 || args->len == 2 ||
args->len == 4 || args->len == 8))
return -EINVAL;
eventfd = eventfd_ctx_fdget(args->fd);
if (IS_ERR(eventfd))
return PTR_ERR(eventfd);
p = kzalloc_obj(*p);
if (!p) {
ret = -ENOMEM;
goto fail;
}
INIT_LIST_HEAD(&p->list);
p->addr = args->addr;
p->length = args->len;
p->eventfd = eventfd;
p->type = ioreq_type_from_flags(args->flags);
/*
* ACRN_IOEVENTFD_FLAG_DATAMATCH flag is set in virtio 1.0 support, the
* writing of notification register of each virtqueue may trigger the
* notification. There is no data matching requirement.
*/
if (args->flags & ACRN_IOEVENTFD_FLAG_DATAMATCH)
p->data = args->data;
else
p->wildcard = true;
mutex_lock(&vm->ioeventfds_lock);
if (hsm_ioeventfd_is_conflict(vm, p)) {
ret = -EEXIST;
goto unlock_fail;
}
/* register the I/O range into ioreq client */
ret = acrn_ioreq_range_add(vm->ioeventfd_client, p->type,
p->addr, p->addr + p->length - 1);
if (ret < 0)
goto unlock_fail;
list_add_tail(&p->list, &vm->ioeventfds);
mutex_unlock(&vm->ioeventfds_lock);
return 0;
unlock_fail:
mutex_unlock(&vm->ioeventfds_lock);
kfree(p);
fail:
eventfd_ctx_put(eventfd);
return ret;
}
static int acrn_ioeventfd_deassign(struct acrn_vm *vm,
struct acrn_ioeventfd *args)
{
struct hsm_ioeventfd *p;
struct eventfd_ctx *eventfd;
eventfd = eventfd_ctx_fdget(args->fd);
if (IS_ERR(eventfd))
return PTR_ERR(eventfd);
mutex_lock(&vm->ioeventfds_lock);
list_for_each_entry(p, &vm->ioeventfds, list) {
if (p->eventfd != eventfd)
continue;
acrn_ioreq_range_del(vm->ioeventfd_client, p->type,
p->addr, p->addr + p->length - 1);
acrn_ioeventfd_shutdown(vm, p);
break;
}
mutex_unlock(&vm->ioeventfds_lock);
eventfd_ctx_put(eventfd);
return 0;
}
static struct hsm_ioeventfd *hsm_ioeventfd_match(struct acrn_vm *vm, u64 addr,
u64 data, int len, int type)
{
struct hsm_ioeventfd *p = NULL;
lockdep_assert_held(&vm->ioeventfds_lock);
list_for_each_entry(p, &vm->ioeventfds, list) {
if (p->type == type && p->addr == addr && p->length >= len &&
(p->wildcard || p->data == data))
return p;
}
return NULL;
}
static int acrn_ioeventfd_handler(struct acrn_ioreq_client *client,
struct acrn_io_request *req)
{
struct hsm_ioeventfd *p;
u64 addr, val;
int size;
if (req->type == ACRN_IOREQ_TYPE_MMIO) {
/*
* I/O requests are dispatched by range check only, so a
* acrn_ioreq_client need process both READ and WRITE accesses
* of same range. READ accesses are safe to be ignored here
* because virtio PCI devices write the notify registers for
* notification.
*/
if (req->reqs.mmio_request.direction == ACRN_IOREQ_DIR_READ) {
/* reading does nothing and return 0 */
req->reqs.mmio_request.value = 0;
return 0;
}
addr = req->reqs.mmio_request.address;
size = req->reqs.mmio_request.size;
val = req->reqs.mmio_request.value;
} else {
if (req->reqs.pio_request.direction == ACRN_IOREQ_DIR_READ) {
/* reading does nothing and return 0 */
req->reqs.pio_request.value = 0;
return 0;
}
addr = req->reqs.pio_request.address;
size = req->reqs.pio_request.size;
val = req->reqs.pio_request.value;
}
mutex_lock(&client->vm->ioeventfds_lock);
p = hsm_ioeventfd_match(client->vm, addr, val, size, req->type);
if (p)
eventfd_signal(p->eventfd);
mutex_unlock(&client->vm->ioeventfds_lock);
return 0;
}
int acrn_ioeventfd_config(struct acrn_vm *vm, struct acrn_ioeventfd *args)
{
int ret;
if (args->flags & ACRN_IOEVENTFD_FLAG_DEASSIGN)
ret = acrn_ioeventfd_deassign(vm, args);
else
ret = acrn_ioeventfd_assign(vm, args);
return ret;
}
int acrn_ioeventfd_init(struct acrn_vm *vm)
{
char name[ACRN_NAME_LEN];
mutex_init(&vm->ioeventfds_lock);
INIT_LIST_HEAD(&vm->ioeventfds);
snprintf(name, sizeof(name), "ioeventfd-%u", vm->vmid);
vm->ioeventfd_client = acrn_ioreq_client_create(vm,
acrn_ioeventfd_handler,
NULL, false, name);
if (!vm->ioeventfd_client) {
dev_err(acrn_dev.this_device, "Failed to create ioeventfd ioreq client!\n");
return -EINVAL;
}
dev_dbg(acrn_dev.this_device, "VM %u ioeventfd init.\n", vm->vmid);
return 0;
}
void acrn_ioeventfd_deinit(struct acrn_vm *vm)
{
struct hsm_ioeventfd *p, *next;
dev_dbg(acrn_dev.this_device, "VM %u ioeventfd deinit.\n", vm->vmid);
acrn_ioreq_client_destroy(vm->ioeventfd_client);
mutex_lock(&vm->ioeventfds_lock);
list_for_each_entry_safe(p, next, &vm->ioeventfds, list)
acrn_ioeventfd_shutdown(vm, p);
mutex_unlock(&vm->ioeventfds_lock);
}
+652
View File
@@ -0,0 +1,652 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACRN_HSM: Handle I/O requests
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
*
* Authors:
* Jason Chen CJ <jason.cj.chen@intel.com>
* Fengwei Yin <fengwei.yin@intel.com>
*/
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/kthread.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <asm/acrn.h>
#include "acrn_drv.h"
static void ioreq_pause(void);
static void ioreq_resume(void);
static void ioreq_dispatcher(struct work_struct *work);
static struct workqueue_struct *ioreq_wq;
static DECLARE_WORK(ioreq_work, ioreq_dispatcher);
static inline bool has_pending_request(struct acrn_ioreq_client *client)
{
return !bitmap_empty(client->ioreqs_map, ACRN_IO_REQUEST_MAX);
}
static inline bool is_destroying(struct acrn_ioreq_client *client)
{
return test_bit(ACRN_IOREQ_CLIENT_DESTROYING, &client->flags);
}
static int ioreq_complete_request(struct acrn_vm *vm, u16 vcpu,
struct acrn_io_request *acrn_req)
{
bool polling_mode;
int ret = 0;
polling_mode = acrn_req->completion_polling;
/* Add barrier() to make sure the writes are done before completion */
smp_store_release(&acrn_req->processed, ACRN_IOREQ_STATE_COMPLETE);
/*
* To fulfill the requirement of real-time in several industry
* scenarios, like automotive, ACRN can run under the partition mode,
* in which User VMs and Service VM are bound to dedicated CPU cores.
* Polling mode of handling the I/O request is introduced to achieve a
* faster I/O request handling. In polling mode, the hypervisor polls
* I/O request's completion. Once an I/O request is marked as
* ACRN_IOREQ_STATE_COMPLETE, hypervisor resumes from the polling point
* to continue the I/O request flow. Thus, the completion notification
* from HSM of I/O request is not needed. Please note,
* completion_polling needs to be read before the I/O request being
* marked as ACRN_IOREQ_STATE_COMPLETE to avoid racing with the
* hypervisor.
*/
if (!polling_mode) {
ret = hcall_notify_req_finish(vm->vmid, vcpu);
if (ret < 0)
dev_err(acrn_dev.this_device,
"Notify I/O request finished failed!\n");
}
return ret;
}
static int acrn_ioreq_complete_request(struct acrn_ioreq_client *client,
u16 vcpu,
struct acrn_io_request *acrn_req)
{
int ret;
if (vcpu >= client->vm->vcpu_num)
return -EINVAL;
clear_bit(vcpu, client->ioreqs_map);
if (!acrn_req) {
acrn_req = (struct acrn_io_request *)client->vm->ioreq_buf;
acrn_req += vcpu;
}
ret = ioreq_complete_request(client->vm, vcpu, acrn_req);
return ret;
}
int acrn_ioreq_request_default_complete(struct acrn_vm *vm, u16 vcpu)
{
int ret = 0;
spin_lock_bh(&vm->ioreq_clients_lock);
if (vm->default_client)
ret = acrn_ioreq_complete_request(vm->default_client,
vcpu, NULL);
spin_unlock_bh(&vm->ioreq_clients_lock);
return ret;
}
/**
* acrn_ioreq_range_add() - Add an iorange monitored by an ioreq client
* @client: The ioreq client
* @type: Type (ACRN_IOREQ_TYPE_MMIO or ACRN_IOREQ_TYPE_PORTIO)
* @start: Start address of iorange
* @end: End address of iorange
*
* Return: 0 on success, <0 on error
*/
int acrn_ioreq_range_add(struct acrn_ioreq_client *client,
u32 type, u64 start, u64 end)
{
struct acrn_ioreq_range *range;
if (end < start) {
dev_err(acrn_dev.this_device,
"Invalid IO range [0x%llx,0x%llx]\n", start, end);
return -EINVAL;
}
range = kzalloc_obj(*range);
if (!range)
return -ENOMEM;
range->type = type;
range->start = start;
range->end = end;
write_lock_bh(&client->range_lock);
list_add(&range->list, &client->range_list);
write_unlock_bh(&client->range_lock);
return 0;
}
/**
* acrn_ioreq_range_del() - Del an iorange monitored by an ioreq client
* @client: The ioreq client
* @type: Type (ACRN_IOREQ_TYPE_MMIO or ACRN_IOREQ_TYPE_PORTIO)
* @start: Start address of iorange
* @end: End address of iorange
*/
void acrn_ioreq_range_del(struct acrn_ioreq_client *client,
u32 type, u64 start, u64 end)
{
struct acrn_ioreq_range *range;
write_lock_bh(&client->range_lock);
list_for_each_entry(range, &client->range_list, list) {
if (type == range->type &&
start == range->start &&
end == range->end) {
list_del(&range->list);
kfree(range);
break;
}
}
write_unlock_bh(&client->range_lock);
}
/*
* ioreq_task() is the execution entity of handler thread of an I/O client.
* The handler callback of the I/O client is called within the handler thread.
*/
static int ioreq_task(void *data)
{
struct acrn_ioreq_client *client = data;
struct acrn_io_request *req;
unsigned long *ioreqs_map;
int vcpu, ret;
/*
* Lockless access to ioreqs_map is safe, because
* 1) set_bit() and clear_bit() are atomic operations.
* 2) I/O requests arrives serialized. The access flow of ioreqs_map is:
* set_bit() - in ioreq_work handler
* Handler callback handles corresponding I/O request
* clear_bit() - in handler thread (include ACRN userspace)
* Mark corresponding I/O request completed
* Loop again if a new I/O request occurs
*/
ioreqs_map = client->ioreqs_map;
while (!kthread_should_stop()) {
acrn_ioreq_client_wait(client);
while (has_pending_request(client)) {
vcpu = find_first_bit(ioreqs_map, client->vm->vcpu_num);
req = client->vm->ioreq_buf->req_slot + vcpu;
ret = client->handler(client, req);
if (ret < 0) {
dev_err(acrn_dev.this_device,
"IO handle failure: %d\n", ret);
break;
}
acrn_ioreq_complete_request(client, vcpu, req);
}
}
return 0;
}
/*
* For the non-default I/O clients, give them chance to complete the current
* I/O requests if there are any. For the default I/O client, it is safe to
* clear all pending I/O requests because the clearing request is from ACRN
* userspace.
*/
void acrn_ioreq_request_clear(struct acrn_vm *vm)
{
struct acrn_ioreq_client *client;
bool has_pending = false;
unsigned long vcpu;
int retry = 10;
/*
* IO requests of this VM will be completed directly in
* acrn_ioreq_dispatch if ACRN_VM_FLAG_CLEARING_IOREQ flag is set.
*/
set_bit(ACRN_VM_FLAG_CLEARING_IOREQ, &vm->flags);
/*
* acrn_ioreq_request_clear is only called in VM reset case. Simply
* wait 100ms in total for the IO requests' completion.
*/
do {
spin_lock_bh(&vm->ioreq_clients_lock);
list_for_each_entry(client, &vm->ioreq_clients, list) {
has_pending = has_pending_request(client);
if (has_pending)
break;
}
spin_unlock_bh(&vm->ioreq_clients_lock);
if (has_pending)
schedule_timeout_interruptible(HZ / 100);
} while (has_pending && --retry > 0);
if (retry == 0)
dev_warn(acrn_dev.this_device,
"%s cannot flush pending request!\n", client->name);
/* Clear all ioreqs belonging to the default client */
spin_lock_bh(&vm->ioreq_clients_lock);
client = vm->default_client;
if (client) {
for_each_set_bit(vcpu, client->ioreqs_map, ACRN_IO_REQUEST_MAX)
acrn_ioreq_complete_request(client, vcpu, NULL);
}
spin_unlock_bh(&vm->ioreq_clients_lock);
/* Clear ACRN_VM_FLAG_CLEARING_IOREQ flag after the clearing */
clear_bit(ACRN_VM_FLAG_CLEARING_IOREQ, &vm->flags);
}
int acrn_ioreq_client_wait(struct acrn_ioreq_client *client)
{
if (client->is_default) {
/*
* In the default client, a user space thread waits on the
* waitqueue. The is_destroying() check is used to notify user
* space the client is going to be destroyed.
*/
wait_event_interruptible(client->wq,
has_pending_request(client) ||
is_destroying(client));
if (is_destroying(client))
return -ENODEV;
} else {
wait_event_interruptible(client->wq,
has_pending_request(client) ||
kthread_should_stop());
}
return 0;
}
static bool is_cfg_addr(struct acrn_io_request *req)
{
return ((req->type == ACRN_IOREQ_TYPE_PORTIO) &&
(req->reqs.pio_request.address == 0xcf8));
}
static bool is_cfg_data(struct acrn_io_request *req)
{
return ((req->type == ACRN_IOREQ_TYPE_PORTIO) &&
((req->reqs.pio_request.address >= 0xcfc) &&
(req->reqs.pio_request.address < (0xcfc + 4))));
}
/* The low 8-bit of supported pci_reg addr.*/
#define PCI_LOWREG_MASK 0xFC
/* The high 4-bit of supported pci_reg addr */
#define PCI_HIGHREG_MASK 0xF00
/* Max number of supported functions */
#define PCI_FUNCMAX 7
/* Max number of supported slots */
#define PCI_SLOTMAX 31
/* Max number of supported buses */
#define PCI_BUSMAX 255
#define CONF1_ENABLE 0x80000000UL
/*
* A PCI configuration space access via PIO 0xCF8 and 0xCFC normally has two
* following steps:
* 1) writes address into 0xCF8 port
* 2) accesses data in/from 0xCFC
* This function combines such paired PCI configuration space I/O requests into
* one ACRN_IOREQ_TYPE_PCICFG type I/O request and continues the processing.
*/
static bool handle_cf8cfc(struct acrn_vm *vm,
struct acrn_io_request *req, u16 vcpu)
{
int offset, pci_cfg_addr, pci_reg;
bool is_handled = false;
if (is_cfg_addr(req)) {
WARN_ON(req->reqs.pio_request.size != 4);
if (req->reqs.pio_request.direction == ACRN_IOREQ_DIR_WRITE)
vm->pci_conf_addr = req->reqs.pio_request.value;
else
req->reqs.pio_request.value = vm->pci_conf_addr;
is_handled = true;
} else if (is_cfg_data(req)) {
if (!(vm->pci_conf_addr & CONF1_ENABLE)) {
if (req->reqs.pio_request.direction ==
ACRN_IOREQ_DIR_READ)
req->reqs.pio_request.value = 0xffffffff;
is_handled = true;
} else {
offset = req->reqs.pio_request.address - 0xcfc;
req->type = ACRN_IOREQ_TYPE_PCICFG;
pci_cfg_addr = vm->pci_conf_addr;
req->reqs.pci_request.bus =
(pci_cfg_addr >> 16) & PCI_BUSMAX;
req->reqs.pci_request.dev =
(pci_cfg_addr >> 11) & PCI_SLOTMAX;
req->reqs.pci_request.func =
(pci_cfg_addr >> 8) & PCI_FUNCMAX;
pci_reg = (pci_cfg_addr & PCI_LOWREG_MASK) +
((pci_cfg_addr >> 16) & PCI_HIGHREG_MASK);
req->reqs.pci_request.reg = pci_reg + offset;
}
}
if (is_handled)
ioreq_complete_request(vm, vcpu, req);
return is_handled;
}
static bool acrn_in_range(struct acrn_ioreq_range *range,
struct acrn_io_request *req)
{
bool ret = false;
if (range->type == req->type) {
switch (req->type) {
case ACRN_IOREQ_TYPE_MMIO:
if (req->reqs.mmio_request.address >= range->start &&
(req->reqs.mmio_request.address +
req->reqs.mmio_request.size - 1) <= range->end)
ret = true;
break;
case ACRN_IOREQ_TYPE_PORTIO:
if (req->reqs.pio_request.address >= range->start &&
(req->reqs.pio_request.address +
req->reqs.pio_request.size - 1) <= range->end)
ret = true;
break;
default:
break;
}
}
return ret;
}
static struct acrn_ioreq_client *find_ioreq_client(struct acrn_vm *vm,
struct acrn_io_request *req)
{
struct acrn_ioreq_client *client, *found = NULL;
struct acrn_ioreq_range *range;
lockdep_assert_held(&vm->ioreq_clients_lock);
list_for_each_entry(client, &vm->ioreq_clients, list) {
read_lock_bh(&client->range_lock);
list_for_each_entry(range, &client->range_list, list) {
if (acrn_in_range(range, req)) {
found = client;
break;
}
}
read_unlock_bh(&client->range_lock);
if (found)
break;
}
return found ? found : vm->default_client;
}
/**
* acrn_ioreq_client_create() - Create an ioreq client
* @vm: The VM that this client belongs to
* @handler: The ioreq_handler of ioreq client acrn_hsm will create a kernel
* thread and call the handler to handle I/O requests.
* @priv: Private data for the handler
* @is_default: If it is the default client
* @name: The name of ioreq client
*
* Return: acrn_ioreq_client pointer on success, NULL on error
*/
struct acrn_ioreq_client *acrn_ioreq_client_create(struct acrn_vm *vm,
ioreq_handler_t handler,
void *priv, bool is_default,
const char *name)
{
struct acrn_ioreq_client *client;
if (!handler && !is_default) {
dev_dbg(acrn_dev.this_device,
"Cannot create non-default client w/o handler!\n");
return NULL;
}
client = kzalloc_obj(*client);
if (!client)
return NULL;
client->handler = handler;
client->vm = vm;
client->priv = priv;
client->is_default = is_default;
if (name)
strscpy(client->name, name);
rwlock_init(&client->range_lock);
INIT_LIST_HEAD(&client->range_list);
init_waitqueue_head(&client->wq);
if (client->handler) {
client->thread = kthread_run(ioreq_task, client, "VM%u-%s",
client->vm->vmid, client->name);
if (IS_ERR(client->thread)) {
kfree(client);
return NULL;
}
}
spin_lock_bh(&vm->ioreq_clients_lock);
if (is_default)
vm->default_client = client;
else
list_add(&client->list, &vm->ioreq_clients);
spin_unlock_bh(&vm->ioreq_clients_lock);
dev_dbg(acrn_dev.this_device, "Created ioreq client %s.\n", name);
return client;
}
/**
* acrn_ioreq_client_destroy() - Destroy an ioreq client
* @client: The ioreq client
*/
void acrn_ioreq_client_destroy(struct acrn_ioreq_client *client)
{
struct acrn_ioreq_range *range, *next;
struct acrn_vm *vm = client->vm;
dev_dbg(acrn_dev.this_device,
"Destroy ioreq client %s.\n", client->name);
ioreq_pause();
set_bit(ACRN_IOREQ_CLIENT_DESTROYING, &client->flags);
if (client->is_default)
wake_up_interruptible(&client->wq);
else
kthread_stop(client->thread);
spin_lock_bh(&vm->ioreq_clients_lock);
if (client->is_default)
vm->default_client = NULL;
else
list_del(&client->list);
spin_unlock_bh(&vm->ioreq_clients_lock);
write_lock_bh(&client->range_lock);
list_for_each_entry_safe(range, next, &client->range_list, list) {
list_del(&range->list);
kfree(range);
}
write_unlock_bh(&client->range_lock);
kfree(client);
ioreq_resume();
}
static int acrn_ioreq_dispatch(struct acrn_vm *vm)
{
struct acrn_ioreq_client *client;
struct acrn_io_request *req;
int i;
for (i = 0; i < vm->vcpu_num; i++) {
req = vm->ioreq_buf->req_slot + i;
/* barrier the read of processed of acrn_io_request */
if (smp_load_acquire(&req->processed) ==
ACRN_IOREQ_STATE_PENDING) {
/* Complete the IO request directly in clearing stage */
if (test_bit(ACRN_VM_FLAG_CLEARING_IOREQ, &vm->flags)) {
ioreq_complete_request(vm, i, req);
continue;
}
if (handle_cf8cfc(vm, req, i))
continue;
spin_lock_bh(&vm->ioreq_clients_lock);
client = find_ioreq_client(vm, req);
if (!client) {
dev_err(acrn_dev.this_device,
"Failed to find ioreq client!\n");
spin_unlock_bh(&vm->ioreq_clients_lock);
return -EINVAL;
}
if (!client->is_default)
req->kernel_handled = 1;
else
req->kernel_handled = 0;
/*
* Add barrier() to make sure the writes are done
* before setting ACRN_IOREQ_STATE_PROCESSING
*/
smp_store_release(&req->processed,
ACRN_IOREQ_STATE_PROCESSING);
set_bit(i, client->ioreqs_map);
wake_up_interruptible(&client->wq);
spin_unlock_bh(&vm->ioreq_clients_lock);
}
}
return 0;
}
static void ioreq_dispatcher(struct work_struct *work)
{
struct acrn_vm *vm;
read_lock(&acrn_vm_list_lock);
list_for_each_entry(vm, &acrn_vm_list, list) {
if (!vm->ioreq_buf)
break;
acrn_ioreq_dispatch(vm);
}
read_unlock(&acrn_vm_list_lock);
}
static void ioreq_intr_handler(void)
{
queue_work(ioreq_wq, &ioreq_work);
}
static void ioreq_pause(void)
{
/* Flush and unarm the handler to ensure no I/O requests pending */
acrn_remove_intr_handler();
drain_workqueue(ioreq_wq);
}
static void ioreq_resume(void)
{
/* Schedule after enabling in case other clients miss interrupt */
acrn_setup_intr_handler(ioreq_intr_handler);
queue_work(ioreq_wq, &ioreq_work);
}
int acrn_ioreq_intr_setup(void)
{
acrn_setup_intr_handler(ioreq_intr_handler);
ioreq_wq = alloc_ordered_workqueue("ioreq_wq",
WQ_HIGHPRI | WQ_MEM_RECLAIM);
if (!ioreq_wq) {
dev_err(acrn_dev.this_device, "Failed to alloc workqueue!\n");
acrn_remove_intr_handler();
return -ENOMEM;
}
return 0;
}
void acrn_ioreq_intr_remove(void)
{
if (ioreq_wq)
destroy_workqueue(ioreq_wq);
acrn_remove_intr_handler();
}
int acrn_ioreq_init(struct acrn_vm *vm, u64 buf_vma)
{
struct acrn_ioreq_buffer *set_buffer;
struct page *page;
int ret;
if (vm->ioreq_buf)
return -EEXIST;
set_buffer = kzalloc_obj(*set_buffer);
if (!set_buffer)
return -ENOMEM;
ret = pin_user_pages_fast(buf_vma, 1,
FOLL_WRITE | FOLL_LONGTERM, &page);
if (unlikely(ret != 1) || !page) {
dev_err(acrn_dev.this_device, "Failed to pin ioreq page!\n");
ret = -EFAULT;
goto free_buf;
}
vm->ioreq_buf = page_address(page);
vm->ioreq_page = page;
set_buffer->ioreq_buf = page_to_phys(page);
ret = hcall_set_ioreq_buffer(vm->vmid, virt_to_phys(set_buffer));
if (ret < 0) {
dev_err(acrn_dev.this_device, "Failed to init ioreq buffer!\n");
unpin_user_page(page);
vm->ioreq_buf = NULL;
goto free_buf;
}
dev_dbg(acrn_dev.this_device,
"Init ioreq buffer %p!\n", vm->ioreq_buf);
ret = 0;
free_buf:
kfree(set_buffer);
return ret;
}
void acrn_ioreq_deinit(struct acrn_vm *vm)
{
struct acrn_ioreq_client *client, *next;
dev_dbg(acrn_dev.this_device,
"Deinit ioreq buffer %p!\n", vm->ioreq_buf);
/* Destroy all clients belonging to this VM */
list_for_each_entry_safe(client, next, &vm->ioreq_clients, list)
acrn_ioreq_client_destroy(client);
if (vm->default_client)
acrn_ioreq_client_destroy(vm->default_client);
if (vm->ioreq_buf && vm->ioreq_page) {
unpin_user_page(vm->ioreq_page);
vm->ioreq_buf = NULL;
}
}
+244
View File
@@ -0,0 +1,244 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACRN HSM irqfd: use eventfd objects to inject virtual interrupts
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
*
* Authors:
* Shuo Liu <shuo.a.liu@intel.com>
* Yakui Zhao <yakui.zhao@intel.com>
*/
#include <linux/eventfd.h>
#include <linux/file.h>
#include <linux/poll.h>
#include <linux/slab.h>
#include "acrn_drv.h"
/* Cleanup work has been queued; set via test_and_set_bit(). */
#define HSM_IRQFD_FLAG_SHUTDOWN 0
/**
* struct hsm_irqfd - Properties of HSM irqfd
* @vm: Associated VM pointer
* @wait: Entry of wait-queue
* @shutdown: Async shutdown work
* @eventfd: Associated eventfd
* @list: Entry within &acrn_vm.irqfds of irqfds of a VM
* @pt: Structure for select/poll on the associated eventfd
* @msi: MSI data
* @flags: Internal lifecycle flags (HSM_IRQFD_FLAG_*)
*/
struct hsm_irqfd {
struct acrn_vm *vm;
wait_queue_entry_t wait;
struct work_struct shutdown;
struct eventfd_ctx *eventfd;
struct list_head list;
poll_table pt;
struct acrn_msi_entry msi;
unsigned long flags;
};
static void acrn_irqfd_inject(struct hsm_irqfd *irqfd)
{
struct acrn_vm *vm = irqfd->vm;
acrn_msi_inject(vm, irqfd->msi.msi_addr,
irqfd->msi.msi_data);
}
/* Queue the cleanup work at most once. Safe from atomic context. */
static void hsm_irqfd_queue_shutdown(struct hsm_irqfd *irqfd)
{
if (!test_and_set_bit(HSM_IRQFD_FLAG_SHUTDOWN, &irqfd->flags))
queue_work(irqfd->vm->irqfd_wq, &irqfd->shutdown);
}
/* Sole owner of @irqfd: unhook waitqueue, drop eventfd ref, free. */
static void hsm_irqfd_shutdown_work(struct work_struct *work)
{
struct hsm_irqfd *irqfd = container_of(work, struct hsm_irqfd,
shutdown);
struct acrn_vm *vm = irqfd->vm;
u64 cnt;
mutex_lock(&vm->irqfds_lock);
if (!list_empty(&irqfd->list))
list_del_init(&irqfd->list);
mutex_unlock(&vm->irqfds_lock);
eventfd_ctx_remove_wait_queue(irqfd->eventfd, &irqfd->wait, &cnt);
eventfd_ctx_put(irqfd->eventfd);
kfree(irqfd);
}
/* Called with wqh->lock held and interrupts disabled */
static int hsm_irqfd_wakeup(wait_queue_entry_t *wait, unsigned int mode,
int sync, void *key)
{
unsigned long poll_bits = (unsigned long)key;
struct hsm_irqfd *irqfd;
irqfd = container_of(wait, struct hsm_irqfd, wait);
if (poll_bits & POLLIN)
/* An event has been signaled, inject an interrupt */
acrn_irqfd_inject(irqfd);
if (poll_bits & POLLHUP)
/* Defer teardown to the cleanup work; can't sleep here. */
hsm_irqfd_queue_shutdown(irqfd);
return 0;
}
static void hsm_irqfd_poll_func(struct file *file, wait_queue_head_t *wqh,
poll_table *pt)
{
struct hsm_irqfd *irqfd;
irqfd = container_of(pt, struct hsm_irqfd, pt);
add_wait_queue(wqh, &irqfd->wait);
}
/*
* Assign an eventfd to a VM and create a HSM irqfd associated with the
* eventfd. The properties of the HSM irqfd are built from a &struct
* acrn_irqfd.
*/
static int acrn_irqfd_assign(struct acrn_vm *vm, struct acrn_irqfd *args)
{
struct eventfd_ctx *eventfd = NULL;
struct hsm_irqfd *irqfd, *tmp;
__poll_t events;
int ret = 0;
irqfd = kzalloc_obj(*irqfd);
if (!irqfd)
return -ENOMEM;
irqfd->vm = vm;
memcpy(&irqfd->msi, &args->msi, sizeof(args->msi));
INIT_LIST_HEAD(&irqfd->list);
INIT_WORK(&irqfd->shutdown, hsm_irqfd_shutdown_work);
CLASS(fd, f)(args->fd);
if (fd_empty(f)) {
ret = -EBADF;
goto out;
}
eventfd = eventfd_ctx_fileget(fd_file(f));
if (IS_ERR(eventfd)) {
ret = PTR_ERR(eventfd);
goto out;
}
irqfd->eventfd = eventfd;
/*
* Install custom wake-up handling to be notified whenever underlying
* eventfd is signaled.
*/
init_waitqueue_func_entry(&irqfd->wait, hsm_irqfd_wakeup);
init_poll_funcptr(&irqfd->pt, hsm_irqfd_poll_func);
/*
* Hold irqfds_lock across waitqueue install and list_add so the
* irqfd is not visible to deassign/deinit before its waitqueue
* entry is in place, and any racing EPOLLHUP cleanup work blocks
* on irqfds_lock until publication completes.
*/
mutex_lock(&vm->irqfds_lock);
list_for_each_entry(tmp, &vm->irqfds, list) {
if (irqfd->eventfd != tmp->eventfd)
continue;
ret = -EBUSY;
mutex_unlock(&vm->irqfds_lock);
goto fail;
}
events = vfs_poll(fd_file(f), &irqfd->pt);
list_add_tail(&irqfd->list, &vm->irqfds);
if (events & EPOLLIN)
acrn_irqfd_inject(irqfd);
mutex_unlock(&vm->irqfds_lock);
return 0;
fail:
eventfd_ctx_put(eventfd);
out:
kfree(irqfd);
return ret;
}
static int acrn_irqfd_deassign(struct acrn_vm *vm,
struct acrn_irqfd *args)
{
struct hsm_irqfd *irqfd, *tmp;
struct eventfd_ctx *eventfd;
eventfd = eventfd_ctx_fdget(args->fd);
if (IS_ERR(eventfd))
return PTR_ERR(eventfd);
mutex_lock(&vm->irqfds_lock);
list_for_each_entry_safe(irqfd, tmp, &vm->irqfds, list) {
if (irqfd->eventfd == eventfd) {
list_del_init(&irqfd->list);
hsm_irqfd_queue_shutdown(irqfd);
break;
}
}
mutex_unlock(&vm->irqfds_lock);
eventfd_ctx_put(eventfd);
/* Wait for cleanup work to finish so the eventfd is fully detached. */
flush_workqueue(vm->irqfd_wq);
return 0;
}
int acrn_irqfd_config(struct acrn_vm *vm, struct acrn_irqfd *args)
{
int ret;
if (args->flags & ACRN_IRQFD_FLAG_DEASSIGN)
ret = acrn_irqfd_deassign(vm, args);
else
ret = acrn_irqfd_assign(vm, args);
return ret;
}
int acrn_irqfd_init(struct acrn_vm *vm)
{
INIT_LIST_HEAD(&vm->irqfds);
mutex_init(&vm->irqfds_lock);
vm->irqfd_wq = alloc_workqueue("acrn_irqfd-%u", WQ_PERCPU, 0, vm->vmid);
if (!vm->irqfd_wq)
return -ENOMEM;
dev_dbg(acrn_dev.this_device, "VM %u irqfd init.\n", vm->vmid);
return 0;
}
void acrn_irqfd_deinit(struct acrn_vm *vm)
{
struct hsm_irqfd *irqfd, *next;
dev_dbg(acrn_dev.this_device, "VM %u irqfd deinit.\n", vm->vmid);
mutex_lock(&vm->irqfds_lock);
list_for_each_entry_safe(irqfd, next, &vm->irqfds, list) {
list_del_init(&irqfd->list);
hsm_irqfd_queue_shutdown(irqfd);
}
mutex_unlock(&vm->irqfds_lock);
/* Drain all cleanup work before tearing the workqueue down. */
flush_workqueue(vm->irqfd_wq);
destroy_workqueue(vm->irqfd_wq);
}
+367
View File
@@ -0,0 +1,367 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACRN: Memory mapping management
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
*
* Authors:
* Fei Li <lei1.li@intel.com>
* Shuo Liu <shuo.a.liu@intel.com>
*/
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include "acrn_drv.h"
static int modify_region(struct acrn_vm *vm, struct vm_memory_region_op *region)
{
struct vm_memory_region_batch *regions;
int ret;
regions = kzalloc_obj(*regions);
if (!regions)
return -ENOMEM;
regions->vmid = vm->vmid;
regions->regions_num = 1;
regions->regions_gpa = virt_to_phys(region);
ret = hcall_set_memory_regions(virt_to_phys(regions));
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Failed to set memory region for VM[%u]!\n", vm->vmid);
kfree(regions);
return ret;
}
/**
* acrn_mm_region_add() - Set up the EPT mapping of a memory region.
* @vm: User VM.
* @user_gpa: A GPA of User VM.
* @service_gpa: A GPA of Service VM.
* @size: Size of the region.
* @mem_type: Combination of ACRN_MEM_TYPE_*.
* @mem_access_right: Combination of ACRN_MEM_ACCESS_*.
*
* Return: 0 on success, <0 on error.
*/
int acrn_mm_region_add(struct acrn_vm *vm, u64 user_gpa, u64 service_gpa,
u64 size, u32 mem_type, u32 mem_access_right)
{
struct vm_memory_region_op *region;
int ret = 0;
region = kzalloc_obj(*region);
if (!region)
return -ENOMEM;
region->type = ACRN_MEM_REGION_ADD;
region->user_vm_pa = user_gpa;
region->service_vm_pa = service_gpa;
region->size = size;
region->attr = ((mem_type & ACRN_MEM_TYPE_MASK) |
(mem_access_right & ACRN_MEM_ACCESS_RIGHT_MASK));
ret = modify_region(vm, region);
dev_dbg(acrn_dev.this_device,
"%s: user-GPA[%p] service-GPA[%p] size[0x%llx].\n",
__func__, (void *)user_gpa, (void *)service_gpa, size);
kfree(region);
return ret;
}
/**
* acrn_mm_region_del() - Del the EPT mapping of a memory region.
* @vm: User VM.
* @user_gpa: A GPA of the User VM.
* @size: Size of the region.
*
* Return: 0 on success, <0 for error.
*/
int acrn_mm_region_del(struct acrn_vm *vm, u64 user_gpa, u64 size)
{
struct vm_memory_region_op *region;
int ret = 0;
region = kzalloc_obj(*region);
if (!region)
return -ENOMEM;
region->type = ACRN_MEM_REGION_DEL;
region->user_vm_pa = user_gpa;
region->service_vm_pa = 0UL;
region->size = size;
region->attr = 0U;
ret = modify_region(vm, region);
dev_dbg(acrn_dev.this_device, "%s: user-GPA[%p] size[0x%llx].\n",
__func__, (void *)user_gpa, size);
kfree(region);
return ret;
}
int acrn_vm_memseg_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
{
int ret;
if (memmap->type == ACRN_MEMMAP_RAM)
return acrn_vm_ram_map(vm, memmap);
if (memmap->type != ACRN_MEMMAP_MMIO) {
dev_dbg(acrn_dev.this_device,
"Invalid memmap type: %u\n", memmap->type);
return -EINVAL;
}
ret = acrn_mm_region_add(vm, memmap->user_vm_pa,
memmap->service_vm_pa, memmap->len,
ACRN_MEM_TYPE_UC, memmap->attr);
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Add memory region failed, VM[%u]!\n", vm->vmid);
return ret;
}
int acrn_vm_memseg_unmap(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
{
int ret;
if (memmap->type != ACRN_MEMMAP_MMIO) {
dev_dbg(acrn_dev.this_device,
"Invalid memmap type: %u\n", memmap->type);
return -EINVAL;
}
ret = acrn_mm_region_del(vm, memmap->user_vm_pa, memmap->len);
if (ret < 0)
dev_dbg(acrn_dev.this_device,
"Del memory region failed, VM[%u]!\n", vm->vmid);
return ret;
}
/**
* acrn_vm_ram_map() - Create a RAM EPT mapping of User VM.
* @vm: The User VM pointer
* @memmap: Info of the EPT mapping
*
* Return: 0 on success, <0 for error.
*/
int acrn_vm_ram_map(struct acrn_vm *vm, struct acrn_vm_memmap *memmap)
{
struct vm_memory_region_batch *regions_info;
int nr_pages, i, order, nr_regions = 0;
struct vm_memory_mapping *region_mapping;
struct vm_memory_region_op *vm_region;
struct page **pages = NULL, *page;
void *remap_vaddr;
int ret, pinned;
u64 user_vm_pa;
struct vm_area_struct *vma;
if (!vm || !memmap)
return -EINVAL;
/* Get the page number of the map region */
nr_pages = memmap->len >> PAGE_SHIFT;
if (!nr_pages)
return -EINVAL;
mmap_read_lock(current->mm);
vma = vma_lookup(current->mm, memmap->vma_base);
if (vma && ((vma->vm_flags & VM_PFNMAP) != 0)) {
unsigned long start_pfn, cur_pfn;
bool writable;
if ((memmap->vma_base + memmap->len) > vma->vm_end) {
mmap_read_unlock(current->mm);
return -EINVAL;
}
for (i = 0; i < nr_pages; i++) {
struct follow_pfnmap_args args = {
.vma = vma,
.address = memmap->vma_base + i * PAGE_SIZE,
};
ret = follow_pfnmap_start(&args);
if (ret)
break;
cur_pfn = args.pfn;
if (i == 0)
start_pfn = cur_pfn;
writable = args.writable;
follow_pfnmap_end(&args);
/* Disallow write access if the PTE is not writable. */
if (!writable &&
(memmap->attr & ACRN_MEM_ACCESS_WRITE)) {
ret = -EFAULT;
break;
}
/* Disallow refcounted pages. */
if (pfn_valid(cur_pfn) &&
!PageReserved(pfn_to_page(cur_pfn))) {
ret = -EFAULT;
break;
}
/* Disallow non-contiguous ranges. */
if (cur_pfn != start_pfn + i) {
ret = -EINVAL;
break;
}
}
mmap_read_unlock(current->mm);
if (ret) {
dev_dbg(acrn_dev.this_device,
"Failed to lookup PFN at VMA:%p.\n", (void *)memmap->vma_base);
return ret;
}
return acrn_mm_region_add(vm, memmap->user_vm_pa,
PFN_PHYS(start_pfn), memmap->len,
ACRN_MEM_TYPE_WB, memmap->attr);
}
mmap_read_unlock(current->mm);
pages = vzalloc(array_size(nr_pages, sizeof(*pages)));
if (!pages)
return -ENOMEM;
/* Lock the pages of user memory map region */
pinned = pin_user_pages_fast(memmap->vma_base,
nr_pages, FOLL_WRITE | FOLL_LONGTERM,
pages);
if (pinned < 0) {
ret = pinned;
goto free_pages;
} else if (pinned != nr_pages) {
ret = -EFAULT;
goto put_pages;
}
/* Create a kernel map for the map region */
remap_vaddr = vmap(pages, nr_pages, VM_MAP, PAGE_KERNEL);
if (!remap_vaddr) {
ret = -ENOMEM;
goto put_pages;
}
/* Record Service VM va <-> User VM pa mapping */
mutex_lock(&vm->regions_mapping_lock);
region_mapping = &vm->regions_mapping[vm->regions_mapping_count];
if (vm->regions_mapping_count < ACRN_MEM_MAPPING_MAX) {
region_mapping->pages = pages;
region_mapping->npages = nr_pages;
region_mapping->size = memmap->len;
region_mapping->service_vm_va = remap_vaddr;
region_mapping->user_vm_pa = memmap->user_vm_pa;
vm->regions_mapping_count++;
} else {
dev_warn(acrn_dev.this_device,
"Run out of memory mapping slots!\n");
ret = -ENOMEM;
mutex_unlock(&vm->regions_mapping_lock);
goto unmap_no_count;
}
mutex_unlock(&vm->regions_mapping_lock);
/* Calculate count of vm_memory_region_op */
for (i = 0; i < nr_pages; i += 1 << order) {
page = pages[i];
VM_BUG_ON_PAGE(PageTail(page), page);
order = compound_order(page);
nr_regions++;
}
/* Prepare the vm_memory_region_batch */
regions_info = kzalloc_flex(*regions_info, regions_op, nr_regions);
if (!regions_info) {
ret = -ENOMEM;
goto unmap_kernel_map;
}
regions_info->regions_num = nr_regions;
/* Fill each vm_memory_region_op */
vm_region = regions_info->regions_op;
regions_info->vmid = vm->vmid;
regions_info->regions_gpa = virt_to_phys(vm_region);
user_vm_pa = memmap->user_vm_pa;
for (i = 0; i < nr_pages; i += 1 << order) {
u32 region_size;
page = pages[i];
VM_BUG_ON_PAGE(PageTail(page), page);
order = compound_order(page);
region_size = PAGE_SIZE << order;
vm_region->type = ACRN_MEM_REGION_ADD;
vm_region->user_vm_pa = user_vm_pa;
vm_region->service_vm_pa = page_to_phys(page);
vm_region->size = region_size;
vm_region->attr = (ACRN_MEM_TYPE_WB & ACRN_MEM_TYPE_MASK) |
(memmap->attr & ACRN_MEM_ACCESS_RIGHT_MASK);
vm_region++;
user_vm_pa += region_size;
}
/* Inform the ACRN Hypervisor to set up EPT mappings */
ret = hcall_set_memory_regions(virt_to_phys(regions_info));
if (ret < 0) {
dev_dbg(acrn_dev.this_device,
"Failed to set regions, VM[%u]!\n", vm->vmid);
goto unset_region;
}
kfree(regions_info);
dev_dbg(acrn_dev.this_device,
"%s: VM[%u] service-GVA[%p] user-GPA[%p] size[0x%llx]\n",
__func__, vm->vmid,
remap_vaddr, (void *)memmap->user_vm_pa, memmap->len);
return ret;
unset_region:
kfree(regions_info);
unmap_kernel_map:
mutex_lock(&vm->regions_mapping_lock);
vm->regions_mapping_count--;
mutex_unlock(&vm->regions_mapping_lock);
unmap_no_count:
vunmap(remap_vaddr);
put_pages:
for (i = 0; i < pinned; i++)
unpin_user_page(pages[i]);
free_pages:
vfree(pages);
return ret;
}
/**
* acrn_vm_all_ram_unmap() - Destroy a RAM EPT mapping of User VM.
* @vm: The User VM
*/
void acrn_vm_all_ram_unmap(struct acrn_vm *vm)
{
struct vm_memory_mapping *region_mapping;
int i, j;
mutex_lock(&vm->regions_mapping_lock);
for (i = 0; i < vm->regions_mapping_count; i++) {
region_mapping = &vm->regions_mapping[i];
vunmap(region_mapping->service_vm_va);
for (j = 0; j < region_mapping->npages; j++)
unpin_user_page(region_mapping->pages[j]);
vfree(region_mapping->pages);
}
mutex_unlock(&vm->regions_mapping_lock);
}
+126
View File
@@ -0,0 +1,126 @@
// SPDX-License-Identifier: GPL-2.0
/*
* ACRN_HSM: Virtual Machine management
*
* Copyright (C) 2020 Intel Corporation. All rights reserved.
*
* Authors:
* Jason Chen CJ <jason.cj.chen@intel.com>
* Yakui Zhao <yakui.zhao@intel.com>
*/
#include <linux/io.h>
#include <linux/mm.h>
#include <linux/slab.h>
#include "acrn_drv.h"
/* List of VMs */
LIST_HEAD(acrn_vm_list);
/*
* acrn_vm_list is read in a worker thread which dispatch I/O requests and
* is wrote in VM creation ioctl. Use the rwlock mechanism to protect it.
*/
DEFINE_RWLOCK(acrn_vm_list_lock);
struct acrn_vm *acrn_vm_create(struct acrn_vm *vm,
struct acrn_vm_creation *vm_param)
{
int ret;
ret = hcall_create_vm(virt_to_phys(vm_param));
if (ret < 0 || vm_param->vmid == ACRN_INVALID_VMID) {
dev_err(acrn_dev.this_device,
"Failed to create VM! Error: %d\n", ret);
return NULL;
}
mutex_init(&vm->regions_mapping_lock);
INIT_LIST_HEAD(&vm->ioreq_clients);
spin_lock_init(&vm->ioreq_clients_lock);
vm->vmid = vm_param->vmid;
vm->vcpu_num = vm_param->vcpu_num;
if (acrn_ioreq_init(vm, vm_param->ioreq_buf) < 0) {
hcall_destroy_vm(vm_param->vmid);
vm->vmid = ACRN_INVALID_VMID;
return NULL;
}
write_lock_bh(&acrn_vm_list_lock);
list_add(&vm->list, &acrn_vm_list);
write_unlock_bh(&acrn_vm_list_lock);
acrn_ioeventfd_init(vm);
acrn_irqfd_init(vm);
dev_dbg(acrn_dev.this_device, "VM %u created.\n", vm->vmid);
return vm;
}
int acrn_vm_destroy(struct acrn_vm *vm)
{
int ret;
if (vm->vmid == ACRN_INVALID_VMID ||
test_and_set_bit(ACRN_VM_FLAG_DESTROYED, &vm->flags))
return 0;
ret = hcall_destroy_vm(vm->vmid);
if (ret < 0) {
dev_err(acrn_dev.this_device,
"Failed to destroy VM %u\n", vm->vmid);
clear_bit(ACRN_VM_FLAG_DESTROYED, &vm->flags);
return ret;
}
/* Remove from global VM list */
write_lock_bh(&acrn_vm_list_lock);
list_del_init(&vm->list);
write_unlock_bh(&acrn_vm_list_lock);
acrn_ioeventfd_deinit(vm);
acrn_irqfd_deinit(vm);
acrn_ioreq_deinit(vm);
if (vm->monitor_page) {
put_page(vm->monitor_page);
vm->monitor_page = NULL;
}
acrn_vm_all_ram_unmap(vm);
dev_dbg(acrn_dev.this_device, "VM %u destroyed.\n", vm->vmid);
vm->vmid = ACRN_INVALID_VMID;
return 0;
}
/**
* acrn_msi_inject() - Inject a MSI interrupt into a User VM
* @vm: User VM
* @msi_addr: The MSI address
* @msi_data: The MSI data
*
* Return: 0 on success, <0 on error
*/
int acrn_msi_inject(struct acrn_vm *vm, u64 msi_addr, u64 msi_data)
{
struct acrn_msi_entry *msi;
int ret;
/* might be used in interrupt context, so use GFP_ATOMIC */
msi = kzalloc_obj(*msi, GFP_ATOMIC);
if (!msi)
return -ENOMEM;
/*
* msi_addr: addr[19:12] with dest vcpu id
* msi_data: data[7:0] with vector
*/
msi->msi_addr = msi_addr;
msi->msi_data = msi_data;
ret = hcall_inject_msi(vm->vmid, virt_to_phys(msi));
if (ret < 0)
dev_err(acrn_dev.this_device,
"Failed to inject MSI to VM %u!\n", vm->vmid);
kfree(msi);
return ret;
}
+23
View File
@@ -0,0 +1,23 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Confidential computing related collateral
#
if VIRT_DRIVERS
source "drivers/virt/coco/efi_secret/Kconfig"
source "drivers/virt/coco/pkvm-guest/Kconfig"
source "drivers/virt/coco/sev-guest/Kconfig"
source "drivers/virt/coco/tdx-guest/Kconfig"
source "drivers/virt/coco/arm-cca-guest/Kconfig"
source "drivers/virt/coco/guest/Kconfig"
endif
source "drivers/virt/coco/tdx-host/Kconfig"
config TSM
bool
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Confidential computing related collateral
#
obj-$(CONFIG_EFI_SECRET) += efi_secret/
obj-$(CONFIG_ARM_PKVM_GUEST) += pkvm-guest/
obj-$(CONFIG_SEV_GUEST) += sev-guest/
obj-$(CONFIG_INTEL_TDX_GUEST) += tdx-guest/
obj-$(CONFIG_INTEL_TDX_HOST) += tdx-host/
obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest/
obj-$(CONFIG_TSM) += tsm-core.o
obj-$(CONFIG_TSM_GUEST) += guest/
@@ -0,0 +1,10 @@
config ARM_CCA_GUEST
tristate "Arm CCA Guest driver"
depends on ARM64
select TSM_REPORTS
help
The driver provides userspace interface to request and
attestation report from the Realm Management Monitor(RMM).
If you choose 'M' here, this module will be called
arm-cca-guest.
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_ARM_CCA_GUEST) += arm-cca-guest.o
@@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2023 ARM Ltd.
*/
#include <linux/arm-smccc.h>
#include <linux/cc_platform.h>
#include <linux/kernel.h>
#include <linux/device-id/platform.h>
#include <linux/module.h>
#include <linux/smp.h>
#include <linux/tsm.h>
#include <linux/types.h>
#include <asm/rsi.h>
/**
* struct arm_cca_token_info - a descriptor for the token buffer.
* @challenge: Pointer to the challenge data
* @challenge_size: Size of the challenge data
* @granule: PA of the granule to which the token will be written
* @offset: Offset within granule to start of buffer in bytes
* @result: result of rsi_attestation_token_continue operation
*/
struct arm_cca_token_info {
void *challenge;
unsigned long challenge_size;
phys_addr_t granule;
unsigned long offset;
unsigned long result;
};
static void arm_cca_attestation_init(void *param)
{
struct arm_cca_token_info *info;
info = (struct arm_cca_token_info *)param;
info->result = rsi_attestation_token_init(info->challenge,
info->challenge_size);
}
/**
* arm_cca_attestation_continue - Retrieve the attestation token data.
*
* @param: pointer to the arm_cca_token_info
*
* Attestation token generation is a long running operation and therefore
* the token data may not be retrieved in a single call. Moreover, the
* token retrieval operation must be requested on the same CPU on which the
* attestation token generation was initialised.
* This helper function is therefore scheduled on the same CPU multiple
* times until the entire token data is retrieved.
*/
static void arm_cca_attestation_continue(void *param)
{
unsigned long len;
unsigned long size;
struct arm_cca_token_info *info;
info = (struct arm_cca_token_info *)param;
size = RSI_GRANULE_SIZE - info->offset;
info->result = rsi_attestation_token_continue(info->granule,
info->offset, size, &len);
info->offset += len;
}
/**
* arm_cca_report_new - Generate a new attestation token.
*
* @report: pointer to the TSM report context information.
* @data: pointer to the context specific data for this module.
*
* Initialise the attestation token generation using the challenge data
* passed in the TSM descriptor. Allocate memory for the attestation token
* and schedule calls to retrieve the attestation token on the same CPU
* on which the attestation token generation was initialised.
*
* The challenge data must be at least 32 bytes and no more than 64 bytes. If
* less than 64 bytes are provided it will be zero padded to 64 bytes.
*
* Return:
* * %0 - Attestation token generated successfully.
* * %-EINVAL - A parameter was not valid.
* * %-ENOMEM - Out of memory.
* * %-EFAULT - Failed to get IPA for memory page(s).
* * A negative status code as returned by smp_call_function_single().
*/
static int arm_cca_report_new(struct tsm_report *report, void *data)
{
int ret;
int cpu;
long max_size;
unsigned long token_size = 0;
struct arm_cca_token_info info;
void *buf;
u8 *token __free(kvfree) = NULL;
struct tsm_report_desc *desc = &report->desc;
if (desc->inblob_len < 32 || desc->inblob_len > 64)
return -EINVAL;
/*
* The attestation token 'init' and 'continue' calls must be
* performed on the same CPU. smp_call_function_single() is used
* instead of simply calling get_cpu() because of the need to
* allocate outblob based on the returned value from the 'init'
* call and that cannot be done in an atomic context.
*/
cpu = smp_processor_id();
info.challenge = desc->inblob;
info.challenge_size = desc->inblob_len;
ret = smp_call_function_single(cpu, arm_cca_attestation_init,
&info, true);
if (ret)
return ret;
max_size = info.result;
if (max_size <= 0)
return -EINVAL;
/* Allocate outblob */
token = kvzalloc(max_size, GFP_KERNEL);
if (!token)
return -ENOMEM;
/*
* Since the outblob may not be physically contiguous, use a page
* to bounce the buffer from RMM.
*/
buf = alloc_pages_exact(RSI_GRANULE_SIZE, GFP_KERNEL);
if (!buf)
return -ENOMEM;
/* Get the PA of the memory page(s) that were allocated */
info.granule = (unsigned long)virt_to_phys(buf);
/* Loop until the token is ready or there is an error */
do {
/* Retrieve one RSI_GRANULE_SIZE data per loop iteration */
info.offset = 0;
do {
/*
* Schedule a call to retrieve a sub-granule chunk
* of data per loop iteration.
*/
ret = smp_call_function_single(cpu,
arm_cca_attestation_continue,
(void *)&info, true);
if (ret != 0) {
token_size = 0;
goto exit_free_granule_page;
}
} while (info.result == RSI_INCOMPLETE &&
info.offset < RSI_GRANULE_SIZE);
/* Break out in case of failure */
if (info.result != RSI_SUCCESS && info.result != RSI_INCOMPLETE) {
ret = -ENXIO;
token_size = 0;
goto exit_free_granule_page;
}
/*
* Copy the retrieved token data from the granule
* to the token buffer, ensuring that the RMM doesn't
* overflow the buffer.
*/
if (WARN_ON(token_size + info.offset > max_size))
break;
memcpy(&token[token_size], buf, info.offset);
token_size += info.offset;
} while (info.result == RSI_INCOMPLETE);
report->outblob = no_free_ptr(token);
exit_free_granule_page:
report->outblob_len = token_size;
free_pages_exact(buf, RSI_GRANULE_SIZE);
return ret;
}
static const struct tsm_report_ops arm_cca_tsm_ops = {
.name = KBUILD_MODNAME,
.report_new = arm_cca_report_new,
};
/**
* arm_cca_guest_init - Register with the Trusted Security Module (TSM)
* interface.
*
* Return:
* * %0 - Registered successfully with the TSM interface.
* * %-ENODEV - The execution context is not an Arm Realm.
* * %-EBUSY - Already registered.
*/
static int __init arm_cca_guest_init(void)
{
int ret;
if (!is_realm_world())
return -ENODEV;
ret = tsm_report_register(&arm_cca_tsm_ops, NULL);
if (ret < 0)
pr_err("Error %d registering with TSM\n", ret);
return ret;
}
module_init(arm_cca_guest_init);
/**
* arm_cca_guest_exit - unregister with the Trusted Security Module (TSM)
* interface.
*/
static void __exit arm_cca_guest_exit(void)
{
tsm_report_unregister(&arm_cca_tsm_ops);
}
module_exit(arm_cca_guest_exit);
/* modalias, so userspace can autoload this module when RSI is available */
static const struct platform_device_id arm_cca_match[] __maybe_unused = {
{ RSI_PDEV_NAME, 0},
{ }
};
MODULE_DEVICE_TABLE(platform, arm_cca_match);
MODULE_AUTHOR("Sami Mujawar <sami.mujawar@arm.com>");
MODULE_DESCRIPTION("Arm CCA Guest TSM Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
config EFI_SECRET
tristate "EFI secret area securityfs support"
depends on EFI && (X86_64 || ARM64)
select EFI_COCO_SECRET
select SECURITYFS
help
This is a driver for accessing the EFI secret area via securityfs.
The EFI secret area is a memory area designated by the firmware for
confidential computing secret injection (for example for AMD SEV
guests). The driver exposes the secrets as files in
<securityfs>/secrets/coco. Files can be read and deleted (deleting
a file wipes the secret from memory).
To compile this driver as a module, choose M here.
The module will be called efi_secret.
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_EFI_SECRET) += efi_secret.o
@@ -0,0 +1,319 @@
// SPDX-License-Identifier: GPL-2.0
/*
* efi_secret module
*
* Copyright (C) 2022 IBM Corporation
* Author: Dov Murik <dovmurik@linux.ibm.com>
*/
/**
* DOC: efi_secret: Allow reading EFI confidential computing (coco) secret area
* via securityfs interface.
*
* When the module is loaded (and securityfs is mounted, typically under
* /sys/kernel/security), a "secrets/coco" directory is created in securityfs.
* In it, a file is created for each secret entry. The name of each such file
* is the GUID of the secret entry, and its content is the secret data.
*/
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/io.h>
#include <linux/security.h>
#include <linux/efi.h>
#include <linux/cacheflush.h>
#define EFI_SECRET_NUM_FILES 64
struct efi_secret {
struct dentry *secrets_dir;
void __iomem *secret_data;
u64 secret_data_len;
};
/*
* Structure of the EFI secret area
*
* Offset Length
* (bytes) (bytes) Usage
* ------- ------- -----
* 0 16 Secret table header GUID (must be 1e74f542-71dd-4d66-963e-ef4287ff173b)
* 16 4 Length of bytes of the entire secret area
*
* 20 16 First secret entry's GUID
* 36 4 First secret entry's length in bytes (= 16 + 4 + x)
* 40 x First secret entry's data
*
* 40+x 16 Second secret entry's GUID
* 56+x 4 Second secret entry's length in bytes (= 16 + 4 + y)
* 60+x y Second secret entry's data
*
* (... and so on for additional entries)
*
* The GUID of each secret entry designates the usage of the secret data.
*/
/**
* struct secret_header - Header of entire secret area; this should be followed
* by instances of struct secret_entry.
* @guid: Must be EFI_SECRET_TABLE_HEADER_GUID
* @len: Length in bytes of entire secret area, including header
*/
struct secret_header {
efi_guid_t guid;
u32 len;
} __attribute((packed));
/**
* struct secret_entry - Holds one secret entry
* @guid: Secret-specific GUID (or NULL_GUID if this secret entry was deleted)
* @len: Length of secret entry, including its guid and len fields
* @data: The secret data (full of zeros if this secret entry was deleted)
*/
struct secret_entry {
efi_guid_t guid;
u32 len;
u8 data[];
} __attribute((packed));
static size_t secret_entry_data_len(struct secret_entry *e)
{
return e->len - sizeof(*e);
}
static struct efi_secret the_efi_secret;
static inline struct efi_secret *efi_secret_get(void)
{
return &the_efi_secret;
}
static int efi_secret_bin_file_show(struct seq_file *file, void *data)
{
struct secret_entry *e = file->private;
if (e)
seq_write(file, e->data, secret_entry_data_len(e));
return 0;
}
DEFINE_SHOW_ATTRIBUTE(efi_secret_bin_file);
/*
* Overwrite memory content with zeroes, and ensure that dirty cache lines are
* actually written back to memory, to clear out the secret.
*/
static void wipe_memory(void *addr, size_t size)
{
memzero_explicit(addr, size);
#ifdef CONFIG_X86
clflush_cache_range(addr, size);
#endif
}
static int efi_secret_unlink(struct inode *dir, struct dentry *dentry)
{
struct inode *inode = d_inode(dentry);
struct secret_entry *e = (struct secret_entry *)inode->i_private;
if (e) {
/* Zero out the secret data */
wipe_memory(e->data, secret_entry_data_len(e));
e->guid = NULL_GUID;
}
inode->i_private = NULL;
return simple_unlink(inode, dentry);
}
static const struct inode_operations efi_secret_dir_inode_operations = {
.lookup = simple_lookup,
.unlink = efi_secret_unlink,
};
static int efi_secret_map_area(struct platform_device *dev)
{
int ret;
struct efi_secret *s = efi_secret_get();
struct linux_efi_coco_secret_area *secret_area;
if (efi.coco_secret == EFI_INVALID_TABLE_ADDR) {
dev_err(&dev->dev, "Secret area address is not available\n");
return -EINVAL;
}
secret_area = memremap(efi.coco_secret, sizeof(*secret_area), MEMREMAP_WB);
if (secret_area == NULL) {
dev_err(&dev->dev, "Could not map secret area EFI config entry\n");
return -ENOMEM;
}
if (!secret_area->base_pa || secret_area->size < sizeof(struct secret_header)) {
dev_err(&dev->dev,
"Invalid secret area memory location (base_pa=0x%llx size=0x%llx)\n",
secret_area->base_pa, secret_area->size);
ret = -EINVAL;
goto unmap;
}
s->secret_data = ioremap_encrypted(secret_area->base_pa, secret_area->size);
if (s->secret_data == NULL) {
dev_err(&dev->dev, "Could not map secret area\n");
ret = -ENOMEM;
goto unmap;
}
s->secret_data_len = secret_area->size;
ret = 0;
unmap:
memunmap(secret_area);
return ret;
}
static void efi_secret_securityfs_teardown(struct platform_device *dev)
{
struct efi_secret *s = efi_secret_get();
securityfs_remove(s->secrets_dir);
s->secrets_dir = NULL;
dev_dbg(&dev->dev, "Removed securityfs entries\n");
}
static int efi_secret_securityfs_setup(struct platform_device *dev)
{
struct efi_secret *s = efi_secret_get();
int ret = 0, i = 0, bytes_left;
unsigned char *ptr;
struct secret_header *h;
struct secret_entry *e;
struct dentry *dent, *dir;
char guid_str[EFI_VARIABLE_GUID_LEN + 1];
ptr = (void __force *)s->secret_data;
h = (struct secret_header *)ptr;
if (efi_guidcmp(h->guid, EFI_SECRET_TABLE_HEADER_GUID)) {
/*
* This is not an error: it just means that EFI defines secret
* area but it was not populated by the Guest Owner.
*/
dev_dbg(&dev->dev, "EFI secret area does not start with correct GUID\n");
return -ENODEV;
}
if (h->len < sizeof(*h)) {
dev_err(&dev->dev, "EFI secret area reported length is too small\n");
return -EINVAL;
}
if (h->len > s->secret_data_len) {
dev_err(&dev->dev, "EFI secret area reported length is too big\n");
return -EINVAL;
}
s->secrets_dir = NULL;
dent = securityfs_create_dir("secrets", NULL);
if (IS_ERR(dent)) {
dev_err(&dev->dev, "Error creating secrets securityfs directory entry err=%ld\n",
PTR_ERR(dent));
return PTR_ERR(dent);
}
s->secrets_dir = dent;
dir = securityfs_create_dir("coco", s->secrets_dir);
if (IS_ERR(dir)) {
dev_err(&dev->dev, "Error creating coco securityfs directory entry err=%ld\n",
PTR_ERR(dir));
return PTR_ERR(dir);
}
d_inode(dir)->i_op = &efi_secret_dir_inode_operations;
bytes_left = h->len - sizeof(*h);
ptr += sizeof(*h);
while (bytes_left >= (int)sizeof(*e) && i < EFI_SECRET_NUM_FILES) {
e = (struct secret_entry *)ptr;
if (e->len < sizeof(*e) || e->len > (unsigned int)bytes_left) {
dev_err(&dev->dev, "EFI secret area is corrupted\n");
ret = -EINVAL;
goto err_cleanup;
}
/* Skip deleted entries (which will have NULL_GUID) */
if (efi_guidcmp(e->guid, NULL_GUID)) {
efi_guid_to_str(&e->guid, guid_str);
dent = securityfs_create_file(guid_str, 0440, dir, (void *)e,
&efi_secret_bin_file_fops);
if (IS_ERR(dent)) {
dev_err(&dev->dev, "Error creating efi_secret securityfs entry\n");
ret = PTR_ERR(dent);
goto err_cleanup;
}
i++;
}
ptr += e->len;
bytes_left -= e->len;
}
dev_info(&dev->dev, "Created %d entries in securityfs secrets/coco\n", i);
return 0;
err_cleanup:
efi_secret_securityfs_teardown(dev);
return ret;
}
static void efi_secret_unmap_area(void)
{
struct efi_secret *s = efi_secret_get();
if (s->secret_data) {
iounmap(s->secret_data);
s->secret_data = NULL;
s->secret_data_len = 0;
}
}
static int efi_secret_probe(struct platform_device *dev)
{
int ret;
ret = efi_secret_map_area(dev);
if (ret)
return ret;
ret = efi_secret_securityfs_setup(dev);
if (ret)
goto err_unmap;
return ret;
err_unmap:
efi_secret_unmap_area();
return ret;
}
static void efi_secret_remove(struct platform_device *dev)
{
efi_secret_securityfs_teardown(dev);
efi_secret_unmap_area();
}
static struct platform_driver efi_secret_driver = {
.probe = efi_secret_probe,
.remove = efi_secret_remove,
.driver = {
.name = "efi_secret",
},
};
module_platform_driver(efi_secret_driver);
MODULE_DESCRIPTION("Confidential computing EFI secret area access");
MODULE_AUTHOR("IBM");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:efi_secret");
@@ -0,0 +1,16 @@
# SPDX-License-Identifier: GPL-2.0-only
#
# Confidential computing shared guest collateral
#
config TSM_GUEST
bool
config TSM_REPORTS
select TSM_GUEST
select CONFIGFS_FS
tristate
config TSM_MEASUREMENTS
select TSM_GUEST
select CRYPTO_HASH_INFO
bool
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TSM_REPORTS) += tsm_report.o
tsm_report-y := report.o
obj-$(CONFIG_TSM_MEASUREMENTS) += tsm-mr.o
@@ -0,0 +1,539 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2023 Intel Corporation. All rights reserved. */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/tsm.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <linux/rwsem.h>
#include <linux/string.h>
#include <linux/module.h>
#include <linux/cleanup.h>
#include <linux/configfs.h>
static struct tsm_provider {
const struct tsm_report_ops *ops;
void *data;
atomic_t count;
} provider;
static DECLARE_RWSEM(tsm_rwsem);
/**
* DOC: Trusted Security Module (TSM) Attestation Report Interface
*
* The TSM report interface is a common provider of blobs that facilitate
* attestation of a TVM (confidential computing guest) by an attestation
* service. A TSM report combines a user-defined blob (likely a public-key with
* a nonce for a key-exchange protocol) with a signed attestation report. That
* combined blob is then used to obtain secrets provided by an agent that can
* validate the attestation report. The expectation is that this interface is
* invoked infrequently, however configfs allows for multiple agents to
* own their own report generation instances to generate reports as
* often as needed.
*
* The attestation report format is TSM provider specific, when / if a standard
* materializes that can be published instead of the vendor layout. Until then
* the 'provider' attribute indicates the format of 'outblob', and optionally
* 'auxblob' and 'manifestblob'.
*/
struct tsm_report_state {
struct tsm_report report;
unsigned long write_generation;
unsigned long read_generation;
struct config_item cfg;
};
enum tsm_data_select {
TSM_REPORT,
TSM_CERTS,
TSM_MANIFEST,
};
static struct tsm_report *to_tsm_report(struct config_item *cfg)
{
struct tsm_report_state *state =
container_of(cfg, struct tsm_report_state, cfg);
return &state->report;
}
static struct tsm_report_state *to_state(struct tsm_report *report)
{
return container_of(report, struct tsm_report_state, report);
}
static int try_advance_write_generation(struct tsm_report *report)
{
struct tsm_report_state *state = to_state(report);
lockdep_assert_held_write(&tsm_rwsem);
/*
* Malicious or broken userspace has written enough times for
* read_generation == write_generation by modular arithmetic without an
* interim read. Stop accepting updates until the current report
* configuration is read.
*/
if (state->write_generation == state->read_generation - 1)
return -EBUSY;
state->write_generation++;
return 0;
}
static ssize_t tsm_report_privlevel_store(struct config_item *cfg,
const char *buf, size_t len)
{
struct tsm_report *report = to_tsm_report(cfg);
unsigned int val;
int rc;
rc = kstrtouint(buf, 0, &val);
if (rc)
return rc;
guard(rwsem_write)(&tsm_rwsem);
if (!provider.ops)
return -ENXIO;
/*
* The valid privilege levels that a TSM might accept, if it accepts a
* privilege level setting at all, are a max of TSM_PRIVLEVEL_MAX (see
* SEV-SNP GHCB) and a minimum of a TSM selected floor value no less
* than 0.
*/
if (provider.ops->privlevel_floor > val || val > TSM_REPORT_PRIVLEVEL_MAX)
return -EINVAL;
rc = try_advance_write_generation(report);
if (rc)
return rc;
report->desc.privlevel = val;
return len;
}
CONFIGFS_ATTR_WO(tsm_report_, privlevel);
static ssize_t tsm_report_privlevel_floor_show(struct config_item *cfg,
char *buf)
{
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return -ENXIO;
return sysfs_emit(buf, "%u\n", provider.ops->privlevel_floor);
}
CONFIGFS_ATTR_RO(tsm_report_, privlevel_floor);
static ssize_t tsm_report_service_provider_store(struct config_item *cfg,
const char *buf, size_t len)
{
struct tsm_report *report = to_tsm_report(cfg);
size_t sp_len;
char *sp;
int rc;
guard(rwsem_write)(&tsm_rwsem);
rc = try_advance_write_generation(report);
if (rc)
return rc;
sp_len = (buf[len - 1] != '\n') ? len : len - 1;
sp = kstrndup(buf, sp_len, GFP_KERNEL);
if (!sp)
return -ENOMEM;
kfree(report->desc.service_provider);
report->desc.service_provider = sp;
return len;
}
CONFIGFS_ATTR_WO(tsm_report_, service_provider);
static ssize_t tsm_report_service_guid_store(struct config_item *cfg,
const char *buf, size_t len)
{
struct tsm_report *report = to_tsm_report(cfg);
int rc;
guard(rwsem_write)(&tsm_rwsem);
rc = try_advance_write_generation(report);
if (rc)
return rc;
report->desc.service_guid = guid_null;
rc = guid_parse(buf, &report->desc.service_guid);
if (rc)
return rc;
return len;
}
CONFIGFS_ATTR_WO(tsm_report_, service_guid);
static ssize_t tsm_report_service_manifest_version_store(struct config_item *cfg,
const char *buf, size_t len)
{
struct tsm_report *report = to_tsm_report(cfg);
unsigned int val;
int rc;
rc = kstrtouint(buf, 0, &val);
if (rc)
return rc;
guard(rwsem_write)(&tsm_rwsem);
rc = try_advance_write_generation(report);
if (rc)
return rc;
report->desc.service_manifest_version = val;
return len;
}
CONFIGFS_ATTR_WO(tsm_report_, service_manifest_version);
static ssize_t tsm_report_inblob_write(struct config_item *cfg,
const void *buf, size_t count)
{
struct tsm_report *report = to_tsm_report(cfg);
int rc;
guard(rwsem_write)(&tsm_rwsem);
rc = try_advance_write_generation(report);
if (rc)
return rc;
report->desc.inblob_len = count;
memcpy(report->desc.inblob, buf, count);
return count;
}
CONFIGFS_BIN_ATTR_WO(tsm_report_, inblob, NULL, TSM_REPORT_INBLOB_MAX);
static ssize_t tsm_report_generation_show(struct config_item *cfg, char *buf)
{
struct tsm_report *report = to_tsm_report(cfg);
struct tsm_report_state *state = to_state(report);
guard(rwsem_read)(&tsm_rwsem);
return sysfs_emit(buf, "%lu\n", state->write_generation);
}
CONFIGFS_ATTR_RO(tsm_report_, generation);
static ssize_t tsm_report_provider_show(struct config_item *cfg, char *buf)
{
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return -ENXIO;
return sysfs_emit(buf, "%s\n", provider.ops->name);
}
CONFIGFS_ATTR_RO(tsm_report_, provider);
static ssize_t __read_report(struct tsm_report *report, void *buf, size_t count,
enum tsm_data_select select)
{
loff_t offset = 0;
ssize_t len;
u8 *out;
if (select == TSM_REPORT) {
out = report->outblob;
len = report->outblob_len;
} else if (select == TSM_MANIFEST) {
out = report->manifestblob;
len = report->manifestblob_len;
} else {
out = report->auxblob;
len = report->auxblob_len;
}
/*
* Recall that a NULL @buf is configfs requesting the size of
* the buffer.
*/
if (!buf)
return len;
return memory_read_from_buffer(buf, count, &offset, out, len);
}
static ssize_t read_cached_report(struct tsm_report *report, void *buf,
size_t count, enum tsm_data_select select)
{
struct tsm_report_state *state = to_state(report);
guard(rwsem_read)(&tsm_rwsem);
if (!report->desc.inblob_len)
return -EINVAL;
/*
* A given TSM backend always fills in ->outblob regardless of
* whether the report includes an auxblob/manifestblob or not.
*/
if (!report->outblob ||
state->read_generation != state->write_generation)
return -EWOULDBLOCK;
return __read_report(report, buf, count, select);
}
static ssize_t tsm_report_read(struct tsm_report *report, void *buf,
size_t count, enum tsm_data_select select)
{
struct tsm_report_state *state = to_state(report);
const struct tsm_report_ops *ops;
ssize_t rc;
/* try to read from the existing report if present and valid... */
rc = read_cached_report(report, buf, count, select);
if (rc >= 0 || rc != -EWOULDBLOCK)
return rc;
/* slow path, report may need to be regenerated... */
guard(rwsem_write)(&tsm_rwsem);
ops = provider.ops;
if (!ops)
return -ENXIO;
if (!report->desc.inblob_len)
return -EINVAL;
/* did another thread already generate this report? */
if (report->outblob &&
state->read_generation == state->write_generation)
goto out;
kvfree(report->outblob);
kvfree(report->auxblob);
kvfree(report->manifestblob);
report->outblob = NULL;
report->auxblob = NULL;
report->manifestblob = NULL;
rc = ops->report_new(report, provider.data);
if (rc < 0)
return rc;
state->read_generation = state->write_generation;
out:
return __read_report(report, buf, count, select);
}
static ssize_t tsm_report_outblob_read(struct config_item *cfg, void *buf,
size_t count)
{
struct tsm_report *report = to_tsm_report(cfg);
return tsm_report_read(report, buf, count, TSM_REPORT);
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, outblob, NULL, TSM_REPORT_OUTBLOB_MAX);
static ssize_t tsm_report_auxblob_read(struct config_item *cfg, void *buf,
size_t count)
{
struct tsm_report *report = to_tsm_report(cfg);
return tsm_report_read(report, buf, count, TSM_CERTS);
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, auxblob, NULL, TSM_REPORT_OUTBLOB_MAX);
static ssize_t tsm_report_manifestblob_read(struct config_item *cfg, void *buf,
size_t count)
{
struct tsm_report *report = to_tsm_report(cfg);
return tsm_report_read(report, buf, count, TSM_MANIFEST);
}
CONFIGFS_BIN_ATTR_RO(tsm_report_, manifestblob, NULL, TSM_REPORT_OUTBLOB_MAX);
static struct configfs_attribute *tsm_report_attrs[] = {
[TSM_REPORT_GENERATION] = &tsm_report_attr_generation,
[TSM_REPORT_PROVIDER] = &tsm_report_attr_provider,
[TSM_REPORT_PRIVLEVEL] = &tsm_report_attr_privlevel,
[TSM_REPORT_PRIVLEVEL_FLOOR] = &tsm_report_attr_privlevel_floor,
[TSM_REPORT_SERVICE_PROVIDER] = &tsm_report_attr_service_provider,
[TSM_REPORT_SERVICE_GUID] = &tsm_report_attr_service_guid,
[TSM_REPORT_SERVICE_MANIFEST_VER] = &tsm_report_attr_service_manifest_version,
NULL,
};
static struct configfs_bin_attribute *tsm_report_bin_attrs[] = {
[TSM_REPORT_INBLOB] = &tsm_report_attr_inblob,
[TSM_REPORT_OUTBLOB] = &tsm_report_attr_outblob,
[TSM_REPORT_AUXBLOB] = &tsm_report_attr_auxblob,
[TSM_REPORT_MANIFESTBLOB] = &tsm_report_attr_manifestblob,
NULL,
};
static void tsm_report_item_release(struct config_item *cfg)
{
struct tsm_report *report = to_tsm_report(cfg);
struct tsm_report_state *state = to_state(report);
kvfree(report->manifestblob);
kvfree(report->auxblob);
kvfree(report->outblob);
kfree(report->desc.service_provider);
kfree(state);
}
static struct configfs_item_operations tsm_report_item_ops = {
.release = tsm_report_item_release,
};
static bool tsm_report_is_visible(struct config_item *item,
struct configfs_attribute *attr, int n)
{
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return false;
if (!provider.ops->report_attr_visible)
return true;
return provider.ops->report_attr_visible(n);
}
static bool tsm_report_is_bin_visible(struct config_item *item,
struct configfs_bin_attribute *attr, int n)
{
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return false;
if (!provider.ops->report_bin_attr_visible)
return true;
return provider.ops->report_bin_attr_visible(n);
}
static struct configfs_group_operations tsm_report_attr_group_ops = {
.is_visible = tsm_report_is_visible,
.is_bin_visible = tsm_report_is_bin_visible,
};
static const struct config_item_type tsm_report_type = {
.ct_owner = THIS_MODULE,
.ct_bin_attrs = tsm_report_bin_attrs,
.ct_attrs = tsm_report_attrs,
.ct_item_ops = &tsm_report_item_ops,
.ct_group_ops = &tsm_report_attr_group_ops,
};
static struct config_item *tsm_report_make_item(struct config_group *group,
const char *name)
{
struct tsm_report_state *state;
guard(rwsem_read)(&tsm_rwsem);
if (!provider.ops)
return ERR_PTR(-ENXIO);
state = kzalloc_obj(*state);
if (!state)
return ERR_PTR(-ENOMEM);
atomic_inc(&provider.count);
config_item_init_type_name(&state->cfg, name, &tsm_report_type);
return &state->cfg;
}
static void tsm_report_drop_item(struct config_group *group, struct config_item *item)
{
config_item_put(item);
atomic_dec(&provider.count);
}
static struct configfs_group_operations tsm_report_group_ops = {
.make_item = tsm_report_make_item,
.drop_item = tsm_report_drop_item,
};
static const struct config_item_type tsm_reports_type = {
.ct_owner = THIS_MODULE,
.ct_group_ops = &tsm_report_group_ops,
};
static const struct config_item_type tsm_root_group_type = {
.ct_owner = THIS_MODULE,
};
static struct configfs_subsystem tsm_configfs = {
.su_group = {
.cg_item = {
.ci_namebuf = "tsm",
.ci_type = &tsm_root_group_type,
},
},
.su_mutex = __MUTEX_INITIALIZER(tsm_configfs.su_mutex),
};
int tsm_report_register(const struct tsm_report_ops *ops, void *priv)
{
const struct tsm_report_ops *conflict;
guard(rwsem_write)(&tsm_rwsem);
conflict = provider.ops;
if (conflict) {
pr_err("\"%s\" ops already registered\n", conflict->name);
return -EBUSY;
}
if (atomic_read(&provider.count)) {
pr_err("configfs/tsm/report not empty\n");
return -EBUSY;
}
provider.ops = ops;
provider.data = priv;
return 0;
}
EXPORT_SYMBOL_GPL(tsm_report_register);
int tsm_report_unregister(const struct tsm_report_ops *ops)
{
guard(rwsem_write)(&tsm_rwsem);
if (ops != provider.ops)
return -EBUSY;
if (atomic_read(&provider.count))
pr_warn("\"%s\" unregistered with items present in configfs/tsm/report\n",
provider.ops->name);
provider.ops = NULL;
provider.data = NULL;
return 0;
}
EXPORT_SYMBOL_GPL(tsm_report_unregister);
static struct config_group *tsm_report_group;
static int __init tsm_report_init(void)
{
struct config_group *root = &tsm_configfs.su_group;
struct config_group *tsm;
int rc;
config_group_init(root);
rc = configfs_register_subsystem(&tsm_configfs);
if (rc)
return rc;
tsm = configfs_register_default_group(root, "report",
&tsm_reports_type);
if (IS_ERR(tsm)) {
configfs_unregister_subsystem(&tsm_configfs);
return PTR_ERR(tsm);
}
tsm_report_group = tsm;
return 0;
}
module_init(tsm_report_init);
static void __exit tsm_report_exit(void)
{
configfs_unregister_default_group(tsm_report_group);
configfs_unregister_subsystem(&tsm_configfs);
}
module_exit(tsm_report_exit);
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Provide Trusted Security Module attestation reports via configfs");
@@ -0,0 +1,251 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/sysfs.h>
#define CREATE_TRACE_POINTS
#include <trace/events/tsm_mr.h>
/*
* struct tm_context - contains everything necessary to implement sysfs
* attributes for MRs.
* @rwsem: protects the MR cache from concurrent access.
* @agrp: contains all MR attributes created by tsm_mr_create_attribute_group().
* @tm: input to tsm_mr_create_attribute_group() containing MR definitions/ops.
* @in_sync: %true if MR cache is up-to-date.
* @mrs: array of &struct bin_attribute, one for each MR.
*
* This internal structure contains everything needed to implement
* tm_digest_read() and tm_digest_write().
*
* Given tm->refresh() is potentially expensive, tm_digest_read() caches MR
* values and calls tm->refresh() only when necessary. Only live MRs (i.e., with
* %TSM_MR_F_LIVE set) can trigger tm->refresh(), while others are assumed to
* retain their values from the last tm->write(). @in_sync tracks if there have
* been tm->write() calls since the last tm->refresh(). That is, tm->refresh()
* will be called only when a live MR is being read and the cache is stale
* (@in_sync is %false).
*
* tm_digest_write() sets @in_sync to %false and calls tm->write(), whose
* semantics is arch and MR specific. Most (if not all) writable MRs support the
* extension semantics (i.e., tm->write() extends the input buffer into the MR).
*/
struct tm_context {
struct rw_semaphore rwsem;
struct attribute_group agrp;
const struct tsm_measurements *tm;
bool in_sync;
struct bin_attribute mrs[];
};
static ssize_t tm_digest_read(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buffer,
loff_t off, size_t count)
{
struct tm_context *ctx;
const struct tsm_measurement_register *mr;
int rc;
ctx = attr->private;
rc = down_read_interruptible(&ctx->rwsem);
if (rc)
return rc;
mr = &ctx->tm->mrs[attr - ctx->mrs];
/*
* @ctx->in_sync indicates if the MR cache is stale. It is a global
* instead of a per-MR flag for simplicity, as most (if not all) archs
* allow reading all MRs in oneshot.
*
* ctx->refresh() is necessary only for LIVE MRs, while others retain
* their values from their respective last ctx->write().
*/
if ((mr->mr_flags & TSM_MR_F_LIVE) && !ctx->in_sync) {
up_read(&ctx->rwsem);
rc = down_write_killable(&ctx->rwsem);
if (rc)
return rc;
if (!ctx->in_sync) {
rc = ctx->tm->refresh(ctx->tm);
ctx->in_sync = !rc;
trace_tsm_mr_refresh(mr, rc);
}
downgrade_write(&ctx->rwsem);
}
memcpy(buffer, mr->mr_value + off, count);
trace_tsm_mr_read(mr);
up_read(&ctx->rwsem);
return rc ?: count;
}
static ssize_t tm_digest_write(struct file *filp, struct kobject *kobj,
const struct bin_attribute *attr, char *buffer,
loff_t off, size_t count)
{
struct tm_context *ctx;
const struct tsm_measurement_register *mr;
ssize_t rc;
/* partial writes are not supported */
if (off != 0 || count != attr->size)
return -EINVAL;
ctx = attr->private;
mr = &ctx->tm->mrs[attr - ctx->mrs];
rc = down_write_killable(&ctx->rwsem);
if (rc)
return rc;
rc = ctx->tm->write(ctx->tm, mr, buffer);
/* mark MR cache stale */
if (!rc) {
ctx->in_sync = false;
trace_tsm_mr_write(mr, buffer);
}
up_write(&ctx->rwsem);
return rc ?: count;
}
/**
* tsm_mr_create_attribute_group() - creates an attribute group for measurement
* registers (MRs)
* @tm: pointer to &struct tsm_measurements containing the MR definitions.
*
* This function creates attributes corresponding to the MR definitions
* provided by @tm->mrs.
*
* The created attributes will reference @tm and its members. The caller must
* not free @tm until after tsm_mr_free_attribute_group() is called.
*
* Context: Process context. May sleep due to memory allocation.
*
* Return:
* * On success, the pointer to a an attribute group is returned; otherwise
* * %-EINVAL - Invalid MR definitions.
* * %-ENOMEM - Out of memory.
*/
const struct attribute_group *
tsm_mr_create_attribute_group(const struct tsm_measurements *tm)
{
size_t nlen;
if (!tm || !tm->mrs)
return ERR_PTR(-EINVAL);
/* aggregated length of all MR names */
nlen = 0;
for (size_t i = 0; i < tm->nr_mrs; ++i) {
if ((tm->mrs[i].mr_flags & TSM_MR_F_LIVE) && !tm->refresh)
return ERR_PTR(-EINVAL);
if ((tm->mrs[i].mr_flags & TSM_MR_F_WRITABLE) && !tm->write)
return ERR_PTR(-EINVAL);
if (!tm->mrs[i].mr_name)
return ERR_PTR(-EINVAL);
if (tm->mrs[i].mr_flags & TSM_MR_F_NOHASH)
continue;
if (tm->mrs[i].mr_hash >= HASH_ALGO__LAST)
return ERR_PTR(-EINVAL);
/* MR sysfs attribute names have the form of MRNAME:HASH */
nlen += strlen(tm->mrs[i].mr_name) + 1 +
strlen(hash_algo_name[tm->mrs[i].mr_hash]) + 1;
}
/*
* @attrs and the MR name strings are combined into a single allocation
* so that we don't have to free MR names one-by-one in
* tsm_mr_free_attribute_group()
*/
const struct bin_attribute **attrs __free(kfree) =
kzalloc(sizeof(*attrs) * (tm->nr_mrs + 1) + nlen, GFP_KERNEL);
struct tm_context *ctx __free(kfree) =
kzalloc_flex(*ctx, mrs, tm->nr_mrs);
char *name, *end;
if (!ctx || !attrs)
return ERR_PTR(-ENOMEM);
/* @attrs is followed immediately by MR name strings */
name = (char *)&attrs[tm->nr_mrs + 1];
end = name + nlen;
for (size_t i = 0; i < tm->nr_mrs; ++i) {
struct bin_attribute *bap = &ctx->mrs[i];
sysfs_bin_attr_init(bap);
if (tm->mrs[i].mr_flags & TSM_MR_F_NOHASH)
bap->attr.name = tm->mrs[i].mr_name;
else if (name < end) {
bap->attr.name = name;
name += snprintf(name, end - name, "%s:%s",
tm->mrs[i].mr_name,
hash_algo_name[tm->mrs[i].mr_hash]);
++name;
} else
return ERR_PTR(-EINVAL);
/* check for duplicated MR definitions */
for (size_t j = 0; j < i; ++j)
if (!strcmp(bap->attr.name, attrs[j]->attr.name))
return ERR_PTR(-EINVAL);
if (tm->mrs[i].mr_flags & TSM_MR_F_READABLE) {
bap->attr.mode |= 0444;
bap->read = tm_digest_read;
}
if (tm->mrs[i].mr_flags & TSM_MR_F_WRITABLE) {
bap->attr.mode |= 0200;
bap->write = tm_digest_write;
}
bap->size = tm->mrs[i].mr_size;
bap->private = ctx;
attrs[i] = bap;
}
if (name != end)
return ERR_PTR(-EINVAL);
init_rwsem(&ctx->rwsem);
ctx->agrp.name = "measurements";
ctx->agrp.bin_attrs = no_free_ptr(attrs);
ctx->tm = tm;
return &no_free_ptr(ctx)->agrp;
}
EXPORT_SYMBOL_GPL(tsm_mr_create_attribute_group);
/**
* tsm_mr_free_attribute_group() - frees the attribute group returned by
* tsm_mr_create_attribute_group()
* @attr_grp: attribute group returned by tsm_mr_create_attribute_group()
*
* Context: Process context.
*/
void tsm_mr_free_attribute_group(const struct attribute_group *attr_grp)
{
if (!IS_ERR_OR_NULL(attr_grp)) {
kfree(attr_grp->bin_attrs);
kfree(container_of(attr_grp, struct tm_context, agrp));
}
}
EXPORT_SYMBOL_GPL(tsm_mr_free_attribute_group);
@@ -0,0 +1,10 @@
config ARM_PKVM_GUEST
bool "Arm pKVM protected guest driver"
depends on ARM64 && DMA_RESTRICTED_POOL
help
Protected guests running under the pKVM hypervisor on arm64
are isolated from the host and must issue hypercalls to enable
interaction with virtual devices. This driver implements
support for probing and issuing these hypercalls.
If unsure, say 'N'.
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_ARM_PKVM_GUEST) += arm-pkvm-guest.o
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* Support for the hypercall interface exposed to protected guests by
* pKVM.
*
* Author: Will Deacon <will@kernel.org>
* Copyright (C) 2024 Google LLC
*/
#include <linux/arm-smccc.h>
#include <linux/array_size.h>
#include <linux/io.h>
#include <linux/mem_encrypt.h>
#include <linux/mm.h>
#include <linux/pgtable.h>
#include <asm/hypervisor.h>
static size_t pkvm_granule;
static int arm_smccc_do_one_page(u32 func_id, phys_addr_t phys)
{
phys_addr_t end = phys + PAGE_SIZE;
while (phys < end) {
struct arm_smccc_res res;
arm_smccc_1_1_invoke(func_id, phys, 0, 0, &res);
if (res.a0 != SMCCC_RET_SUCCESS)
return -EPERM;
phys += pkvm_granule;
}
return 0;
}
static int __set_memory_range(u32 func_id, unsigned long start, int numpages)
{
void *addr = (void *)start, *end = addr + numpages * PAGE_SIZE;
while (addr < end) {
int err;
err = arm_smccc_do_one_page(func_id, virt_to_phys(addr));
if (err)
return err;
addr += PAGE_SIZE;
}
return 0;
}
static int pkvm_set_memory_encrypted(unsigned long addr, int numpages)
{
return __set_memory_range(ARM_SMCCC_VENDOR_HYP_KVM_MEM_UNSHARE_FUNC_ID,
addr, numpages);
}
static int pkvm_set_memory_decrypted(unsigned long addr, int numpages)
{
return __set_memory_range(ARM_SMCCC_VENDOR_HYP_KVM_MEM_SHARE_FUNC_ID,
addr, numpages);
}
static const struct arm64_mem_crypt_ops pkvm_crypt_ops = {
.encrypt = pkvm_set_memory_encrypted,
.decrypt = pkvm_set_memory_decrypted,
};
static int mmio_guard_ioremap_hook(phys_addr_t phys, size_t size,
pgprot_t *prot)
{
phys_addr_t end;
pteval_t protval = pgprot_val(*prot);
/*
* We only expect MMIO emulation for regions mapped with device
* attributes.
*/
if (protval != PROT_DEVICE_nGnRE && protval != PROT_DEVICE_nGnRnE)
return 0;
end = PAGE_ALIGN(phys + size);
phys = PAGE_ALIGN_DOWN(phys);
while (phys < end) {
const int func_id = ARM_SMCCC_VENDOR_HYP_KVM_MMIO_GUARD_FUNC_ID;
WARN_ON_ONCE(arm_smccc_do_one_page(func_id, phys));
phys += PAGE_SIZE;
}
return 0;
}
void pkvm_init_hyp_services(void)
{
int i;
struct arm_smccc_res res;
const u32 funcs[] = {
ARM_SMCCC_KVM_FUNC_HYP_MEMINFO,
ARM_SMCCC_KVM_FUNC_MEM_SHARE,
ARM_SMCCC_KVM_FUNC_MEM_UNSHARE,
};
for (i = 0; i < ARRAY_SIZE(funcs); ++i) {
if (!kvm_arm_hyp_service_available(funcs[i]))
return;
}
arm_smccc_1_1_invoke(ARM_SMCCC_VENDOR_HYP_KVM_HYP_MEMINFO_FUNC_ID,
0, 0, 0, &res);
if (res.a0 > PAGE_SIZE) /* Includes error codes */
return;
pkvm_granule = res.a0;
arm64_mem_crypt_ops_register(&pkvm_crypt_ops);
if (kvm_arm_hyp_service_available(ARM_SMCCC_KVM_FUNC_MMIO_GUARD))
arm64_ioremap_prot_hook_register(&mmio_guard_ioremap_hook);
}
@@ -0,0 +1,13 @@
config SEV_GUEST
tristate "AMD SEV Guest driver"
default m
depends on AMD_MEM_ENCRYPT
select TSM_REPORTS
help
SEV-SNP firmware provides the guest a mechanism to communicate with
the PSP without risk from a malicious hypervisor who wishes to read,
alter, drop or replay the messages sent. The driver provides
userspace interface to communicate with the PSP to request the
attestation report and more.
If you choose 'M' here, this module will be called sev-guest.
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0-only
obj-$(CONFIG_SEV_GUEST) += sev-guest.o
@@ -0,0 +1,717 @@
// SPDX-License-Identifier: GPL-2.0-only
/*
* AMD Secure Encrypted Virtualization (SEV) guest driver interface
*
* Copyright (C) 2021-2024 Advanced Micro Devices, Inc.
*
* Author: Brijesh Singh <brijesh.singh@amd.com>
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/mutex.h>
#include <linux/io.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/set_memory.h>
#include <linux/fs.h>
#include <linux/tsm.h>
#include <crypto/gcm.h>
#include <linux/psp-sev.h>
#include <linux/sockptr.h>
#include <linux/cleanup.h>
#include <linux/uuid.h>
#include <linux/configfs.h>
#include <linux/mm.h>
#include <uapi/linux/sev-guest.h>
#include <uapi/linux/psp-sev.h>
#include <asm/svm.h>
#include <asm/sev.h>
#define DEVICE_NAME "sev-guest"
#define SVSM_MAX_RETRIES 3
struct snp_guest_dev {
struct device *dev;
struct miscdevice misc;
struct snp_msg_desc *msg_desc;
};
/*
* The VMPCK ID represents the key used by the SNP guest to communicate with the
* SEV firmware in the AMD Secure Processor (ASP, aka PSP). By default, the key
* used will be the key associated with the VMPL at which the guest is running.
* Should the default key be wiped (see snp_disable_vmpck()), this parameter
* allows for using one of the remaining VMPCKs.
*/
static int vmpck_id = -1;
module_param(vmpck_id, int, 0444);
MODULE_PARM_DESC(vmpck_id, "The VMPCK ID to use when communicating with the PSP.");
static inline struct snp_guest_dev *to_snp_dev(struct file *file)
{
struct miscdevice *dev = file->private_data;
return container_of(dev, struct snp_guest_dev, misc);
}
struct snp_req_resp {
sockptr_t req_data;
sockptr_t resp_data;
};
static int get_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
{
struct snp_report_req *report_req __free(kfree) = NULL;
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
struct snp_report_resp *report_resp;
struct snp_guest_req req = {};
int rc, resp_len;
if (!arg->req_data || !arg->resp_data)
return -EINVAL;
report_req = kzalloc_obj(*report_req, GFP_KERNEL_ACCOUNT);
if (!report_req)
return -ENOMEM;
if (copy_from_user(report_req, (void __user *)arg->req_data, sizeof(*report_req)))
return -EFAULT;
/*
* The intermediate response buffer is used while decrypting the
* response payload. Make sure that it has enough space to cover the
* authtag.
*/
resp_len = sizeof(report_resp->data) + mdesc->ctx->authsize;
report_resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
if (!report_resp)
return -ENOMEM;
req.msg_version = arg->msg_version;
req.msg_type = SNP_MSG_REPORT_REQ;
req.vmpck_id = mdesc->vmpck_id;
req.req_buf = report_req;
req.req_sz = sizeof(*report_req);
req.resp_buf = report_resp->data;
req.resp_sz = resp_len;
req.exit_code = SVM_VMGEXIT_GUEST_REQUEST;
rc = snp_send_guest_request(mdesc, &req);
arg->exitinfo2 = req.exitinfo2;
if (rc)
goto e_free;
if (copy_to_user((void __user *)arg->resp_data, report_resp, sizeof(*report_resp)))
rc = -EFAULT;
e_free:
kfree(report_resp);
return rc;
}
static int get_derived_key(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg)
{
struct snp_derived_key_resp *derived_key_resp __free(kfree) = NULL;
struct snp_derived_key_req *derived_key_req __free(kfree) = NULL;
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
struct snp_guest_req req = {};
int rc, resp_len;
if (!arg->req_data || !arg->resp_data)
return -EINVAL;
/*
* The intermediate response buffer is used while decrypting the
* response payload. Make sure that it has enough space to cover the
* authtag.
*/
resp_len = sizeof(derived_key_resp->data) + mdesc->ctx->authsize;
derived_key_resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
if (!derived_key_resp)
return -ENOMEM;
derived_key_req = kzalloc_obj(*derived_key_req, GFP_KERNEL_ACCOUNT);
if (!derived_key_req)
return -ENOMEM;
if (copy_from_user(derived_key_req, (void __user *)arg->req_data,
sizeof(*derived_key_req)))
return -EFAULT;
req.msg_version = arg->msg_version;
req.msg_type = SNP_MSG_KEY_REQ;
req.vmpck_id = mdesc->vmpck_id;
req.req_buf = derived_key_req;
req.req_sz = sizeof(*derived_key_req);
req.resp_buf = derived_key_resp;
req.resp_sz = resp_len;
req.exit_code = SVM_VMGEXIT_GUEST_REQUEST;
rc = snp_send_guest_request(mdesc, &req);
arg->exitinfo2 = req.exitinfo2;
if (!rc) {
if (copy_to_user((void __user *)arg->resp_data, derived_key_resp,
sizeof(derived_key_resp->data)))
rc = -EFAULT;
}
/* The response buffer contains the sensitive data, explicitly clear it. */
memzero_explicit(derived_key_resp, sizeof(*derived_key_resp));
return rc;
}
static int get_ext_report(struct snp_guest_dev *snp_dev, struct snp_guest_request_ioctl *arg,
struct snp_req_resp *io)
{
struct snp_ext_report_req *report_req __free(kfree) = NULL;
struct snp_msg_desc *mdesc = snp_dev->msg_desc;
struct snp_report_resp *report_resp;
struct snp_guest_req req = {};
int ret, npages = 0, resp_len;
sockptr_t certs_address;
u64 pfn;
if (sockptr_is_null(io->req_data) || sockptr_is_null(io->resp_data))
return -EINVAL;
report_req = kzalloc_obj(*report_req, GFP_KERNEL_ACCOUNT);
if (!report_req)
return -ENOMEM;
if (copy_from_sockptr(report_req, io->req_data, sizeof(*report_req)))
return -EFAULT;
/* caller does not want certificate data */
if (!report_req->certs_len || !report_req->certs_address)
goto cmd;
if (report_req->certs_len > SEV_FW_BLOB_MAX_SIZE ||
!IS_ALIGNED(report_req->certs_len, PAGE_SIZE))
return -EINVAL;
if (sockptr_is_kernel(io->resp_data)) {
certs_address = KERNEL_SOCKPTR((void *)report_req->certs_address);
} else {
certs_address = USER_SOCKPTR((void __user *)report_req->certs_address);
if (!access_ok(certs_address.user, report_req->certs_len))
return -EFAULT;
}
/*
* Initialize the intermediate buffer with all zeros. This buffer
* is used in the guest request message to get the certs blob from
* the host. If host does not supply any certs in it, then copy
* zeros to indicate that certificate data was not provided.
*/
npages = report_req->certs_len >> PAGE_SHIFT;
req.certs_data = alloc_pages_exact(npages << PAGE_SHIFT,
GFP_KERNEL_ACCOUNT | __GFP_ZERO);
if (!req.certs_data)
return -ENOMEM;
pfn = PHYS_PFN(virt_to_phys(req.certs_data));
ret = set_memory_decrypted((unsigned long)req.certs_data, npages);
if (ret) {
pr_err("failed to mark page shared, ret=%d\n", ret);
snp_leak_pages(pfn, npages);
return -EFAULT;
}
cmd:
/*
* The intermediate response buffer is used while decrypting the
* response payload. Make sure that it has enough space to cover the
* authtag.
*/
resp_len = sizeof(report_resp->data) + mdesc->ctx->authsize;
report_resp = kzalloc(resp_len, GFP_KERNEL_ACCOUNT);
if (!report_resp) {
ret = -ENOMEM;
goto e_free_data;
}
req.input.data_npages = npages;
req.msg_version = arg->msg_version;
req.msg_type = SNP_MSG_REPORT_REQ;
req.vmpck_id = mdesc->vmpck_id;
req.req_buf = &report_req->data;
req.req_sz = sizeof(report_req->data);
req.resp_buf = report_resp->data;
req.resp_sz = resp_len;
req.exit_code = SVM_VMGEXIT_EXT_GUEST_REQUEST;
ret = snp_send_guest_request(mdesc, &req);
arg->exitinfo2 = req.exitinfo2;
/* If certs length is invalid then copy the returned length */
if (arg->vmm_error == SNP_GUEST_VMM_ERR_INVALID_LEN) {
report_req->certs_len = req.input.data_npages << PAGE_SHIFT;
if (copy_to_sockptr(io->req_data, report_req, sizeof(*report_req)))
ret = -EFAULT;
}
if (ret)
goto e_free;
if (npages && copy_to_sockptr(certs_address, req.certs_data, report_req->certs_len)) {
ret = -EFAULT;
goto e_free;
}
if (copy_to_sockptr(io->resp_data, report_resp, sizeof(*report_resp)))
ret = -EFAULT;
e_free:
kfree(report_resp);
e_free_data:
if (npages) {
if (set_memory_encrypted((unsigned long)req.certs_data, npages)) {
WARN_ONCE(ret, "failed to restore encryption mask (leak it)\n");
snp_leak_pages(pfn, npages);
} else {
free_pages_exact(req.certs_data, npages << PAGE_SHIFT);
}
}
return ret;
}
static long snp_guest_ioctl(struct file *file, unsigned int ioctl, unsigned long arg)
{
struct snp_guest_dev *snp_dev = to_snp_dev(file);
void __user *argp = (void __user *)arg;
struct snp_guest_request_ioctl input;
struct snp_req_resp io;
int ret = -ENOTTY;
if (copy_from_user(&input, argp, sizeof(input)))
return -EFAULT;
input.exitinfo2 = 0xff;
/* Message version must be non-zero */
if (!input.msg_version)
return -EINVAL;
switch (ioctl) {
case SNP_GET_REPORT:
ret = get_report(snp_dev, &input);
break;
case SNP_GET_DERIVED_KEY:
ret = get_derived_key(snp_dev, &input);
break;
case SNP_GET_EXT_REPORT:
/*
* As get_ext_report() may be called from the ioctl() path and a
* kernel internal path (configfs-tsm), decorate the passed
* buffers as user pointers.
*/
io.req_data = USER_SOCKPTR((void __user *)input.req_data);
io.resp_data = USER_SOCKPTR((void __user *)input.resp_data);
ret = get_ext_report(snp_dev, &input, &io);
break;
default:
break;
}
if (input.exitinfo2 && copy_to_user(argp, &input, sizeof(input)))
return -EFAULT;
return ret;
}
static const struct file_operations snp_guest_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = snp_guest_ioctl,
};
struct snp_msg_report_resp_hdr {
u32 status;
u32 report_size;
u8 rsvd[24];
};
struct snp_msg_cert_entry {
guid_t guid;
u32 offset;
u32 length;
};
static int sev_svsm_report_new(struct tsm_report *report, void *data)
{
unsigned int rep_len, man_len, certs_len;
struct tsm_report_desc *desc = &report->desc;
struct svsm_attest_call ac = {};
unsigned int retry_count;
void *rep, *man, *certs;
struct svsm_call call;
unsigned int size;
bool try_again;
void *buffer;
u64 call_id;
int ret;
/*
* Allocate pages for the request:
* - Report blob (4K)
* - Manifest blob (4K)
* - Certificate blob (16K)
*
* Above addresses must be 4K aligned
*/
rep_len = SZ_4K;
man_len = SZ_4K;
certs_len = SEV_FW_BLOB_MAX_SIZE;
if (guid_is_null(&desc->service_guid)) {
call_id = SVSM_ATTEST_CALL(SVSM_ATTEST_SERVICES);
} else {
export_guid(ac.service_guid, &desc->service_guid);
ac.service_manifest_ver = desc->service_manifest_version;
call_id = SVSM_ATTEST_CALL(SVSM_ATTEST_SINGLE_SERVICE);
}
retry_count = 0;
retry:
memset(&call, 0, sizeof(call));
size = rep_len + man_len + certs_len;
buffer = alloc_pages_exact(size, __GFP_ZERO);
if (!buffer)
return -ENOMEM;
rep = buffer;
ac.report_buf.pa = __pa(rep);
ac.report_buf.len = rep_len;
man = rep + rep_len;
ac.manifest_buf.pa = __pa(man);
ac.manifest_buf.len = man_len;
certs = man + man_len;
ac.certificates_buf.pa = __pa(certs);
ac.certificates_buf.len = certs_len;
ac.nonce.pa = __pa(desc->inblob);
ac.nonce.len = desc->inblob_len;
ret = snp_issue_svsm_attest_req(call_id, &call, &ac);
if (ret) {
free_pages_exact(buffer, size);
switch (call.rax_out) {
case SVSM_ERR_INVALID_PARAMETER:
try_again = false;
if (ac.report_buf.len > rep_len) {
rep_len = PAGE_ALIGN(ac.report_buf.len);
try_again = true;
}
if (ac.manifest_buf.len > man_len) {
man_len = PAGE_ALIGN(ac.manifest_buf.len);
try_again = true;
}
if (ac.certificates_buf.len > certs_len) {
certs_len = PAGE_ALIGN(ac.certificates_buf.len);
try_again = true;
}
/* If one of the buffers wasn't large enough, retry the request */
if (try_again && retry_count < SVSM_MAX_RETRIES) {
retry_count++;
goto retry;
}
return -EINVAL;
default:
pr_err_ratelimited("SVSM attestation request failed (%d / 0x%llx)\n",
ret, call.rax_out);
return -EINVAL;
}
}
/*
* Allocate all the blob memory buffers at once so that the cleanup is
* done for errors that occur after the first allocation (i.e. before
* using no_free_ptr()).
*/
rep_len = ac.report_buf.len;
void *rbuf __free(kvfree) = kvzalloc(rep_len, GFP_KERNEL);
man_len = ac.manifest_buf.len;
void *mbuf __free(kvfree) = kvzalloc(man_len, GFP_KERNEL);
certs_len = ac.certificates_buf.len;
void *cbuf __free(kvfree) = certs_len ? kvzalloc(certs_len, GFP_KERNEL) : NULL;
if (!rbuf || !mbuf || (certs_len && !cbuf)) {
free_pages_exact(buffer, size);
return -ENOMEM;
}
memcpy(rbuf, rep, rep_len);
report->outblob = no_free_ptr(rbuf);
report->outblob_len = rep_len;
memcpy(mbuf, man, man_len);
report->manifestblob = no_free_ptr(mbuf);
report->manifestblob_len = man_len;
if (certs_len) {
memcpy(cbuf, certs, certs_len);
report->auxblob = no_free_ptr(cbuf);
report->auxblob_len = certs_len;
}
free_pages_exact(buffer, size);
return 0;
}
static int sev_report_new(struct tsm_report *report, void *data)
{
struct snp_msg_cert_entry *cert_table;
struct tsm_report_desc *desc = &report->desc;
struct snp_guest_dev *snp_dev = data;
struct snp_msg_report_resp_hdr hdr;
const u32 report_size = SZ_4K;
const u32 ext_size = SEV_FW_BLOB_MAX_SIZE;
u32 certs_size, i, size = report_size + ext_size;
int ret;
if (desc->inblob_len != SNP_REPORT_USER_DATA_SIZE)
return -EINVAL;
if (desc->service_provider) {
if (strcmp(desc->service_provider, "svsm"))
return -EINVAL;
return sev_svsm_report_new(report, data);
}
void *buf __free(kvfree) = kvzalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
cert_table = buf + report_size;
struct snp_ext_report_req ext_req = {
.data = { .vmpl = desc->privlevel },
.certs_address = (__u64)cert_table,
.certs_len = ext_size,
};
memcpy(&ext_req.data.user_data, desc->inblob, desc->inblob_len);
struct snp_guest_request_ioctl input = {
.msg_version = 1,
.req_data = (__u64)&ext_req,
.resp_data = (__u64)buf,
.exitinfo2 = 0xff,
};
struct snp_req_resp io = {
.req_data = KERNEL_SOCKPTR(&ext_req),
.resp_data = KERNEL_SOCKPTR(buf),
};
ret = get_ext_report(snp_dev, &input, &io);
if (ret)
return ret;
memcpy(&hdr, buf, sizeof(hdr));
if (hdr.status == SEV_RET_INVALID_PARAM)
return -EINVAL;
if (hdr.status == SEV_RET_INVALID_KEY)
return -EINVAL;
if (hdr.status)
return -ENXIO;
if ((hdr.report_size + sizeof(hdr)) > report_size)
return -ENOMEM;
void *rbuf __free(kvfree) = kvzalloc(hdr.report_size, GFP_KERNEL);
if (!rbuf)
return -ENOMEM;
memcpy(rbuf, buf + sizeof(hdr), hdr.report_size);
report->outblob = no_free_ptr(rbuf);
report->outblob_len = hdr.report_size;
certs_size = 0;
for (i = 0; i < ext_size / sizeof(struct snp_msg_cert_entry); i++) {
struct snp_msg_cert_entry *ent = &cert_table[i];
if (guid_is_null(&ent->guid) && !ent->offset && !ent->length)
break;
certs_size = max(certs_size, ent->offset + ent->length);
}
/* Suspicious that the response populated entries without populating size */
if (!certs_size && i)
dev_warn_ratelimited(snp_dev->dev, "certificate slots conveyed without size\n");
/* No certs to report */
if (!certs_size)
return 0;
/* Suspicious that the certificate blob size contract was violated
*/
if (certs_size > ext_size) {
dev_warn_ratelimited(snp_dev->dev, "certificate data truncated\n");
certs_size = ext_size;
}
void *cbuf __free(kvfree) = kvzalloc(certs_size, GFP_KERNEL);
if (!cbuf)
return -ENOMEM;
memcpy(cbuf, cert_table, certs_size);
report->auxblob = no_free_ptr(cbuf);
report->auxblob_len = certs_size;
return 0;
}
static bool sev_report_attr_visible(int n)
{
switch (n) {
case TSM_REPORT_GENERATION:
case TSM_REPORT_PROVIDER:
case TSM_REPORT_PRIVLEVEL:
case TSM_REPORT_PRIVLEVEL_FLOOR:
return true;
case TSM_REPORT_SERVICE_PROVIDER:
case TSM_REPORT_SERVICE_GUID:
case TSM_REPORT_SERVICE_MANIFEST_VER:
return snp_vmpl;
}
return false;
}
static bool sev_report_bin_attr_visible(int n)
{
switch (n) {
case TSM_REPORT_INBLOB:
case TSM_REPORT_OUTBLOB:
case TSM_REPORT_AUXBLOB:
return true;
case TSM_REPORT_MANIFESTBLOB:
return snp_vmpl;
}
return false;
}
static struct tsm_report_ops sev_tsm_report_ops = {
.name = KBUILD_MODNAME,
.report_new = sev_report_new,
.report_attr_visible = sev_report_attr_visible,
.report_bin_attr_visible = sev_report_bin_attr_visible,
};
static void unregister_sev_tsm(void *data)
{
tsm_report_unregister(&sev_tsm_report_ops);
}
static int __init sev_guest_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct snp_guest_dev *snp_dev;
struct snp_msg_desc *mdesc;
struct miscdevice *misc;
int ret;
BUILD_BUG_ON(sizeof(struct snp_guest_msg) > PAGE_SIZE);
if (!cc_platform_has(CC_ATTR_GUEST_SEV_SNP))
return -ENODEV;
snp_dev = devm_kzalloc(&pdev->dev, sizeof(struct snp_guest_dev), GFP_KERNEL);
if (!snp_dev)
return -ENOMEM;
mdesc = snp_msg_alloc();
if (IS_ERR_OR_NULL(mdesc))
return -ENOMEM;
ret = snp_msg_init(mdesc, vmpck_id);
if (ret)
goto e_msg_init;
platform_set_drvdata(pdev, snp_dev);
snp_dev->dev = dev;
misc = &snp_dev->misc;
misc->minor = MISC_DYNAMIC_MINOR;
misc->name = DEVICE_NAME;
misc->fops = &snp_guest_fops;
/* Set the privlevel_floor attribute based on the vmpck_id */
sev_tsm_report_ops.privlevel_floor = mdesc->vmpck_id;
ret = tsm_report_register(&sev_tsm_report_ops, snp_dev);
if (ret)
goto e_msg_init;
ret = devm_add_action_or_reset(&pdev->dev, unregister_sev_tsm, NULL);
if (ret)
goto e_msg_init;
ret = misc_register(misc);
if (ret)
goto e_msg_init;
snp_dev->msg_desc = mdesc;
dev_info(dev, "Initialized SEV guest driver (using VMPCK%d communication key)\n",
mdesc->vmpck_id);
return 0;
e_msg_init:
snp_msg_free(mdesc);
return ret;
}
static void __exit sev_guest_remove(struct platform_device *pdev)
{
struct snp_guest_dev *snp_dev = platform_get_drvdata(pdev);
snp_msg_free(snp_dev->msg_desc);
misc_deregister(&snp_dev->misc);
}
/*
* This driver is meant to be a common SEV guest interface driver and to
* support any SEV guest API. As such, even though it has been introduced
* with the SEV-SNP support, it is named "sev-guest".
*
* sev_guest_remove() lives in .exit.text. For drivers registered via
* module_platform_driver_probe() this is ok because they cannot get unbound
* at runtime. So mark the driver struct with __refdata to prevent modpost
* triggering a section mismatch warning.
*/
static struct platform_driver sev_guest_driver __refdata = {
.remove = __exit_p(sev_guest_remove),
.driver = {
.name = "sev-guest",
},
};
module_platform_driver_probe(sev_guest_driver, sev_guest_probe);
MODULE_AUTHOR("Brijesh Singh <brijesh.singh@amd.com>");
MODULE_LICENSE("GPL");
MODULE_VERSION("1.0.0");
MODULE_DESCRIPTION("AMD SEV Guest Driver");
MODULE_ALIAS("platform:sev-guest");
@@ -0,0 +1,12 @@
config TDX_GUEST_DRIVER
tristate "TDX Guest driver"
depends on INTEL_TDX_GUEST
select TSM_REPORTS
select TSM_MEASUREMENTS
help
The driver provides userspace interface to communicate with
the TDX module to request the TDX guest details like attestation
report.
To compile this driver as module, choose M here. The module will
be called tdx-guest.
@@ -0,0 +1,2 @@
# SPDX-License-Identifier: GPL-2.0
obj-$(CONFIG_TDX_GUEST_DRIVER) += tdx-guest.o
@@ -0,0 +1,455 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TDX guest user interface driver
*
* Copyright (C) 2022 Intel Corporation
*/
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include <linux/set_memory.h>
#include <linux/io.h>
#include <linux/delay.h>
#include <linux/sockptr.h>
#include <linux/tsm.h>
#include <linux/tsm-mr.h>
#include <uapi/linux/tdx-guest.h>
#include <asm/cpu_device_id.h>
#include <asm/tdx.h>
/* TDREPORT buffer */
static u8 *tdx_report_buf;
/* Lock to serialize TDG.MR.REPORT and TDG.MR.RTMR.EXTEND TDCALLs */
static DEFINE_MUTEX(mr_lock);
/* TDREPORT fields */
enum {
TDREPORT_reportdata = 128,
TDREPORT_tee_tcb_info = 256,
TDREPORT_tdinfo = TDREPORT_tee_tcb_info + 256,
TDREPORT_attributes = TDREPORT_tdinfo,
TDREPORT_xfam = TDREPORT_attributes + sizeof(u64),
TDREPORT_mrtd = TDREPORT_xfam + sizeof(u64),
TDREPORT_mrconfigid = TDREPORT_mrtd + SHA384_DIGEST_SIZE,
TDREPORT_mrowner = TDREPORT_mrconfigid + SHA384_DIGEST_SIZE,
TDREPORT_mrownerconfig = TDREPORT_mrowner + SHA384_DIGEST_SIZE,
TDREPORT_rtmr0 = TDREPORT_mrownerconfig + SHA384_DIGEST_SIZE,
TDREPORT_rtmr1 = TDREPORT_rtmr0 + SHA384_DIGEST_SIZE,
TDREPORT_rtmr2 = TDREPORT_rtmr1 + SHA384_DIGEST_SIZE,
TDREPORT_rtmr3 = TDREPORT_rtmr2 + SHA384_DIGEST_SIZE,
TDREPORT_servtd_hash = TDREPORT_rtmr3 + SHA384_DIGEST_SIZE,
};
static int tdx_do_report(sockptr_t data, sockptr_t tdreport)
{
scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
u8 *reportdata = tdx_report_buf + TDREPORT_reportdata;
int ret;
if (!sockptr_is_null(data) &&
copy_from_sockptr(reportdata, data, TDX_REPORTDATA_LEN))
return -EFAULT;
ret = tdx_mcall_get_report0(reportdata, tdx_report_buf);
if (WARN_ONCE(ret, "tdx_mcall_get_report0() failed: %d", ret))
return ret;
if (!sockptr_is_null(tdreport) &&
copy_to_sockptr(tdreport, tdx_report_buf, TDX_REPORT_LEN))
return -EFAULT;
}
return 0;
}
static int tdx_do_extend(u8 mr_ind, const u8 *data)
{
scoped_cond_guard(mutex_intr, return -EINTR, &mr_lock) {
/*
* TDX requires @extend_buf to be 64-byte aligned.
* It's safe to use REPORTDATA buffer for that purpose because
* tdx_mr_report/extend_lock() are mutually exclusive.
*/
u8 *extend_buf = tdx_report_buf + TDREPORT_reportdata;
int ret;
memcpy(extend_buf, data, SHA384_DIGEST_SIZE);
ret = tdx_mcall_extend_rtmr(mr_ind, extend_buf);
if (WARN_ONCE(ret, "tdx_mcall_extend_rtmr(%u) failed: %d", mr_ind, ret))
return ret;
}
return 0;
}
#define TDX_MR_(r) .mr_value = (void *)TDREPORT_##r, TSM_MR_(r, SHA384)
static struct tsm_measurement_register tdx_mrs[] = {
{ TDX_MR_(rtmr0) | TSM_MR_F_RTMR },
{ TDX_MR_(rtmr1) | TSM_MR_F_RTMR },
{ TDX_MR_(rtmr2) | TSM_MR_F_RTMR },
{ TDX_MR_(rtmr3) | TSM_MR_F_RTMR },
{ TDX_MR_(mrtd) },
{ TDX_MR_(mrconfigid) | TSM_MR_F_NOHASH },
{ TDX_MR_(mrowner) | TSM_MR_F_NOHASH },
{ TDX_MR_(mrownerconfig) | TSM_MR_F_NOHASH },
};
#undef TDX_MR_
static int tdx_mr_refresh(const struct tsm_measurements *tm)
{
return tdx_do_report(KERNEL_SOCKPTR(NULL), KERNEL_SOCKPTR(NULL));
}
static int tdx_mr_extend(const struct tsm_measurements *tm,
const struct tsm_measurement_register *mr,
const u8 *data)
{
return tdx_do_extend(mr - tm->mrs, data);
}
static struct tsm_measurements tdx_measurements = {
.mrs = tdx_mrs,
.nr_mrs = ARRAY_SIZE(tdx_mrs),
.refresh = tdx_mr_refresh,
.write = tdx_mr_extend,
};
static const struct attribute_group *tdx_mr_init(void)
{
const struct attribute_group *g;
int rc;
u8 *buf __free(kfree) = kzalloc(TDX_REPORT_LEN, GFP_KERNEL);
if (!buf)
return ERR_PTR(-ENOMEM);
tdx_report_buf = buf;
rc = tdx_mr_refresh(&tdx_measurements);
if (rc)
return ERR_PTR(rc);
/*
* @mr_value was initialized with the offset only, while the base
* address is being added here.
*/
for (size_t i = 0; i < ARRAY_SIZE(tdx_mrs); ++i)
*(long *)&tdx_mrs[i].mr_value += (long)buf;
g = tsm_mr_create_attribute_group(&tdx_measurements);
if (!IS_ERR(g))
tdx_report_buf = no_free_ptr(buf);
return g;
}
static void tdx_mr_deinit(const struct attribute_group *mr_grp)
{
tsm_mr_free_attribute_group(mr_grp);
kfree(tdx_report_buf);
}
/*
* Intel's SGX QE implementation generally uses Quote size less
* than 8K (2K Quote data + ~5K of certificate blob).
* DICE-based attestation uses layered evidence that requires
* larger Quote size (~100K).
*/
#define GET_QUOTE_BUF_SIZE SZ_128K
#define GET_QUOTE_CMD_VER 1
/* TDX GetQuote status codes */
#define GET_QUOTE_SUCCESS 0
#define GET_QUOTE_IN_FLIGHT 0xffffffffffffffff
#define TDX_QUOTE_MAX_LEN (GET_QUOTE_BUF_SIZE - sizeof(struct tdx_quote_buf))
/* struct tdx_quote_buf: Format of Quote request buffer.
* @version: Quote format version, filled by TD.
* @status: Status code of Quote request, filled by VMM.
* @in_len: Length of TDREPORT, filled by TD.
* @out_len: Length of Quote data, filled by VMM.
* @data: Quote data on output or TDREPORT on input.
*
* More details of Quote request buffer can be found in TDX
* Guest-Host Communication Interface (GHCI) for Intel TDX 1.0,
* section titled "TDG.VP.VMCALL<GetQuote>"
*/
struct tdx_quote_buf {
u64 version;
u64 status;
u32 in_len;
u32 out_len;
u8 data[];
};
/* Quote data buffer */
static void *quote_data;
/* Lock to streamline quote requests */
static DEFINE_MUTEX(quote_lock);
/*
* GetQuote request timeout in seconds. Expect that 30 seconds
* is enough time for QE to respond to any Quote requests.
*/
static u32 getquote_timeout = 30;
static long tdx_get_report0(struct tdx_report_req __user *req)
{
return tdx_do_report(USER_SOCKPTR(req->reportdata),
USER_SOCKPTR(req->tdreport));
}
static void free_quote_buf(void *buf)
{
size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE);
unsigned int count = len >> PAGE_SHIFT;
if (set_memory_encrypted((unsigned long)buf, count)) {
pr_err("Failed to restore encryption mask for Quote buffer, leak it\n");
return;
}
free_pages_exact(buf, len);
}
static void *alloc_quote_buf(void)
{
size_t len = PAGE_ALIGN(GET_QUOTE_BUF_SIZE);
unsigned int count = len >> PAGE_SHIFT;
void *addr;
addr = alloc_pages_exact(len, GFP_KERNEL | __GFP_ZERO);
if (!addr)
return NULL;
if (set_memory_decrypted((unsigned long)addr, count))
return NULL;
return addr;
}
/*
* wait_for_quote_completion() - Wait for Quote request completion
* @quote_buf: Address of Quote buffer.
* @timeout: Timeout in seconds to wait for the Quote generation.
*
* As per TDX GHCI v1.0 specification, sec titled "TDG.VP.VMCALL<GetQuote>",
* the status field in the Quote buffer will be set to GET_QUOTE_IN_FLIGHT
* while VMM processes the GetQuote request, and will change it to success
* or error code after processing is complete. So wait till the status
* changes from GET_QUOTE_IN_FLIGHT or the request being timed out.
*/
static int wait_for_quote_completion(struct tdx_quote_buf *quote_buf, u32 timeout)
{
int i = 0;
/*
* Quote requests usually take a few seconds to complete, so waking up
* once per second to recheck the status is fine for this use case.
*/
while (quote_buf->status == GET_QUOTE_IN_FLIGHT && i++ < timeout) {
if (msleep_interruptible(MSEC_PER_SEC))
return -EINTR;
}
return (i == timeout) ? -ETIMEDOUT : 0;
}
static int tdx_report_new_locked(struct tsm_report *report, void *data)
{
u8 *buf;
struct tdx_quote_buf *quote_buf = quote_data;
struct tsm_report_desc *desc = &report->desc;
u32 out_len;
int ret;
u64 err;
/*
* If the previous request is timedout or interrupted, and the
* Quote buf status is still in GET_QUOTE_IN_FLIGHT (owned by
* VMM), don't permit any new request.
*/
if (quote_buf->status == GET_QUOTE_IN_FLIGHT)
return -EBUSY;
if (desc->inblob_len != TDX_REPORTDATA_LEN)
return -EINVAL;
memset(quote_data, 0, GET_QUOTE_BUF_SIZE);
/* Update Quote buffer header */
quote_buf->version = GET_QUOTE_CMD_VER;
quote_buf->in_len = TDX_REPORT_LEN;
ret = tdx_do_report(KERNEL_SOCKPTR(desc->inblob),
KERNEL_SOCKPTR(quote_buf->data));
if (ret)
return ret;
err = tdx_hcall_get_quote(quote_data, GET_QUOTE_BUF_SIZE);
if (err) {
pr_err("GetQuote hypercall failed, status:%llx\n", err);
return -EIO;
}
ret = wait_for_quote_completion(quote_buf, getquote_timeout);
if (ret) {
pr_err("GetQuote request timedout\n");
return ret;
}
if (quote_buf->status != GET_QUOTE_SUCCESS) {
pr_debug("GetQuote request failed, status:%llx\n", quote_buf->status);
return -EIO;
}
out_len = READ_ONCE(quote_buf->out_len);
if (out_len > TDX_QUOTE_MAX_LEN)
return -EFBIG;
buf = kvmemdup(quote_buf->data, out_len, GFP_KERNEL);
if (!buf)
return -ENOMEM;
report->outblob = buf;
report->outblob_len = out_len;
/*
* TODO: parse the PEM-formatted cert chain out of the quote buffer when
* provided
*/
return ret;
}
static int tdx_report_new(struct tsm_report *report, void *data)
{
scoped_cond_guard(mutex_intr, return -EINTR, &quote_lock)
return tdx_report_new_locked(report, data);
}
static bool tdx_report_attr_visible(int n)
{
switch (n) {
case TSM_REPORT_GENERATION:
case TSM_REPORT_PROVIDER:
return true;
}
return false;
}
static bool tdx_report_bin_attr_visible(int n)
{
switch (n) {
case TSM_REPORT_INBLOB:
case TSM_REPORT_OUTBLOB:
return true;
}
return false;
}
static long tdx_guest_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
switch (cmd) {
case TDX_CMD_GET_REPORT0:
return tdx_get_report0((struct tdx_report_req __user *)arg);
default:
return -ENOTTY;
}
}
static const struct file_operations tdx_guest_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = tdx_guest_ioctl,
};
static const struct attribute_group *tdx_attr_groups[] = {
NULL, /* measurements */
NULL
};
static struct miscdevice tdx_misc_dev = {
.name = KBUILD_MODNAME,
.minor = MISC_DYNAMIC_MINOR,
.fops = &tdx_guest_fops,
.groups = tdx_attr_groups,
};
static const struct x86_cpu_id tdx_guest_ids[] = {
X86_MATCH_FEATURE(X86_FEATURE_TDX_GUEST, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, tdx_guest_ids);
static const struct tsm_report_ops tdx_tsm_ops = {
.name = KBUILD_MODNAME,
.report_new = tdx_report_new,
.report_attr_visible = tdx_report_attr_visible,
.report_bin_attr_visible = tdx_report_bin_attr_visible,
};
static int __init tdx_guest_init(void)
{
int ret;
if (!x86_match_cpu(tdx_guest_ids))
return -ENODEV;
tdx_attr_groups[0] = tdx_mr_init();
if (IS_ERR(tdx_attr_groups[0]))
return PTR_ERR(tdx_attr_groups[0]);
ret = misc_register(&tdx_misc_dev);
if (ret)
goto deinit_mr;
quote_data = alloc_quote_buf();
if (!quote_data) {
pr_err("Failed to allocate Quote buffer\n");
ret = -ENOMEM;
goto free_misc;
}
ret = tsm_report_register(&tdx_tsm_ops, NULL);
if (ret)
goto free_quote;
return 0;
free_quote:
free_quote_buf(quote_data);
free_misc:
misc_deregister(&tdx_misc_dev);
deinit_mr:
tdx_mr_deinit(tdx_attr_groups[0]);
return ret;
}
module_init(tdx_guest_init);
static void __exit tdx_guest_exit(void)
{
tsm_report_unregister(&tdx_tsm_ops);
free_quote_buf(quote_data);
misc_deregister(&tdx_misc_dev);
tdx_mr_deinit(tdx_attr_groups[0]);
}
module_exit(tdx_guest_exit);
MODULE_AUTHOR("Kuppuswamy Sathyanarayanan <sathyanarayanan.kuppuswamy@linux.intel.com>");
MODULE_DESCRIPTION("TDX Guest Driver");
MODULE_LICENSE("GPL");
@@ -0,0 +1,6 @@
config TDX_HOST_SERVICES
tristate
depends on INTEL_TDX_HOST
select FW_LOADER
select FW_UPLOAD
default m
@@ -0,0 +1 @@
obj-$(CONFIG_TDX_HOST_SERVICES) += tdx-host.o
@@ -0,0 +1,238 @@
// SPDX-License-Identifier: GPL-2.0
/*
* TDX host user interface driver
*
* Copyright (C) 2025 Intel Corporation
*/
#include <linux/device/faux.h>
#include <linux/firmware.h>
#include <linux/module.h>
#include <linux/sysfs.h>
#include <asm/cpu_device_id.h>
#include <asm/seamldr.h>
#include <asm/tdx.h>
static const struct x86_cpu_id tdx_host_ids[] = {
X86_MATCH_FEATURE(X86_FEATURE_TDX_HOST_PLATFORM, NULL),
{}
};
MODULE_DEVICE_TABLE(x86cpu, tdx_host_ids);
static ssize_t version_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
const struct tdx_sys_info *tdx_sysinfo = tdx_get_sysinfo();
const struct tdx_sys_info_version *ver;
int ret;
if (!tdx_sysinfo)
return -ENXIO;
/*
* The version number can change during an update.
* Lock out updates while printing the version.
*/
seamldr_lock_module_update();
ver = &tdx_sysinfo->version;
ret = sysfs_emit(buf, TDX_VERSION_FMT "\n", ver->major_version,
ver->minor_version,
ver->update_version);
seamldr_unlock_module_update();
return ret;
}
static DEVICE_ATTR_RO(version);
static struct attribute *tdx_host_attrs[] = {
&dev_attr_version.attr,
NULL,
};
static const struct attribute_group tdx_host_group = {
.attrs = tdx_host_attrs,
};
static ssize_t seamldr_version_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
struct seamldr_info info;
int ret;
ret = seamldr_get_info(&info);
if (ret)
return ret;
return sysfs_emit(buf, TDX_VERSION_FMT "\n", info.major_version,
info.minor_version,
info.update_version);
}
static ssize_t num_remaining_updates_show(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct seamldr_info info;
int ret;
ret = seamldr_get_info(&info);
if (ret)
return ret;
return sysfs_emit(buf, "%u\n", info.num_remaining_updates);
}
/*
* These attributes are intended for managing TDX module updates. Reading
* them issues a slow, serialized P-SEAMLDR query, so keep them admin-only.
*/
static DEVICE_ATTR_ADMIN_RO(seamldr_version);
static DEVICE_ATTR_ADMIN_RO(num_remaining_updates);
static struct attribute *seamldr_attrs[] = {
&dev_attr_seamldr_version.attr,
&dev_attr_num_remaining_updates.attr,
NULL,
};
static bool supports_runtime_update(void)
{
const struct tdx_sys_info *sysinfo = tdx_get_sysinfo();
if (!sysinfo)
return false;
if (!tdx_supports_runtime_update(sysinfo))
return false;
/*
* This bug makes P-SEAMLDR calls clobber the current VMCS
* which breaks KVM. Avoid P-SEAMLDR calls by hiding all
* attributes if the CPU has this bug.
*/
if (boot_cpu_has_bug(X86_BUG_SEAMRET_INVD_VMCS))
return false;
return true;
}
static umode_t seamldr_group_visible(struct kobject *kobj, struct attribute *attr, int idx)
{
if (!supports_runtime_update())
return 0;
return attr->mode;
}
static const struct attribute_group seamldr_group = {
.attrs = seamldr_attrs,
.is_visible = seamldr_group_visible,
};
static const struct attribute_group *tdx_host_groups[] = {
&tdx_host_group,
&seamldr_group,
NULL,
};
static enum fw_upload_err tdx_fw_prepare(struct fw_upload *fwl,
const u8 *data, u32 data_len)
{
return FW_UPLOAD_ERR_NONE;
}
static enum fw_upload_err tdx_fw_write(struct fw_upload *fwl, const u8 *data,
u32 offset, u32 data_len, u32 *written)
{
int ret;
ret = seamldr_install_module(data, data_len);
switch (ret) {
case 0:
*written = data_len;
return FW_UPLOAD_ERR_NONE;
default:
return FW_UPLOAD_ERR_FW_INVALID;
}
}
static enum fw_upload_err tdx_fw_poll_complete(struct fw_upload *fwl)
{
/*
* The upload completed during tdx_fw_write().
* Never poll for completion.
*/
return FW_UPLOAD_ERR_NONE;
}
static void tdx_fw_cancel(struct fw_upload *fwl)
{
/*
* TDX module updates are not cancellable.
* Provide a no-op callback to satisfy fw_upload_ops.
*/
}
static const struct fw_upload_ops tdx_fw_ops = {
.prepare = tdx_fw_prepare,
.write = tdx_fw_write,
.poll_complete = tdx_fw_poll_complete,
.cancel = tdx_fw_cancel,
};
static void seamldr_deinit(void *tdx_fwl)
{
firmware_upload_unregister(tdx_fwl);
}
static int seamldr_init(struct device *dev)
{
struct fw_upload *tdx_fwl;
if (!supports_runtime_update())
return 0;
tdx_fwl = firmware_upload_register(THIS_MODULE, dev, "tdx_module",
&tdx_fw_ops, NULL);
if (IS_ERR(tdx_fwl))
return PTR_ERR(tdx_fwl);
return devm_add_action_or_reset(dev, seamldr_deinit, tdx_fwl);
}
static int tdx_host_probe(struct faux_device *fdev)
{
return seamldr_init(&fdev->dev);
}
static const struct faux_device_ops tdx_host_ops = {
.probe = tdx_host_probe,
};
static struct faux_device *fdev;
static int __init tdx_host_init(void)
{
if (!x86_match_cpu(tdx_host_ids) || !tdx_get_sysinfo())
return -ENODEV;
fdev = faux_device_create_with_groups(KBUILD_MODNAME, NULL,
&tdx_host_ops,
tdx_host_groups);
if (!fdev)
return -ENODEV;
return 0;
}
module_init(tdx_host_init);
static void __exit tdx_host_exit(void)
{
faux_device_destroy(fdev);
}
module_exit(tdx_host_exit);
MODULE_DESCRIPTION("TDX Host Services");
MODULE_LICENSE("GPL");
+132
View File
@@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright(c) 2024-2025 Intel Corporation. All rights reserved. */
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/tsm.h>
#include <linux/device.h>
#include <linux/module.h>
#include <linux/cleanup.h>
#include <linux/pci-tsm.h>
static void tsm_release(struct device *);
static const struct class tsm_class = {
.name = "tsm",
.dev_release = tsm_release
};
static DEFINE_IDA(tsm_ida);
static int match_id(struct device *dev, const void *data)
{
struct tsm_dev *tsm_dev = container_of(dev, struct tsm_dev, dev);
int id = *(const int *)data;
return tsm_dev->id == id;
}
struct tsm_dev *find_tsm_dev(int id)
{
struct device *dev = class_find_device(&tsm_class, NULL, &id, match_id);
if (!dev)
return NULL;
return container_of(dev, struct tsm_dev, dev);
}
static struct tsm_dev *alloc_tsm_dev(struct device *parent)
{
struct device *dev;
int id;
struct tsm_dev *tsm_dev __free(kfree) =
kzalloc_obj(*tsm_dev);
if (!tsm_dev)
return ERR_PTR(-ENOMEM);
id = ida_alloc(&tsm_ida, GFP_KERNEL);
if (id < 0)
return ERR_PTR(id);
tsm_dev->id = id;
dev = &tsm_dev->dev;
dev->parent = parent;
dev->class = &tsm_class;
device_initialize(dev);
return no_free_ptr(tsm_dev);
}
static struct tsm_dev *tsm_register_pci_or_reset(struct tsm_dev *tsm_dev,
struct pci_tsm_ops *pci_ops)
{
int rc;
if (!pci_ops)
return tsm_dev;
tsm_dev->pci_ops = pci_ops;
rc = pci_tsm_register(tsm_dev);
if (rc) {
dev_err(tsm_dev->dev.parent,
"PCI/TSM registration failure: %d\n", rc);
device_unregister(&tsm_dev->dev);
return ERR_PTR(rc);
}
/* Notify TSM userspace that PCI/TSM operations are now possible */
kobject_uevent(&tsm_dev->dev.kobj, KOBJ_CHANGE);
return tsm_dev;
}
struct tsm_dev *tsm_register(struct device *parent, struct pci_tsm_ops *pci_ops)
{
struct tsm_dev *tsm_dev __free(put_tsm_dev) = alloc_tsm_dev(parent);
struct device *dev;
int rc;
if (IS_ERR(tsm_dev))
return tsm_dev;
dev = &tsm_dev->dev;
rc = dev_set_name(dev, "tsm%d", tsm_dev->id);
if (rc)
return ERR_PTR(rc);
rc = device_add(dev);
if (rc)
return ERR_PTR(rc);
return tsm_register_pci_or_reset(no_free_ptr(tsm_dev), pci_ops);
}
EXPORT_SYMBOL_GPL(tsm_register);
void tsm_unregister(struct tsm_dev *tsm_dev)
{
if (tsm_dev->pci_ops)
pci_tsm_unregister(tsm_dev);
device_unregister(&tsm_dev->dev);
}
EXPORT_SYMBOL_GPL(tsm_unregister);
static void tsm_release(struct device *dev)
{
struct tsm_dev *tsm_dev = container_of(dev, typeof(*tsm_dev), dev);
ida_free(&tsm_ida, tsm_dev->id);
kfree(tsm_dev);
}
static int __init tsm_init(void)
{
return class_register(&tsm_class);
}
module_init(tsm_init)
static void __exit tsm_exit(void)
{
class_unregister(&tsm_class);
}
module_exit(tsm_exit)
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("TEE Security Manager Class Device");
+932
View File
@@ -0,0 +1,932 @@
/*
* Freescale Hypervisor Management Driver
* Copyright (C) 2008-2011 Freescale Semiconductor, Inc.
* Author: Timur Tabi <timur@freescale.com>
*
* This file is licensed under the terms of the GNU General Public License
* version 2. This program is licensed "as is" without any warranty of any
* kind, whether express or implied.
*
* The Freescale hypervisor management driver provides several services to
* drivers and applications related to the Freescale hypervisor:
*
* 1. An ioctl interface for querying and managing partitions.
*
* 2. A file interface to reading incoming doorbells.
*
* 3. An interrupt handler for shutting down the partition upon receiving the
* shutdown doorbell from a manager partition.
*
* 4. A kernel interface for receiving callbacks when a managed partition
* shuts down.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/fs.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/pagemap.h>
#include <linux/slab.h>
#include <linux/poll.h>
#include <linux/of.h>
#include <linux/of_irq.h>
#include <linux/reboot.h>
#include <linux/uaccess.h>
#include <linux/notifier.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <asm/fsl_hcalls.h>
#include <linux/fsl_hypervisor.h>
static BLOCKING_NOTIFIER_HEAD(failover_subscribers);
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_RESTART
*
* Restart a running partition
*/
static long ioctl_restart(struct fsl_hv_ioctl_restart __user *p)
{
struct fsl_hv_ioctl_restart param;
/* Get the parameters from the user */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_restart)))
return -EFAULT;
param.ret = fh_partition_restart(param.partition);
if (copy_to_user(&p->ret, &param.ret, sizeof(__u32)))
return -EFAULT;
return 0;
}
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_STATUS
*
* Query the status of a partition
*/
static long ioctl_status(struct fsl_hv_ioctl_status __user *p)
{
struct fsl_hv_ioctl_status param;
u32 status;
/* Get the parameters from the user */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_status)))
return -EFAULT;
param.ret = fh_partition_get_status(param.partition, &status);
if (!param.ret)
param.status = status;
if (copy_to_user(p, &param, sizeof(struct fsl_hv_ioctl_status)))
return -EFAULT;
return 0;
}
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_START
*
* Start a stopped partition.
*/
static long ioctl_start(struct fsl_hv_ioctl_start __user *p)
{
struct fsl_hv_ioctl_start param;
/* Get the parameters from the user */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_start)))
return -EFAULT;
param.ret = fh_partition_start(param.partition, param.entry_point,
param.load);
if (copy_to_user(&p->ret, &param.ret, sizeof(__u32)))
return -EFAULT;
return 0;
}
/*
* Ioctl interface for FSL_HV_IOCTL_PARTITION_STOP
*
* Stop a running partition
*/
static long ioctl_stop(struct fsl_hv_ioctl_stop __user *p)
{
struct fsl_hv_ioctl_stop param;
/* Get the parameters from the user */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_stop)))
return -EFAULT;
param.ret = fh_partition_stop(param.partition);
if (copy_to_user(&p->ret, &param.ret, sizeof(__u32)))
return -EFAULT;
return 0;
}
/*
* Ioctl interface for FSL_HV_IOCTL_MEMCPY
*
* The FH_MEMCPY hypercall takes an array of address/address/size structures
* to represent the data being copied. As a convenience to the user, this
* ioctl takes a user-create buffer and a pointer to a guest physically
* contiguous buffer in the remote partition, and creates the
* address/address/size array for the hypercall.
*/
static long ioctl_memcpy(struct fsl_hv_ioctl_memcpy __user *p)
{
struct fsl_hv_ioctl_memcpy param;
struct page **pages = NULL;
void *sg_list_unaligned = NULL;
struct fh_sg_list *sg_list = NULL;
unsigned int num_pages;
unsigned long lb_offset; /* Offset within a page of the local buffer */
unsigned int i;
long ret = 0;
int num_pinned = 0; /* return value from get_user_pages_fast() */
phys_addr_t remote_paddr; /* The next address in the remote buffer */
uint32_t count; /* The number of bytes left to copy */
/* Get the parameters from the user */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_memcpy)))
return -EFAULT;
/*
* One partition must be local, the other must be remote. In other
* words, if source and target are both -1, or are both not -1, then
* return an error.
*/
if ((param.source == -1) == (param.target == -1))
return -EINVAL;
/*
* The array of pages returned by get_user_pages_fast() covers only
* page-aligned memory. Since the user buffer is probably not
* page-aligned, we need to handle the discrepancy.
*
* We calculate the offset within a page of the S/G list, and make
* adjustments accordingly. This will result in a page list that looks
* like this:
*
* ---- <-- first page starts before the buffer
* | |
* |////|-> ----
* |////| | |
* ---- | |
* | |
* ---- | |
* |////| | |
* |////| | |
* |////| | |
* ---- | |
* | |
* ---- | |
* |////| | |
* |////| | |
* |////| | |
* ---- | |
* | |
* ---- | |
* |////| | |
* |////|-> ----
* | | <-- last page ends after the buffer
* ----
*
* The distance between the start of the first page and the start of the
* buffer is lb_offset. The hashed (///) areas are the parts of the
* page list that contain the actual buffer.
*
* The advantage of this approach is that the number of pages is
* equal to the number of entries in the S/G list that we give to the
* hypervisor.
*/
lb_offset = param.local_vaddr & (PAGE_SIZE - 1);
if (param.count == 0 ||
param.count > U64_MAX - lb_offset - PAGE_SIZE + 1)
return -EINVAL;
num_pages = (param.count + lb_offset + PAGE_SIZE - 1) >> PAGE_SHIFT;
/* Allocate the buffers we need */
/*
* 'pages' is an array of struct page pointers that's initialized by
* get_user_pages_fast().
*/
pages = kzalloc_objs(struct page *, num_pages);
if (!pages) {
pr_debug("fsl-hv: could not allocate page list\n");
return -ENOMEM;
}
/*
* sg_list is the list of fh_sg_list objects that we pass to the
* hypervisor.
*/
sg_list_unaligned = kmalloc(num_pages * sizeof(struct fh_sg_list) +
sizeof(struct fh_sg_list) - 1, GFP_KERNEL);
if (!sg_list_unaligned) {
pr_debug("fsl-hv: could not allocate S/G list\n");
ret = -ENOMEM;
goto free_pages;
}
sg_list = PTR_ALIGN(sg_list_unaligned, sizeof(struct fh_sg_list));
/* Get the physical addresses of the source buffer */
num_pinned = get_user_pages_fast(param.local_vaddr - lb_offset,
num_pages, param.source != -1 ? FOLL_WRITE : 0, pages);
if (num_pinned != num_pages) {
pr_debug("fsl-hv: could not lock source buffer\n");
ret = (num_pinned < 0) ? num_pinned : -EFAULT;
goto exit;
}
/*
* Build the fh_sg_list[] array. The first page is special
* because it's misaligned.
*/
if (param.source == -1) {
sg_list[0].source = page_to_phys(pages[0]) + lb_offset;
sg_list[0].target = param.remote_paddr;
} else {
sg_list[0].source = param.remote_paddr;
sg_list[0].target = page_to_phys(pages[0]) + lb_offset;
}
sg_list[0].size = min_t(uint64_t, param.count, PAGE_SIZE - lb_offset);
remote_paddr = param.remote_paddr + sg_list[0].size;
count = param.count - sg_list[0].size;
for (i = 1; i < num_pages; i++) {
if (param.source == -1) {
/* local to remote */
sg_list[i].source = page_to_phys(pages[i]);
sg_list[i].target = remote_paddr;
} else {
/* remote to local */
sg_list[i].source = remote_paddr;
sg_list[i].target = page_to_phys(pages[i]);
}
sg_list[i].size = min_t(uint64_t, count, PAGE_SIZE);
remote_paddr += sg_list[i].size;
count -= sg_list[i].size;
}
param.ret = fh_partition_memcpy(param.source, param.target,
virt_to_phys(sg_list), num_pages);
exit:
if (pages && (num_pinned > 0)) {
for (i = 0; i < num_pinned; i++)
put_page(pages[i]);
}
kfree(sg_list_unaligned);
free_pages:
kfree(pages);
if (!ret)
if (copy_to_user(&p->ret, &param.ret, sizeof(__u32)))
return -EFAULT;
return ret;
}
/*
* Ioctl interface for FSL_HV_IOCTL_DOORBELL
*
* Ring a doorbell
*/
static long ioctl_doorbell(struct fsl_hv_ioctl_doorbell __user *p)
{
struct fsl_hv_ioctl_doorbell param;
/* Get the parameters from the user. */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_doorbell)))
return -EFAULT;
param.ret = ev_doorbell_send(param.doorbell);
if (copy_to_user(&p->ret, &param.ret, sizeof(__u32)))
return -EFAULT;
return 0;
}
static long ioctl_dtprop(struct fsl_hv_ioctl_prop __user *p, int set)
{
struct fsl_hv_ioctl_prop param;
char __user *upath, *upropname;
void __user *upropval;
char *path, *propname;
void *propval;
int ret = 0;
/* Get the parameters from the user. */
if (copy_from_user(&param, p, sizeof(struct fsl_hv_ioctl_prop)))
return -EFAULT;
upath = (char __user *)(uintptr_t)param.path;
upropname = (char __user *)(uintptr_t)param.propname;
upropval = (void __user *)(uintptr_t)param.propval;
path = strndup_user(upath, FH_DTPROP_MAX_PATHLEN);
if (IS_ERR(path))
return PTR_ERR(path);
propname = strndup_user(upropname, FH_DTPROP_MAX_PATHLEN);
if (IS_ERR(propname)) {
ret = PTR_ERR(propname);
goto err_free_path;
}
if (param.proplen > FH_DTPROP_MAX_PROPLEN) {
ret = -EINVAL;
goto err_free_propname;
}
propval = kmalloc(param.proplen, GFP_KERNEL);
if (!propval) {
ret = -ENOMEM;
goto err_free_propname;
}
if (set) {
if (copy_from_user(propval, upropval, param.proplen)) {
ret = -EFAULT;
goto err_free_propval;
}
param.ret = fh_partition_set_dtprop(param.handle,
virt_to_phys(path),
virt_to_phys(propname),
virt_to_phys(propval),
param.proplen);
} else {
param.ret = fh_partition_get_dtprop(param.handle,
virt_to_phys(path),
virt_to_phys(propname),
virt_to_phys(propval),
&param.proplen);
if (param.ret == 0) {
if (copy_to_user(upropval, propval, param.proplen) ||
put_user(param.proplen, &p->proplen)) {
ret = -EFAULT;
goto err_free_propval;
}
}
}
if (put_user(param.ret, &p->ret))
ret = -EFAULT;
err_free_propval:
kfree(propval);
err_free_propname:
kfree(propname);
err_free_path:
kfree(path);
return ret;
}
/*
* Ioctl main entry point
*/
static long fsl_hv_ioctl(struct file *file, unsigned int cmd,
unsigned long argaddr)
{
void __user *arg = (void __user *)argaddr;
long ret;
switch (cmd) {
case FSL_HV_IOCTL_PARTITION_RESTART:
ret = ioctl_restart(arg);
break;
case FSL_HV_IOCTL_PARTITION_GET_STATUS:
ret = ioctl_status(arg);
break;
case FSL_HV_IOCTL_PARTITION_START:
ret = ioctl_start(arg);
break;
case FSL_HV_IOCTL_PARTITION_STOP:
ret = ioctl_stop(arg);
break;
case FSL_HV_IOCTL_MEMCPY:
ret = ioctl_memcpy(arg);
break;
case FSL_HV_IOCTL_DOORBELL:
ret = ioctl_doorbell(arg);
break;
case FSL_HV_IOCTL_GETPROP:
ret = ioctl_dtprop(arg, 0);
break;
case FSL_HV_IOCTL_SETPROP:
ret = ioctl_dtprop(arg, 1);
break;
default:
pr_debug("fsl-hv: bad ioctl dir=%u type=%u cmd=%u size=%u\n",
_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd),
_IOC_SIZE(cmd));
return -ENOTTY;
}
return ret;
}
/* Linked list of processes that have us open */
static struct list_head db_list;
/* spinlock for db_list */
static DEFINE_SPINLOCK(db_list_lock);
/* The size of the doorbell event queue. This must be a power of two. */
#define QSIZE 16
/* Returns the next head/tail pointer, wrapping around the queue if necessary */
#define nextp(x) (((x) + 1) & (QSIZE - 1))
/* Per-open data structure */
struct doorbell_queue {
struct list_head list;
spinlock_t lock;
wait_queue_head_t wait;
unsigned int head;
unsigned int tail;
uint32_t q[QSIZE];
};
/* Linked list of ISRs that we registered */
struct list_head isr_list;
/* Per-ISR data structure */
struct doorbell_isr {
struct list_head list;
unsigned int irq;
uint32_t doorbell; /* The doorbell handle */
uint32_t partition; /* The partition handle, if used */
};
/*
* Add a doorbell to all of the doorbell queues
*/
static void fsl_hv_queue_doorbell(uint32_t doorbell)
{
struct doorbell_queue *dbq;
unsigned long flags;
/* Prevent another core from modifying db_list */
spin_lock_irqsave(&db_list_lock, flags);
list_for_each_entry(dbq, &db_list, list) {
if (dbq->head != nextp(dbq->tail)) {
dbq->q[dbq->tail] = doorbell;
/*
* This memory barrier eliminates the need to grab
* the spinlock for dbq.
*/
smp_wmb();
dbq->tail = nextp(dbq->tail);
wake_up_interruptible(&dbq->wait);
}
}
spin_unlock_irqrestore(&db_list_lock, flags);
}
/*
* Interrupt handler for all doorbells
*
* We use the same interrupt handler for all doorbells. Whenever a doorbell
* is rung, and we receive an interrupt, we just put the handle for that
* doorbell (passed to us as *data) into all of the queues.
*/
static irqreturn_t fsl_hv_isr(int irq, void *data)
{
fsl_hv_queue_doorbell((uintptr_t) data);
return IRQ_HANDLED;
}
/*
* State change thread function
*
* The state change notification arrives in an interrupt, but we can't call
* blocking_notifier_call_chain() in an interrupt handler. We could call
* atomic_notifier_call_chain(), but that would require the clients' call-back
* function to run in interrupt context. Since we don't want to impose that
* restriction on the clients, we use a threaded IRQ to process the
* notification in kernel context.
*/
static irqreturn_t fsl_hv_state_change_thread(int irq, void *data)
{
struct doorbell_isr *dbisr = data;
blocking_notifier_call_chain(&failover_subscribers, dbisr->partition,
NULL);
return IRQ_HANDLED;
}
/*
* Interrupt handler for state-change doorbells
*/
static irqreturn_t fsl_hv_state_change_isr(int irq, void *data)
{
unsigned int status;
struct doorbell_isr *dbisr = data;
int ret;
/* It's still a doorbell, so add it to all the queues. */
fsl_hv_queue_doorbell(dbisr->doorbell);
/* Determine the new state, and if it's stopped, notify the clients. */
ret = fh_partition_get_status(dbisr->partition, &status);
if (!ret && (status == FH_PARTITION_STOPPED))
return IRQ_WAKE_THREAD;
return IRQ_HANDLED;
}
/*
* Returns a bitmask indicating whether a read will block
*/
static __poll_t fsl_hv_poll(struct file *filp, struct poll_table_struct *p)
{
struct doorbell_queue *dbq = filp->private_data;
unsigned long flags;
__poll_t mask;
spin_lock_irqsave(&dbq->lock, flags);
poll_wait(filp, &dbq->wait, p);
mask = (dbq->head == dbq->tail) ? 0 : (EPOLLIN | EPOLLRDNORM);
spin_unlock_irqrestore(&dbq->lock, flags);
return mask;
}
/*
* Return the handles for any incoming doorbells
*
* If there are doorbell handles in the queue for this open instance, then
* return them to the caller as an array of 32-bit integers. Otherwise,
* block until there is at least one handle to return.
*/
static ssize_t fsl_hv_read(struct file *filp, char __user *buf, size_t len,
loff_t *off)
{
struct doorbell_queue *dbq = filp->private_data;
uint32_t __user *p = (uint32_t __user *) buf; /* for put_user() */
unsigned long flags;
ssize_t count = 0;
/* Make sure we stop when the user buffer is full. */
while (len >= sizeof(uint32_t)) {
uint32_t dbell; /* Local copy of doorbell queue data */
spin_lock_irqsave(&dbq->lock, flags);
/*
* If the queue is empty, then either we're done or we need
* to block. If the application specified O_NONBLOCK, then
* we return the appropriate error code.
*/
if (dbq->head == dbq->tail) {
spin_unlock_irqrestore(&dbq->lock, flags);
if (count)
break;
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dbq->wait,
dbq->head != dbq->tail))
return -ERESTARTSYS;
continue;
}
/*
* Even though we have an smp_wmb() in the ISR, the core
* might speculatively execute the "dbell = ..." below while
* it's evaluating the if-statement above. In that case, the
* value put into dbell could be stale if the core accepts the
* speculation. To prevent that, we need a read memory barrier
* here as well.
*/
smp_rmb();
/* Copy the data to a temporary local buffer, because
* we can't call copy_to_user() from inside a spinlock
*/
dbell = dbq->q[dbq->head];
dbq->head = nextp(dbq->head);
spin_unlock_irqrestore(&dbq->lock, flags);
if (put_user(dbell, p))
return -EFAULT;
p++;
count += sizeof(uint32_t);
len -= sizeof(uint32_t);
}
return count;
}
/*
* Open the driver and prepare for reading doorbells.
*
* Every time an application opens the driver, we create a doorbell queue
* for that file handle. This queue is used for any incoming doorbells.
*/
static int fsl_hv_open(struct inode *inode, struct file *filp)
{
struct doorbell_queue *dbq;
unsigned long flags;
dbq = kzalloc_obj(struct doorbell_queue);
if (!dbq) {
pr_err("fsl-hv: out of memory\n");
return -ENOMEM;
}
spin_lock_init(&dbq->lock);
init_waitqueue_head(&dbq->wait);
spin_lock_irqsave(&db_list_lock, flags);
list_add(&dbq->list, &db_list);
spin_unlock_irqrestore(&db_list_lock, flags);
filp->private_data = dbq;
return 0;
}
/*
* Close the driver
*/
static int fsl_hv_close(struct inode *inode, struct file *filp)
{
struct doorbell_queue *dbq = filp->private_data;
unsigned long flags;
spin_lock_irqsave(&db_list_lock, flags);
list_del(&dbq->list);
spin_unlock_irqrestore(&db_list_lock, flags);
kfree(dbq);
return 0;
}
static const struct file_operations fsl_hv_fops = {
.owner = THIS_MODULE,
.open = fsl_hv_open,
.release = fsl_hv_close,
.poll = fsl_hv_poll,
.read = fsl_hv_read,
.unlocked_ioctl = fsl_hv_ioctl,
.compat_ioctl = compat_ptr_ioctl,
};
static struct miscdevice fsl_hv_misc_dev = {
MISC_DYNAMIC_MINOR,
"fsl-hv",
&fsl_hv_fops
};
static irqreturn_t fsl_hv_shutdown_isr(int irq, void *data)
{
orderly_poweroff(false);
return IRQ_HANDLED;
}
/*
* Returns the handle of the parent of the given node
*
* The handle is the value of the 'hv-handle' property
*/
static int get_parent_handle(struct device_node *np)
{
struct device_node *parent;
const uint32_t *prop;
uint32_t handle;
int len;
parent = of_get_parent(np);
if (!parent)
/* It's not really possible for this to fail */
return -ENODEV;
/*
* The proper name for the handle property is "hv-handle", but some
* older versions of the hypervisor used "reg".
*/
prop = of_get_property(parent, "hv-handle", &len);
if (!prop)
prop = of_get_property(parent, "reg", &len);
if (!prop || (len != sizeof(uint32_t))) {
/* This can happen only if the node is malformed */
of_node_put(parent);
return -ENODEV;
}
handle = be32_to_cpup(prop);
of_node_put(parent);
return handle;
}
/*
* Register a callback for failover events
*
* This function is called by device drivers to register their callback
* functions for fail-over events.
*/
int fsl_hv_failover_register(struct notifier_block *nb)
{
return blocking_notifier_chain_register(&failover_subscribers, nb);
}
EXPORT_SYMBOL(fsl_hv_failover_register);
/*
* Unregister a callback for failover events
*/
int fsl_hv_failover_unregister(struct notifier_block *nb)
{
return blocking_notifier_chain_unregister(&failover_subscribers, nb);
}
EXPORT_SYMBOL(fsl_hv_failover_unregister);
/*
* Return TRUE if we're running under FSL hypervisor
*
* This function checks to see if we're running under the Freescale
* hypervisor, and returns zero if we're not, or non-zero if we are.
*
* First, it checks if MSR[GS]==1, which means we're running under some
* hypervisor. Then it checks if there is a hypervisor node in the device
* tree. Currently, that means there needs to be a node in the root called
* "hypervisor" and which has a property named "fsl,hv-version".
*/
static int has_fsl_hypervisor(void)
{
struct device_node *node;
int ret;
node = of_find_node_by_path("/hypervisor");
if (!node)
return 0;
ret = of_property_present(node, "fsl,hv-version");
of_node_put(node);
return ret;
}
/*
* Freescale hypervisor management driver init
*
* This function is called when this module is loaded.
*
* Register ourselves as a miscellaneous driver. This will register the
* fops structure and create the right sysfs entries for udev.
*/
static int __init fsl_hypervisor_init(void)
{
struct device_node *np;
struct doorbell_isr *dbisr, *n;
int ret;
pr_info("Freescale hypervisor management driver\n");
if (!has_fsl_hypervisor()) {
pr_info("fsl-hv: no hypervisor found\n");
return -ENODEV;
}
ret = misc_register(&fsl_hv_misc_dev);
if (ret) {
pr_err("fsl-hv: cannot register device\n");
return ret;
}
INIT_LIST_HEAD(&db_list);
INIT_LIST_HEAD(&isr_list);
for_each_compatible_node(np, NULL, "epapr,hv-receive-doorbell") {
unsigned int irq;
const uint32_t *handle;
handle = of_get_property(np, "interrupts", NULL);
irq = irq_of_parse_and_map(np, 0);
if (!handle || !irq) {
pr_err("fsl-hv: no 'interrupts' property in %pOF node\n",
np);
continue;
}
dbisr = kzalloc_obj(*dbisr);
if (!dbisr)
goto out_of_memory;
dbisr->irq = irq;
dbisr->doorbell = be32_to_cpup(handle);
if (of_device_is_compatible(np, "fsl,hv-shutdown-doorbell")) {
/* The shutdown doorbell gets its own ISR */
ret = request_irq(irq, fsl_hv_shutdown_isr, 0,
np->name, NULL);
} else if (of_device_is_compatible(np,
"fsl,hv-state-change-doorbell")) {
/*
* The state change doorbell triggers a notification if
* the state of the managed partition changes to
* "stopped". We need a separate interrupt handler for
* that, and we also need to know the handle of the
* target partition, not just the handle of the
* doorbell.
*/
dbisr->partition = ret = get_parent_handle(np);
if (ret < 0) {
pr_err("fsl-hv: node %pOF has missing or "
"malformed parent\n", np);
kfree(dbisr);
continue;
}
ret = request_threaded_irq(irq, fsl_hv_state_change_isr,
fsl_hv_state_change_thread,
0, np->name, dbisr);
} else
ret = request_irq(irq, fsl_hv_isr, 0, np->name, dbisr);
if (ret < 0) {
pr_err("fsl-hv: could not request irq %u for node %pOF\n",
irq, np);
kfree(dbisr);
continue;
}
list_add(&dbisr->list, &isr_list);
pr_info("fsl-hv: registered handler for doorbell %u\n",
dbisr->doorbell);
}
return 0;
out_of_memory:
list_for_each_entry_safe(dbisr, n, &isr_list, list) {
free_irq(dbisr->irq, dbisr);
list_del(&dbisr->list);
kfree(dbisr);
}
misc_deregister(&fsl_hv_misc_dev);
return -ENOMEM;
}
/*
* Freescale hypervisor management driver termination
*
* This function is called when this driver is unloaded.
*/
static void __exit fsl_hypervisor_exit(void)
{
struct doorbell_isr *dbisr, *n;
list_for_each_entry_safe(dbisr, n, &isr_list, list) {
free_irq(dbisr->irq, dbisr);
list_del(&dbisr->list);
kfree(dbisr);
}
misc_deregister(&fsl_hv_misc_dev);
}
module_init(fsl_hypervisor_init);
module_exit(fsl_hypervisor_exit);
MODULE_AUTHOR("Timur Tabi <timur@freescale.com>");
MODULE_DESCRIPTION("Freescale hypervisor management driver");
MODULE_LICENSE("GPL v2");
@@ -0,0 +1,26 @@
# SPDX-License-Identifier: GPL-2.0
#
# Copyright 2020-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Amazon Nitro Enclaves (NE) support.
# Nitro is a hypervisor that has been developed by Amazon.
config NITRO_ENCLAVES
tristate "Nitro Enclaves Support"
depends on (ARM64 || X86) && HOTPLUG_CPU && PCI && SMP
help
This driver consists of support for enclave lifetime management
for Nitro Enclaves (NE).
To compile this driver as a module, choose M here.
The module will be called nitro_enclaves.
config NITRO_ENCLAVES_MISC_DEV_TEST
bool "Tests for the misc device functionality of the Nitro Enclaves" if !KUNIT_ALL_TESTS
depends on NITRO_ENCLAVES && KUNIT=y
default KUNIT_ALL_TESTS
help
Enable KUnit tests for the misc device functionality of the Nitro
Enclaves. Select this option only if you will boot the kernel for
the purpose of running unit tests (e.g. under UML or qemu). If
unsure, say N.
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
#
# Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
# Enclave lifetime management support for Nitro Enclaves (NE).
obj-$(CONFIG_NITRO_ENCLAVES) += nitro_enclaves.o
nitro_enclaves-y := ne_pci_dev.o ne_misc_dev.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
#ifndef _NE_MISC_DEV_H_
#define _NE_MISC_DEV_H_
#include <linux/cpumask.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/mm.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/wait.h>
#include "ne_pci_dev.h"
/**
* struct ne_mem_region - Entry in the enclave user space memory regions list.
* @mem_region_list_entry: Entry in the list of enclave memory regions.
* @memory_size: Size of the user space memory region.
* @nr_pages: Number of pages that make up the memory region.
* @pages: Pages that make up the user space memory region.
* @userspace_addr: User space address of the memory region.
*/
struct ne_mem_region {
struct list_head mem_region_list_entry;
u64 memory_size;
unsigned long nr_pages;
struct page **pages;
u64 userspace_addr;
};
/**
* struct ne_enclave - Per-enclave data used for enclave lifetime management.
* @enclave_info_mutex : Mutex for accessing this internal state.
* @enclave_list_entry : Entry in the list of created enclaves.
* @eventq: Wait queue used for out-of-band event notifications
* triggered from the PCI device event handler to
* the enclave process via the poll function.
* @has_event: Variable used to determine if the out-of-band event
* was triggered.
* @max_mem_regions: The maximum number of memory regions that can be
* handled by the hypervisor.
* @mem_regions_list: Enclave user space memory regions list.
* @mem_size: Enclave memory size.
* @mm : Enclave process abstraction mm data struct.
* @nr_mem_regions: Number of memory regions associated with the enclave.
* @nr_parent_vm_cores : The size of the threads per core array. The
* total number of CPU cores available on the
* parent / primary VM.
* @nr_threads_per_core: The number of threads that a full CPU core has.
* @nr_vcpus: Number of vcpus associated with the enclave.
* @numa_node: NUMA node of the enclave memory and CPUs.
* @slot_uid: Slot unique id mapped to the enclave.
* @state: Enclave state, updated during enclave lifetime.
* @threads_per_core: Enclave full CPU cores array, indexed by core id,
* consisting of cpumasks with all their threads.
* Full CPU cores are taken from the NE CPU pool
* and are available to the enclave.
* @vcpu_ids: Cpumask of the vCPUs that are set for the enclave.
*/
struct ne_enclave {
struct mutex enclave_info_mutex;
struct list_head enclave_list_entry;
wait_queue_head_t eventq;
bool has_event;
u64 max_mem_regions;
struct list_head mem_regions_list;
u64 mem_size;
struct mm_struct *mm;
unsigned int nr_mem_regions;
unsigned int nr_parent_vm_cores;
unsigned int nr_threads_per_core;
unsigned int nr_vcpus;
int numa_node;
u64 slot_uid;
u16 state;
cpumask_var_t *threads_per_core;
cpumask_var_t vcpu_ids;
};
/**
* enum ne_state - States available for an enclave.
* @NE_STATE_INIT: The enclave has not been started yet.
* @NE_STATE_RUNNING: The enclave was started and is running as expected.
* @NE_STATE_STOPPED: The enclave exited without userspace interaction.
*/
enum ne_state {
NE_STATE_INIT = 0,
NE_STATE_RUNNING = 2,
NE_STATE_STOPPED = U16_MAX,
};
/**
* struct ne_devs - Data structure to keep refs to the NE misc and PCI devices.
* @ne_misc_dev: Nitro Enclaves misc device.
* @ne_pci_dev : Nitro Enclaves PCI device.
*/
struct ne_devs {
struct miscdevice *ne_misc_dev;
struct ne_pci_dev *ne_pci_dev;
};
/* Nitro Enclaves (NE) data structure for keeping refs to the NE misc and PCI devices. */
extern struct ne_devs ne_devs;
#endif /* _NE_MISC_DEV_H_ */
@@ -0,0 +1,154 @@
// SPDX-License-Identifier: GPL-2.0
#include <kunit/test.h>
#define MAX_PHYS_REGIONS 16
#define INVALID_VALUE (~0ull)
struct ne_phys_regions_test {
u64 paddr;
u64 size;
int expect_rc;
unsigned long expect_num;
u64 expect_last_paddr;
u64 expect_last_size;
} phys_regions_test_cases[] = {
/*
* Add the region from 0x1000 to (0x1000 + 0x200000 - 1):
* Expected result:
* Failed, start address is not 2M-aligned
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 0
* regions = {}
*/
{0x1000, 0x200000, -EINVAL, 0, INVALID_VALUE, INVALID_VALUE},
/*
* Add the region from 0x200000 to (0x200000 + 0x1000 - 1):
* Expected result:
* Failed, size is not 2M-aligned
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 0
* regions = {}
*/
{0x200000, 0x1000, -EINVAL, 0, INVALID_VALUE, INVALID_VALUE},
/*
* Add the region from 0x200000 to (0x200000 + 0x200000 - 1):
* Expected result:
* Successful
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 1
* regions = {
* {start=0x200000, end=0x3fffff}, // len=0x200000
* }
*/
{0x200000, 0x200000, 0, 1, 0x200000, 0x200000},
/*
* Add the region from 0x0 to (0x0 + 0x200000 - 1):
* Expected result:
* Successful
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 2
* regions = {
* {start=0x200000, end=0x3fffff}, // len=0x200000
* {start=0x0, end=0x1fffff}, // len=0x200000
* }
*/
{0x0, 0x200000, 0, 2, 0x0, 0x200000},
/*
* Add the region from 0x600000 to (0x600000 + 0x400000 - 1):
* Expected result:
* Successful
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 3
* regions = {
* {start=0x200000, end=0x3fffff}, // len=0x200000
* {start=0x0, end=0x1fffff}, // len=0x200000
* {start=0x600000, end=0x9fffff}, // len=0x400000
* }
*/
{0x600000, 0x400000, 0, 3, 0x600000, 0x400000},
/*
* Add the region from 0xa00000 to (0xa00000 + 0x400000 - 1):
* Expected result:
* Successful, merging case!
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 3
* regions = {
* {start=0x200000, end=0x3fffff}, // len=0x200000
* {start=0x0, end=0x1fffff}, // len=0x200000
* {start=0x600000, end=0xdfffff}, // len=0x800000
* }
*/
{0xa00000, 0x400000, 0, 3, 0x600000, 0x800000},
/*
* Add the region from 0x1000 to (0x1000 + 0x200000 - 1):
* Expected result:
* Failed, start address is not 2M-aligned
*
* Now the instance of struct ne_phys_contig_mem_regions is:
* num = 3
* regions = {
* {start=0x200000, end=0x3fffff}, // len=0x200000
* {start=0x0, end=0x1fffff}, // len=0x200000
* {start=0x600000, end=0xdfffff}, // len=0x800000
* }
*/
{0x1000, 0x200000, -EINVAL, 3, 0x600000, 0x800000},
};
static void ne_misc_dev_test_merge_phys_contig_memory_regions(struct kunit *test)
{
struct ne_phys_contig_mem_regions phys_contig_mem_regions = {};
int rc = 0;
int i = 0;
phys_contig_mem_regions.regions = kunit_kcalloc(test, MAX_PHYS_REGIONS,
sizeof(*phys_contig_mem_regions.regions),
GFP_KERNEL);
KUNIT_ASSERT_TRUE(test, phys_contig_mem_regions.regions);
for (i = 0; i < ARRAY_SIZE(phys_regions_test_cases); i++) {
struct ne_phys_regions_test *test_case = &phys_regions_test_cases[i];
unsigned long num = 0;
rc = ne_merge_phys_contig_memory_regions(&phys_contig_mem_regions,
test_case->paddr, test_case->size);
KUNIT_EXPECT_EQ(test, rc, test_case->expect_rc);
KUNIT_EXPECT_EQ(test, phys_contig_mem_regions.num, test_case->expect_num);
if (test_case->expect_last_paddr == INVALID_VALUE)
continue;
num = phys_contig_mem_regions.num;
KUNIT_EXPECT_EQ(test, phys_contig_mem_regions.regions[num - 1].start,
test_case->expect_last_paddr);
KUNIT_EXPECT_EQ(test, range_len(&phys_contig_mem_regions.regions[num - 1]),
test_case->expect_last_size);
}
kunit_kfree(test, phys_contig_mem_regions.regions);
}
static struct kunit_case ne_misc_dev_test_cases[] = {
KUNIT_CASE(ne_misc_dev_test_merge_phys_contig_memory_regions),
{}
};
static struct kunit_suite ne_misc_dev_test_suite = {
.name = "ne_misc_dev_test",
.test_cases = ne_misc_dev_test_cases,
};
kunit_test_suite(ne_misc_dev_test_suite);
@@ -0,0 +1,626 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright 2020-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
/**
* DOC: Nitro Enclaves (NE) PCI device driver.
*/
#include <linux/delay.h>
#include <linux/device.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/nitro_enclaves.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/wait.h>
#include "ne_misc_dev.h"
#include "ne_pci_dev.h"
/**
* NE_DEFAULT_TIMEOUT_MSECS - Default timeout to wait for a reply from
* the NE PCI device.
*/
#define NE_DEFAULT_TIMEOUT_MSECS (120000) /* 120 sec */
static const struct pci_device_id ne_pci_ids[] = {
{ PCI_DEVICE(PCI_VENDOR_ID_AMAZON, PCI_DEVICE_ID_NE) },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ne_pci_ids);
/**
* ne_submit_request() - Submit command request to the PCI device based on the
* command type.
* @pdev: PCI device to send the command to.
* @cmd_type: Command type of the request sent to the PCI device.
* @cmd_request: Command request payload.
* @cmd_request_size: Size of the command request payload.
*
* Context: Process context. This function is called with the ne_pci_dev mutex held.
*/
static void ne_submit_request(struct pci_dev *pdev, enum ne_pci_dev_cmd_type cmd_type,
void *cmd_request, size_t cmd_request_size)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
memcpy_toio(ne_pci_dev->iomem_base + NE_SEND_DATA, cmd_request, cmd_request_size);
iowrite32(cmd_type, ne_pci_dev->iomem_base + NE_COMMAND);
}
/**
* ne_retrieve_reply() - Retrieve reply from the PCI device.
* @pdev: PCI device to receive the reply from.
* @cmd_reply: Command reply payload.
* @cmd_reply_size: Size of the command reply payload.
*
* Context: Process context. This function is called with the ne_pci_dev mutex held.
*/
static void ne_retrieve_reply(struct pci_dev *pdev, struct ne_pci_dev_cmd_reply *cmd_reply,
size_t cmd_reply_size)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
memcpy_fromio(cmd_reply, ne_pci_dev->iomem_base + NE_RECV_DATA, cmd_reply_size);
}
/**
* ne_wait_for_reply() - Wait for a reply of a PCI device command.
* @pdev: PCI device for which a reply is waited.
*
* Context: Process context. This function is called with the ne_pci_dev mutex held.
* Return:
* * 0 on success.
* * Negative return value on failure.
*/
static int ne_wait_for_reply(struct pci_dev *pdev)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
int rc = -EINVAL;
/*
* TODO: Update to _interruptible and handle interrupted wait event
* e.g. -ERESTARTSYS, incoming signals + update timeout, if needed.
*/
rc = wait_event_timeout(ne_pci_dev->cmd_reply_wait_q,
atomic_read(&ne_pci_dev->cmd_reply_avail) != 0,
msecs_to_jiffies(NE_DEFAULT_TIMEOUT_MSECS));
if (!rc)
return -ETIMEDOUT;
return 0;
}
int ne_do_request(struct pci_dev *pdev, enum ne_pci_dev_cmd_type cmd_type,
void *cmd_request, size_t cmd_request_size,
struct ne_pci_dev_cmd_reply *cmd_reply, size_t cmd_reply_size)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
int rc = -EINVAL;
if (cmd_type <= INVALID_CMD || cmd_type >= MAX_CMD) {
dev_err_ratelimited(&pdev->dev, "Invalid cmd type=%u\n", cmd_type);
return -EINVAL;
}
if (!cmd_request) {
dev_err_ratelimited(&pdev->dev, "Null cmd request for cmd type=%u\n",
cmd_type);
return -EINVAL;
}
if (cmd_request_size > NE_SEND_DATA_SIZE) {
dev_err_ratelimited(&pdev->dev, "Invalid req size=%zu for cmd type=%u\n",
cmd_request_size, cmd_type);
return -EINVAL;
}
if (!cmd_reply) {
dev_err_ratelimited(&pdev->dev, "Null cmd reply for cmd type=%u\n",
cmd_type);
return -EINVAL;
}
if (cmd_reply_size > NE_RECV_DATA_SIZE) {
dev_err_ratelimited(&pdev->dev, "Invalid reply size=%zu for cmd type=%u\n",
cmd_reply_size, cmd_type);
return -EINVAL;
}
/*
* Use this mutex so that the PCI device handles one command request at
* a time.
*/
mutex_lock(&ne_pci_dev->pci_dev_mutex);
atomic_set(&ne_pci_dev->cmd_reply_avail, 0);
ne_submit_request(pdev, cmd_type, cmd_request, cmd_request_size);
rc = ne_wait_for_reply(pdev);
if (rc < 0) {
dev_err_ratelimited(&pdev->dev, "Error in wait for reply for cmd type=%u [rc=%d]\n",
cmd_type, rc);
goto unlock_mutex;
}
ne_retrieve_reply(pdev, cmd_reply, cmd_reply_size);
atomic_set(&ne_pci_dev->cmd_reply_avail, 0);
if (cmd_reply->rc < 0) {
rc = cmd_reply->rc;
dev_err_ratelimited(&pdev->dev, "Error in cmd process logic, cmd type=%u [rc=%d]\n",
cmd_type, rc);
goto unlock_mutex;
}
rc = 0;
unlock_mutex:
mutex_unlock(&ne_pci_dev->pci_dev_mutex);
return rc;
}
/**
* ne_reply_handler() - Interrupt handler for retrieving a reply matching a
* request sent to the PCI device for enclave lifetime
* management.
* @irq: Received interrupt for a reply sent by the PCI device.
* @args: PCI device private data structure.
*
* Context: Interrupt context.
* Return:
* * IRQ_HANDLED on handled interrupt.
*/
static irqreturn_t ne_reply_handler(int irq, void *args)
{
struct ne_pci_dev *ne_pci_dev = (struct ne_pci_dev *)args;
atomic_set(&ne_pci_dev->cmd_reply_avail, 1);
/* TODO: Update to _interruptible. */
wake_up(&ne_pci_dev->cmd_reply_wait_q);
return IRQ_HANDLED;
}
/**
* ne_event_work_handler() - Work queue handler for notifying enclaves on a
* state change received by the event interrupt
* handler.
* @work: Item containing the NE PCI device for which an out-of-band event
* was issued.
*
* An out-of-band event is being issued by the Nitro Hypervisor when at least
* one enclave is changing state without client interaction.
*
* Context: Work queue context.
*/
static void ne_event_work_handler(struct work_struct *work)
{
struct ne_pci_dev_cmd_reply cmd_reply = {};
struct ne_enclave *ne_enclave = NULL;
struct ne_pci_dev *ne_pci_dev =
container_of(work, struct ne_pci_dev, notify_work);
struct pci_dev *pdev = ne_pci_dev->pdev;
int rc = -EINVAL;
struct slot_info_req slot_info_req = {};
mutex_lock(&ne_pci_dev->enclaves_list_mutex);
/*
* Iterate over all enclaves registered for the Nitro Enclaves
* PCI device and determine for which enclave(s) the out-of-band event
* is corresponding to.
*/
list_for_each_entry(ne_enclave, &ne_pci_dev->enclaves_list, enclave_list_entry) {
mutex_lock(&ne_enclave->enclave_info_mutex);
/*
* Enclaves that were never started cannot receive out-of-band
* events.
*/
if (ne_enclave->state != NE_STATE_RUNNING)
goto unlock;
slot_info_req.slot_uid = ne_enclave->slot_uid;
rc = ne_do_request(pdev, SLOT_INFO,
&slot_info_req, sizeof(slot_info_req),
&cmd_reply, sizeof(cmd_reply));
if (rc < 0)
dev_err(&pdev->dev, "Error in slot info [rc=%d]\n", rc);
/* Notify enclave process that the enclave state changed. */
if (ne_enclave->state != cmd_reply.state) {
ne_enclave->state = cmd_reply.state;
ne_enclave->has_event = true;
wake_up_interruptible(&ne_enclave->eventq);
}
unlock:
mutex_unlock(&ne_enclave->enclave_info_mutex);
}
mutex_unlock(&ne_pci_dev->enclaves_list_mutex);
}
/**
* ne_event_handler() - Interrupt handler for PCI device out-of-band events.
* This interrupt does not supply any data in the MMIO
* region. It notifies a change in the state of any of
* the launched enclaves.
* @irq: Received interrupt for an out-of-band event.
* @args: PCI device private data structure.
*
* Context: Interrupt context.
* Return:
* * IRQ_HANDLED on handled interrupt.
*/
static irqreturn_t ne_event_handler(int irq, void *args)
{
struct ne_pci_dev *ne_pci_dev = (struct ne_pci_dev *)args;
queue_work(ne_pci_dev->event_wq, &ne_pci_dev->notify_work);
return IRQ_HANDLED;
}
/**
* ne_setup_msix() - Setup MSI-X vectors for the PCI device.
* @pdev: PCI device to setup the MSI-X for.
*
* Context: Process context.
* Return:
* * 0 on success.
* * Negative return value on failure.
*/
static int ne_setup_msix(struct pci_dev *pdev)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
int nr_vecs = 0;
int rc = -EINVAL;
nr_vecs = pci_msix_vec_count(pdev);
if (nr_vecs < 0) {
rc = nr_vecs;
dev_err(&pdev->dev, "Error in getting vec count [rc=%d]\n", rc);
return rc;
}
rc = pci_alloc_irq_vectors(pdev, nr_vecs, nr_vecs, PCI_IRQ_MSIX);
if (rc < 0) {
dev_err(&pdev->dev, "Error in alloc MSI-X vecs [rc=%d]\n", rc);
return rc;
}
/*
* This IRQ gets triggered every time the PCI device responds to a
* command request. The reply is then retrieved, reading from the MMIO
* space of the PCI device.
*/
rc = request_irq(pci_irq_vector(pdev, NE_VEC_REPLY), ne_reply_handler,
0, "enclave_cmd", ne_pci_dev);
if (rc < 0) {
dev_err(&pdev->dev, "Error in request irq reply [rc=%d]\n", rc);
goto free_irq_vectors;
}
ne_pci_dev->event_wq = create_singlethread_workqueue("ne_pci_dev_wq");
if (!ne_pci_dev->event_wq) {
rc = -ENOMEM;
dev_err(&pdev->dev, "Cannot get wq for dev events [rc=%d]\n", rc);
goto free_reply_irq_vec;
}
INIT_WORK(&ne_pci_dev->notify_work, ne_event_work_handler);
/*
* This IRQ gets triggered every time any enclave's state changes. Its
* handler then scans for the changes and propagates them to the user
* space.
*/
rc = request_irq(pci_irq_vector(pdev, NE_VEC_EVENT), ne_event_handler,
0, "enclave_evt", ne_pci_dev);
if (rc < 0) {
dev_err(&pdev->dev, "Error in request irq event [rc=%d]\n", rc);
goto destroy_wq;
}
return 0;
destroy_wq:
destroy_workqueue(ne_pci_dev->event_wq);
free_reply_irq_vec:
free_irq(pci_irq_vector(pdev, NE_VEC_REPLY), ne_pci_dev);
free_irq_vectors:
pci_free_irq_vectors(pdev);
return rc;
}
/**
* ne_teardown_msix() - Teardown MSI-X vectors for the PCI device.
* @pdev: PCI device to teardown the MSI-X for.
*
* Context: Process context.
*/
static void ne_teardown_msix(struct pci_dev *pdev)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
free_irq(pci_irq_vector(pdev, NE_VEC_EVENT), ne_pci_dev);
flush_work(&ne_pci_dev->notify_work);
destroy_workqueue(ne_pci_dev->event_wq);
free_irq(pci_irq_vector(pdev, NE_VEC_REPLY), ne_pci_dev);
pci_free_irq_vectors(pdev);
}
/**
* ne_pci_dev_enable() - Select the PCI device version and enable it.
* @pdev: PCI device to select version for and then enable.
*
* Context: Process context.
* Return:
* * 0 on success.
* * Negative return value on failure.
*/
static int ne_pci_dev_enable(struct pci_dev *pdev)
{
u8 dev_enable_reply = 0;
u16 dev_version_reply = 0;
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
iowrite16(NE_VERSION_MAX, ne_pci_dev->iomem_base + NE_VERSION);
dev_version_reply = ioread16(ne_pci_dev->iomem_base + NE_VERSION);
if (dev_version_reply != NE_VERSION_MAX) {
dev_err(&pdev->dev, "Error in pci dev version cmd\n");
return -EIO;
}
iowrite8(NE_ENABLE_ON, ne_pci_dev->iomem_base + NE_ENABLE);
dev_enable_reply = ioread8(ne_pci_dev->iomem_base + NE_ENABLE);
if (dev_enable_reply != NE_ENABLE_ON) {
dev_err(&pdev->dev, "Error in pci dev enable cmd\n");
return -EIO;
}
return 0;
}
/**
* ne_pci_dev_disable() - Disable the PCI device.
* @pdev: PCI device to disable.
*
* Context: Process context.
*/
static void ne_pci_dev_disable(struct pci_dev *pdev)
{
u8 dev_disable_reply = 0;
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
const unsigned int sleep_time = 10; /* 10 ms */
unsigned int sleep_time_count = 0;
iowrite8(NE_ENABLE_OFF, ne_pci_dev->iomem_base + NE_ENABLE);
/*
* Check for NE_ENABLE_OFF in a loop, to handle cases when the device
* state is not immediately set to disabled and going through a
* transitory state of disabling.
*/
while (sleep_time_count < NE_DEFAULT_TIMEOUT_MSECS) {
dev_disable_reply = ioread8(ne_pci_dev->iomem_base + NE_ENABLE);
if (dev_disable_reply == NE_ENABLE_OFF)
return;
msleep_interruptible(sleep_time);
sleep_time_count += sleep_time;
}
dev_disable_reply = ioread8(ne_pci_dev->iomem_base + NE_ENABLE);
if (dev_disable_reply != NE_ENABLE_OFF)
dev_err(&pdev->dev, "Error in pci dev disable cmd\n");
}
/**
* ne_pci_probe() - Probe function for the NE PCI device.
* @pdev: PCI device to match with the NE PCI driver.
* @id : PCI device id table associated with the NE PCI driver.
*
* Context: Process context.
* Return:
* * 0 on success.
* * Negative return value on failure.
*/
static int ne_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct ne_pci_dev *ne_pci_dev = NULL;
int rc = -EINVAL;
ne_pci_dev = kzalloc_obj(*ne_pci_dev);
if (!ne_pci_dev)
return -ENOMEM;
rc = pci_enable_device(pdev);
if (rc < 0) {
dev_err(&pdev->dev, "Error in pci dev enable [rc=%d]\n", rc);
goto free_ne_pci_dev;
}
pci_set_master(pdev);
rc = pci_request_regions_exclusive(pdev, "nitro_enclaves");
if (rc < 0) {
dev_err(&pdev->dev, "Error in pci request regions [rc=%d]\n", rc);
goto disable_pci_dev;
}
ne_pci_dev->iomem_base = pci_iomap(pdev, PCI_BAR_NE, 0);
if (!ne_pci_dev->iomem_base) {
rc = -ENOMEM;
dev_err(&pdev->dev, "Error in pci iomap [rc=%d]\n", rc);
goto release_pci_regions;
}
pci_set_drvdata(pdev, ne_pci_dev);
rc = ne_setup_msix(pdev);
if (rc < 0) {
dev_err(&pdev->dev, "Error in pci dev msix setup [rc=%d]\n", rc);
goto iounmap_pci_bar;
}
ne_pci_dev_disable(pdev);
rc = ne_pci_dev_enable(pdev);
if (rc < 0) {
dev_err(&pdev->dev, "Error in ne_pci_dev enable [rc=%d]\n", rc);
goto teardown_msix;
}
atomic_set(&ne_pci_dev->cmd_reply_avail, 0);
init_waitqueue_head(&ne_pci_dev->cmd_reply_wait_q);
INIT_LIST_HEAD(&ne_pci_dev->enclaves_list);
mutex_init(&ne_pci_dev->enclaves_list_mutex);
mutex_init(&ne_pci_dev->pci_dev_mutex);
ne_pci_dev->pdev = pdev;
ne_devs.ne_pci_dev = ne_pci_dev;
rc = misc_register(ne_devs.ne_misc_dev);
if (rc < 0) {
dev_err(&pdev->dev, "Error in misc dev register [rc=%d]\n", rc);
goto disable_ne_pci_dev;
}
return 0;
disable_ne_pci_dev:
ne_devs.ne_pci_dev = NULL;
ne_pci_dev_disable(pdev);
teardown_msix:
ne_teardown_msix(pdev);
iounmap_pci_bar:
pci_set_drvdata(pdev, NULL);
pci_iounmap(pdev, ne_pci_dev->iomem_base);
release_pci_regions:
pci_release_regions(pdev);
disable_pci_dev:
pci_disable_device(pdev);
free_ne_pci_dev:
kfree(ne_pci_dev);
return rc;
}
/**
* ne_pci_remove() - Remove function for the NE PCI device.
* @pdev: PCI device associated with the NE PCI driver.
*
* Context: Process context.
*/
static void ne_pci_remove(struct pci_dev *pdev)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
misc_deregister(ne_devs.ne_misc_dev);
ne_devs.ne_pci_dev = NULL;
ne_pci_dev_disable(pdev);
ne_teardown_msix(pdev);
pci_set_drvdata(pdev, NULL);
pci_iounmap(pdev, ne_pci_dev->iomem_base);
pci_release_regions(pdev);
pci_disable_device(pdev);
kfree(ne_pci_dev);
}
/**
* ne_pci_shutdown() - Shutdown function for the NE PCI device.
* @pdev: PCI device associated with the NE PCI driver.
*
* Context: Process context.
*/
static void ne_pci_shutdown(struct pci_dev *pdev)
{
struct ne_pci_dev *ne_pci_dev = pci_get_drvdata(pdev);
if (!ne_pci_dev)
return;
misc_deregister(ne_devs.ne_misc_dev);
ne_devs.ne_pci_dev = NULL;
ne_pci_dev_disable(pdev);
ne_teardown_msix(pdev);
pci_set_drvdata(pdev, NULL);
pci_iounmap(pdev, ne_pci_dev->iomem_base);
pci_release_regions(pdev);
pci_disable_device(pdev);
kfree(ne_pci_dev);
}
/*
* TODO: Add suspend / resume functions for power management w/ CONFIG_PM, if
* needed.
*/
/* NE PCI device driver. */
struct pci_driver ne_pci_driver = {
.name = "nitro_enclaves",
.id_table = ne_pci_ids,
.probe = ne_pci_probe,
.remove = ne_pci_remove,
.shutdown = ne_pci_shutdown,
};
@@ -0,0 +1,331 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright 2020-2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*/
#ifndef _NE_PCI_DEV_H_
#define _NE_PCI_DEV_H_
#include <linux/atomic.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/pci_ids.h>
#include <linux/wait.h>
/**
* DOC: Nitro Enclaves (NE) PCI device
*/
/**
* PCI_DEVICE_ID_NE - Nitro Enclaves PCI device id.
*/
#define PCI_DEVICE_ID_NE (0xe4c1)
/**
* PCI_BAR_NE - Nitro Enclaves PCI device MMIO BAR.
*/
#define PCI_BAR_NE (0x03)
/**
* DOC: Device registers in the NE PCI device MMIO BAR
*/
/**
* NE_ENABLE - (1 byte) Register to notify the device that the driver is using
* it (Read/Write).
*/
#define NE_ENABLE (0x0000)
#define NE_ENABLE_OFF (0x00)
#define NE_ENABLE_ON (0x01)
/**
* NE_VERSION - (2 bytes) Register to select the device run-time version
* (Read/Write).
*/
#define NE_VERSION (0x0002)
#define NE_VERSION_MAX (0x0001)
/**
* NE_COMMAND - (4 bytes) Register to notify the device what command was
* requested (Write-Only).
*/
#define NE_COMMAND (0x0004)
/**
* NE_EVTCNT - (4 bytes) Register to notify the driver that a reply or a device
* event is available (Read-Only):
* - Lower half - command reply counter
* - Higher half - out-of-band device event counter
*/
#define NE_EVTCNT (0x000c)
#define NE_EVTCNT_REPLY_SHIFT (0)
#define NE_EVTCNT_REPLY_MASK (0x0000ffff)
#define NE_EVTCNT_REPLY(cnt) (((cnt) & NE_EVTCNT_REPLY_MASK) >> \
NE_EVTCNT_REPLY_SHIFT)
#define NE_EVTCNT_EVENT_SHIFT (16)
#define NE_EVTCNT_EVENT_MASK (0xffff0000)
#define NE_EVTCNT_EVENT(cnt) (((cnt) & NE_EVTCNT_EVENT_MASK) >> \
NE_EVTCNT_EVENT_SHIFT)
/**
* NE_SEND_DATA - (240 bytes) Buffer for sending the command request payload
* (Read/Write).
*/
#define NE_SEND_DATA (0x0010)
/**
* NE_RECV_DATA - (240 bytes) Buffer for receiving the command reply payload
* (Read-Only).
*/
#define NE_RECV_DATA (0x0100)
/**
* DOC: Device MMIO buffer sizes
*/
/**
* NE_SEND_DATA_SIZE - Size of the send buffer, in bytes.
*/
#define NE_SEND_DATA_SIZE (240)
/**
* NE_RECV_DATA_SIZE - Size of the receive buffer, in bytes.
*/
#define NE_RECV_DATA_SIZE (240)
/**
* DOC: MSI-X interrupt vectors
*/
/**
* NE_VEC_REPLY - MSI-X vector used for command reply notification.
*/
#define NE_VEC_REPLY (0)
/**
* NE_VEC_EVENT - MSI-X vector used for out-of-band events e.g. enclave crash.
*/
#define NE_VEC_EVENT (1)
/**
* enum ne_pci_dev_cmd_type - Device command types.
* @INVALID_CMD: Invalid command.
* @ENCLAVE_START: Start an enclave, after setting its resources.
* @ENCLAVE_GET_SLOT: Get the slot uid of an enclave.
* @ENCLAVE_STOP: Terminate an enclave.
* @SLOT_ALLOC : Allocate a slot for an enclave.
* @SLOT_FREE: Free the slot allocated for an enclave
* @SLOT_ADD_MEM: Add a memory region to an enclave slot.
* @SLOT_ADD_VCPU: Add a vCPU to an enclave slot.
* @SLOT_COUNT : Get the number of allocated slots.
* @NEXT_SLOT: Get the next slot in the list of allocated slots.
* @SLOT_INFO: Get the info for a slot e.g. slot uid, vCPUs count.
* @SLOT_ADD_BULK_VCPUS: Add a number of vCPUs, not providing CPU ids.
* @MAX_CMD: A gatekeeper for max possible command type.
*/
enum ne_pci_dev_cmd_type {
INVALID_CMD = 0,
ENCLAVE_START = 1,
ENCLAVE_GET_SLOT = 2,
ENCLAVE_STOP = 3,
SLOT_ALLOC = 4,
SLOT_FREE = 5,
SLOT_ADD_MEM = 6,
SLOT_ADD_VCPU = 7,
SLOT_COUNT = 8,
NEXT_SLOT = 9,
SLOT_INFO = 10,
SLOT_ADD_BULK_VCPUS = 11,
MAX_CMD,
};
/**
* DOC: Device commands - payload structure for requests and replies.
*/
/**
* struct enclave_start_req - ENCLAVE_START request.
* @slot_uid: Slot unique id mapped to the enclave to start.
* @enclave_cid: Context ID (CID) for the enclave vsock device.
* If 0, CID is autogenerated.
* @flags: Flags for the enclave to start with (e.g. debug mode).
*/
struct enclave_start_req {
u64 slot_uid;
u64 enclave_cid;
u64 flags;
};
/**
* struct enclave_get_slot_req - ENCLAVE_GET_SLOT request.
* @enclave_cid: Context ID (CID) for the enclave vsock device.
*/
struct enclave_get_slot_req {
u64 enclave_cid;
};
/**
* struct enclave_stop_req - ENCLAVE_STOP request.
* @slot_uid: Slot unique id mapped to the enclave to stop.
*/
struct enclave_stop_req {
u64 slot_uid;
};
/**
* struct slot_alloc_req - SLOT_ALLOC request.
* @unused: In order to avoid weird sizeof edge cases.
*/
struct slot_alloc_req {
u8 unused;
};
/**
* struct slot_free_req - SLOT_FREE request.
* @slot_uid: Slot unique id mapped to the slot to free.
*/
struct slot_free_req {
u64 slot_uid;
};
/* TODO: Add flags field to the request to add memory region. */
/**
* struct slot_add_mem_req - SLOT_ADD_MEM request.
* @slot_uid: Slot unique id mapped to the slot to add the memory region to.
* @paddr: Physical address of the memory region to add to the slot.
* @size: Memory size, in bytes, of the memory region to add to the slot.
*/
struct slot_add_mem_req {
u64 slot_uid;
u64 paddr;
u64 size;
};
/**
* struct slot_add_vcpu_req - SLOT_ADD_VCPU request.
* @slot_uid: Slot unique id mapped to the slot to add the vCPU to.
* @vcpu_id: vCPU ID of the CPU to add to the enclave.
* @padding: Padding for the overall data structure.
*/
struct slot_add_vcpu_req {
u64 slot_uid;
u32 vcpu_id;
u8 padding[4];
};
/**
* struct slot_count_req - SLOT_COUNT request.
* @unused: In order to avoid weird sizeof edge cases.
*/
struct slot_count_req {
u8 unused;
};
/**
* struct next_slot_req - NEXT_SLOT request.
* @slot_uid: Slot unique id of the next slot in the iteration.
*/
struct next_slot_req {
u64 slot_uid;
};
/**
* struct slot_info_req - SLOT_INFO request.
* @slot_uid: Slot unique id mapped to the slot to get information about.
*/
struct slot_info_req {
u64 slot_uid;
};
/**
* struct slot_add_bulk_vcpus_req - SLOT_ADD_BULK_VCPUS request.
* @slot_uid: Slot unique id mapped to the slot to add vCPUs to.
* @nr_vcpus: Number of vCPUs to add to the slot.
*/
struct slot_add_bulk_vcpus_req {
u64 slot_uid;
u64 nr_vcpus;
};
/**
* struct ne_pci_dev_cmd_reply - NE PCI device command reply.
* @rc : Return code of the logic that processed the request.
* @padding0: Padding for the overall data structure.
* @slot_uid: Valid for all commands except SLOT_COUNT.
* @enclave_cid: Valid for ENCLAVE_START command.
* @slot_count : Valid for SLOT_COUNT command.
* @mem_regions: Valid for SLOT_ALLOC and SLOT_INFO commands.
* @mem_size: Valid for SLOT_INFO command.
* @nr_vcpus: Valid for SLOT_INFO command.
* @flags: Valid for SLOT_INFO command.
* @state: Valid for SLOT_INFO command.
* @padding1: Padding for the overall data structure.
*/
struct ne_pci_dev_cmd_reply {
s32 rc;
u8 padding0[4];
u64 slot_uid;
u64 enclave_cid;
u64 slot_count;
u64 mem_regions;
u64 mem_size;
u64 nr_vcpus;
u64 flags;
u16 state;
u8 padding1[6];
};
/**
* struct ne_pci_dev - Nitro Enclaves (NE) PCI device.
* @cmd_reply_avail: Variable set if a reply has been sent by the
* PCI device.
* @cmd_reply_wait_q: Wait queue for handling command reply from the
* PCI device.
* @enclaves_list: List of the enclaves managed by the PCI device.
* @enclaves_list_mutex: Mutex for accessing the list of enclaves.
* @event_wq: Work queue for handling out-of-band events
* triggered by the Nitro Hypervisor which require
* enclave state scanning and propagation to the
* enclave process.
* @iomem_base : MMIO region of the PCI device.
* @notify_work: Work item for every received out-of-band event.
* @pci_dev_mutex: Mutex for accessing the PCI device MMIO space.
* @pdev: PCI device data structure.
*/
struct ne_pci_dev {
atomic_t cmd_reply_avail;
wait_queue_head_t cmd_reply_wait_q;
struct list_head enclaves_list;
struct mutex enclaves_list_mutex;
struct workqueue_struct *event_wq;
void __iomem *iomem_base;
struct work_struct notify_work;
struct mutex pci_dev_mutex;
struct pci_dev *pdev;
};
/**
* ne_do_request() - Submit command request to the PCI device based on the command
* type and retrieve the associated reply.
* @pdev: PCI device to send the command to and receive the reply from.
* @cmd_type: Command type of the request sent to the PCI device.
* @cmd_request: Command request payload.
* @cmd_request_size: Size of the command request payload.
* @cmd_reply: Command reply payload.
* @cmd_reply_size: Size of the command reply payload.
*
* Context: Process context. This function uses the ne_pci_dev mutex to handle
* one command at a time.
* Return:
* * 0 on success.
* * Negative return value on failure.
*/
int ne_do_request(struct pci_dev *pdev, enum ne_pci_dev_cmd_type cmd_type,
void *cmd_request, size_t cmd_request_size,
struct ne_pci_dev_cmd_reply *cmd_reply,
size_t cmd_reply_size);
/* Nitro Enclaves (NE) PCI device driver */
extern struct pci_driver ne_pci_driver;
#endif /* _NE_PCI_DEV_H_ */
@@ -0,0 +1,20 @@
# SPDX-License-Identifier: GPL-2.0-only
config VBOXGUEST
tristate "Virtual Box Guest integration support"
depends on (ARM64 || X86 || COMPILE_TEST) && PCI && INPUT
depends on HAS_IOPORT
help
This is a driver for the Virtual Box Guest PCI device used in
Virtual Box virtual machines. Enabling this driver will add
support for Virtual Box Guest integration features such as
copy-and-paste, seamless mode and OpenGL pass-through.
This driver also offers vboxguest IPC functionality which is needed
for the vboxfs driver which offers folder sharing support.
If you enable this driver you should also enable the VBOXVIDEO option.
Although it is possible to build this module in, it is advised
to build this driver as a module, so that it can be updated
independently of the kernel. Select M to build this driver as a
module.
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
vboxguest-y := vboxguest_linux.o vboxguest_core.o vboxguest_utils.o
obj-$(CONFIG_VBOXGUEST) += vboxguest.o
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,214 @@
/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */
/* Copyright (C) 2010-2016 Oracle Corporation */
#ifndef __VBOXGUEST_CORE_H__
#define __VBOXGUEST_CORE_H__
#include <linux/input.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/miscdevice.h>
#include <linux/spinlock.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <linux/vboxguest.h>
#include "vmmdev.h"
/*
* The mainline kernel version (this version) of the vboxguest module
* contained a bug where it defined VBGL_IOCTL_VMMDEV_REQUEST_BIG and
* VBGL_IOCTL_LOG using _IOC(_IOC_READ | _IOC_WRITE, 'V', ...) instead
* of _IO(V, ...) as the out of tree VirtualBox upstream version does.
*
* These _ALT definitions keep compatibility with the wrong defines the
* mainline kernel version used for a while.
* Note the VirtualBox userspace bits have always been built against
* VirtualBox upstream's headers, so this is likely not necessary. But
* we must never break our ABI so we keep these around to be 100% sure.
*/
#define VBG_IOCTL_VMMDEV_REQUEST_BIG_ALT _IOC(_IOC_READ | _IOC_WRITE, 'V', 3, 0)
#define VBG_IOCTL_LOG_ALT(s) _IOC(_IOC_READ | _IOC_WRITE, 'V', 9, s)
struct vbg_session;
/** VBox guest memory balloon. */
struct vbg_mem_balloon {
/** Work handling VMMDEV_EVENT_BALLOON_CHANGE_REQUEST events */
struct work_struct work;
/** Pre-allocated vmmdev_memballoon_info req for query */
struct vmmdev_memballoon_info *get_req;
/** Pre-allocated vmmdev_memballoon_change req for inflate / deflate */
struct vmmdev_memballoon_change *change_req;
/** The current number of chunks in the balloon. */
u32 chunks;
/** The maximum number of chunks in the balloon. */
u32 max_chunks;
/**
* Array of pointers to page arrays. A page * array is allocated for
* each chunk when inflating, and freed when the deflating.
*/
struct page ***pages;
};
/**
* Per bit usage tracker for a u32 mask.
*
* Used for optimal handling of guest properties and event filter.
*/
struct vbg_bit_usage_tracker {
/** Per bit usage counters. */
u32 per_bit_usage[32];
/** The current mask according to per_bit_usage. */
u32 mask;
};
/** VBox guest device (data) extension. */
struct vbg_dev {
struct device *dev;
/** The base of the adapter I/O ports. */
u16 io_port;
/** Pointer to the mapping of the VMMDev adapter memory. */
struct vmmdev_memory *mmio;
/** Host version */
char host_version[64];
/** Host features */
unsigned int host_features;
/**
* Dummy page and vmap address for reserved kernel virtual-address
* space for the guest mappings, only used on hosts lacking vtx.
*/
struct page *guest_mappings_dummy_page;
void *guest_mappings;
/** Spinlock protecting pending_events. */
spinlock_t event_spinlock;
/** Preallocated struct vmmdev_events for the IRQ handler. */
struct vmmdev_events *ack_events_req;
/** Wait-for-event list for threads waiting for multiple events. */
wait_queue_head_t event_wq;
/** Mask of pending events. */
u32 pending_events;
/** Wait-for-event list for threads waiting on HGCM async completion. */
wait_queue_head_t hgcm_wq;
/** Pre-allocated hgcm cancel2 req. for cancellation on timeout */
struct vmmdev_hgcm_cancel2 *cancel_req;
/** Mutex protecting cancel_req accesses */
struct mutex cancel_req_mutex;
/** Pre-allocated mouse-status request for the input-device handling. */
struct vmmdev_mouse_status *mouse_status_req;
/** Input device for reporting abs mouse coordinates to the guest. */
struct input_dev *input;
/** Memory balloon information. */
struct vbg_mem_balloon mem_balloon;
/** Lock for session related items in vbg_dev and vbg_session */
struct mutex session_mutex;
/** Events we won't permit anyone to filter out. */
u32 fixed_events;
/**
* Usage counters for the host events (excludes fixed events),
* Protected by session_mutex.
*/
struct vbg_bit_usage_tracker event_filter_tracker;
/**
* The event filter last reported to the host (or UINT32_MAX).
* Protected by session_mutex.
*/
u32 event_filter_host;
/**
* Guest capabilities which have been switched to acquire_mode.
*/
u32 acquire_mode_guest_caps;
/**
* Guest capabilities acquired by vbg_acquire_session_capabilities().
* Only one session can acquire a capability at a time.
*/
u32 acquired_guest_caps;
/**
* Usage counters for guest capabilities requested through
* vbg_set_session_capabilities(). Indexed by capability bit
* number, one count per session using a capability.
* Protected by session_mutex.
*/
struct vbg_bit_usage_tracker set_guest_caps_tracker;
/**
* The guest capabilities last reported to the host (or UINT32_MAX).
* Protected by session_mutex.
*/
u32 guest_caps_host;
/**
* Heartbeat timer which fires with interval
* cNsHearbeatInterval and its handler sends
* VMMDEVREQ_GUEST_HEARTBEAT to VMMDev.
*/
struct timer_list heartbeat_timer;
/** Heartbeat timer interval in ms. */
int heartbeat_interval_ms;
/** Preallocated VMMDEVREQ_GUEST_HEARTBEAT request. */
struct vmmdev_request_header *guest_heartbeat_req;
/** "vboxguest" char-device */
struct miscdevice misc_device;
/** "vboxuser" char-device */
struct miscdevice misc_device_user;
};
/** The VBoxGuest per session data. */
struct vbg_session {
/** Pointer to the device extension. */
struct vbg_dev *gdev;
/**
* Array containing HGCM client IDs associated with this session.
* These will be automatically disconnected when the session is closed.
* Protected by vbg_gdev.session_mutex.
*/
u32 hgcm_client_ids[64];
/**
* Host events requested by the session.
* An event type requested in any guest session will be added to the
* host filter. Protected by vbg_gdev.session_mutex.
*/
u32 event_filter;
/**
* Guest capabilities acquired by vbg_acquire_session_capabilities().
* Only one session can acquire a capability at a time.
*/
u32 acquired_guest_caps;
/**
* Guest capabilities set through vbg_set_session_capabilities().
* A capability claimed by any guest session will be reported to the
* host. Protected by vbg_gdev.session_mutex.
*/
u32 set_guest_caps;
/** VMMDEV_REQUESTOR_* flags */
u32 requestor;
/** Set on CANCEL_ALL_WAITEVENTS, protected by vbg_devevent_spinlock. */
bool cancel_waiters;
};
int vbg_core_init(struct vbg_dev *gdev, u32 fixed_events);
void vbg_core_exit(struct vbg_dev *gdev);
struct vbg_session *vbg_core_open_session(struct vbg_dev *gdev, u32 requestor);
void vbg_core_close_session(struct vbg_session *session);
int vbg_core_ioctl(struct vbg_session *session, unsigned int req, void *data);
int vbg_core_set_mouse_status(struct vbg_dev *gdev, u32 features);
irqreturn_t vbg_core_isr(int irq, void *dev_id);
void vbg_linux_mouse_event(struct vbg_dev *gdev);
/* Private (non exported) functions form vboxguest_utils.c */
void *vbg_req_alloc(size_t len, enum vmmdev_request_type req_type,
u32 requestor);
void vbg_req_free(void *req, size_t len);
int vbg_req_perform(struct vbg_dev *gdev, void *req);
int vbg_hgcm_call32(
struct vbg_dev *gdev, u32 requestor, u32 client_id, u32 function,
u32 timeout_ms, struct vmmdev_hgcm_function_parameter32 *parm32,
u32 parm_count, int *vbox_status);
#endif
@@ -0,0 +1,502 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* vboxguest linux pci driver, char-dev and input-device code,
*
* Copyright (C) 2006-2016 Oracle Corporation
*/
#include <linux/cred.h>
#include <linux/input.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/poll.h>
#include <linux/vbox_utils.h>
#include "vboxguest_core.h"
/** The device name. */
#define DEVICE_NAME "vboxguest"
/** The device name for the device node open to everyone. */
#define DEVICE_NAME_USER "vboxuser"
/** VirtualBox PCI vendor ID. */
#define VBOX_VENDORID 0x80ee
/** VMMDev PCI card product ID. */
#define VMMDEV_DEVICEID 0xcafe
/** Mutex protecting the global vbg_gdev pointer used by vbg_get/put_gdev. */
static DEFINE_MUTEX(vbg_gdev_mutex);
/** Global vbg_gdev pointer used by vbg_get/put_gdev. */
static struct vbg_dev *vbg_gdev;
static u32 vbg_misc_device_requestor(struct inode *inode)
{
u32 requestor = VMMDEV_REQUESTOR_USERMODE |
VMMDEV_REQUESTOR_CON_DONT_KNOW |
VMMDEV_REQUESTOR_TRUST_NOT_GIVEN;
if (from_kuid(current_user_ns(), current_uid()) == 0)
requestor |= VMMDEV_REQUESTOR_USR_ROOT;
else
requestor |= VMMDEV_REQUESTOR_USR_USER;
if (in_egroup_p(inode->i_gid))
requestor |= VMMDEV_REQUESTOR_GRP_VBOX;
return requestor;
}
static int vbg_misc_device_open(struct inode *inode, struct file *filp)
{
struct vbg_session *session;
struct vbg_dev *gdev;
/* misc_open sets filp->private_data to our misc device */
gdev = container_of(filp->private_data, struct vbg_dev, misc_device);
session = vbg_core_open_session(gdev, vbg_misc_device_requestor(inode));
if (IS_ERR(session))
return PTR_ERR(session);
filp->private_data = session;
return 0;
}
static int vbg_misc_device_user_open(struct inode *inode, struct file *filp)
{
struct vbg_session *session;
struct vbg_dev *gdev;
/* misc_open sets filp->private_data to our misc device */
gdev = container_of(filp->private_data, struct vbg_dev,
misc_device_user);
session = vbg_core_open_session(gdev, vbg_misc_device_requestor(inode) |
VMMDEV_REQUESTOR_USER_DEVICE);
if (IS_ERR(session))
return PTR_ERR(session);
filp->private_data = session;
return 0;
}
/**
* vbg_misc_device_close - Close device.
* @inode: Pointer to inode info structure.
* @filp: Associated file pointer.
*
* Return: %0 on success, negated errno on failure.
*/
static int vbg_misc_device_close(struct inode *inode, struct file *filp)
{
vbg_core_close_session(filp->private_data);
filp->private_data = NULL;
return 0;
}
/**
* vbg_misc_device_ioctl - Device I/O Control entry point.
* @filp: Associated file pointer.
* @req: The request specified to ioctl().
* @arg: The argument specified to ioctl().
*
* Return: %0 on success, negated errno on failure.
*/
static long vbg_misc_device_ioctl(struct file *filp, unsigned int req,
unsigned long arg)
{
struct vbg_session *session = filp->private_data;
size_t returned_size, size;
struct vbg_ioctl_hdr hdr;
bool is_vmmdev_req;
int ret = 0;
void *buf;
if (copy_from_user(&hdr, (void *)arg, sizeof(hdr)))
return -EFAULT;
if (hdr.version != VBG_IOCTL_HDR_VERSION)
return -EINVAL;
if (hdr.size_in < sizeof(hdr) ||
(hdr.size_out && hdr.size_out < sizeof(hdr)))
return -EINVAL;
size = max(hdr.size_in, hdr.size_out);
if (_IOC_SIZE(req) && _IOC_SIZE(req) != size)
return -EINVAL;
if (size > SZ_16M)
return -E2BIG;
/*
* IOCTL_VMMDEV_REQUEST needs the buffer to be below 4G to avoid
* the need for a bounce-buffer and another copy later on.
*/
is_vmmdev_req = (req & ~IOCSIZE_MASK) == VBG_IOCTL_VMMDEV_REQUEST(0) ||
req == VBG_IOCTL_VMMDEV_REQUEST_BIG ||
req == VBG_IOCTL_VMMDEV_REQUEST_BIG_ALT;
if (is_vmmdev_req)
buf = vbg_req_alloc(size, VBG_IOCTL_HDR_TYPE_DEFAULT,
session->requestor);
else
buf = kmalloc(size, GFP_KERNEL);
if (!buf)
return -ENOMEM;
*((struct vbg_ioctl_hdr *)buf) = hdr;
if (copy_from_user(buf + sizeof(hdr), (void *)arg + sizeof(hdr),
hdr.size_in - sizeof(hdr))) {
ret = -EFAULT;
goto out;
}
if (hdr.size_in < size)
memset(buf + hdr.size_in, 0, size - hdr.size_in);
ret = vbg_core_ioctl(session, req, buf);
if (ret)
goto out;
returned_size = ((struct vbg_ioctl_hdr *)buf)->size_out;
if (returned_size > size) {
vbg_debug("%s: too much output data %zu > %zu\n",
__func__, returned_size, size);
returned_size = size;
}
if (copy_to_user((void *)arg, buf, returned_size) != 0)
ret = -EFAULT;
out:
if (is_vmmdev_req)
vbg_req_free(buf, size);
else
kfree(buf);
return ret;
}
/* The file_operations structures. */
static const struct file_operations vbg_misc_device_fops = {
.owner = THIS_MODULE,
.open = vbg_misc_device_open,
.release = vbg_misc_device_close,
.unlocked_ioctl = vbg_misc_device_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vbg_misc_device_ioctl,
#endif
};
static const struct file_operations vbg_misc_device_user_fops = {
.owner = THIS_MODULE,
.open = vbg_misc_device_user_open,
.release = vbg_misc_device_close,
.unlocked_ioctl = vbg_misc_device_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = vbg_misc_device_ioctl,
#endif
};
/*
* Called when the input device is first opened.
*
* Sets up absolute mouse reporting.
*/
static int vbg_input_open(struct input_dev *input)
{
struct vbg_dev *gdev = input_get_drvdata(input);
u32 feat = VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE | VMMDEV_MOUSE_NEW_PROTOCOL;
return vbg_core_set_mouse_status(gdev, feat);
}
/*
* Called if all open handles to the input device are closed.
*
* Disables absolute reporting.
*/
static void vbg_input_close(struct input_dev *input)
{
struct vbg_dev *gdev = input_get_drvdata(input);
vbg_core_set_mouse_status(gdev, 0);
}
/*
* Creates the kernel input device.
*
* Return: 0 on success, negated errno on failure.
*/
static int vbg_create_input_device(struct vbg_dev *gdev)
{
struct input_dev *input;
input = devm_input_allocate_device(gdev->dev);
if (!input)
return -ENOMEM;
input->id.bustype = BUS_PCI;
input->id.vendor = VBOX_VENDORID;
input->id.product = VMMDEV_DEVICEID;
input->open = vbg_input_open;
input->close = vbg_input_close;
input->dev.parent = gdev->dev;
input->name = "VirtualBox mouse integration";
input_set_abs_params(input, ABS_X, VMMDEV_MOUSE_RANGE_MIN,
VMMDEV_MOUSE_RANGE_MAX, 0, 0);
input_set_abs_params(input, ABS_Y, VMMDEV_MOUSE_RANGE_MIN,
VMMDEV_MOUSE_RANGE_MAX, 0, 0);
input_set_capability(input, EV_KEY, BTN_MOUSE);
input_set_drvdata(input, gdev);
gdev->input = input;
return input_register_device(gdev->input);
}
static ssize_t host_version_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct vbg_dev *gdev = dev_get_drvdata(dev);
return sprintf(buf, "%s\n", gdev->host_version);
}
static ssize_t host_features_show(struct device *dev,
struct device_attribute *attr, char *buf)
{
struct vbg_dev *gdev = dev_get_drvdata(dev);
return sprintf(buf, "%#x\n", gdev->host_features);
}
static DEVICE_ATTR_RO(host_version);
static DEVICE_ATTR_RO(host_features);
static struct attribute *vbg_pci_attrs[] = {
&dev_attr_host_version.attr,
&dev_attr_host_features.attr,
NULL,
};
ATTRIBUTE_GROUPS(vbg_pci);
/*
* Does the PCI detection and init of the device.
*
* Return: 0 on success, negated errno on failure.
*/
static int vbg_pci_probe(struct pci_dev *pci, const struct pci_device_id *id)
{
struct device *dev = &pci->dev;
resource_size_t io, io_len, mmio, mmio_len;
struct vmmdev_memory *vmmdev;
struct vbg_dev *gdev;
int ret;
gdev = devm_kzalloc(dev, sizeof(*gdev), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
ret = pci_enable_device(pci);
if (ret != 0) {
vbg_err("vboxguest: Error enabling device: %d\n", ret);
return ret;
}
ret = -ENODEV;
io = pci_resource_start(pci, 0);
io_len = pci_resource_len(pci, 0);
if (!io || !io_len) {
vbg_err("vboxguest: Error IO-port resource (0) is missing\n");
goto err_disable_pcidev;
}
if (devm_request_region(dev, io, io_len, DEVICE_NAME) == NULL) {
vbg_err("vboxguest: Error could not claim IO resource\n");
ret = -EBUSY;
goto err_disable_pcidev;
}
mmio = pci_resource_start(pci, 1);
mmio_len = pci_resource_len(pci, 1);
if (!mmio || !mmio_len) {
vbg_err("vboxguest: Error MMIO resource (1) is missing\n");
goto err_disable_pcidev;
}
if (devm_request_mem_region(dev, mmio, mmio_len, DEVICE_NAME) == NULL) {
vbg_err("vboxguest: Error could not claim MMIO resource\n");
ret = -EBUSY;
goto err_disable_pcidev;
}
vmmdev = devm_ioremap(dev, mmio, mmio_len);
if (!vmmdev) {
vbg_err("vboxguest: Error ioremap failed; MMIO addr=%pap size=%pap\n",
&mmio, &mmio_len);
goto err_disable_pcidev;
}
/* Validate MMIO region version and size. */
if (vmmdev->version != VMMDEV_MEMORY_VERSION ||
vmmdev->size < 32 || vmmdev->size > mmio_len) {
vbg_err("vboxguest: Bogus VMMDev memory; version=%08x (expected %08x) size=%d (expected <= %d)\n",
vmmdev->version, VMMDEV_MEMORY_VERSION,
vmmdev->size, (int)mmio_len);
goto err_disable_pcidev;
}
gdev->io_port = io;
gdev->mmio = vmmdev;
gdev->dev = dev;
gdev->misc_device.minor = MISC_DYNAMIC_MINOR;
gdev->misc_device.name = DEVICE_NAME;
gdev->misc_device.fops = &vbg_misc_device_fops;
gdev->misc_device_user.minor = MISC_DYNAMIC_MINOR;
gdev->misc_device_user.name = DEVICE_NAME_USER;
gdev->misc_device_user.fops = &vbg_misc_device_user_fops;
ret = vbg_core_init(gdev, VMMDEV_EVENT_MOUSE_POSITION_CHANGED);
if (ret)
goto err_disable_pcidev;
ret = vbg_create_input_device(gdev);
if (ret) {
vbg_err("vboxguest: Error creating input device: %d\n", ret);
goto err_vbg_core_exit;
}
ret = request_irq(pci->irq, vbg_core_isr, IRQF_SHARED, DEVICE_NAME,
gdev);
if (ret) {
vbg_err("vboxguest: Error requesting irq: %d\n", ret);
goto err_vbg_core_exit;
}
ret = misc_register(&gdev->misc_device);
if (ret) {
vbg_err("vboxguest: Error misc_register %s failed: %d\n",
DEVICE_NAME, ret);
goto err_free_irq;
}
ret = misc_register(&gdev->misc_device_user);
if (ret) {
vbg_err("vboxguest: Error misc_register %s failed: %d\n",
DEVICE_NAME_USER, ret);
goto err_unregister_misc_device;
}
mutex_lock(&vbg_gdev_mutex);
if (!vbg_gdev)
vbg_gdev = gdev;
else
ret = -EBUSY;
mutex_unlock(&vbg_gdev_mutex);
if (ret) {
vbg_err("vboxguest: Error more then 1 vbox guest pci device\n");
goto err_unregister_misc_device_user;
}
pci_set_drvdata(pci, gdev);
return 0;
err_unregister_misc_device_user:
misc_deregister(&gdev->misc_device_user);
err_unregister_misc_device:
misc_deregister(&gdev->misc_device);
err_free_irq:
free_irq(pci->irq, gdev);
err_vbg_core_exit:
vbg_core_exit(gdev);
err_disable_pcidev:
pci_disable_device(pci);
return ret;
}
static void vbg_pci_remove(struct pci_dev *pci)
{
struct vbg_dev *gdev = pci_get_drvdata(pci);
mutex_lock(&vbg_gdev_mutex);
vbg_gdev = NULL;
mutex_unlock(&vbg_gdev_mutex);
free_irq(pci->irq, gdev);
misc_deregister(&gdev->misc_device_user);
misc_deregister(&gdev->misc_device);
vbg_core_exit(gdev);
pci_disable_device(pci);
}
struct vbg_dev *vbg_get_gdev(void)
{
mutex_lock(&vbg_gdev_mutex);
/*
* Note on success we keep the mutex locked until vbg_put_gdev(),
* this stops vbg_pci_remove from removing the device from underneath
* vboxsf. vboxsf will only hold a reference for a short while.
*/
if (vbg_gdev)
return vbg_gdev;
mutex_unlock(&vbg_gdev_mutex);
return ERR_PTR(-ENODEV);
}
EXPORT_SYMBOL(vbg_get_gdev);
void vbg_put_gdev(struct vbg_dev *gdev)
{
WARN_ON(gdev != vbg_gdev);
mutex_unlock(&vbg_gdev_mutex);
}
EXPORT_SYMBOL(vbg_put_gdev);
/*
* Callback for mouse events.
*
* This is called at the end of the ISR, after leaving the event spinlock, if
* VMMDEV_EVENT_MOUSE_POSITION_CHANGED was raised by the host.
*
* @gdev: The device extension.
*/
void vbg_linux_mouse_event(struct vbg_dev *gdev)
{
int rc;
/* Report events to the kernel input device */
gdev->mouse_status_req->mouse_features = 0;
gdev->mouse_status_req->pointer_pos_x = 0;
gdev->mouse_status_req->pointer_pos_y = 0;
rc = vbg_req_perform(gdev, gdev->mouse_status_req);
if (rc >= 0) {
input_report_abs(gdev->input, ABS_X,
gdev->mouse_status_req->pointer_pos_x);
input_report_abs(gdev->input, ABS_Y,
gdev->mouse_status_req->pointer_pos_y);
input_sync(gdev->input);
}
}
static const struct pci_device_id vbg_pci_ids[] = {
{ .vendor = VBOX_VENDORID, .device = VMMDEV_DEVICEID },
{}
};
MODULE_DEVICE_TABLE(pci, vbg_pci_ids);
static struct pci_driver vbg_pci_driver = {
.name = DEVICE_NAME,
.dev_groups = vbg_pci_groups,
.id_table = vbg_pci_ids,
.probe = vbg_pci_probe,
.remove = vbg_pci_remove,
};
module_pci_driver(vbg_pci_driver);
MODULE_AUTHOR("Oracle Corporation");
MODULE_DESCRIPTION("Oracle VM VirtualBox Guest Additions for Linux Module");
MODULE_LICENSE("GPL");
@@ -0,0 +1,834 @@
/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */
/*
* vboxguest vmm-req and hgcm-call code, VBoxGuestR0LibHGCMInternal.cpp,
* VBoxGuestR0LibGenericRequest.cpp and RTErrConvertToErrno.cpp in vbox svn.
*
* Copyright (C) 2006-2016 Oracle Corporation
*/
#include <linux/errno.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/sizes.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/vmalloc.h>
#include <linux/vbox_err.h>
#include <linux/vbox_utils.h>
#include "vboxguest_core.h"
/* Get the pointer to the first parameter of a HGCM call request. */
#define VMMDEV_HGCM_CALL_PARMS(a) \
((struct vmmdev_hgcm_function_parameter *)( \
(u8 *)(a) + sizeof(struct vmmdev_hgcm_call)))
/* The max parameter buffer size for a user request. */
#define VBG_MAX_HGCM_USER_PARM (24 * SZ_1M)
/* The max parameter buffer size for a kernel request. */
#define VBG_MAX_HGCM_KERNEL_PARM (16 * SZ_1M)
#define VBG_DEBUG_PORT 0x504
/* This protects vbg_log_buf and serializes VBG_DEBUG_PORT accesses */
static DEFINE_SPINLOCK(vbg_log_lock);
static char vbg_log_buf[128];
#define VBG_LOG(name, pr_func) \
void name(const char *fmt, ...) \
{ \
unsigned long flags; \
va_list args; \
int i, count; \
\
va_start(args, fmt); \
spin_lock_irqsave(&vbg_log_lock, flags); \
\
count = vscnprintf(vbg_log_buf, sizeof(vbg_log_buf), fmt, args);\
for (i = 0; i < count; i++) \
outb(vbg_log_buf[i], VBG_DEBUG_PORT); \
\
pr_func("%s", vbg_log_buf); \
\
spin_unlock_irqrestore(&vbg_log_lock, flags); \
va_end(args); \
} \
EXPORT_SYMBOL(name)
VBG_LOG(vbg_info, pr_info);
VBG_LOG(vbg_warn, pr_warn);
VBG_LOG(vbg_err, pr_err);
VBG_LOG(vbg_err_ratelimited, pr_err_ratelimited);
#if defined(DEBUG) && !defined(CONFIG_DYNAMIC_DEBUG)
VBG_LOG(vbg_debug, pr_debug);
#endif
void *vbg_req_alloc(size_t len, enum vmmdev_request_type req_type,
u32 requestor)
{
struct vmmdev_request_header *req;
int order = get_order(PAGE_ALIGN(len));
req = (void *)__get_free_pages(GFP_KERNEL | GFP_DMA32, order);
if (!req)
return NULL;
memset(req, 0xaa, len);
req->size = len;
req->version = VMMDEV_REQUEST_HEADER_VERSION;
req->request_type = req_type;
req->rc = VERR_GENERAL_FAILURE;
req->reserved1 = 0;
req->requestor = requestor;
return req;
}
void vbg_req_free(void *req, size_t len)
{
if (!req)
return;
free_pages((unsigned long)req, get_order(PAGE_ALIGN(len)));
}
/* Note this function returns a VBox status code, not a negative errno!! */
int vbg_req_perform(struct vbg_dev *gdev, void *req)
{
unsigned long phys_req = virt_to_phys(req);
outl(phys_req, gdev->io_port + VMMDEV_PORT_OFF_REQUEST);
/*
* The host changes the request as a result of the outl, make sure
* the outl and any reads of the req happen in the correct order.
*/
mb();
return ((struct vmmdev_request_header *)req)->rc;
}
static bool hgcm_req_done(struct vbg_dev *gdev,
struct vmmdev_hgcmreq_header *header)
{
unsigned long flags;
bool done;
spin_lock_irqsave(&gdev->event_spinlock, flags);
done = header->flags & VMMDEV_HGCM_REQ_DONE;
spin_unlock_irqrestore(&gdev->event_spinlock, flags);
return done;
}
int vbg_hgcm_connect(struct vbg_dev *gdev, u32 requestor,
struct vmmdev_hgcm_service_location *loc,
u32 *client_id, int *vbox_status)
{
struct vmmdev_hgcm_connect *hgcm_connect = NULL;
int rc;
hgcm_connect = vbg_req_alloc(sizeof(*hgcm_connect),
VMMDEVREQ_HGCM_CONNECT, requestor);
if (!hgcm_connect)
return -ENOMEM;
hgcm_connect->header.flags = 0;
memcpy(&hgcm_connect->loc, loc, sizeof(*loc));
hgcm_connect->client_id = 0;
rc = vbg_req_perform(gdev, hgcm_connect);
if (rc == VINF_HGCM_ASYNC_EXECUTE)
wait_event(gdev->hgcm_wq,
hgcm_req_done(gdev, &hgcm_connect->header));
if (rc >= 0) {
*client_id = hgcm_connect->client_id;
rc = hgcm_connect->header.result;
}
vbg_req_free(hgcm_connect, sizeof(*hgcm_connect));
*vbox_status = rc;
return 0;
}
EXPORT_SYMBOL(vbg_hgcm_connect);
int vbg_hgcm_disconnect(struct vbg_dev *gdev, u32 requestor,
u32 client_id, int *vbox_status)
{
struct vmmdev_hgcm_disconnect *hgcm_disconnect = NULL;
int rc;
hgcm_disconnect = vbg_req_alloc(sizeof(*hgcm_disconnect),
VMMDEVREQ_HGCM_DISCONNECT,
requestor);
if (!hgcm_disconnect)
return -ENOMEM;
hgcm_disconnect->header.flags = 0;
hgcm_disconnect->client_id = client_id;
rc = vbg_req_perform(gdev, hgcm_disconnect);
if (rc == VINF_HGCM_ASYNC_EXECUTE)
wait_event(gdev->hgcm_wq,
hgcm_req_done(gdev, &hgcm_disconnect->header));
if (rc >= 0)
rc = hgcm_disconnect->header.result;
vbg_req_free(hgcm_disconnect, sizeof(*hgcm_disconnect));
*vbox_status = rc;
return 0;
}
EXPORT_SYMBOL(vbg_hgcm_disconnect);
static u32 hgcm_call_buf_size_in_pages(void *buf, u32 len)
{
u32 size = PAGE_ALIGN(len + ((unsigned long)buf & ~PAGE_MASK));
return size >> PAGE_SHIFT;
}
static void hgcm_call_add_pagelist_size(void *buf, u32 len, size_t *extra)
{
u32 page_count;
page_count = hgcm_call_buf_size_in_pages(buf, len);
*extra += offsetof(struct vmmdev_hgcm_pagelist, pages[page_count]);
}
static int hgcm_call_preprocess_linaddr(
const struct vmmdev_hgcm_function_parameter *src_parm,
void **bounce_buf_ret, size_t *extra)
{
void *buf, *bounce_buf;
bool copy_in;
u32 len;
int ret;
buf = (void *)src_parm->u.pointer.u.linear_addr;
len = src_parm->u.pointer.size;
copy_in = src_parm->type != VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT;
if (len > VBG_MAX_HGCM_USER_PARM)
return -E2BIG;
bounce_buf = kvmalloc(len, GFP_KERNEL);
if (!bounce_buf)
return -ENOMEM;
*bounce_buf_ret = bounce_buf;
if (copy_in) {
ret = copy_from_user(bounce_buf, (void __user *)buf, len);
if (ret)
return -EFAULT;
} else {
memset(bounce_buf, 0, len);
}
hgcm_call_add_pagelist_size(bounce_buf, len, extra);
return 0;
}
/**
* hgcm_call_preprocess - Preprocesses the HGCM call, validate parameters,
* alloc bounce buffers and figure out how much extra storage we need for
* page lists.
* @src_parm: Pointer to source function call parameters
* @parm_count: Number of function call parameters.
* @bounce_bufs_ret: Where to return the allocated bouncebuffer array
* @extra: Where to return the extra request space needed for
* physical page lists.
*
* Return: %0 or negative errno value.
*/
static int hgcm_call_preprocess(
const struct vmmdev_hgcm_function_parameter *src_parm,
u32 parm_count, void ***bounce_bufs_ret, size_t *extra)
{
void *buf, **bounce_bufs = NULL;
u32 i, len;
int ret;
for (i = 0; i < parm_count; i++, src_parm++) {
switch (src_parm->type) {
case VMMDEV_HGCM_PARM_TYPE_32BIT:
case VMMDEV_HGCM_PARM_TYPE_64BIT:
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT:
if (!bounce_bufs) {
bounce_bufs = kcalloc(parm_count,
sizeof(void *),
GFP_KERNEL);
if (!bounce_bufs)
return -ENOMEM;
*bounce_bufs_ret = bounce_bufs;
}
ret = hgcm_call_preprocess_linaddr(src_parm,
&bounce_bufs[i],
extra);
if (ret)
return ret;
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT:
buf = (void *)src_parm->u.pointer.u.linear_addr;
len = src_parm->u.pointer.size;
if (WARN_ON(len > VBG_MAX_HGCM_KERNEL_PARM))
return -E2BIG;
hgcm_call_add_pagelist_size(buf, len, extra);
break;
default:
return -EINVAL;
}
}
return 0;
}
/**
* hgcm_call_linear_addr_type_to_pagelist_flags - Translates linear address
* types to page list direction flags.
* @type: The type.
*
* Return: page list flags.
*/
static u32 hgcm_call_linear_addr_type_to_pagelist_flags(
enum vmmdev_hgcm_function_parameter_type type)
{
switch (type) {
default:
WARN_ON(1);
fallthrough;
case VMMDEV_HGCM_PARM_TYPE_LINADDR:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL:
return VMMDEV_HGCM_F_PARM_DIRECTION_BOTH;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN:
return VMMDEV_HGCM_F_PARM_DIRECTION_TO_HOST;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT:
return VMMDEV_HGCM_F_PARM_DIRECTION_FROM_HOST;
}
}
static void hgcm_call_init_linaddr(struct vmmdev_hgcm_call *call,
struct vmmdev_hgcm_function_parameter *dst_parm, void *buf, u32 len,
enum vmmdev_hgcm_function_parameter_type type, u32 *off_extra)
{
struct vmmdev_hgcm_pagelist *dst_pg_lst;
struct page *page;
bool is_vmalloc;
u32 i, page_count;
dst_parm->type = type;
if (len == 0) {
dst_parm->u.pointer.size = 0;
dst_parm->u.pointer.u.linear_addr = 0;
return;
}
dst_pg_lst = (void *)call + *off_extra;
page_count = hgcm_call_buf_size_in_pages(buf, len);
is_vmalloc = is_vmalloc_addr(buf);
dst_parm->type = VMMDEV_HGCM_PARM_TYPE_PAGELIST;
dst_parm->u.page_list.size = len;
dst_parm->u.page_list.offset = *off_extra;
dst_pg_lst->flags = hgcm_call_linear_addr_type_to_pagelist_flags(type);
dst_pg_lst->offset_first_page = (unsigned long)buf & ~PAGE_MASK;
dst_pg_lst->page_count = page_count;
for (i = 0; i < page_count; i++) {
if (is_vmalloc)
page = vmalloc_to_page(buf);
else
page = virt_to_page(buf);
dst_pg_lst->pages[i] = page_to_phys(page);
buf += PAGE_SIZE;
}
*off_extra += offsetof(struct vmmdev_hgcm_pagelist, pages[page_count]);
}
/**
* hgcm_call_init_call - Initializes the call request that we're sending
* to the host.
* @call: The call to initialize.
* @client_id: The client ID of the caller.
* @function: The function number of the function to call.
* @src_parm: Pointer to source function call parameters.
* @parm_count: Number of function call parameters.
* @bounce_bufs: The bouncebuffer array.
*/
static void hgcm_call_init_call(
struct vmmdev_hgcm_call *call, u32 client_id, u32 function,
const struct vmmdev_hgcm_function_parameter *src_parm,
u32 parm_count, void **bounce_bufs)
{
struct vmmdev_hgcm_function_parameter *dst_parm =
VMMDEV_HGCM_CALL_PARMS(call);
u32 i, off_extra = (uintptr_t)(dst_parm + parm_count) - (uintptr_t)call;
void *buf;
call->header.flags = 0;
call->header.result = VINF_SUCCESS;
call->client_id = client_id;
call->function = function;
call->parm_count = parm_count;
for (i = 0; i < parm_count; i++, src_parm++, dst_parm++) {
switch (src_parm->type) {
case VMMDEV_HGCM_PARM_TYPE_32BIT:
case VMMDEV_HGCM_PARM_TYPE_64BIT:
*dst_parm = *src_parm;
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT:
hgcm_call_init_linaddr(call, dst_parm, bounce_bufs[i],
src_parm->u.pointer.size,
src_parm->type, &off_extra);
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT:
buf = (void *)src_parm->u.pointer.u.linear_addr;
hgcm_call_init_linaddr(call, dst_parm, buf,
src_parm->u.pointer.size,
src_parm->type, &off_extra);
break;
default:
WARN_ON(1);
dst_parm->type = VMMDEV_HGCM_PARM_TYPE_INVALID;
}
}
}
/**
* hgcm_cancel_call - Tries to cancel a pending HGCM call.
* @gdev: The VBoxGuest device extension.
* @call: The call to cancel.
*
* Return: VBox status code
*/
static int hgcm_cancel_call(struct vbg_dev *gdev, struct vmmdev_hgcm_call *call)
{
int rc;
/*
* We use a pre-allocated request for cancellations, which is
* protected by cancel_req_mutex. This means that all cancellations
* get serialized, this should be fine since they should be rare.
*/
mutex_lock(&gdev->cancel_req_mutex);
gdev->cancel_req->phys_req_to_cancel = virt_to_phys(call);
rc = vbg_req_perform(gdev, gdev->cancel_req);
mutex_unlock(&gdev->cancel_req_mutex);
if (rc == VERR_NOT_IMPLEMENTED) {
call->header.flags |= VMMDEV_HGCM_REQ_CANCELLED;
call->header.header.request_type = VMMDEVREQ_HGCM_CANCEL;
rc = vbg_req_perform(gdev, call);
if (rc == VERR_INVALID_PARAMETER)
rc = VERR_NOT_FOUND;
}
if (rc >= 0)
call->header.flags |= VMMDEV_HGCM_REQ_CANCELLED;
return rc;
}
/**
* vbg_hgcm_do_call - Performs the call and completion wait.
* @gdev: The VBoxGuest device extension.
* @call: The call to execute.
* @timeout_ms: Timeout in ms.
* @interruptible: whether this call is interruptible
* @leak_it: Where to return the leak it / free it, indicator.
* Cancellation fun.
*
* Return: %0 or negative errno value.
*/
static int vbg_hgcm_do_call(struct vbg_dev *gdev, struct vmmdev_hgcm_call *call,
u32 timeout_ms, bool interruptible, bool *leak_it)
{
int rc, cancel_rc, ret;
long timeout;
*leak_it = false;
rc = vbg_req_perform(gdev, call);
/*
* If the call failed, then pretend success. Upper layers will
* interpret the result code in the packet.
*/
if (rc < 0) {
call->header.result = rc;
return 0;
}
if (rc != VINF_HGCM_ASYNC_EXECUTE)
return 0;
/* Host decided to process the request asynchronously, wait for it */
if (timeout_ms == U32_MAX)
timeout = MAX_SCHEDULE_TIMEOUT;
else
timeout = msecs_to_jiffies(timeout_ms);
if (interruptible) {
timeout = wait_event_interruptible_timeout(gdev->hgcm_wq,
hgcm_req_done(gdev, &call->header),
timeout);
} else {
timeout = wait_event_timeout(gdev->hgcm_wq,
hgcm_req_done(gdev, &call->header),
timeout);
}
/* timeout > 0 means hgcm_req_done has returned true, so success */
if (timeout > 0)
return 0;
if (timeout == 0)
ret = -ETIMEDOUT;
else
ret = -EINTR;
/* Cancel the request */
cancel_rc = hgcm_cancel_call(gdev, call);
if (cancel_rc >= 0)
return ret;
/*
* Failed to cancel, this should mean that the cancel has lost the
* race with normal completion, wait while the host completes it.
*/
if (cancel_rc == VERR_NOT_FOUND || cancel_rc == VERR_SEM_DESTROYED)
timeout = msecs_to_jiffies(500);
else
timeout = msecs_to_jiffies(2000);
timeout = wait_event_timeout(gdev->hgcm_wq,
hgcm_req_done(gdev, &call->header),
timeout);
if (WARN_ON(timeout == 0)) {
/* We really should never get here */
vbg_err("%s: Call timedout and cancellation failed, leaking the request\n",
__func__);
*leak_it = true;
return ret;
}
/* The call has completed normally after all */
return 0;
}
/**
* hgcm_call_copy_back_result - Copies the result of the call back to
* the caller info structure and user buffers.
* @call: HGCM call request.
* @dst_parm: Pointer to function call parameters destination.
* @parm_count: Number of function call parameters.
* @bounce_bufs: The bouncebuffer array.
*
* Return: %0 or negative errno value.
*/
static int hgcm_call_copy_back_result(
const struct vmmdev_hgcm_call *call,
struct vmmdev_hgcm_function_parameter *dst_parm,
u32 parm_count, void **bounce_bufs)
{
const struct vmmdev_hgcm_function_parameter *src_parm =
VMMDEV_HGCM_CALL_PARMS(call);
void __user *p;
int ret;
u32 i;
/* Copy back parameters. */
for (i = 0; i < parm_count; i++, src_parm++, dst_parm++) {
switch (dst_parm->type) {
case VMMDEV_HGCM_PARM_TYPE_32BIT:
case VMMDEV_HGCM_PARM_TYPE_64BIT:
*dst_parm = *src_parm;
break;
case VMMDEV_HGCM_PARM_TYPE_PAGELIST:
dst_parm->u.page_list.size = src_parm->u.page_list.size;
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_IN:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_KERNEL_OUT:
dst_parm->u.pointer.size = src_parm->u.pointer.size;
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT:
dst_parm->u.pointer.size = src_parm->u.pointer.size;
p = (void __user *)dst_parm->u.pointer.u.linear_addr;
ret = copy_to_user(p, bounce_bufs[i],
min(src_parm->u.pointer.size,
dst_parm->u.pointer.size));
if (ret)
return -EFAULT;
break;
default:
WARN_ON(1);
return -EINVAL;
}
}
return 0;
}
int vbg_hgcm_call(struct vbg_dev *gdev, u32 requestor, u32 client_id,
u32 function, u32 timeout_ms,
struct vmmdev_hgcm_function_parameter *parms, u32 parm_count,
int *vbox_status)
{
struct vmmdev_hgcm_call *call;
void **bounce_bufs = NULL;
bool leak_it;
size_t size;
int i, ret;
size = sizeof(struct vmmdev_hgcm_call) +
parm_count * sizeof(struct vmmdev_hgcm_function_parameter);
/*
* Validate and buffer the parameters for the call. This also increases
* call_size with the amount of extra space needed for page lists.
*/
ret = hgcm_call_preprocess(parms, parm_count, &bounce_bufs, &size);
if (ret) {
/* Even on error bounce bufs may still have been allocated */
goto free_bounce_bufs;
}
call = vbg_req_alloc(size, VMMDEVREQ_HGCM_CALL, requestor);
if (!call) {
ret = -ENOMEM;
goto free_bounce_bufs;
}
hgcm_call_init_call(call, client_id, function, parms, parm_count,
bounce_bufs);
ret = vbg_hgcm_do_call(gdev, call, timeout_ms,
requestor & VMMDEV_REQUESTOR_USERMODE, &leak_it);
if (ret == 0) {
*vbox_status = call->header.result;
ret = hgcm_call_copy_back_result(call, parms, parm_count,
bounce_bufs);
}
if (!leak_it)
vbg_req_free(call, size);
free_bounce_bufs:
if (bounce_bufs) {
for (i = 0; i < parm_count; i++)
kvfree(bounce_bufs[i]);
kfree(bounce_bufs);
}
return ret;
}
EXPORT_SYMBOL(vbg_hgcm_call);
#ifdef CONFIG_COMPAT
int vbg_hgcm_call32(
struct vbg_dev *gdev, u32 requestor, u32 client_id, u32 function,
u32 timeout_ms, struct vmmdev_hgcm_function_parameter32 *parm32,
u32 parm_count, int *vbox_status)
{
struct vmmdev_hgcm_function_parameter *parm64 = NULL;
u32 i, size;
int ret = 0;
/* KISS allocate a temporary request and convert the parameters. */
size = parm_count * sizeof(struct vmmdev_hgcm_function_parameter);
parm64 = kzalloc(size, GFP_KERNEL);
if (!parm64)
return -ENOMEM;
for (i = 0; i < parm_count; i++) {
switch (parm32[i].type) {
case VMMDEV_HGCM_PARM_TYPE_32BIT:
parm64[i].type = VMMDEV_HGCM_PARM_TYPE_32BIT;
parm64[i].u.value32 = parm32[i].u.value32;
break;
case VMMDEV_HGCM_PARM_TYPE_64BIT:
parm64[i].type = VMMDEV_HGCM_PARM_TYPE_64BIT;
parm64[i].u.value64 = parm32[i].u.value64;
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT:
case VMMDEV_HGCM_PARM_TYPE_LINADDR:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN:
parm64[i].type = parm32[i].type;
parm64[i].u.pointer.size = parm32[i].u.pointer.size;
parm64[i].u.pointer.u.linear_addr =
parm32[i].u.pointer.u.linear_addr;
break;
default:
ret = -EINVAL;
}
if (ret < 0)
goto out_free;
}
ret = vbg_hgcm_call(gdev, requestor, client_id, function, timeout_ms,
parm64, parm_count, vbox_status);
if (ret < 0)
goto out_free;
/* Copy back. */
for (i = 0; i < parm_count; i++, parm32++, parm64++) {
switch (parm64[i].type) {
case VMMDEV_HGCM_PARM_TYPE_32BIT:
parm32[i].u.value32 = parm64[i].u.value32;
break;
case VMMDEV_HGCM_PARM_TYPE_64BIT:
parm32[i].u.value64 = parm64[i].u.value64;
break;
case VMMDEV_HGCM_PARM_TYPE_LINADDR_OUT:
case VMMDEV_HGCM_PARM_TYPE_LINADDR:
case VMMDEV_HGCM_PARM_TYPE_LINADDR_IN:
parm32[i].u.pointer.size = parm64[i].u.pointer.size;
break;
default:
WARN_ON(1);
ret = -EINVAL;
}
}
out_free:
kfree(parm64);
return ret;
}
#endif
static const int vbg_status_code_to_errno_table[] = {
[-VERR_ACCESS_DENIED] = -EPERM,
[-VERR_FILE_NOT_FOUND] = -ENOENT,
[-VERR_PROCESS_NOT_FOUND] = -ESRCH,
[-VERR_INTERRUPTED] = -EINTR,
[-VERR_DEV_IO_ERROR] = -EIO,
[-VERR_TOO_MUCH_DATA] = -E2BIG,
[-VERR_BAD_EXE_FORMAT] = -ENOEXEC,
[-VERR_INVALID_HANDLE] = -EBADF,
[-VERR_TRY_AGAIN] = -EAGAIN,
[-VERR_NO_MEMORY] = -ENOMEM,
[-VERR_INVALID_POINTER] = -EFAULT,
[-VERR_RESOURCE_BUSY] = -EBUSY,
[-VERR_ALREADY_EXISTS] = -EEXIST,
[-VERR_NOT_SAME_DEVICE] = -EXDEV,
[-VERR_NOT_A_DIRECTORY] = -ENOTDIR,
[-VERR_PATH_NOT_FOUND] = -ENOTDIR,
[-VERR_INVALID_NAME] = -ENOENT,
[-VERR_IS_A_DIRECTORY] = -EISDIR,
[-VERR_INVALID_PARAMETER] = -EINVAL,
[-VERR_TOO_MANY_OPEN_FILES] = -ENFILE,
[-VERR_INVALID_FUNCTION] = -ENOTTY,
[-VERR_SHARING_VIOLATION] = -ETXTBSY,
[-VERR_FILE_TOO_BIG] = -EFBIG,
[-VERR_DISK_FULL] = -ENOSPC,
[-VERR_SEEK_ON_DEVICE] = -ESPIPE,
[-VERR_WRITE_PROTECT] = -EROFS,
[-VERR_BROKEN_PIPE] = -EPIPE,
[-VERR_DEADLOCK] = -EDEADLK,
[-VERR_FILENAME_TOO_LONG] = -ENAMETOOLONG,
[-VERR_FILE_LOCK_FAILED] = -ENOLCK,
[-VERR_NOT_IMPLEMENTED] = -ENOSYS,
[-VERR_NOT_SUPPORTED] = -ENOSYS,
[-VERR_DIR_NOT_EMPTY] = -ENOTEMPTY,
[-VERR_TOO_MANY_SYMLINKS] = -ELOOP,
[-VERR_NO_MORE_FILES] = -ENODATA,
[-VERR_NO_DATA] = -ENODATA,
[-VERR_NET_NO_NETWORK] = -ENONET,
[-VERR_NET_NOT_UNIQUE_NAME] = -ENOTUNIQ,
[-VERR_NO_TRANSLATION] = -EILSEQ,
[-VERR_NET_NOT_SOCKET] = -ENOTSOCK,
[-VERR_NET_DEST_ADDRESS_REQUIRED] = -EDESTADDRREQ,
[-VERR_NET_MSG_SIZE] = -EMSGSIZE,
[-VERR_NET_PROTOCOL_TYPE] = -EPROTOTYPE,
[-VERR_NET_PROTOCOL_NOT_AVAILABLE] = -ENOPROTOOPT,
[-VERR_NET_PROTOCOL_NOT_SUPPORTED] = -EPROTONOSUPPORT,
[-VERR_NET_SOCKET_TYPE_NOT_SUPPORTED] = -ESOCKTNOSUPPORT,
[-VERR_NET_OPERATION_NOT_SUPPORTED] = -EOPNOTSUPP,
[-VERR_NET_PROTOCOL_FAMILY_NOT_SUPPORTED] = -EPFNOSUPPORT,
[-VERR_NET_ADDRESS_FAMILY_NOT_SUPPORTED] = -EAFNOSUPPORT,
[-VERR_NET_ADDRESS_IN_USE] = -EADDRINUSE,
[-VERR_NET_ADDRESS_NOT_AVAILABLE] = -EADDRNOTAVAIL,
[-VERR_NET_DOWN] = -ENETDOWN,
[-VERR_NET_UNREACHABLE] = -ENETUNREACH,
[-VERR_NET_CONNECTION_RESET] = -ENETRESET,
[-VERR_NET_CONNECTION_ABORTED] = -ECONNABORTED,
[-VERR_NET_CONNECTION_RESET_BY_PEER] = -ECONNRESET,
[-VERR_NET_NO_BUFFER_SPACE] = -ENOBUFS,
[-VERR_NET_ALREADY_CONNECTED] = -EISCONN,
[-VERR_NET_NOT_CONNECTED] = -ENOTCONN,
[-VERR_NET_SHUTDOWN] = -ESHUTDOWN,
[-VERR_NET_TOO_MANY_REFERENCES] = -ETOOMANYREFS,
[-VERR_TIMEOUT] = -ETIMEDOUT,
[-VERR_NET_CONNECTION_REFUSED] = -ECONNREFUSED,
[-VERR_NET_HOST_DOWN] = -EHOSTDOWN,
[-VERR_NET_HOST_UNREACHABLE] = -EHOSTUNREACH,
[-VERR_NET_ALREADY_IN_PROGRESS] = -EALREADY,
[-VERR_NET_IN_PROGRESS] = -EINPROGRESS,
[-VERR_MEDIA_NOT_PRESENT] = -ENOMEDIUM,
[-VERR_MEDIA_NOT_RECOGNIZED] = -EMEDIUMTYPE,
};
int vbg_status_code_to_errno(int rc)
{
if (rc >= 0)
return 0;
rc = -rc;
if (rc >= ARRAY_SIZE(vbg_status_code_to_errno_table) ||
vbg_status_code_to_errno_table[rc] == 0) {
vbg_warn("%s: Unhandled err %d\n", __func__, -rc);
return -EPROTO;
}
return vbg_status_code_to_errno_table[rc];
}
EXPORT_SYMBOL(vbg_status_code_to_errno);
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */
/*
* VBox Guest additions version info, this is used by the host to determine
* supported guest-addition features in some cases. So this will need to be
* synced with vbox upstreams versioning scheme when we implement / port
* new features from the upstream out-of-tree vboxguest driver.
*/
#ifndef __VBOX_VERSION_H__
#define __VBOX_VERSION_H__
#define VBG_VERSION_MAJOR 6
#define VBG_VERSION_MINOR 0
#define VBG_VERSION_BUILD 0
#define VBG_SVN_REV 127566
#define VBG_VERSION_STRING "6.0.0"
#endif
+453
View File
@@ -0,0 +1,453 @@
/* SPDX-License-Identifier: (GPL-2.0 OR CDDL-1.0) */
/*
* Virtual Device for Guest <-> VMM/Host communication interface
*
* Copyright (C) 2006-2016 Oracle Corporation
*/
#ifndef __VBOX_VMMDEV_H__
#define __VBOX_VMMDEV_H__
#include <asm/bitsperlong.h>
#include <linux/sizes.h>
#include <linux/types.h>
#include <linux/vbox_vmmdev_types.h>
/* Port for generic request interface (relative offset). */
#define VMMDEV_PORT_OFF_REQUEST 0
/** Layout of VMMDEV RAM region that contains information for guest. */
struct vmmdev_memory {
/** The size of this structure. */
u32 size;
/** The structure version. (VMMDEV_MEMORY_VERSION) */
u32 version;
union {
struct {
/** Flag telling that VMMDev has events pending. */
u8 have_events;
/** Explicit padding, MBZ. */
u8 padding[3];
} V1_04;
struct {
/** Pending events flags, set by host. */
u32 host_events;
/** Mask of events the guest wants, set by guest. */
u32 guest_event_mask;
} V1_03;
} V;
/* struct vbva_memory, not used */
};
VMMDEV_ASSERT_SIZE(vmmdev_memory, 8 + 8);
/** Version of vmmdev_memory structure (vmmdev_memory::version). */
#define VMMDEV_MEMORY_VERSION (1)
/* Host mouse capabilities has been changed. */
#define VMMDEV_EVENT_MOUSE_CAPABILITIES_CHANGED BIT(0)
/* HGCM event. */
#define VMMDEV_EVENT_HGCM BIT(1)
/* A display change request has been issued. */
#define VMMDEV_EVENT_DISPLAY_CHANGE_REQUEST BIT(2)
/* Credentials are available for judgement. */
#define VMMDEV_EVENT_JUDGE_CREDENTIALS BIT(3)
/* The guest has been restored. */
#define VMMDEV_EVENT_RESTORED BIT(4)
/* Seamless mode state changed. */
#define VMMDEV_EVENT_SEAMLESS_MODE_CHANGE_REQUEST BIT(5)
/* Memory balloon size changed. */
#define VMMDEV_EVENT_BALLOON_CHANGE_REQUEST BIT(6)
/* Statistics interval changed. */
#define VMMDEV_EVENT_STATISTICS_INTERVAL_CHANGE_REQUEST BIT(7)
/* VRDP status changed. */
#define VMMDEV_EVENT_VRDP BIT(8)
/* New mouse position data available. */
#define VMMDEV_EVENT_MOUSE_POSITION_CHANGED BIT(9)
/* CPU hotplug event occurred. */
#define VMMDEV_EVENT_CPU_HOTPLUG BIT(10)
/* The mask of valid events, for sanity checking. */
#define VMMDEV_EVENT_VALID_EVENT_MASK 0x000007ffU
/*
* Additions are allowed to work only if additions_major == vmmdev_current &&
* additions_minor <= vmmdev_current. Additions version is reported to host
* (VMMDev) by VMMDEVREQ_REPORT_GUEST_INFO.
*/
#define VMMDEV_VERSION 0x00010004
#define VMMDEV_VERSION_MAJOR (VMMDEV_VERSION >> 16)
#define VMMDEV_VERSION_MINOR (VMMDEV_VERSION & 0xffff)
/* Maximum request packet size. */
#define VMMDEV_MAX_VMMDEVREQ_SIZE 1048576
/* Version of vmmdev_request_header structure. */
#define VMMDEV_REQUEST_HEADER_VERSION 0x10001
/** struct vmmdev_request_header - Generic VMMDev request header. */
struct vmmdev_request_header {
/** IN: Size of the structure in bytes (including body). */
u32 size;
/** IN: Version of the structure. */
u32 version;
/** IN: Type of the request. */
enum vmmdev_request_type request_type;
/** OUT: Return code. */
s32 rc;
/** Reserved field no.1. MBZ. */
u32 reserved1;
/** IN: Requestor information (VMMDEV_REQUESTOR_*) */
u32 requestor;
};
VMMDEV_ASSERT_SIZE(vmmdev_request_header, 24);
/**
* struct vmmdev_mouse_status - Mouse status request structure.
*
* Used by VMMDEVREQ_GET_MOUSE_STATUS and VMMDEVREQ_SET_MOUSE_STATUS.
*/
struct vmmdev_mouse_status {
/** header */
struct vmmdev_request_header header;
/** Mouse feature mask. See VMMDEV_MOUSE_*. */
u32 mouse_features;
/** Mouse x position. */
s32 pointer_pos_x;
/** Mouse y position. */
s32 pointer_pos_y;
};
VMMDEV_ASSERT_SIZE(vmmdev_mouse_status, 24 + 12);
/* The guest can (== wants to) handle absolute coordinates. */
#define VMMDEV_MOUSE_GUEST_CAN_ABSOLUTE BIT(0)
/*
* The host can (== wants to) send absolute coordinates.
* (Input not captured.)
*/
#define VMMDEV_MOUSE_HOST_WANTS_ABSOLUTE BIT(1)
/*
* The guest can *NOT* switch to software cursor and therefore depends on the
* host cursor.
*
* When guest additions are installed and the host has promised to display the
* cursor itself, the guest installs a hardware mouse driver. Don't ask the
* guest to switch to a software cursor then.
*/
#define VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR BIT(2)
/* The host does NOT provide support for drawing the cursor itself. */
#define VMMDEV_MOUSE_HOST_CANNOT_HWPOINTER BIT(3)
/* The guest can read VMMDev events to find out about pointer movement */
#define VMMDEV_MOUSE_NEW_PROTOCOL BIT(4)
/*
* If the guest changes the status of the VMMDEV_MOUSE_GUEST_NEEDS_HOST_CURSOR
* bit, the host will honour this.
*/
#define VMMDEV_MOUSE_HOST_RECHECKS_NEEDS_HOST_CURSOR BIT(5)
/*
* The host supplies an absolute pointing device. The Guest Additions may
* wish to use this to decide whether to install their own driver.
*/
#define VMMDEV_MOUSE_HOST_HAS_ABS_DEV BIT(6)
/* The minimum value our pointing device can return. */
#define VMMDEV_MOUSE_RANGE_MIN 0
/* The maximum value our pointing device can return. */
#define VMMDEV_MOUSE_RANGE_MAX 0xFFFF
/**
* struct vmmdev_host_version - VirtualBox host version request structure.
*
* VBG uses this to detect the precense of new features in the interface.
*/
struct vmmdev_host_version {
/** Header. */
struct vmmdev_request_header header;
/** Major version. */
u16 major;
/** Minor version. */
u16 minor;
/** Build number. */
u32 build;
/** SVN revision. */
u32 revision;
/** Feature mask. */
u32 features;
};
VMMDEV_ASSERT_SIZE(vmmdev_host_version, 24 + 16);
/* Physical page lists are supported by HGCM. */
#define VMMDEV_HVF_HGCM_PHYS_PAGE_LIST BIT(0)
/**
* struct vmmdev_mask - Structure to set / clear bits in a mask used for
* VMMDEVREQ_SET_GUEST_CAPABILITIES and VMMDEVREQ_CTL_GUEST_FILTER_MASK.
*/
struct vmmdev_mask {
/** Header. */
struct vmmdev_request_header header;
/** Mask of bits to be set. */
u32 or_mask;
/** Mask of bits to be cleared. */
u32 not_mask;
};
VMMDEV_ASSERT_SIZE(vmmdev_mask, 24 + 8);
/* The guest supports seamless display rendering. */
#define VMMDEV_GUEST_SUPPORTS_SEAMLESS BIT(0)
/* The guest supports mapping guest to host windows. */
#define VMMDEV_GUEST_SUPPORTS_GUEST_HOST_WINDOW_MAPPING BIT(1)
/*
* The guest graphical additions are active.
* Used for fast activation and deactivation of certain graphical operations
* (e.g. resizing & seamless). The legacy VMMDEVREQ_REPORT_GUEST_CAPABILITIES
* request sets this automatically, but VMMDEVREQ_SET_GUEST_CAPABILITIES does
* not.
*/
#define VMMDEV_GUEST_SUPPORTS_GRAPHICS BIT(2)
/* The mask of valid capabilities, for sanity checking. */
#define VMMDEV_GUEST_CAPABILITIES_MASK 0x00000007U
/** struct vmmdev_hypervisorinfo - Hypervisor info structure. */
struct vmmdev_hypervisorinfo {
/** Header. */
struct vmmdev_request_header header;
/**
* Guest virtual address of proposed hypervisor start.
* Not used by VMMDEVREQ_GET_HYPERVISOR_INFO.
*/
u32 hypervisor_start;
/** Hypervisor size in bytes. */
u32 hypervisor_size;
};
VMMDEV_ASSERT_SIZE(vmmdev_hypervisorinfo, 24 + 8);
/** struct vmmdev_events - Pending events structure. */
struct vmmdev_events {
/** Header. */
struct vmmdev_request_header header;
/** OUT: Pending event mask. */
u32 events;
};
VMMDEV_ASSERT_SIZE(vmmdev_events, 24 + 4);
#define VMMDEV_OSTYPE_LINUX26 0x53000
#define VMMDEV_OSTYPE_X64 BIT(8)
/** struct vmmdev_guestinfo - Guest information report. */
struct vmmdev_guest_info {
/** Header. */
struct vmmdev_request_header header;
/**
* The VMMDev interface version expected by additions.
* *Deprecated*, do not use anymore! Will be removed.
*/
u32 interface_version;
/** Guest OS type. */
u32 os_type;
};
VMMDEV_ASSERT_SIZE(vmmdev_guest_info, 24 + 8);
#define VMMDEV_GUEST_INFO2_ADDITIONS_FEATURES_REQUESTOR_INFO BIT(0)
/** struct vmmdev_guestinfo2 - Guest information report, version 2. */
struct vmmdev_guest_info2 {
/** Header. */
struct vmmdev_request_header header;
/** Major version. */
u16 additions_major;
/** Minor version. */
u16 additions_minor;
/** Build number. */
u32 additions_build;
/** SVN revision. */
u32 additions_revision;
/** Feature mask. */
u32 additions_features;
/**
* The intentional meaning of this field was:
* Some additional information, for example 'Beta 1' or something like
* that.
*
* The way it was implemented was implemented: VBG_VERSION_STRING.
*
* This means the first three members are duplicated in this field (if
* the guest build config is sane). So, the user must check this and
* chop it off before usage. There is, because of the Main code's blind
* trust in the field's content, no way back.
*/
char name[128];
};
VMMDEV_ASSERT_SIZE(vmmdev_guest_info2, 24 + 144);
enum vmmdev_guest_facility_type {
VBOXGUEST_FACILITY_TYPE_UNKNOWN = 0,
VBOXGUEST_FACILITY_TYPE_VBOXGUEST_DRIVER = 20,
/* VBoxGINA / VBoxCredProv / pam_vbox. */
VBOXGUEST_FACILITY_TYPE_AUTO_LOGON = 90,
VBOXGUEST_FACILITY_TYPE_VBOX_SERVICE = 100,
/* VBoxTray (Windows), VBoxClient (Linux, Unix). */
VBOXGUEST_FACILITY_TYPE_VBOX_TRAY_CLIENT = 101,
VBOXGUEST_FACILITY_TYPE_SEAMLESS = 1000,
VBOXGUEST_FACILITY_TYPE_GRAPHICS = 1100,
VBOXGUEST_FACILITY_TYPE_ALL = 0x7ffffffe,
/* Ensure the enum is a 32 bit data-type */
VBOXGUEST_FACILITY_TYPE_SIZEHACK = 0x7fffffff
};
enum vmmdev_guest_facility_status {
VBOXGUEST_FACILITY_STATUS_INACTIVE = 0,
VBOXGUEST_FACILITY_STATUS_PAUSED = 1,
VBOXGUEST_FACILITY_STATUS_PRE_INIT = 20,
VBOXGUEST_FACILITY_STATUS_INIT = 30,
VBOXGUEST_FACILITY_STATUS_ACTIVE = 50,
VBOXGUEST_FACILITY_STATUS_TERMINATING = 100,
VBOXGUEST_FACILITY_STATUS_TERMINATED = 101,
VBOXGUEST_FACILITY_STATUS_FAILED = 800,
VBOXGUEST_FACILITY_STATUS_UNKNOWN = 999,
/* Ensure the enum is a 32 bit data-type */
VBOXGUEST_FACILITY_STATUS_SIZEHACK = 0x7fffffff
};
/** struct vmmdev_guest_status - Guest Additions status structure. */
struct vmmdev_guest_status {
/** Header. */
struct vmmdev_request_header header;
/** Facility the status is indicated for. */
enum vmmdev_guest_facility_type facility;
/** Current guest status. */
enum vmmdev_guest_facility_status status;
/** Flags, not used at the moment. */
u32 flags;
};
VMMDEV_ASSERT_SIZE(vmmdev_guest_status, 24 + 12);
#define VMMDEV_MEMORY_BALLOON_CHUNK_SIZE (1048576)
#define VMMDEV_MEMORY_BALLOON_CHUNK_PAGES (1048576 / 4096)
/** struct vmmdev_memballoon_info - Memory-balloon info structure. */
struct vmmdev_memballoon_info {
/** Header. */
struct vmmdev_request_header header;
/** Balloon size in megabytes. */
u32 balloon_chunks;
/** Guest ram size in megabytes. */
u32 phys_mem_chunks;
/**
* Setting this to VMMDEV_EVENT_BALLOON_CHANGE_REQUEST indicates that
* the request is a response to that event.
* (Don't confuse this with VMMDEVREQ_ACKNOWLEDGE_EVENTS.)
*/
u32 event_ack;
};
VMMDEV_ASSERT_SIZE(vmmdev_memballoon_info, 24 + 12);
/** struct vmmdev_memballoon_change - Change the size of the balloon. */
struct vmmdev_memballoon_change {
/** Header. */
struct vmmdev_request_header header;
/** The number of pages in the array. */
u32 pages;
/** true = inflate, false = deflate. */
u32 inflate;
/** Physical address (u64) of each page. */
u64 phys_page[VMMDEV_MEMORY_BALLOON_CHUNK_PAGES];
};
/** struct vmmdev_write_core_dump - Write Core Dump request data. */
struct vmmdev_write_core_dump {
/** Header. */
struct vmmdev_request_header header;
/** Flags (reserved, MBZ). */
u32 flags;
};
VMMDEV_ASSERT_SIZE(vmmdev_write_core_dump, 24 + 4);
/** struct vmmdev_heartbeat - Heart beat check state structure. */
struct vmmdev_heartbeat {
/** Header. */
struct vmmdev_request_header header;
/** OUT: Guest heartbeat interval in nanosec. */
u64 interval_ns;
/** Heartbeat check flag. */
u8 enabled;
/** Explicit padding, MBZ. */
u8 padding[3];
} __packed;
VMMDEV_ASSERT_SIZE(vmmdev_heartbeat, 24 + 12);
#define VMMDEV_HGCM_REQ_DONE BIT(0)
#define VMMDEV_HGCM_REQ_CANCELLED BIT(1)
/** struct vmmdev_hgcmreq_header - vmmdev HGCM requests header. */
struct vmmdev_hgcmreq_header {
/** Request header. */
struct vmmdev_request_header header;
/** HGCM flags. */
u32 flags;
/** Result code. */
s32 result;
};
VMMDEV_ASSERT_SIZE(vmmdev_hgcmreq_header, 24 + 8);
/** struct vmmdev_hgcm_connect - HGCM connect request structure. */
struct vmmdev_hgcm_connect {
/** HGCM request header. */
struct vmmdev_hgcmreq_header header;
/** IN: Description of service to connect to. */
struct vmmdev_hgcm_service_location loc;
/** OUT: Client identifier assigned by local instance of HGCM. */
u32 client_id;
};
VMMDEV_ASSERT_SIZE(vmmdev_hgcm_connect, 32 + 132 + 4);
/** struct vmmdev_hgcm_disconnect - HGCM disconnect request structure. */
struct vmmdev_hgcm_disconnect {
/** HGCM request header. */
struct vmmdev_hgcmreq_header header;
/** IN: Client identifier. */
u32 client_id;
};
VMMDEV_ASSERT_SIZE(vmmdev_hgcm_disconnect, 32 + 4);
#define VMMDEV_HGCM_MAX_PARMS 32
/** struct vmmdev_hgcm_call - HGCM call request structure. */
struct vmmdev_hgcm_call {
/* request header */
struct vmmdev_hgcmreq_header header;
/** IN: Client identifier. */
u32 client_id;
/** IN: Service function number. */
u32 function;
/** IN: Number of parameters. */
u32 parm_count;
/** Parameters follow in form: HGCMFunctionParameter32|64 parms[X]; */
};
VMMDEV_ASSERT_SIZE(vmmdev_hgcm_call, 32 + 12);
/**
* struct vmmdev_hgcm_cancel2 - HGCM cancel request structure, version 2.
*
* After the request header.rc will be:
*
* VINF_SUCCESS when cancelled.
* VERR_NOT_FOUND if the specified request cannot be found.
* VERR_INVALID_PARAMETER if the address is invalid valid.
*/
struct vmmdev_hgcm_cancel2 {
/** Header. */
struct vmmdev_request_header header;
/** The physical address of the request to cancel. */
u32 phys_req_to_cancel;
};
VMMDEV_ASSERT_SIZE(vmmdev_hgcm_cancel2, 24 + 4);
#endif
+181
View File
@@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (C) 2022 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* The "Virtual Machine Generation ID" is exposed via ACPI or DT and changes when a
* virtual machine forks or is cloned. This driver exists for shepherding that
* information to random.c.
*/
#include <linux/acpi.h>
#include <linux/interrupt.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/random.h>
ACPI_MODULE_NAME("vmgenid");
enum { VMGENID_SIZE = 16 };
struct vmgenid_state {
u8 *next_id;
u8 this_id[VMGENID_SIZE];
};
static void vmgenid_notify(struct device *device)
{
struct vmgenid_state *state = device->driver_data;
u8 old_id[VMGENID_SIZE];
memcpy(old_id, state->this_id, sizeof(old_id));
memcpy(state->this_id, state->next_id, sizeof(state->this_id));
if (!memcmp(old_id, state->this_id, sizeof(old_id)))
return;
add_vmfork_randomness(state->this_id, sizeof(state->this_id));
}
static void setup_vmgenid_state(struct vmgenid_state *state, void *virt_addr)
{
state->next_id = virt_addr;
memcpy(state->this_id, state->next_id, sizeof(state->this_id));
add_device_randomness(state->this_id, sizeof(state->this_id));
}
#ifdef CONFIG_ACPI
static void vmgenid_acpi_handler(acpi_handle __always_unused handle,
u32 __always_unused event, void *dev)
{
vmgenid_notify(dev);
}
static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state)
{
struct acpi_device *device = ACPI_COMPANION(dev);
struct acpi_buffer parsed = { ACPI_ALLOCATE_BUFFER };
union acpi_object *obj;
phys_addr_t phys_addr;
acpi_status status;
void *virt_addr;
int ret = 0;
status = acpi_evaluate_object(device->handle, "ADDR", NULL, &parsed);
if (ACPI_FAILURE(status)) {
ACPI_EXCEPTION((AE_INFO, status, "Evaluating ADDR"));
return -ENODEV;
}
obj = parsed.pointer;
if (!obj || obj->type != ACPI_TYPE_PACKAGE || obj->package.count != 2 ||
obj->package.elements[0].type != ACPI_TYPE_INTEGER ||
obj->package.elements[1].type != ACPI_TYPE_INTEGER) {
ret = -EINVAL;
goto out;
}
phys_addr = (obj->package.elements[0].integer.value << 0) |
(obj->package.elements[1].integer.value << 32);
virt_addr = devm_memremap(&device->dev, phys_addr, VMGENID_SIZE, MEMREMAP_WB);
if (IS_ERR(virt_addr)) {
ret = PTR_ERR(virt_addr);
goto out;
}
setup_vmgenid_state(state, virt_addr);
status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY,
vmgenid_acpi_handler, dev);
if (ACPI_FAILURE(status)) {
ret = -ENODEV;
goto out;
}
dev->driver_data = state;
out:
ACPI_FREE(parsed.pointer);
return ret;
}
#else
static int vmgenid_add_acpi(struct device *dev, struct vmgenid_state *state)
{
return -EINVAL;
}
#endif
static irqreturn_t vmgenid_of_irq_handler(int __always_unused irq, void *dev)
{
vmgenid_notify(dev);
return IRQ_HANDLED;
}
static int vmgenid_add_of(struct platform_device *pdev,
struct vmgenid_state *state)
{
void *virt_addr;
int ret;
virt_addr = devm_platform_get_and_ioremap_resource(pdev, 0, NULL);
if (IS_ERR(virt_addr))
return PTR_ERR(virt_addr);
setup_vmgenid_state(state, virt_addr);
ret = platform_get_irq(pdev, 0);
if (ret < 0)
return ret;
ret = devm_request_irq(&pdev->dev, ret, vmgenid_of_irq_handler,
IRQF_SHARED, "vmgenid", &pdev->dev);
if (ret < 0)
return ret;
pdev->dev.driver_data = state;
return 0;
}
static int vmgenid_add(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct vmgenid_state *state;
int ret;
state = devm_kmalloc(dev, sizeof(*state), GFP_KERNEL);
if (!state)
return -ENOMEM;
if (dev->of_node)
ret = vmgenid_add_of(pdev, state);
else
ret = vmgenid_add_acpi(dev, state);
if (ret < 0)
devm_kfree(dev, state);
return ret;
}
static const struct of_device_id vmgenid_of_ids[] = {
{ .compatible = "microsoft,vmgenid", },
{ },
};
MODULE_DEVICE_TABLE(of, vmgenid_of_ids);
static const struct acpi_device_id vmgenid_acpi_ids[] = {
{ "VMGENCTR", 0 },
{ "VM_GEN_COUNTER", 0 },
{ }
};
MODULE_DEVICE_TABLE(acpi, vmgenid_acpi_ids);
static struct platform_driver vmgenid_plaform_driver = {
.probe = vmgenid_add,
.driver = {
.name = "vmgenid",
.acpi_match_table = vmgenid_acpi_ids,
.of_match_table = vmgenid_of_ids,
},
};
module_platform_driver(vmgenid_plaform_driver)
MODULE_DESCRIPTION("Virtual Machine Generation ID");
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("Jason A. Donenfeld <Jason@zx2c4.com>");