nuttx/drivers/pci/pci_ep_test.c
Catalin Visinescu 081e4c478a drivers/: Multiple Drivers Are Registered With World Writable - Part 2
Permissions (Part 2)

Description:

In kernel builds, any unprivileged process running on the NuttX
device can open /dev/efuse and attempt to read/write fuse content.
Reading the fuses may provide valuable information to an attacker
controlling the user process. The write operation, in extreme cases
where the fuse blocks are not locked, may brick the device.

DISCLAIMER: I tried to be strict with the settings, better to relax them
later if it's needed.

This is part of https://github.com/apache/nuttx/issues/19410

See https://github.com/apache/nuttx/issues/19410

Compiles ok.

Signed-off-by: Catalin Visinescu <catalin_visinescu@yahoo.com>
2026-07-15 15:27:28 +08:00

863 lines
23 KiB
C

/****************************************************************************
* drivers/pci/pci_ep_test.c
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership. The
* ASF licenses this file to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*
****************************************************************************/
/****************************************************************************
* Included Files
****************************************************************************/
#include <stdio.h>
#include <nuttx/debug.h>
#include <stdint.h>
#include <nuttx/bits.h>
#include <nuttx/crc32.h>
#include <nuttx/mm/mm.h>
#include <nuttx/kmalloc.h>
#include <nuttx/semaphore.h>
#include <nuttx/irq.h>
#include <nuttx/pci/pci.h>
#include <nuttx/pci/pci_regs.h>
#include <nuttx/pci/pci_ep_test.h>
#include "pci_drivers.h"
/****************************************************************************
* Pre-processor Definitions
****************************************************************************/
#define PCI_EP_TEST_DEVICE_NAME "/dev/pci-ep-test"
#define PCI_EP_TEST_IRQ_TYPE_UNDEFINED -1
#define PCI_EP_TEST_IRQ_TYPE_LEGACY 0
#define PCI_EP_TEST_IRQ_TYPE_MSI 1
#define PCI_EP_TEST_IRQ_TYPE_MSIX 2
#define PCI_EP_TEST_MAGIC 0x0
#define PCI_EP_TEST_COMMAND 0x4
#define PCI_EP_TEST_COMMAND_LEGACY_IRQ BIT(0)
#define PCI_EP_TEST_COMMAND_MSI_IRQ BIT(1)
#define PCI_EP_TEST_COMMAND_MSIX_IRQ BIT(2)
#define PCI_EP_TEST_COMMAND_READ BIT(3)
#define PCI_EP_TEST_COMMAND_WRITE BIT(4)
#define PCI_EP_TEST_COMMAND_COPY BIT(5)
#define PCI_EP_TEST_STATUS 0x8
#define PCI_EP_TEST_STATUS_READ_SUCCESS BIT(0)
#define PCI_EP_TEST_STATUS_READ_FAIL BIT(1)
#define PCI_EP_TEST_STATUS_WRITE_SUCCESS BIT(2)
#define PCI_EP_TEST_STATUS_WRITE_FAIL BIT(3)
#define PCI_EP_TEST_STATUS_COPY_SUCCESS BIT(4)
#define PCI_EP_TEST_STATUS_COPY_FAIL BIT(5)
#define PCI_EP_TEST_STATUS_IRQ_RAISED BIT(6)
#define PCI_EP_TEST_STATUS_SRC_ADDR_INVALID BIT(7)
#define PCI_EP_TEST_STATUS_DST_ADDR_INVALID BIT(8)
#define PCI_EP_TEST_LOWER_SRC_ADDR 0x0c
#define PCI_EP_TEST_UPPER_SRC_ADDR 0x10
#define PCI_EP_TEST_LOWER_DST_ADDR 0x14
#define PCI_EP_TEST_UPPER_DST_ADDR 0x18
#define PCI_EP_TEST_SIZE 0x1c
#define PCI_EP_TEST_CHECKSUM 0x20
#define PCI_EP_TEST_IRQ_TYPE 0x24
#define PCI_EP_TEST_IRQ_NUMBER 0x28
#define PCI_EP_TEST_FLAGS 0x2c
/****************************************************************************
* Private Types
****************************************************************************/
struct pci_ep_test_s
{
FAR struct pci_device_s *pdev;
FAR void *base;
FAR void *bar[PCI_STD_NUM_BARS];
sem_t irq_raise;
int irq;
int irq_type;
mutex_t mutex;
int test_bar;
size_t alignment;
char name[32];
};
/****************************************************************************
* Private Functions Definitions
****************************************************************************/
static int pci_ep_test_probe(FAR struct pci_device_s *dev);
static int pci_ep_test_open(FAR struct file *filep);
static int pci_ep_test_close(FAR struct file *filep);
static int pci_ep_test_ioctl(FAR struct file *filep,
int cmd, unsigned long arge);
/****************************************************************************
* Private Data
****************************************************************************/
static const struct pci_device_id_s g_pci_ep_test_id_table[] =
{
{ PCI_DEVICE(0x104c, 0xb500), },
{ }
};
static struct pci_driver_s g_pci_ep_test_drv =
{
.id_table = g_pci_ep_test_id_table,
.probe = pci_ep_test_probe,
};
static const struct file_operations g_pci_ep_test_fops =
{
pci_ep_test_open, /* open */
pci_ep_test_close, /* close */
NULL, /* read */
NULL, /* write */
NULL, /* seek */
pci_ep_test_ioctl, /* ioctl */
NULL, /* mmap */
};
static int g_pci_ep_idr = 0;
/****************************************************************************
* Private Functions
****************************************************************************/
static inline uint32_t
pci_ep_test_read_dword(FAR struct pci_ep_test_s *test,
uint32_t offset)
{
uint32_t value;
pci_read_mmio_dword(test->pdev, test->base + offset, &value);
return value;
}
static inline void
pci_ep_test_write_dword(FAR struct pci_ep_test_s *test,
uint32_t offset, uint32_t value)
{
pci_write_mmio_dword(test->pdev, test->base + offset, value);
}
static inline uint32_t
pci_ep_test_bar_read_dword(FAR struct pci_ep_test_s *test,
int bar, uint32_t offset)
{
uint32_t value;
pci_read_mmio_dword(test->pdev, test->bar[bar] + offset, &value);
return value;
}
static inline void
pci_ep_test_bar_write_dword(FAR struct pci_ep_test_s *test, int bar,
uint32_t offset, uint32_t value)
{
pci_write_mmio_dword(test->pdev, test->bar[bar] + offset, value);
}
/****************************************************************************
* Name: pci_ep_test_open
*
* Description:
* This function is used to open /dev/X.
*
****************************************************************************/
static int pci_ep_test_open(FAR struct file *filep)
{
FAR struct pci_ep_test_s *test;
test = filep->f_inode->i_private;
DEBUGASSERT(test != NULL);
filep->f_priv = test;
return 0;
}
/****************************************************************************
* Name: pci_ep_test_close
*
* Description:
* This function is used to close /dev/X.
*
****************************************************************************/
static int pci_ep_test_close(FAR struct file *filep)
{
filep->f_priv = NULL;
return 0;
}
/****************************************************************************
* Name: pci_ep_test_bar
*
* Description:
* This function is used to test bar access.
*
****************************************************************************/
static bool pci_ep_test_bar(FAR struct pci_ep_test_s *test,
int barno)
{
FAR struct pci_device_s *pdev = test->pdev;
int size;
int step;
if (NULL == test->bar[barno])
{
pcierr("bar address is null \n");
return false;
}
size = pci_resource_len(pdev, barno);
if (barno == test->test_bar)
{
size = 0x4;
}
for (step = 0; step < size; step += 4)
{
pci_ep_test_bar_write_dword(test, barno, step, 0xa0a0a0a0);
}
for (step = 0; step < size; step += 4)
{
uint32_t val = pci_ep_test_bar_read_dword(test, barno, step);
if (val != 0xa0a0a0a0)
{
pcierr("verify value not pass!!!\n");
return false;
}
}
return true;
}
/****************************************************************************
* Name: pci_ep_test_legacy_irq
*
* Description:
* This function is used to test legacy irq
*
****************************************************************************/
static bool
pci_ep_test_legacy_irq(FAR struct pci_ep_test_s *test)
{
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_TYPE,
PCI_EP_TEST_IRQ_TYPE_LEGACY);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_NUMBER, 0);
pci_ep_test_write_dword(test, PCI_EP_TEST_COMMAND,
PCI_EP_TEST_COMMAND_LEGACY_IRQ);
nxsem_wait(&test->irq_raise);
return true;
}
/****************************************************************************
* Name: pci_ep_test_msi_irq
*
* Description:
* This function is used to test msi or msix irq
*
****************************************************************************/
static bool pci_ep_test_msi_irq(FAR struct pci_ep_test_s *test,
uint16_t msi_num, bool msix)
{
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_TYPE,
msix ? PCI_EP_TEST_IRQ_TYPE_MSIX :
PCI_EP_TEST_IRQ_TYPE_MSI);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_NUMBER, msi_num);
pci_ep_test_write_dword(test, PCI_EP_TEST_COMMAND,
msix ? PCI_EP_TEST_COMMAND_MSIX_IRQ :
PCI_EP_TEST_COMMAND_MSI_IRQ);
nxsem_wait(&test->irq_raise);
return true;
}
/****************************************************************************
* Name: pci_ep_fill_init
*
* Description:
* This function is used to fill init data.
*
****************************************************************************/
static void pci_ep_fill_init(FAR void *addr, size_t size)
{
FAR char *tmp = addr;
int idx;
for (idx = 0; idx < size; idx++)
{
*(tmp + idx) = idx % 255;
}
}
/****************************************************************************
* Name: pci_ep_test_write
*
* Description:
* This function is used to test write.
*
****************************************************************************/
static bool pci_ep_test_write(FAR struct pci_ep_test_s *test,
unsigned long arg)
{
FAR struct pci_ep_test_param_s *param;
FAR void *addr;
uint64_t phys_addr;
uint32_t crc32;
param = (FAR struct pci_ep_test_param_s *)arg;
if (param->size == 0)
{
pcierr("xfer size is zero, invalid param\n");
return false;
}
if (test->irq_type < PCI_EP_TEST_IRQ_TYPE_LEGACY ||
test->irq_type > PCI_EP_TEST_IRQ_TYPE_MSIX)
{
pcierr("invalid IRQ type option\n");
return false;
}
addr = kmm_memalign(test->alignment, param->size);
if (NULL == addr)
{
pcierr("Failed to alloc origin memory addr\n");
return false;
}
pci_ep_fill_init(addr, param->size);
phys_addr = up_addrenv_va_to_pa(addr);
crc32 = crc32part(addr, param->size, ~0);
pci_ep_test_write_dword(test, PCI_EP_TEST_CHECKSUM, crc32);
pci_ep_test_write_dword(test, PCI_EP_TEST_LOWER_SRC_ADDR,
phys_addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_UPPER_SRC_ADDR,
phys_addr >> 32);
pci_ep_test_write_dword(test, PCI_EP_TEST_SIZE, param->size);
pci_ep_test_write_dword(test, PCI_EP_TEST_FLAGS, 0);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_TYPE, test->irq_type);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_NUMBER, 1);
pci_ep_test_write_dword(test, PCI_EP_TEST_COMMAND,
PCI_EP_TEST_COMMAND_READ);
nxsem_wait(&test->irq_raise);
kmm_free(addr);
return !!(pci_ep_test_read_dword(test, PCI_EP_TEST_STATUS) &
PCI_EP_TEST_STATUS_READ_SUCCESS);
}
/****************************************************************************
* Name: pci_ep_test_read
*
* Description:
* This function is used to test read.
*
****************************************************************************/
static bool pci_ep_test_read(FAR struct pci_ep_test_s *test,
unsigned long arg)
{
FAR struct pci_ep_test_param_s *param;
FAR void *addr;
uint64_t phys_addr;
uint32_t crc32;
param = (FAR struct pci_ep_test_param_s *)arg;
if (param->size == 0)
{
pcierr("xfer size is zero, invalid param\n");
return false;
}
if (test->irq_type < PCI_EP_TEST_IRQ_TYPE_LEGACY ||
test->irq_type > PCI_EP_TEST_IRQ_TYPE_MSIX)
{
pcierr("invalid IRQ type option\n");
return false;
}
addr = kmm_memalign(test->alignment, param->size);
if (NULL == addr)
{
pcierr("Failed to alloc origin memory addr\n");
return false;
}
phys_addr = up_addrenv_va_to_pa(addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_LOWER_DST_ADDR,
phys_addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_UPPER_DST_ADDR,
phys_addr >> 32);
pci_ep_test_write_dword(test, PCI_EP_TEST_SIZE, param->size);
pci_ep_test_write_dword(test, PCI_EP_TEST_FLAGS, 0);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_TYPE, test->irq_type);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_NUMBER, 1);
pci_ep_test_write_dword(test, PCI_EP_TEST_COMMAND,
PCI_EP_TEST_COMMAND_WRITE);
nxsem_wait(&test->irq_raise);
crc32 = crc32part(addr, param->size, ~0);
kmm_free(addr);
return crc32 == pci_ep_test_read_dword(test, PCI_EP_TEST_CHECKSUM);
}
/****************************************************************************
* Name: pci_ep_test_copy
*
* Description:
* This function is used to test copy.
*
****************************************************************************/
static bool
pci_ep_test_copy(struct pci_ep_test_s *test, unsigned long arg)
{
FAR struct pci_ep_test_param_s *param;
FAR void *src_addr;
FAR void *dst_addr;
uint64_t src_phys_addr;
uint64_t dst_phys_addr;
uint32_t src_crc32;
uint32_t dst_crc32;
param = (FAR struct pci_ep_test_param_s *)arg;
if (param->size == 0)
{
pcierr("xfer size is zero, invalid param\n");
return false;
}
if (test->irq_type < PCI_EP_TEST_IRQ_TYPE_LEGACY ||
test->irq_type > PCI_EP_TEST_IRQ_TYPE_MSIX)
{
pcierr("invalid IRQ type option\n");
return false;
}
src_addr = kmm_memalign(test->alignment, param->size);
if (NULL == src_addr)
{
pcierr("Failed to alloc origin memory addr\n");
return false;
}
pci_ep_fill_init(src_addr, param->size);
src_phys_addr = up_addrenv_va_to_pa(src_addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_LOWER_SRC_ADDR,
src_phys_addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_UPPER_SRC_ADDR,
src_phys_addr >> 32);
src_crc32 = crc32part(src_addr, param->size, ~0);
dst_addr = kmm_memalign(test->alignment, param->size);
if (NULL == dst_addr)
{
pcierr("Failed to alloc origin memory addr\n");
kmm_free(src_addr);
return false;
}
dst_phys_addr = up_addrenv_va_to_pa(dst_addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_LOWER_DST_ADDR,
dst_phys_addr);
pci_ep_test_write_dword(test, PCI_EP_TEST_UPPER_DST_ADDR,
dst_phys_addr >> 32);
pci_ep_test_write_dword(test, PCI_EP_TEST_SIZE, param->size);
pci_ep_test_write_dword(test, PCI_EP_TEST_FLAGS, 0);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_TYPE, test->irq_type);
pci_ep_test_write_dword(test, PCI_EP_TEST_IRQ_NUMBER, 1);
pci_ep_test_write_dword(test, PCI_EP_TEST_COMMAND,
PCI_EP_TEST_COMMAND_COPY);
nxsem_wait(&test->irq_raise);
dst_crc32 = crc32part(dst_addr, param->size, ~0);
kmm_free(dst_addr);
kmm_free(src_addr);
return src_crc32 == dst_crc32;
}
/****************************************************************************
* Name: pci_ep_test_free_irq
*
* Description:
* This function is used to clear request irq type.
*
****************************************************************************/
static bool pci_ep_test_free_irq(FAR struct pci_ep_test_s *test)
{
up_disable_irq(test->irq);
irq_detach(test->irq);
if (test->irq_type >= PCI_EP_TEST_IRQ_TYPE_MSI)
{
pci_release_irq(test->pdev, &test->irq, 1);
}
else if (test->irq_type == PCI_EP_TEST_IRQ_TYPE_LEGACY)
{
pci_disable_irq(test->pdev);
}
return true;
}
/****************************************************************************
* Name: pci_ep_test_handler
*
* Description:
* request pci irq and register handler
*
****************************************************************************/
static int pci_ep_test_handler(int irq, FAR void *context,
FAR void *args)
{
FAR struct pci_ep_test_s *test =
(FAR struct pci_ep_test_s *)args;
uint32_t reg;
reg = pci_ep_test_read_dword(test, PCI_EP_TEST_STATUS);
if (reg & PCI_EP_TEST_STATUS_IRQ_RAISED)
{
nxsem_post(&test->irq_raise);
reg &= ~PCI_EP_TEST_STATUS_IRQ_RAISED;
}
pci_ep_test_write_dword(test, PCI_EP_TEST_STATUS, reg);
return 0;
}
/****************************************************************************
* Name: pci_ep_test_alloc_irq
*
* Description:
* Alloc pci irq by irq type
*
****************************************************************************/
static int pci_ep_test_alloc_irq(FAR struct pci_ep_test_s *test,
int irq_type)
{
FAR struct pci_device_s *pdev = test->pdev;
int ret = -EINVAL;
switch (irq_type)
{
case PCI_EP_TEST_IRQ_TYPE_LEGACY:
ret = test->irq = pci_get_irq(pdev);
break;
case PCI_EP_TEST_IRQ_TYPE_MSI:
ret = pci_alloc_irq(pdev, &test->irq, 1);
break;
case PCI_EP_TEST_IRQ_TYPE_MSIX:
ret = pci_alloc_irq(pdev, &test->irq, 1);
break;
default:
pcierr("invalid irq type %d\n", irq_type);
break;
}
if (ret < 0)
{
return ret;
}
test->irq_type = irq_type;
if (irq_type >= PCI_EP_TEST_IRQ_TYPE_MSI)
{
ret = pci_connect_irq(pdev, &test->irq, 1);
if (ret < 0)
{
pcierr("Failed to connect MSI %d\n", ret);
return ret;
}
}
else if (irq_type == PCI_EP_TEST_IRQ_TYPE_LEGACY)
{
pci_enable_irq(pdev, test->irq);
}
ret = irq_attach(test->irq, pci_ep_test_handler, test);
if (ret >= 0)
{
up_enable_irq(test->irq);
}
else
{
pci_release_irq(test->pdev, &test->irq, 1);
pcierr("error ret=%d\n", ret);
}
return ret;
}
/****************************************************************************
* Name: pci_ep_test_set_irq
*
* Description:
* This function is used to set request irq type.
*
****************************************************************************/
static bool
pci_ep_test_set_irq(struct pci_ep_test_s *test, int req_irq_type)
{
int ret;
if (req_irq_type < PCI_EP_TEST_IRQ_TYPE_LEGACY ||
req_irq_type > PCI_EP_TEST_COMMAND_MSIX_IRQ)
{
pcierr("invalid irq option\n");
return false;
}
if (test->irq_type == req_irq_type)
{
return true;
}
pci_ep_test_free_irq(test);
ret = pci_ep_test_alloc_irq(test, req_irq_type);
if (ret < 0)
{
pcierr("alloc pci irq %d fail\n", req_irq_type);
return false;
}
return true;
}
/****************************************************************************
* Name: pci_ep_test_ioctl
*
* Description:
* This function is used to respond ioctl cmd.
*
****************************************************************************/
static int pci_ep_test_ioctl(FAR struct file *filep,
int cmd, unsigned long arg)
{
FAR struct pci_ep_test_s *test;
int ret = -EINVAL;
int bar;
test = filep->f_priv;
DEBUGASSERT(test != NULL);
nxmutex_lock(&test->mutex);
switch (cmd)
{
case PCITEST_BAR:
bar = arg;
if (bar > PCI_STD_NUM_BARS || bar < 0)
{
pcierr("bar num %d is invalid\n", bar);
break;
}
ret = pci_ep_test_bar(test, bar);
break;
case PCITEST_LEGACY_IRQ:
ret = pci_ep_test_legacy_irq(test);
break;
case PCITEST_MSI:
case PCITEST_MSIX:
ret = pci_ep_test_msi_irq(test, arg, cmd == PCITEST_MSIX);
break;
case PCITEST_WRITE:
ret = pci_ep_test_write(test, arg);
break;
case PCITEST_READ:
ret = pci_ep_test_read(test, arg);
break;
case PCITEST_COPY:
ret = pci_ep_test_copy(test, arg);
break;
case PCITEST_SET_IRQTYPE:
ret = pci_ep_test_set_irq(test, arg);
break;
case PCITEST_GET_IRQTYPE:
ret = test->irq_type;
break;
case PCITEST_CLEAR_IRQ:
ret = pci_ep_test_free_irq(test);
break;
default:
break;
}
nxmutex_unlock(&test->mutex);
return ret;
}
/****************************************************************************
* Name: pci_ep_test_probe
*
* Description:
* Initialize device
*
****************************************************************************/
static int pci_ep_test_probe(FAR struct pci_device_s *dev)
{
FAR struct pci_ep_test_s *test;
int bar;
int ret;
pciinfo("Enter pci endpoint test probe.\n");
if (pci_is_bridge(dev))
{
pcierr("pci device is not endpoint!!!\n");
return -ENODEV;
}
test = kmm_zalloc(sizeof(*test));
if (NULL == test)
{
pcierr("malloc ptest memory failed\n");
return -ENOMEM;
}
test->pdev = dev;
test->alignment = sizeof(uint32_t);
nxsem_init(&test->irq_raise, 0, 0);
nxmutex_init(&test->mutex);
ret = pci_enable_device(dev);
if (ret < 0)
{
pcierr("Enable endpoint device failed, ret=%d\n", ret);
goto en_err;
}
pci_set_master(dev);
ret = pci_ep_test_alloc_irq(test, PCI_EP_TEST_IRQ_TYPE_LEGACY);
if (ret < 0)
{
pcierr("Fail to alloc pci irq\n");
goto irq_err;
}
for (bar = 0; bar < PCI_STD_NUM_BARS; bar++)
{
if (pci_resource_flags(dev, bar) & PCI_RESOURCE_MEM)
{
test->bar[bar] = pci_map_bar(dev, bar);
if (NULL == test->bar[bar])
{
pcierr("failed to read bar%d\n", bar);
ret = -ENOMEM;
goto bar_err;
}
}
}
test->base = test->bar[0];
snprintf(test->name, sizeof(test->name),
PCI_EP_TEST_DEVICE_NAME ".%d", g_pci_ep_idr++);
register_driver(test->name, &g_pci_ep_test_fops, 0600, test);
pciinfo("pci ep test device register success.\n");
return 0;
bar_err:
pci_ep_test_free_irq(test);
irq_err:
pci_clear_master(dev);
pci_disable_device(dev);
en_err:
nxmutex_destroy(&test->mutex);
nxsem_destroy(&test->irq_raise);
kmm_free(test);
return ret;
}
/****************************************************************************
* Public Functions
****************************************************************************/
/****************************************************************************
* Name: pci_register_ep_test_driver
*
* Description:
* Register a pci driver to test endpoint
*
****************************************************************************/
int pci_register_ep_test_driver(void)
{
return pci_register_driver(&g_pci_ep_test_drv);
}