dc74e8cf23
'destroy_workqueue()' already drains the queue before destroying it, so there is no need to flush it explicitly. Remove the redundant 'flush_workqueue()' calls. This was generated with coccinelle: @@ expression E; @@ - flush_workqueue(E); destroy_workqueue(E); Reviewed-by: Andra Paraschiv <andraprs@amazon.com> Signed-off-by: Christophe JAILLET <christophe.jaillet@wanadoo.fr> Link: https://lore.kernel.org/r/d57f5c7e362837a8dfcde0d726a76b56f114e619.1636736947.git.christophe.jaillet@wanadoo.fr Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
627 lines
15 KiB
C
627 lines
15 KiB
C
// 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(sizeof(*ne_pci_dev), GFP_KERNEL);
|
|
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,
|
|
};
|