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
@@ -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_ */