Compare commits
12 Commits
tesc
...
blob-28jun
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1c71a3f024 | ||
|
|
9fec7f9af0 | ||
|
|
fed943af6b | ||
|
|
3092ddbc88 | ||
|
|
9377438d02 | ||
|
|
48fa764914 | ||
|
|
f09acc02f2 | ||
|
|
0807f9a068 | ||
|
|
b1936206e2 | ||
|
|
1f24e6ec71 | ||
|
|
92a414bba0 | ||
|
|
7452a49d13 |
@@ -220,6 +220,7 @@ config SANDBOX
|
||||
imply VIRTIO_MMIO
|
||||
imply VIRTIO_PCI
|
||||
imply VIRTIO_SANDBOX
|
||||
imply VIRTIO_SANDBOX_EMUL
|
||||
# Re-enable this when fully implemented
|
||||
# imply VIRTIO_BLK
|
||||
imply VIRTIO_NET
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
# (C) Copyright 2000-2003
|
||||
# Wolfgang Denk, DENX Software Engineering, wd@denx.de.
|
||||
|
||||
obj-y := cache.o cpu.o state.o
|
||||
obj-y := cache.o cpu.o mem.o state.o
|
||||
extra-y := start.o os.o
|
||||
extra-$(CONFIG_SANDBOX_SDL) += sdl.o
|
||||
obj-$(CONFIG_XPL_BUILD) += spl.o
|
||||
|
||||
@@ -9,27 +9,16 @@
|
||||
#include <cpu_func.h>
|
||||
#include <errno.h>
|
||||
#include <log.h>
|
||||
#include <mapmem.h>
|
||||
#include <os.h>
|
||||
#include <setjmp.h>
|
||||
#include <asm/global_data.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/malloc.h>
|
||||
#include <asm/state.h>
|
||||
#include <dm/ofnode.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/libfdt.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
/* Enable access to PCI memory with map_sysmem() */
|
||||
static bool enable_pci_map;
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
/* Last device that was mapped into memory, and length of mapping */
|
||||
static struct udevice *map_dev;
|
||||
unsigned long map_len;
|
||||
#endif
|
||||
|
||||
void __noreturn sandbox_exit(void)
|
||||
{
|
||||
/* Do this here while it still has an effect */
|
||||
@@ -61,267 +50,6 @@ int cleanup_before_linux_select(int flags)
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* is_in_sandbox_mem() - Checks if a pointer is within sandbox's emulated DRAM
|
||||
*
|
||||
* This provides a way to check if a pointer is owned by sandbox (and is within
|
||||
* its RAM) or not. Sometimes pointers come from a test which conceptually runs
|
||||
* output sandbox, potentially with direct access to the C-library malloc()
|
||||
* function, or the sandbox stack (which is not actually within the emulated
|
||||
* DRAM.
|
||||
*
|
||||
* Such pointers obviously cannot be mapped into sandbox's DRAM, so we must
|
||||
* detect them an process them separately, by recording a mapping to a tag,
|
||||
* which we can use to map back to the pointer later.
|
||||
*
|
||||
* @ptr: Pointer to check
|
||||
* Return: true if this is within sandbox emulated DRAM, false if not
|
||||
*/
|
||||
static bool is_in_sandbox_mem(const void *ptr)
|
||||
{
|
||||
return (const uint8_t *)ptr >= gd->arch.ram_buf &&
|
||||
(const uint8_t *)ptr < gd->arch.ram_buf + gd->ram_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* phys_to_virt() - Converts a sandbox RAM address to a pointer
|
||||
*
|
||||
* Sandbox uses U-Boot addresses from 0 to the size of DRAM. These index into
|
||||
* the emulated DRAM buffer used by sandbox. This function converts such an
|
||||
* address to a pointer into this buffer, which can be used to access the
|
||||
* memory.
|
||||
*
|
||||
* If the address is outside this range, it is assumed to be a tag
|
||||
*/
|
||||
void *phys_to_virt(phys_addr_t paddr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
struct sandbox_state *state;
|
||||
|
||||
/* If the address is within emulated DRAM, calculate the value */
|
||||
if (paddr < gd->ram_size)
|
||||
return (void *)(gd->arch.ram_buf + paddr);
|
||||
|
||||
/*
|
||||
* Otherwise search out list of tags for the correct pointer previously
|
||||
* created by map_to_sysmem()
|
||||
*/
|
||||
state = state_get_current();
|
||||
list_for_each_entry(mentry, &state->mapmem_head, sibling_node) {
|
||||
if (mentry->tag == paddr) {
|
||||
log_debug("Used map from %lx to %p\n", (ulong)paddr,
|
||||
mentry->ptr);
|
||||
mentry->refcnt++;
|
||||
return mentry->ptr;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s: Cannot map sandbox address %lx (SDRAM from 0 to %lx)\n",
|
||||
__func__, (ulong)paddr, (ulong)gd->ram_size);
|
||||
os_abort();
|
||||
|
||||
/* Not reached */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct sandbox_mapmem_entry *find_tag(const void *ptr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
list_for_each_entry(mentry, &state->mapmem_head, sibling_node) {
|
||||
if (mentry->ptr == ptr) {
|
||||
log_debug("Used map from %p to %lx\n", ptr,
|
||||
mentry->tag);
|
||||
return mentry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
phys_addr_t virt_to_phys(void *ptr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
|
||||
/*
|
||||
* If it is in emulated RAM, don't bother looking for a tag. Just
|
||||
* calculate the pointer using the provides offset into the RAM buffer.
|
||||
*/
|
||||
if (is_in_sandbox_mem(ptr))
|
||||
return (phys_addr_t)((uint8_t *)ptr - gd->arch.ram_buf);
|
||||
|
||||
mentry = find_tag(ptr);
|
||||
if (!mentry) {
|
||||
/* Abort so that gdb can be used here */
|
||||
printf("%s: Cannot map sandbox address %p (SDRAM from 0 to %lx)\n",
|
||||
__func__, ptr, (ulong)gd->ram_size);
|
||||
os_abort();
|
||||
}
|
||||
log_debug("Used map from %p to %lx\n", ptr, mentry->tag);
|
||||
|
||||
return mentry->tag;
|
||||
}
|
||||
|
||||
void *map_physmem(phys_addr_t paddr, unsigned long len, unsigned long flags)
|
||||
{
|
||||
#if defined(CONFIG_PCI) && !defined(CONFIG_XPL_BUILD)
|
||||
unsigned long plen = len;
|
||||
void *ptr;
|
||||
|
||||
map_dev = NULL;
|
||||
if (enable_pci_map && !pci_map_physmem(paddr, &len, &map_dev, &ptr)) {
|
||||
if (plen != len) {
|
||||
printf("%s: Warning: partial map at %x, wanted %lx, got %lx\n",
|
||||
__func__, (uint)paddr, len, plen);
|
||||
}
|
||||
map_len = len;
|
||||
log_debug("pci map %lx -> %p\n", (ulong)paddr, ptr);
|
||||
return ptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
return phys_to_virt(paddr);
|
||||
}
|
||||
|
||||
void unmap_physmem(const void *ptr, unsigned long flags)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
if (map_dev) {
|
||||
pci_unmap_physmem(ptr, map_len, map_dev);
|
||||
map_dev = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If it is in emulated RAM, we didn't create a tag, so nothing to do */
|
||||
if (is_in_sandbox_mem(ptr))
|
||||
return;
|
||||
|
||||
mentry = find_tag(ptr);
|
||||
if (mentry) {
|
||||
if (!--mentry->refcnt) {
|
||||
list_del(&mentry->sibling_node);
|
||||
log_debug("Removed map from %p to %lx\n", ptr,
|
||||
(ulong)mentry->tag);
|
||||
free(mentry);
|
||||
}
|
||||
} else {
|
||||
log_warning("Address not mapped: %p\n", ptr);
|
||||
}
|
||||
}
|
||||
|
||||
phys_addr_t map_to_sysmem(const void *ptr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
|
||||
/*
|
||||
* If it is in emulated RAM, don't bother creating a tag. Just return
|
||||
* the offset into the RAM buffer.
|
||||
*/
|
||||
if (is_in_sandbox_mem(ptr))
|
||||
return (u8 *)ptr - gd->arch.ram_buf;
|
||||
|
||||
/*
|
||||
* See if there is an existing tag with this pointer. If not, set up a
|
||||
* new one.
|
||||
*/
|
||||
mentry = find_tag(ptr);
|
||||
if (!mentry) {
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
mentry = malloc(sizeof(*mentry));
|
||||
if (!mentry) {
|
||||
printf("%s: Error: Out of memory\n", __func__);
|
||||
os_exit(ENOMEM);
|
||||
}
|
||||
mentry->tag = state->next_tag++;
|
||||
mentry->ptr = (void *)ptr;
|
||||
mentry->refcnt = 0;
|
||||
list_add_tail(&mentry->sibling_node, &state->mapmem_head);
|
||||
log_debug("Added map from %p to %lx\n", ptr,
|
||||
(ulong)mentry->tag);
|
||||
}
|
||||
|
||||
mentry->refcnt++;
|
||||
|
||||
/*
|
||||
* Return the tag as the address to use. A later call to map_sysmem()
|
||||
* will return ptr
|
||||
*/
|
||||
return mentry->tag;
|
||||
}
|
||||
|
||||
void sandbox_map_list(void)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
printf("Sandbox memory-mapping\n");
|
||||
printf("%8s %16s %6s\n", "Addr", "Mapping", "Refcnt");
|
||||
list_for_each_entry(mentry, &state->mapmem_head, sibling_node) {
|
||||
printf("%8lx %p %6d\n", mentry->tag, mentry->ptr,
|
||||
mentry->refcnt);
|
||||
}
|
||||
}
|
||||
|
||||
unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
if (!state->allow_memio)
|
||||
return 0;
|
||||
|
||||
switch (size) {
|
||||
case SB_SIZE_8:
|
||||
return *(u8 *)addr;
|
||||
case SB_SIZE_16:
|
||||
return *(u16 *)addr;
|
||||
case SB_SIZE_32:
|
||||
return *(u32 *)addr;
|
||||
case SB_SIZE_64:
|
||||
return *(u64 *)addr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
if (!state->allow_memio)
|
||||
return;
|
||||
|
||||
switch (size) {
|
||||
case SB_SIZE_8:
|
||||
*(u8 *)addr = val;
|
||||
break;
|
||||
case SB_SIZE_16:
|
||||
*(u16 *)addr = val;
|
||||
break;
|
||||
case SB_SIZE_32:
|
||||
*(u32 *)addr = val;
|
||||
break;
|
||||
case SB_SIZE_64:
|
||||
*(u64 *)addr = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void sandbox_set_enable_memio(bool enable)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
state->allow_memio = enable;
|
||||
}
|
||||
|
||||
void sandbox_set_enable_pci_map(int enable)
|
||||
{
|
||||
enable_pci_map = enable;
|
||||
}
|
||||
|
||||
void dcache_enable(void)
|
||||
{
|
||||
}
|
||||
|
||||
337
arch/sandbox/cpu/mem.c
Normal file
337
arch/sandbox/cpu/mem.c
Normal file
@@ -0,0 +1,337 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Copyright (c) 2011 The Chromium OS Authors.
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY LOGC_SANDBOX
|
||||
|
||||
#include <alist.h>
|
||||
#include <errno.h>
|
||||
#include <log.h>
|
||||
#include <malloc.h>
|
||||
#include <os.h>
|
||||
#include <asm/global_data.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/state.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
DECLARE_GLOBAL_DATA_PTR;
|
||||
|
||||
/* Enable access to PCI memory with map_sysmem() */
|
||||
static bool enable_pci_map;
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
/* Last device that was mapped into memory, and length of mapping */
|
||||
static struct udevice *map_dev;
|
||||
unsigned long map_len;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* is_in_sandbox_mem() - Checks if a pointer is within sandbox's emulated DRAM
|
||||
*
|
||||
* This provides a way to check if a pointer is owned by sandbox (and is within
|
||||
* its RAM) or not. Sometimes pointers come from a test which conceptually runs
|
||||
* output sandbox, potentially with direct access to the C-library malloc()
|
||||
* function, or the sandbox stack (which is not actually within the emulated
|
||||
* DRAM.
|
||||
*
|
||||
* Such pointers obviously cannot be mapped into sandbox's DRAM, so we must
|
||||
* detect them an process them separately, by recording a mapping to a tag,
|
||||
* which we can use to map back to the pointer later.
|
||||
*
|
||||
* @ptr: Pointer to check
|
||||
* Return: true if this is within sandbox emulated DRAM, false if not
|
||||
*/
|
||||
static bool is_in_sandbox_mem(const void *ptr)
|
||||
{
|
||||
return (const uint8_t *)ptr >= gd->arch.ram_buf &&
|
||||
(const uint8_t *)ptr < gd->arch.ram_buf + gd->ram_size;
|
||||
}
|
||||
|
||||
/**
|
||||
* phys_to_virt() - Converts a sandbox RAM address to a pointer
|
||||
*
|
||||
* Sandbox uses U-Boot addresses from 0 to the size of DRAM. These index into
|
||||
* the emulated DRAM buffer used by sandbox. This function converts such an
|
||||
* address to a pointer into this buffer, which can be used to access the
|
||||
* memory.
|
||||
*
|
||||
* If the address is outside this range, it is assumed to be a tag
|
||||
*/
|
||||
void *phys_to_virt(phys_addr_t paddr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
struct sandbox_state *state;
|
||||
|
||||
/* If the address is within emulated DRAM, calculate the value */
|
||||
if (paddr < gd->ram_size)
|
||||
return (void *)(gd->arch.ram_buf + paddr);
|
||||
|
||||
/*
|
||||
* Otherwise search out list of tags for the correct pointer previously
|
||||
* created by map_to_sysmem()
|
||||
*/
|
||||
state = state_get_current();
|
||||
list_for_each_entry(mentry, &state->mapmem_head, sibling_node) {
|
||||
if (mentry->tag == paddr) {
|
||||
log_debug("Used map from %lx to %p\n", (ulong)paddr,
|
||||
mentry->ptr);
|
||||
mentry->refcnt++;
|
||||
return mentry->ptr;
|
||||
}
|
||||
}
|
||||
|
||||
printf("%s: Cannot map sandbox address %lx (SDRAM from 0 to %lx)\n",
|
||||
__func__, (ulong)paddr, (ulong)gd->ram_size);
|
||||
os_abort();
|
||||
|
||||
/* Not reached */
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct sandbox_mapmem_entry *find_tag(const void *ptr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
list_for_each_entry(mentry, &state->mapmem_head, sibling_node) {
|
||||
if (mentry->ptr == ptr) {
|
||||
log_debug("Used map from %p to %lx\n", ptr,
|
||||
mentry->tag);
|
||||
return mentry;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
phys_addr_t virt_to_phys(void *ptr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
|
||||
/*
|
||||
* If it is in emulated RAM, don't bother looking for a tag. Just
|
||||
* calculate the pointer using the provides offset into the RAM buffer.
|
||||
*/
|
||||
if (is_in_sandbox_mem(ptr))
|
||||
return (phys_addr_t)((uint8_t *)ptr - gd->arch.ram_buf);
|
||||
|
||||
mentry = find_tag(ptr);
|
||||
if (!mentry) {
|
||||
/* Abort so that gdb can be used here */
|
||||
printf("%s: Cannot map sandbox address %p (SDRAM from 0 to %lx)\n",
|
||||
__func__, ptr, (ulong)gd->ram_size);
|
||||
os_abort();
|
||||
}
|
||||
log_debug("Used map from %p to %lx\n", ptr, mentry->tag);
|
||||
|
||||
return mentry->tag;
|
||||
}
|
||||
|
||||
void *map_physmem(phys_addr_t paddr, unsigned long len, unsigned long flags)
|
||||
{
|
||||
#if defined(CONFIG_PCI) && !defined(CONFIG_XPL_BUILD)
|
||||
unsigned long plen = len;
|
||||
void *ptr;
|
||||
|
||||
map_dev = NULL;
|
||||
if (enable_pci_map && !pci_map_physmem(paddr, &len, &map_dev, &ptr)) {
|
||||
if (plen != len) {
|
||||
printf("%s: Warning: partial map at %x, wanted %lx, got %lx\n",
|
||||
__func__, (uint)paddr, len, plen);
|
||||
}
|
||||
map_len = len;
|
||||
log_debug("pci map %lx -> %p\n", (ulong)paddr, ptr);
|
||||
return ptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
return phys_to_virt(paddr);
|
||||
}
|
||||
|
||||
void unmap_physmem(const void *ptr, unsigned long flags)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
|
||||
#ifdef CONFIG_PCI
|
||||
if (map_dev) {
|
||||
pci_unmap_physmem(ptr, map_len, map_dev);
|
||||
map_dev = NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* If it is in emulated RAM, we didn't create a tag, so nothing to do */
|
||||
if (is_in_sandbox_mem(ptr))
|
||||
return;
|
||||
|
||||
mentry = find_tag(ptr);
|
||||
if (mentry) {
|
||||
if (!--mentry->refcnt) {
|
||||
list_del(&mentry->sibling_node);
|
||||
log_debug("Removed map from %p to %lx\n", ptr,
|
||||
(ulong)mentry->tag);
|
||||
free(mentry);
|
||||
}
|
||||
} else {
|
||||
log_warning("Address not mapped: %p\n", ptr);
|
||||
}
|
||||
}
|
||||
|
||||
phys_addr_t map_to_sysmem(const void *ptr)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
|
||||
/*
|
||||
* If it is in emulated RAM, don't bother creating a tag. Just return
|
||||
* the offset into the RAM buffer.
|
||||
*/
|
||||
if (is_in_sandbox_mem(ptr))
|
||||
return (u8 *)ptr - gd->arch.ram_buf;
|
||||
|
||||
/*
|
||||
* See if there is an existing tag with this pointer. If not, set up a
|
||||
* new one.
|
||||
*/
|
||||
mentry = find_tag(ptr);
|
||||
if (!mentry) {
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
mentry = malloc(sizeof(*mentry));
|
||||
if (!mentry) {
|
||||
printf("%s: Error: Out of memory\n", __func__);
|
||||
os_exit(ENOMEM);
|
||||
}
|
||||
mentry->tag = state->next_tag++;
|
||||
mentry->ptr = (void *)ptr;
|
||||
mentry->refcnt = 0;
|
||||
list_add_tail(&mentry->sibling_node, &state->mapmem_head);
|
||||
log_debug("Added map from %p to %lx\n", ptr,
|
||||
(ulong)mentry->tag);
|
||||
}
|
||||
|
||||
mentry->refcnt++;
|
||||
|
||||
/*
|
||||
* Return the tag as the address to use. A later call to map_sysmem()
|
||||
* will return ptr
|
||||
*/
|
||||
return mentry->tag;
|
||||
}
|
||||
|
||||
void sandbox_map_list(void)
|
||||
{
|
||||
struct sandbox_mapmem_entry *mentry;
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
printf("Sandbox memory-mapping\n");
|
||||
printf("%8s %16s %6s\n", "Addr", "Mapping", "Refcnt");
|
||||
list_for_each_entry(mentry, &state->mapmem_head, sibling_node) {
|
||||
printf("%8lx %p %6d\n", mentry->tag, mentry->ptr,
|
||||
mentry->refcnt);
|
||||
}
|
||||
}
|
||||
|
||||
static bool in_range(const struct sandbox_mmio *mmio, const void *addr)
|
||||
{
|
||||
return addr >= mmio->base && addr < mmio->base + mmio->size;
|
||||
}
|
||||
|
||||
unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
const struct sandbox_mmio *mmio;
|
||||
|
||||
alist_for_each(mmio, &state->mmio) {
|
||||
if (in_range(mmio, addr))
|
||||
return mmio->h_read(mmio->ctx, addr, size);
|
||||
}
|
||||
|
||||
if (!state->allow_memio)
|
||||
return 0;
|
||||
|
||||
switch (size) {
|
||||
case SB_SIZE_8:
|
||||
return *(u8 *)addr;
|
||||
case SB_SIZE_16:
|
||||
return *(u16 *)addr;
|
||||
case SB_SIZE_32:
|
||||
return *(u32 *)addr;
|
||||
case SB_SIZE_64:
|
||||
return *(u64 *)addr;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
const struct sandbox_mmio *mmio;
|
||||
|
||||
alist_for_each(mmio, &state->mmio) {
|
||||
if (in_range(mmio, addr)) {
|
||||
mmio->h_write(mmio->ctx, addr, val, size);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!state->allow_memio)
|
||||
return;
|
||||
|
||||
switch (size) {
|
||||
case SB_SIZE_8:
|
||||
*(u8 *)addr = val;
|
||||
break;
|
||||
case SB_SIZE_16:
|
||||
*(u16 *)addr = val;
|
||||
break;
|
||||
case SB_SIZE_32:
|
||||
*(u32 *)addr = val;
|
||||
break;
|
||||
case SB_SIZE_64:
|
||||
*(u64 *)addr = val;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void sandbox_set_enable_memio(bool enable)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
|
||||
state->allow_memio = enable;
|
||||
}
|
||||
|
||||
void sandbox_set_enable_pci_map(int enable)
|
||||
{
|
||||
enable_pci_map = enable;
|
||||
}
|
||||
|
||||
int sandbox_mmio_add(void *base, ulong size, sandbox_mmio_read_func h_read,
|
||||
sandbox_mmio_write_func h_write, void *ctx)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
struct sandbox_mmio mmio;
|
||||
|
||||
mmio.base = base;
|
||||
mmio.size = size;
|
||||
mmio.h_read = h_read;
|
||||
mmio.h_write = h_write;
|
||||
mmio.ctx = ctx;
|
||||
if (!alist_add(&state->mmio, mmio))
|
||||
return -ENOMEM;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sandbox_mmio_remove(void *ctx)
|
||||
{
|
||||
struct sandbox_state *state = state_get_current();
|
||||
struct sandbox_mmio *from, *to;
|
||||
|
||||
alist_for_each_filter(from, to, &state->mmio) {
|
||||
if (from->ctx != ctx)
|
||||
*to++ = *from;
|
||||
}
|
||||
alist_update_end(&state->mmio, to);
|
||||
}
|
||||
@@ -3,6 +3,7 @@
|
||||
* Copyright (c) 2011-2012 The Chromium OS Authors.
|
||||
*/
|
||||
|
||||
#include <alist.h>
|
||||
#include <bloblist.h>
|
||||
#include <config.h>
|
||||
#include <errno.h>
|
||||
@@ -485,6 +486,7 @@ int state_init(void)
|
||||
printf("Out of memory\n");
|
||||
os_exit(1);
|
||||
}
|
||||
alist_init_struct(&state->mmio, struct sandbox_mmio);
|
||||
|
||||
state_reset_for_test(state);
|
||||
/*
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include <dt-bindings/input/input.h>
|
||||
#include <dt-bindings/pinctrl/sandbox-pinmux.h>
|
||||
#include <dt-bindings/mux/mux.h>
|
||||
#include <dt-bindings/virtio.h>
|
||||
|
||||
/ {
|
||||
model = "sandbox";
|
||||
@@ -1691,7 +1692,7 @@
|
||||
|
||||
sandbox_virtio1 {
|
||||
compatible = "sandbox,virtio1";
|
||||
virtio-type = <4>; /* rng */
|
||||
virtio-type = <VIRTIO_ID_RNG>;
|
||||
};
|
||||
|
||||
sandbox_virtio2 {
|
||||
@@ -1700,7 +1701,15 @@
|
||||
|
||||
sandbox-virtio-blk {
|
||||
compatible = "sandbox,virtio1";
|
||||
virtio-type = <2>; /* block */
|
||||
virtio-type = <VIRTIO_ID_BLOCK>;
|
||||
};
|
||||
|
||||
virtio-blk {
|
||||
compatible = "sandbox,virtio-blk-emul";
|
||||
|
||||
mmio {
|
||||
compatible = "sandbox,virtio-emul";
|
||||
};
|
||||
};
|
||||
|
||||
sandbox_scmi {
|
||||
|
||||
@@ -33,7 +33,22 @@ void unmap_physmem(const void *vaddr, unsigned long flags);
|
||||
/* Map from a pointer to our RAM buffer */
|
||||
phys_addr_t map_to_sysmem(const void *ptr);
|
||||
|
||||
/**
|
||||
* sandbox_read() - Perform a memory read
|
||||
*
|
||||
* @addr: Pointer to read from
|
||||
* @size: Access size of read
|
||||
* Return: Value obtained
|
||||
*/
|
||||
unsigned long sandbox_read(const void *addr, enum sandboxio_size_t size);
|
||||
|
||||
/**
|
||||
* sandbox_write() - Perform a memory write
|
||||
*
|
||||
* @addr: Pointer to write to
|
||||
* @val: Value to write
|
||||
* @size: Access size of write
|
||||
*/
|
||||
void sandbox_write(void *addr, unsigned int val, enum sandboxio_size_t size);
|
||||
|
||||
#define readb(addr) sandbox_read((const void *)addr, SB_SIZE_8)
|
||||
|
||||
@@ -6,11 +6,14 @@
|
||||
#ifndef __SANDBOX_STATE_H
|
||||
#define __SANDBOX_STATE_H
|
||||
|
||||
#include <alist.h>
|
||||
#include <sysreset.h>
|
||||
#include <stdbool.h>
|
||||
#include <linux/list.h>
|
||||
#include <linux/stringify.h>
|
||||
|
||||
enum sandboxio_size_t;
|
||||
|
||||
enum {
|
||||
SB_MAX_BINDS = 4,
|
||||
};
|
||||
@@ -68,6 +71,69 @@ struct sandbox_mapmem_entry {
|
||||
struct list_head sibling_node;
|
||||
};
|
||||
|
||||
/**
|
||||
* sandbox_read() - Read function for sandbox_mmio
|
||||
*
|
||||
* @addr: Pointer to read from
|
||||
* @size: Access size of read
|
||||
* Return: Value obtained
|
||||
*/
|
||||
typedef long (*sandbox_mmio_read_func)(void *ctx, const void *addr,
|
||||
enum sandboxio_size_t size);
|
||||
|
||||
/**
|
||||
* sandbox_write() - Write function for sandbox_mmio
|
||||
*
|
||||
* @addr: Pointer to write to
|
||||
* @val: Value to write
|
||||
* @size: Access size of write
|
||||
*/
|
||||
typedef void (*sandbox_mmio_write_func)(void *ctx, void *addr, unsigned int val,
|
||||
enum sandboxio_size_t size);
|
||||
|
||||
/**
|
||||
* sandbox_mmio_add() - Add a new MMIO region
|
||||
*
|
||||
* Register a new set of read/write functions to be called for a particular
|
||||
* memory region
|
||||
*
|
||||
* @base: Base pointer for region
|
||||
* @size: Size of region
|
||||
* @h_read: Read handler
|
||||
* @h_write: Write handler
|
||||
* @ctx: Context pointer to passed to read/write functions
|
||||
*/
|
||||
int sandbox_mmio_add(void *base, ulong size, sandbox_mmio_read_func h_read,
|
||||
sandbox_mmio_write_func h_write, void *ctx);
|
||||
|
||||
/**
|
||||
* sandbox_mmio_remove() - Remove an MMIO region
|
||||
*
|
||||
* All regions with the given @ctx are removed
|
||||
*
|
||||
* @ctx: Context to search for
|
||||
*/
|
||||
void sandbox_mmio_remove(void *ctx);
|
||||
|
||||
/**
|
||||
* struct sandbox_mmio - defines a region of memory-mapped I/O
|
||||
*
|
||||
* This allows accesses to a region of memory to go through provided functions
|
||||
*
|
||||
* @base: Base pointer of region
|
||||
* @size: Size of region
|
||||
* @h_read: Read handler
|
||||
* @h_write: Write handler
|
||||
* @ctx: Context pointer provided when registering
|
||||
*/
|
||||
struct sandbox_mmio {
|
||||
void *base;
|
||||
ulong size;
|
||||
sandbox_mmio_read_func h_read;
|
||||
sandbox_mmio_write_func h_write;
|
||||
void *ctx;
|
||||
};
|
||||
|
||||
/* The complete state of the test system */
|
||||
struct sandbox_state {
|
||||
const char *cmd; /* Command to execute */
|
||||
@@ -126,6 +192,7 @@ struct sandbox_state {
|
||||
const char *binds[SB_MAX_BINDS]; /* list of -B arguments */
|
||||
int num_binds; /* number of -B arguments */
|
||||
|
||||
struct alist mmio; /* list of struct sandbox_mmio */
|
||||
/*
|
||||
* This struct is getting large.
|
||||
*
|
||||
|
||||
@@ -363,3 +363,4 @@ CONFIG_TEST_FDTDEC=y
|
||||
CONFIG_UNIT_TEST=y
|
||||
CONFIG_UT_TIME=y
|
||||
CONFIG_UT_DM=y
|
||||
CONFIG_VIRTIO_BLK=y
|
||||
|
||||
@@ -9,7 +9,6 @@ CONFIG_PCI=y
|
||||
CONFIG_ANDROID_BOOT_IMAGE=y
|
||||
CONFIG_TIMESTAMP=y
|
||||
CONFIG_FIT=y
|
||||
CONFIG_FIT_SIGNATURE=y
|
||||
# CONFIG_BOOTSTD_FULL is not set
|
||||
# CONFIG_BOOTMETH_CROS is not set
|
||||
# CONFIG_BOOTMETH_VBE is not set
|
||||
@@ -38,5 +37,6 @@ CONFIG_TIMER=y
|
||||
# CONFIG_VIRTIO_MMIO is not set
|
||||
# CONFIG_VIRTIO_PCI is not set
|
||||
# CONFIG_VIRTIO_SANDBOX is not set
|
||||
# CONFIG_VIRTIO_SANDBOX_EMUL is not set
|
||||
# CONFIG_GENERATE_ACPI_TABLE is not set
|
||||
CONFIG_TOOLS_MKEFICAPSULE=y
|
||||
|
||||
@@ -54,6 +54,14 @@ config VIRTIO_SANDBOX
|
||||
This driver provides support for Sandbox implementation of virtio
|
||||
transport driver which is used for testing purpose only.
|
||||
|
||||
config VIRTIO_SANDBOX_EMUL
|
||||
bool "Sandbox MMIO emulator for virtio devices"
|
||||
depends on SANDBOX
|
||||
select VIRTIO
|
||||
help
|
||||
This driver provides an MMIO interface to an emulation of a block
|
||||
device. It is used for testing purpose only.
|
||||
|
||||
config VIRTIO_NET
|
||||
bool "virtio net driver"
|
||||
depends on VIRTIO && NETDEVICES
|
||||
|
||||
@@ -8,6 +8,7 @@ obj-$(CONFIG_VIRTIO_MMIO) += virtio_mmio.o
|
||||
obj-$(CONFIG_VIRTIO_PCI) += virtio_pci_modern.o
|
||||
obj-$(CONFIG_VIRTIO_PCI_LEGACY) += virtio_pci_legacy.o
|
||||
obj-$(CONFIG_VIRTIO_SANDBOX) += virtio_sandbox.o
|
||||
obj-$(CONFIG_VIRTIO_SANDBOX_EMUL) += sandbox_emul.o emul_blk.o
|
||||
obj-$(CONFIG_VIRTIO_NET) += virtio_net.o
|
||||
obj-$(CONFIG_VIRTIO_BLK) += virtio_blk.o
|
||||
obj-$(CONFIG_VIRTIO_RNG) += virtio_rng.o
|
||||
|
||||
153
drivers/virtio/emul_blk.c
Normal file
153
drivers/virtio/emul_blk.c
Normal file
@@ -0,0 +1,153 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* Emulation of a block device. This implements a simple version of the QEMU
|
||||
* side of the interface.
|
||||
*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_VIRTIO
|
||||
|
||||
#include <dm.h>
|
||||
#include <malloc.h>
|
||||
#include <asm/io.h>
|
||||
#include <dt-bindings/virtio.h>
|
||||
#include <linux/sizes.h>
|
||||
#include "virtio_blk.h"
|
||||
#include "virtio_ring.h"
|
||||
#include "sandbox_emul.h"
|
||||
|
||||
enum {
|
||||
DISK_SIZE_MB = 1,
|
||||
SECTOR_SIZE = 512,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct virtio_blk_emul_priv - private data for the block emulator
|
||||
*
|
||||
* @config: virtio block-device-configuration structure, exposed to the driver
|
||||
* through the config space
|
||||
* @disk_data: allocated memory for the virtual disk
|
||||
* @disk_size: total size of the virtual disk in bytes
|
||||
*/
|
||||
struct virtio_blk_emul_priv {
|
||||
struct virtio_blk_config config;
|
||||
void *disk_data;
|
||||
u64 disk_size;
|
||||
};
|
||||
|
||||
static int blk_emul_process_request(struct udevice *dev,
|
||||
struct vring_desc *descs, u32 head_idx,
|
||||
int *writtenp)
|
||||
{
|
||||
struct virtio_blk_emul_priv *priv = dev_get_priv(dev);
|
||||
struct vring_desc *hdr_desc, *data_desc, *status_desc;
|
||||
struct virtio_blk_outhdr *hdr;
|
||||
void *data_buf;
|
||||
u64 offset;
|
||||
u8 *status;
|
||||
|
||||
hdr_desc = &descs[head_idx];
|
||||
if (!(hdr_desc->flags & VRING_DESC_F_NEXT))
|
||||
return -EIO;
|
||||
data_desc = &descs[hdr_desc->next];
|
||||
if (!(data_desc->flags & VRING_DESC_F_NEXT))
|
||||
return -EIO;
|
||||
status_desc = &descs[data_desc->next];
|
||||
|
||||
hdr = (struct virtio_blk_outhdr *)hdr_desc->addr;
|
||||
status = (u8 *)status_desc->addr;
|
||||
|
||||
offset = hdr->sector * SECTOR_SIZE;
|
||||
if (offset + data_desc->len > priv->disk_size) {
|
||||
*status = VIRTIO_BLK_S_IOERR;
|
||||
*writtenp = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
data_buf = (void *)data_desc->addr;
|
||||
|
||||
switch (hdr->type) {
|
||||
case VIRTIO_BLK_T_IN:
|
||||
log_debug("read: sector %lld, len %u\n", hdr->sector,
|
||||
data_desc->len);
|
||||
memcpy(data_buf, priv->disk_data + offset, data_desc->len);
|
||||
*writtenp = data_desc->len;
|
||||
break;
|
||||
case VIRTIO_BLK_T_OUT:
|
||||
log_debug("write: sector %lld, len %u\n", hdr->sector,
|
||||
data_desc->len);
|
||||
memcpy(priv->disk_data + offset, data_buf, data_desc->len);
|
||||
*writtenp = 0;
|
||||
break;
|
||||
default:
|
||||
log_warning("unknown request type 0x%x\n", hdr->type);
|
||||
*status = VIRTIO_BLK_S_UNSUPP;
|
||||
*writtenp = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
*status = VIRTIO_BLK_S_OK;
|
||||
*writtenp += 1; /* For the status byte */
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int blk_emul_get_config(struct udevice *dev, ulong offset, void *buf,
|
||||
enum sandboxio_size_t size)
|
||||
{
|
||||
struct virtio_blk_emul_priv *priv = dev_get_priv(dev);
|
||||
|
||||
if (offset + size > sizeof(priv->config))
|
||||
return -EIO;
|
||||
|
||||
memcpy(buf, (u8 *)&priv->config + offset, size);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static u64 blk_emul_get_features(struct udevice *dev)
|
||||
{
|
||||
return BIT(VIRTIO_BLK_F_BLK_SIZE);
|
||||
}
|
||||
|
||||
static u32 blk_emul_get_device_id(struct udevice *dev)
|
||||
{
|
||||
return VIRTIO_ID_BLOCK;
|
||||
}
|
||||
|
||||
static int virtio_blk_emul_probe(struct udevice *dev)
|
||||
{
|
||||
struct virtio_blk_emul_priv *priv = dev_get_priv(dev);
|
||||
|
||||
priv->disk_size = (u64)DISK_SIZE_MB * SZ_1M;
|
||||
priv->disk_data = calloc(1, priv->disk_size);
|
||||
if (!priv->disk_data)
|
||||
return -ENOMEM;
|
||||
|
||||
priv->config.capacity = priv->disk_size / SECTOR_SIZE;
|
||||
priv->config.blk_size = SECTOR_SIZE;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct virtio_emul_ops blk_emul_ops = {
|
||||
.process_request = blk_emul_process_request,
|
||||
.get_config = blk_emul_get_config,
|
||||
.get_features = blk_emul_get_features,
|
||||
.get_device_id = blk_emul_get_device_id,
|
||||
};
|
||||
|
||||
static const struct udevice_id virtio_blk_emul_ids[] = {
|
||||
{ .compatible = "sandbox,virtio-blk-emul" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(virtio_blk_emul) = {
|
||||
.name = "virtio_blk_emul",
|
||||
.id = UCLASS_VIRTIO_EMUL,
|
||||
.of_match = virtio_blk_emul_ids,
|
||||
.probe = virtio_blk_emul_probe,
|
||||
.ops = &blk_emul_ops,
|
||||
.priv_auto = sizeof(struct virtio_blk_emul_priv),
|
||||
};
|
||||
313
drivers/virtio/sandbox_emul.c
Normal file
313
drivers/virtio/sandbox_emul.c
Normal file
@@ -0,0 +1,313 @@
|
||||
// SPDX-License-Identifier: GPL-2.0+
|
||||
/*
|
||||
* VirtIO Sandbox emulator, for testing purpose only. This emulates the QEMU
|
||||
* side of virtio, using the MMIO driver and handling any accesses
|
||||
*
|
||||
* This handles traffic from the virtio_ring
|
||||
*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#define LOG_CATEGORY UCLASS_VIRTIO
|
||||
|
||||
#include <dm.h>
|
||||
#include <malloc.h>
|
||||
#include <virtio.h>
|
||||
#include <asm/io.h>
|
||||
#include <dt-bindings/virtio.h>
|
||||
#include <asm/state.h>
|
||||
#include <linux/sizes.h>
|
||||
#include "sandbox_emul.h"
|
||||
#include "virtio_types.h"
|
||||
#include "virtio_blk.h"
|
||||
#include "virtio_internal.h"
|
||||
#include "virtio_mmio.h"
|
||||
#include "virtio_ring.h"
|
||||
|
||||
enum {
|
||||
MMIO_SIZE = 0x200,
|
||||
VENDOR_ID = 0xf003,
|
||||
DEVICE_ID = VIRTIO_ID_BLOCK,
|
||||
DISK_SIZE_MB = 16,
|
||||
};
|
||||
|
||||
void process_queue(struct udevice *emul_dev, struct sandbox_emul_priv *priv,
|
||||
uint32_t queue_idx)
|
||||
{
|
||||
struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev);
|
||||
bool processed_something = false;
|
||||
struct virtio_emul_queue *q;
|
||||
struct vring_avail *avail;
|
||||
struct vring_desc *desc;
|
||||
struct vring_used *used;
|
||||
uint old_used_idx;
|
||||
|
||||
if (queue_idx >= priv->num_queues)
|
||||
return;
|
||||
log_debug("Notified on queue %u\n", queue_idx);
|
||||
|
||||
q = &priv->queues[queue_idx];
|
||||
if (!q->ready)
|
||||
return;
|
||||
|
||||
desc = (struct vring_desc *)q->desc_addr;
|
||||
avail = (struct vring_avail *)q->avail_addr;
|
||||
used = (struct vring_used *)q->used_addr;
|
||||
old_used_idx = used->idx;
|
||||
|
||||
while (q->last_avail_idx != avail->idx) {
|
||||
processed_something = true;
|
||||
uint ring_idx = q->last_avail_idx % q->num;
|
||||
uint desc_head_idx = avail->ring[ring_idx];
|
||||
uint used_ring_idx;
|
||||
int written;
|
||||
int ret;
|
||||
|
||||
log_debug("Found request at avail ring index %u (desc head %u)\n",
|
||||
ring_idx, desc_head_idx);
|
||||
|
||||
ret = ops->process_request(emul_dev, desc, desc_head_idx,
|
||||
&written);
|
||||
if (ret)
|
||||
log_warning("Failed to process request (err=%dE)\n",
|
||||
ret);
|
||||
|
||||
used_ring_idx = used->idx % q->num;
|
||||
used->ring[used_ring_idx].id = desc_head_idx;
|
||||
used->ring[used_ring_idx].len = written;
|
||||
used->idx++;
|
||||
q->last_avail_idx++;
|
||||
}
|
||||
|
||||
if (processed_something) {
|
||||
bool needs_interrupt = true;
|
||||
|
||||
log_debug("finished processing, new used_idx is %d.\n",
|
||||
used->idx);
|
||||
if (priv->driver_features & BIT(VIRTIO_RING_F_EVENT_IDX)) {
|
||||
struct {
|
||||
struct vring_avail *avail;
|
||||
unsigned int num;
|
||||
} vr;
|
||||
|
||||
vr.avail = avail;
|
||||
vr.num = q->num;
|
||||
|
||||
needs_interrupt =
|
||||
vring_need_event(vring_used_event((&vr)),
|
||||
used->idx, old_used_idx);
|
||||
log_debug("EVENT_IDX is enabled; driver wants event "
|
||||
"at %u needs_interrupt %d\n",
|
||||
vring_used_event(&vr), needs_interrupt);
|
||||
}
|
||||
|
||||
if (needs_interrupt) {
|
||||
log_debug("sending VRING interrupt\n");
|
||||
priv->interrupt_status |= VIRTIO_MMIO_INT_VRING;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
long h_read(void *ctx, const void *addr, enum sandboxio_size_t size)
|
||||
{
|
||||
struct udevice *dev = ctx;
|
||||
struct udevice *emul_dev = dev_get_parent(dev);
|
||||
struct sandbox_emul_priv *priv = dev_get_priv(dev);
|
||||
ulong offset = (ulong)addr - (ulong)priv->mmio.base;
|
||||
struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev);
|
||||
struct virtio_emul_queue *q;
|
||||
u32 val = 0;
|
||||
|
||||
if (offset >= VIRTIO_MMIO_CONFIG) {
|
||||
ulong config_offset = offset - VIRTIO_MMIO_CONFIG;
|
||||
int ret;
|
||||
|
||||
ret = ops->get_config(emul_dev, config_offset, &val, size);
|
||||
if (ret)
|
||||
log_warning("Failed to process request (err=%dE)\n",
|
||||
ret);
|
||||
return val;
|
||||
}
|
||||
|
||||
if (priv->queue_sel >= priv->num_queues) {
|
||||
log_debug("invalid queue_sel %d\n", priv->queue_sel);
|
||||
return 0;
|
||||
}
|
||||
q = &priv->queues[priv->queue_sel];
|
||||
|
||||
switch (offset) {
|
||||
case VIRTIO_MMIO_MAGIC_VALUE:
|
||||
return ('v' | 'i' << 8 | 'r' << 16 | 't' << 24);
|
||||
case VIRTIO_MMIO_VERSION:
|
||||
return 2;
|
||||
case VIRTIO_MMIO_DEVICE_ID:
|
||||
return ops->get_device_id(emul_dev);
|
||||
case VIRTIO_MMIO_VENDOR_ID:
|
||||
return VENDOR_ID;
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES:
|
||||
return !priv->features_sel ?
|
||||
(priv->features & 0xffffffff) :
|
||||
(priv->features >> 32);
|
||||
case VIRTIO_MMIO_QUEUE_NUM_MAX:
|
||||
return QUEUE_MAX_SIZE;
|
||||
case VIRTIO_MMIO_QUEUE_READY:
|
||||
return q->ready;
|
||||
case VIRTIO_MMIO_INTERRUPT_STATUS:
|
||||
return priv->interrupt_status;
|
||||
case VIRTIO_MMIO_STATUS:
|
||||
return priv->status;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_LOW:
|
||||
return q->desc_addr & 0xffffffff;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_HIGH:
|
||||
return q->desc_addr >> 32;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_LOW:
|
||||
return q->avail_addr & 0xffffffff;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_HIGH:
|
||||
return q->avail_addr >> 32;
|
||||
case VIRTIO_MMIO_QUEUE_USED_LOW:
|
||||
return q->used_addr & 0xffffffff;
|
||||
case VIRTIO_MMIO_QUEUE_USED_HIGH:
|
||||
return q->used_addr >> 32;
|
||||
case VIRTIO_MMIO_CONFIG_GENERATION:
|
||||
return priv->config_generation;
|
||||
default:
|
||||
log_debug("unhandled read from offset 0x%lx\n", offset);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void h_write(void *ctx, void *addr, unsigned int val,
|
||||
enum sandboxio_size_t size)
|
||||
{
|
||||
struct udevice *dev = ctx;
|
||||
struct udevice *emul_dev = dev_get_parent(dev);
|
||||
struct sandbox_emul_priv *priv = dev_get_priv(dev);
|
||||
ulong offset = (ulong)addr - (ulong)priv->mmio.base;
|
||||
struct virtio_emul_queue *q;
|
||||
|
||||
if (offset >= VIRTIO_MMIO_CONFIG)
|
||||
return;
|
||||
|
||||
if (priv->queue_sel >= priv->num_queues && offset != VIRTIO_MMIO_QUEUE_SEL)
|
||||
return;
|
||||
q = &priv->queues[priv->queue_sel];
|
||||
|
||||
switch (offset) {
|
||||
case VIRTIO_MMIO_DEVICE_FEATURES_SEL:
|
||||
priv->features_sel = val;
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES:
|
||||
if (priv->features_sel == 0)
|
||||
priv->driver_features = (priv->driver_features &
|
||||
0xffffffff00000000) | val;
|
||||
else
|
||||
priv->driver_features = (priv->driver_features &
|
||||
0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_DRIVER_FEATURES_SEL:
|
||||
priv->features_sel = val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_SEL:
|
||||
if (val < priv->num_queues)
|
||||
priv->queue_sel = val;
|
||||
else
|
||||
log_debug("tried to select invalid queue %u\n", val);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_NUM:
|
||||
q->num = (val > 0 && val <= QUEUE_MAX_SIZE) ? val : 0;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_READY:
|
||||
q->ready = val & 0x1;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_NOTIFY:
|
||||
process_queue(emul_dev, priv, val);
|
||||
break;
|
||||
case VIRTIO_MMIO_INTERRUPT_ACK:
|
||||
priv->interrupt_status &= ~val;
|
||||
break;
|
||||
case VIRTIO_MMIO_STATUS:
|
||||
priv->status = val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_LOW:
|
||||
q->desc_addr = (q->desc_addr & 0xffffffff00000000) | val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_DESC_HIGH:
|
||||
q->desc_addr = (q->desc_addr & 0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_LOW:
|
||||
q->avail_addr = (q->avail_addr & 0xffffffff00000000) | val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_AVAIL_HIGH:
|
||||
q->avail_addr = (q->avail_addr & 0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_USED_LOW:
|
||||
q->used_addr = (q->used_addr & 0xffffffff00000000) | val;
|
||||
break;
|
||||
case VIRTIO_MMIO_QUEUE_USED_HIGH:
|
||||
q->used_addr = (q->used_addr & 0xffffffff) | ((u64)val << 32);
|
||||
break;
|
||||
default:
|
||||
log_debug("unhandled write to offset 0x%lx\n", offset);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static int sandbox_emul_of_to_plat(struct udevice *dev)
|
||||
{
|
||||
struct udevice *emul_dev = dev_get_parent(dev);
|
||||
struct virtio_emul_ops *ops = virtio_emul_get_ops(emul_dev);
|
||||
struct sandbox_emul_priv *priv = dev_get_priv(dev);
|
||||
int ret;
|
||||
|
||||
/* set up the MMIO base so that virtio_mmio_probe() can find it */
|
||||
priv->mmio.base = memalign(SZ_4K, MMIO_SIZE);
|
||||
if (!priv->mmio.base)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = sandbox_mmio_add(priv->mmio.base, MMIO_SIZE, h_read, h_write,
|
||||
dev);
|
||||
if (ret) {
|
||||
free(priv->mmio.base);
|
||||
return log_msg_ret("sep", ret);
|
||||
}
|
||||
|
||||
priv->num_queues = MAX_VIRTIO_QUEUES;
|
||||
priv->features = BIT(VIRTIO_F_VERSION_1) |
|
||||
BIT(VIRTIO_RING_F_EVENT_IDX) |
|
||||
ops->get_features(emul_dev);
|
||||
|
||||
log_debug("sandbox virtio emulator, mmio %p\n", priv->mmio.base);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int sandbox_emul_remove(struct udevice *dev)
|
||||
{
|
||||
sandbox_mmio_remove(dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct udevice_id virtio_sandbox2_ids[] = {
|
||||
{ .compatible = "sandbox,virtio-emul" },
|
||||
{ }
|
||||
};
|
||||
|
||||
U_BOOT_DRIVER(virtio_emul) = {
|
||||
.name = "virtio-emul",
|
||||
.id = UCLASS_VIRTIO,
|
||||
.of_match = virtio_sandbox2_ids,
|
||||
.probe = virtio_mmio_probe,
|
||||
.remove = sandbox_emul_remove,
|
||||
.ops = &virtio_mmio_ops,
|
||||
.of_to_plat = sandbox_emul_of_to_plat,
|
||||
.priv_auto = sizeof(struct sandbox_emul_priv),
|
||||
};
|
||||
|
||||
UCLASS_DRIVER(virtio_emul) = {
|
||||
.name = "virtio_emul",
|
||||
.id = UCLASS_VIRTIO_EMUL,
|
||||
#if CONFIG_IS_ENABLED(OF_REAL)
|
||||
.post_bind = dm_scan_fdt_dev,
|
||||
#endif
|
||||
};
|
||||
110
drivers/virtio/sandbox_emul.h
Normal file
110
drivers/virtio/sandbox_emul.h
Normal file
@@ -0,0 +1,110 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* VirtIO Sandbox emulator, for testing purpose only. This emulates the QEMU
|
||||
* side of virtio, using the MMIO driver and handling any accesses
|
||||
*
|
||||
* This handles traffic from the virtio_ring
|
||||
*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#ifndef __SANDBOX_EMUL_H
|
||||
#define __SANDBOX_EMUL_H
|
||||
|
||||
#include "virtio_mmio.h"
|
||||
#include "virtio_types.h"
|
||||
|
||||
enum sandboxio_size_t;
|
||||
struct udevice;
|
||||
struct vring_desc;
|
||||
|
||||
enum {
|
||||
MAX_VIRTIO_QUEUES = 8,
|
||||
QUEUE_MAX_SIZE = 256,
|
||||
};
|
||||
|
||||
/**
|
||||
* struct virtio_emul_queue - Emulator's state for a single virtqueue
|
||||
*/
|
||||
struct virtio_emul_queue {
|
||||
__virtio32 num;
|
||||
__virtio32 ready;
|
||||
__virtio64 desc_addr;
|
||||
__virtio64 avail_addr;
|
||||
__virtio64 used_addr;
|
||||
__virtio16 last_avail_idx; // Device's internal counter
|
||||
};
|
||||
|
||||
/**
|
||||
* struct sandbox_emul_priv - Private info for the emulator
|
||||
*/
|
||||
struct sandbox_emul_priv {
|
||||
struct virtio_mmio_priv mmio;
|
||||
int num_queues;
|
||||
int queue_sel;
|
||||
u32 status;
|
||||
u64 features_sel;
|
||||
u64 features;
|
||||
u64 driver_features;
|
||||
u32 interrupt_status;
|
||||
u32 config_generation;
|
||||
struct virtio_emul_queue queues[MAX_VIRTIO_QUEUES];
|
||||
};
|
||||
|
||||
/**
|
||||
* struct virtio_emul_ops - Operations for a virtio device emulator
|
||||
*
|
||||
* @process_request:
|
||||
* @get_config: Reads from the device-specific configuration space
|
||||
* @get_features: Returns the device-specific feature bits
|
||||
*/
|
||||
struct virtio_emul_ops {
|
||||
/**
|
||||
* process_request() - Handles a single request from the driver
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @descs: Pointer to the virtqueue's descriptor table
|
||||
* @head_idx: The index of the first descriptor in the chain for
|
||||
* this request
|
||||
* @writtenp: Returns the total number of bytes written by the
|
||||
* device into the driver's buffers (e.g. for a read
|
||||
* request and the status byte). This is what will be
|
||||
* placed in the `len` field of the used ring element.
|
||||
* @return 0 on success, negative on error.
|
||||
*/
|
||||
int (*process_request)(struct udevice *dev, struct vring_desc *descs,
|
||||
u32 head_idx, int *writtenp);
|
||||
|
||||
/**
|
||||
* get_config() - Reads from the device-specific configuration space
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @offset: The byte offset into the configuration space to read from
|
||||
* @buf: The buffer to copy the configuration data into
|
||||
* @size: The number of bytes to read
|
||||
* @return 0 on success, negative on error.
|
||||
*/
|
||||
int (*get_config)(struct udevice *dev, ulong offset, void *buf,
|
||||
enum sandboxio_size_t size);
|
||||
|
||||
/**
|
||||
* get_features() - Returns the device-specific feature bits
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @return A bitmask of the device-specific features to be OR'd
|
||||
* with the transport features.
|
||||
*/
|
||||
u64 (*get_features)(struct udevice *dev);
|
||||
|
||||
/**
|
||||
* get_device_id() - Returns the virtio device ID
|
||||
*
|
||||
* @dev: The emulator device
|
||||
* @return The virtio device ID for this emulator
|
||||
*/
|
||||
u32 (*get_device_id)(struct udevice *dev);
|
||||
};
|
||||
|
||||
#define virtio_emul_get_ops(dev) ((struct virtio_emul_ops *)(dev)->driver->ops)
|
||||
|
||||
#endif
|
||||
@@ -9,6 +9,9 @@
|
||||
#ifndef _LINUX_VIRTIO_BLK_H
|
||||
#define _LINUX_VIRTIO_BLK_H
|
||||
|
||||
#include <compiler.h>
|
||||
#include "virtio_types.h"
|
||||
|
||||
/* Feature bits */
|
||||
#define VIRTIO_BLK_F_SIZE_MAX 1 /* Indicates maximum segment size */
|
||||
#define VIRTIO_BLK_F_SEG_MAX 2 /* Indicates maximum # of segments */
|
||||
|
||||
19
drivers/virtio/virtio_internal.h
Normal file
19
drivers/virtio/virtio_internal.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Internal header file for virtio
|
||||
*
|
||||
* Copyright 2025 Simon Glass <sjg@chromium.org>
|
||||
*/
|
||||
|
||||
#ifndef _VIRTIO_INTERNAL_H
|
||||
#define _VIRTIO_INTERNAL_H
|
||||
|
||||
struct udevice;
|
||||
|
||||
/* MMIO operations from virtio_mmcio.c */
|
||||
extern const struct dm_virtio_ops virtio_mmio_ops;
|
||||
|
||||
/* exported probe function from virtio_mmcio.c */
|
||||
int virtio_mmio_probe(struct udevice *udev);
|
||||
|
||||
#endif
|
||||
@@ -344,13 +344,14 @@ static int virtio_mmio_of_to_plat(struct udevice *udev)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int virtio_mmio_probe(struct udevice *udev)
|
||||
int virtio_mmio_probe(struct udevice *udev)
|
||||
{
|
||||
struct virtio_mmio_priv *priv = dev_get_priv(udev);
|
||||
struct virtio_dev_priv *uc_priv = dev_get_uclass_priv(udev);
|
||||
u32 magic;
|
||||
|
||||
/* Check magic value */
|
||||
log_debug("probe %p\n", priv->base);
|
||||
magic = readl(priv->base + VIRTIO_MMIO_MAGIC_VALUE);
|
||||
if (magic != ('v' | 'i' << 8 | 'r' << 16 | 't' << 24)) {
|
||||
debug("(%s): wrong magic value 0x%08x!\n", udev->name, magic);
|
||||
@@ -379,13 +380,13 @@ static int virtio_mmio_probe(struct udevice *udev)
|
||||
if (priv->version == 1)
|
||||
writel(PAGE_SIZE, priv->base + VIRTIO_MMIO_GUEST_PAGE_SIZE);
|
||||
|
||||
debug("(%s): device (%d) vendor (%08x) version (%d)\n", udev->name,
|
||||
debug("(%s): device (%04x) vendor (%04x) version (%d)\n", udev->name,
|
||||
uc_priv->device, uc_priv->vendor, priv->version);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct dm_virtio_ops virtio_mmio_ops = {
|
||||
const struct dm_virtio_ops virtio_mmio_ops = {
|
||||
.get_config = virtio_mmio_get_config,
|
||||
.set_config = virtio_mmio_set_config,
|
||||
.generation = virtio_mmio_generation,
|
||||
|
||||
@@ -10,8 +10,14 @@
|
||||
#define __ALIST_H
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <linux/bitops.h>
|
||||
|
||||
#ifdef USE_HOSTCC
|
||||
#include <sys/types.h>
|
||||
#include <stdint.h>
|
||||
#else
|
||||
#include <linux/types.h>
|
||||
#endif
|
||||
#define BIT(nr) (1UL << (nr))
|
||||
|
||||
/**
|
||||
* struct alist - object list that can be allocated and freed
|
||||
@@ -41,10 +47,10 @@
|
||||
*/
|
||||
struct alist {
|
||||
void *data;
|
||||
u16 obj_size;
|
||||
u16 count;
|
||||
u16 alloc;
|
||||
u16 flags;
|
||||
unsigned short obj_size;
|
||||
unsigned short count;
|
||||
unsigned short alloc;
|
||||
unsigned short flags;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -34,6 +34,8 @@ enum uclass_id {
|
||||
UCLASS_PCI_EMUL_PARENT, /* parent for PCI device emulators */
|
||||
UCLASS_USB_EMUL, /* sandbox USB bus device emulator */
|
||||
UCLASS_AXI_EMUL, /* sandbox AXI bus device emulator */
|
||||
UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */
|
||||
UCLASS_VIRTIO_EMUL, /* Emulator for a virtIO transport device */
|
||||
|
||||
/* U-Boot uclasses start here - in alphabetical order */
|
||||
UCLASS_ACPI_PMC, /* (x86) Power-management controller (PMC) */
|
||||
@@ -63,7 +65,6 @@ enum uclass_id {
|
||||
UCLASS_ETH_PHY, /* Ethernet PHY device */
|
||||
UCLASS_EXTCON, /* External Connector Class */
|
||||
UCLASS_FFA, /* Arm Firmware Framework for Armv8-A */
|
||||
UCLASS_FFA_EMUL, /* sandbox FF-A device emulator */
|
||||
UCLASS_FIRMWARE, /* Firmware */
|
||||
UCLASS_FPGA, /* FPGA device */
|
||||
UCLASS_FUZZING_ENGINE, /* Fuzzing engine */
|
||||
|
||||
19
include/dt-bindings/virtio.h
Normal file
19
include/dt-bindings/virtio.h
Normal file
@@ -0,0 +1,19 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* Copyright (C) 2018, Tuomas Tynkkynen <tuomas.tynkkynen@iki.fi>
|
||||
* Copyright (C) 2018, Bin Meng <bmeng.cn@gmail.com>
|
||||
*
|
||||
* Binding file for virtio IDs
|
||||
*
|
||||
* This file is largely based on Linux kernel virtio_*.h files
|
||||
*/
|
||||
|
||||
#ifndef __DT_BINDINGS_VIRTIO
|
||||
#define __DT_BINDINGS_VIRTIO
|
||||
|
||||
#define VIRTIO_ID_NET 1 /* virtio net */
|
||||
#define VIRTIO_ID_BLOCK 2 /* virtio block */
|
||||
#define VIRTIO_ID_RNG 4 /* virtio rng */
|
||||
#define VIRTIO_ID_MAX_NUM 27
|
||||
|
||||
#endif
|
||||
@@ -22,13 +22,10 @@
|
||||
|
||||
#include <virtio_types.h>
|
||||
#include <dm/device.h>
|
||||
#include <dt-bindings/virtio.h>
|
||||
#include <linux/bitops.h>
|
||||
#include <linux/bug.h>
|
||||
#include <linux/typecheck.h>
|
||||
#define VIRTIO_ID_NET 1 /* virtio net */
|
||||
#define VIRTIO_ID_BLOCK 2 /* virtio block */
|
||||
#define VIRTIO_ID_RNG 4 /* virtio rng */
|
||||
#define VIRTIO_ID_MAX_NUM 5
|
||||
|
||||
#define VIRTIO_NET_DRV_NAME "virtio-net"
|
||||
#define VIRTIO_BLK_DRV_NAME "virtio-blk"
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#include <alist.h>
|
||||
#include <display_options.h>
|
||||
#include <log.h>
|
||||
#include <malloc.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
@@ -1,537 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# Copyright The Mbed TLS Contributors
|
||||
# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
|
||||
|
||||
"""
|
||||
This script checks the current state of the source code for minor issues,
|
||||
including incorrect file permissions, presence of tabs, non-Unix line endings,
|
||||
trailing whitespace, and presence of UTF-8 BOM.
|
||||
Note: requires python 3, must be run from Mbed TLS root.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import codecs
|
||||
import inspect
|
||||
import logging
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
try:
|
||||
from typing import FrozenSet, Optional, Pattern # pylint: disable=unused-import
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
import scripts_path # pylint: disable=unused-import
|
||||
from mbedtls_dev import build_tree
|
||||
|
||||
|
||||
class FileIssueTracker:
|
||||
"""Base class for file-wide issue tracking.
|
||||
|
||||
To implement a checker that processes a file as a whole, inherit from
|
||||
this class and implement `check_file_for_issue` and define ``heading``.
|
||||
|
||||
``suffix_exemptions``: files whose name ends with a string in this set
|
||||
will not be checked.
|
||||
|
||||
``path_exemptions``: files whose path (relative to the root of the source
|
||||
tree) matches this regular expression will not be checked. This can be
|
||||
``None`` to match no path. Paths are normalized and converted to ``/``
|
||||
separators before matching.
|
||||
|
||||
``heading``: human-readable description of the issue
|
||||
"""
|
||||
|
||||
suffix_exemptions = frozenset() #type: FrozenSet[str]
|
||||
path_exemptions = None #type: Optional[Pattern[str]]
|
||||
# heading must be defined in derived classes.
|
||||
# pylint: disable=no-member
|
||||
|
||||
def __init__(self):
|
||||
self.files_with_issues = {}
|
||||
|
||||
@staticmethod
|
||||
def normalize_path(filepath):
|
||||
"""Normalize ``filepath`` with / as the directory separator."""
|
||||
filepath = os.path.normpath(filepath)
|
||||
# On Windows, we may have backslashes to separate directories.
|
||||
# We need slashes to match exemption lists.
|
||||
seps = os.path.sep
|
||||
if os.path.altsep is not None:
|
||||
seps += os.path.altsep
|
||||
return '/'.join(filepath.split(seps))
|
||||
|
||||
def should_check_file(self, filepath):
|
||||
"""Whether the given file name should be checked.
|
||||
|
||||
Files whose name ends with a string listed in ``self.suffix_exemptions``
|
||||
or whose path matches ``self.path_exemptions`` will not be checked.
|
||||
"""
|
||||
for files_exemption in self.suffix_exemptions:
|
||||
if filepath.endswith(files_exemption):
|
||||
return False
|
||||
if self.path_exemptions and \
|
||||
re.match(self.path_exemptions, self.normalize_path(filepath)):
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_file_for_issue(self, filepath):
|
||||
"""Check the specified file for the issue that this class is for.
|
||||
|
||||
Subclasses must implement this method.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def record_issue(self, filepath, line_number):
|
||||
"""Record that an issue was found at the specified location."""
|
||||
if filepath not in self.files_with_issues.keys():
|
||||
self.files_with_issues[filepath] = []
|
||||
self.files_with_issues[filepath].append(line_number)
|
||||
|
||||
def output_file_issues(self, logger):
|
||||
"""Log all the locations where the issue was found."""
|
||||
if self.files_with_issues.values():
|
||||
logger.info(self.heading)
|
||||
for filename, lines in sorted(self.files_with_issues.items()):
|
||||
if lines:
|
||||
logger.info("{}: {}".format(
|
||||
filename, ", ".join(str(x) for x in lines)
|
||||
))
|
||||
else:
|
||||
logger.info(filename)
|
||||
logger.info("")
|
||||
|
||||
BINARY_FILE_PATH_RE_LIST = [
|
||||
r'docs/.*\.pdf\Z',
|
||||
r'docs/.*\.png\Z',
|
||||
r'programs/fuzz/corpuses/[^.]+\Z',
|
||||
r'tests/data_files/[^.]+\Z',
|
||||
r'tests/data_files/.*\.(crt|csr|db|der|key|pubkey)\Z',
|
||||
r'tests/data_files/.*\.req\.[^/]+\Z',
|
||||
r'tests/data_files/.*malformed[^/]+\Z',
|
||||
r'tests/data_files/format_pkcs12\.fmt\Z',
|
||||
r'tests/data_files/.*\.bin\Z',
|
||||
]
|
||||
BINARY_FILE_PATH_RE = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST))
|
||||
|
||||
class LineIssueTracker(FileIssueTracker):
|
||||
"""Base class for line-by-line issue tracking.
|
||||
|
||||
To implement a checker that processes files line by line, inherit from
|
||||
this class and implement `line_with_issue`.
|
||||
"""
|
||||
|
||||
# Exclude binary files.
|
||||
path_exemptions = BINARY_FILE_PATH_RE
|
||||
|
||||
def issue_with_line(self, line, filepath, line_number):
|
||||
"""Check the specified line for the issue that this class is for.
|
||||
|
||||
Subclasses must implement this method.
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def check_file_line(self, filepath, line, line_number):
|
||||
if self.issue_with_line(line, filepath, line_number):
|
||||
self.record_issue(filepath, line_number)
|
||||
|
||||
def check_file_for_issue(self, filepath):
|
||||
"""Check the lines of the specified file.
|
||||
|
||||
Subclasses must implement the ``issue_with_line`` method.
|
||||
"""
|
||||
with open(filepath, "rb") as f:
|
||||
for i, line in enumerate(iter(f.readline, b"")):
|
||||
self.check_file_line(filepath, line, i + 1)
|
||||
|
||||
|
||||
def is_windows_file(filepath):
|
||||
_root, ext = os.path.splitext(filepath)
|
||||
return ext in ('.bat', '.dsp', '.dsw', '.sln', '.vcxproj')
|
||||
|
||||
|
||||
class ShebangIssueTracker(FileIssueTracker):
|
||||
"""Track files with a bad, missing or extraneous shebang line.
|
||||
|
||||
Executable scripts must start with a valid shebang (#!) line.
|
||||
"""
|
||||
|
||||
heading = "Invalid shebang line:"
|
||||
|
||||
# Allow either /bin/sh, /bin/bash, or /usr/bin/env.
|
||||
# Allow at most one argument (this is a Linux limitation).
|
||||
# For sh and bash, the argument if present must be options.
|
||||
# For env, the argument must be the base name of the interpreter.
|
||||
_shebang_re = re.compile(rb'^#! ?(?:/bin/(bash|sh)(?: -[^\n ]*)?'
|
||||
rb'|/usr/bin/env ([^\n /]+))$')
|
||||
_extensions = {
|
||||
b'bash': 'sh',
|
||||
b'perl': 'pl',
|
||||
b'python3': 'py',
|
||||
b'sh': 'sh',
|
||||
}
|
||||
|
||||
path_exemptions = re.compile(r'tests/scripts/quiet/.*')
|
||||
|
||||
def is_valid_shebang(self, first_line, filepath):
|
||||
m = re.match(self._shebang_re, first_line)
|
||||
if not m:
|
||||
return False
|
||||
interpreter = m.group(1) or m.group(2)
|
||||
if interpreter not in self._extensions:
|
||||
return False
|
||||
if not filepath.endswith('.' + self._extensions[interpreter]):
|
||||
return False
|
||||
return True
|
||||
|
||||
def check_file_for_issue(self, filepath):
|
||||
is_executable = os.access(filepath, os.X_OK)
|
||||
with open(filepath, "rb") as f:
|
||||
first_line = f.readline()
|
||||
if first_line.startswith(b'#!'):
|
||||
if not is_executable:
|
||||
# Shebang on a non-executable file
|
||||
self.files_with_issues[filepath] = None
|
||||
elif not self.is_valid_shebang(first_line, filepath):
|
||||
self.files_with_issues[filepath] = [1]
|
||||
elif is_executable:
|
||||
# Executable without a shebang
|
||||
self.files_with_issues[filepath] = None
|
||||
|
||||
|
||||
class EndOfFileNewlineIssueTracker(FileIssueTracker):
|
||||
"""Track files that end with an incomplete line
|
||||
(no newline character at the end of the last line)."""
|
||||
|
||||
heading = "Missing newline at end of file:"
|
||||
|
||||
path_exemptions = BINARY_FILE_PATH_RE
|
||||
|
||||
def check_file_for_issue(self, filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
try:
|
||||
f.seek(-1, 2)
|
||||
except OSError:
|
||||
# This script only works on regular files. If we can't seek
|
||||
# 1 before the end, it means that this position is before
|
||||
# the beginning of the file, i.e. that the file is empty.
|
||||
return
|
||||
if f.read(1) != b"\n":
|
||||
self.files_with_issues[filepath] = None
|
||||
|
||||
|
||||
class Utf8BomIssueTracker(FileIssueTracker):
|
||||
"""Track files that start with a UTF-8 BOM.
|
||||
Files should be ASCII or UTF-8. Valid UTF-8 does not start with a BOM."""
|
||||
|
||||
heading = "UTF-8 BOM present:"
|
||||
|
||||
suffix_exemptions = frozenset([".vcxproj", ".sln"])
|
||||
path_exemptions = BINARY_FILE_PATH_RE
|
||||
|
||||
def check_file_for_issue(self, filepath):
|
||||
with open(filepath, "rb") as f:
|
||||
if f.read().startswith(codecs.BOM_UTF8):
|
||||
self.files_with_issues[filepath] = None
|
||||
|
||||
|
||||
class UnicodeIssueTracker(LineIssueTracker):
|
||||
"""Track lines with invalid characters or invalid text encoding."""
|
||||
|
||||
heading = "Invalid UTF-8 or forbidden character:"
|
||||
|
||||
# Only allow valid UTF-8, and only other explicitly allowed characters.
|
||||
# We deliberately exclude all characters that aren't a simple non-blank,
|
||||
# non-zero-width glyph, apart from a very small set (tab, ordinary space,
|
||||
# line breaks, "basic" no-break space and soft hyphen). In particular,
|
||||
# non-ASCII control characters, combinig characters, and Unicode state
|
||||
# changes (e.g. right-to-left text) are forbidden.
|
||||
# Note that we do allow some characters with a risk of visual confusion,
|
||||
# for example '-' (U+002D HYPHEN-MINUS) vs '' (U+00AD SOFT HYPHEN) vs
|
||||
# '‐' (U+2010 HYPHEN), or 'A' (U+0041 LATIN CAPITAL LETTER A) vs
|
||||
# 'Α' (U+0391 GREEK CAPITAL LETTER ALPHA).
|
||||
GOOD_CHARACTERS = ''.join([
|
||||
'\t\n\r -~', # ASCII (tabs and line endings are checked separately)
|
||||
'\u00A0-\u00FF', # Latin-1 Supplement (for NO-BREAK SPACE and punctuation)
|
||||
'\u2010-\u2027\u2030-\u205E', # General Punctuation (printable)
|
||||
'\u2070\u2071\u2074-\u208E\u2090-\u209C', # Superscripts and Subscripts
|
||||
'\u2190-\u21FF', # Arrows
|
||||
'\u2200-\u22FF', # Mathematical Symbols
|
||||
'\u2500-\u257F' # Box Drawings characters used in markdown trees
|
||||
])
|
||||
# Allow any of the characters and ranges above, and anything classified
|
||||
# as a word constituent.
|
||||
GOOD_CHARACTERS_RE = re.compile(r'[\w{}]+\Z'.format(GOOD_CHARACTERS))
|
||||
|
||||
def issue_with_line(self, line, _filepath, line_number):
|
||||
try:
|
||||
text = line.decode('utf-8')
|
||||
except UnicodeDecodeError:
|
||||
return True
|
||||
if line_number == 1 and text.startswith('\uFEFF'):
|
||||
# Strip BOM (U+FEFF ZERO WIDTH NO-BREAK SPACE) at the beginning.
|
||||
# Which files are allowed to have a BOM is handled in
|
||||
# Utf8BomIssueTracker.
|
||||
text = text[1:]
|
||||
return not self.GOOD_CHARACTERS_RE.match(text)
|
||||
|
||||
class UnixLineEndingIssueTracker(LineIssueTracker):
|
||||
"""Track files with non-Unix line endings (i.e. files with CR)."""
|
||||
|
||||
heading = "Non-Unix line endings:"
|
||||
|
||||
def should_check_file(self, filepath):
|
||||
if not super().should_check_file(filepath):
|
||||
return False
|
||||
return not is_windows_file(filepath)
|
||||
|
||||
def issue_with_line(self, line, _filepath, _line_number):
|
||||
return b"\r" in line
|
||||
|
||||
|
||||
class WindowsLineEndingIssueTracker(LineIssueTracker):
|
||||
"""Track files with non-Windows line endings (i.e. CR or LF not in CRLF)."""
|
||||
|
||||
heading = "Non-Windows line endings:"
|
||||
|
||||
def should_check_file(self, filepath):
|
||||
if not super().should_check_file(filepath):
|
||||
return False
|
||||
return is_windows_file(filepath)
|
||||
|
||||
def issue_with_line(self, line, _filepath, _line_number):
|
||||
return not line.endswith(b"\r\n") or b"\r" in line[:-2]
|
||||
|
||||
|
||||
class TrailingWhitespaceIssueTracker(LineIssueTracker):
|
||||
"""Track lines with trailing whitespace."""
|
||||
|
||||
heading = "Trailing whitespace:"
|
||||
suffix_exemptions = frozenset([".dsp", ".md"])
|
||||
|
||||
def issue_with_line(self, line, _filepath, _line_number):
|
||||
return line.rstrip(b"\r\n") != line.rstrip()
|
||||
|
||||
|
||||
class TabIssueTracker(LineIssueTracker):
|
||||
"""Track lines with tabs."""
|
||||
|
||||
heading = "Tabs present:"
|
||||
suffix_exemptions = frozenset([
|
||||
".make",
|
||||
".pem", # some openssl dumps have tabs
|
||||
".sln",
|
||||
"/.gitmodules",
|
||||
"/Makefile",
|
||||
"/Makefile.inc",
|
||||
"/generate_visualc_files.pl",
|
||||
])
|
||||
|
||||
def issue_with_line(self, line, _filepath, _line_number):
|
||||
return b"\t" in line
|
||||
|
||||
|
||||
class MergeArtifactIssueTracker(LineIssueTracker):
|
||||
"""Track lines with merge artifacts.
|
||||
These are leftovers from a ``git merge`` that wasn't fully edited."""
|
||||
|
||||
heading = "Merge artifact:"
|
||||
|
||||
def issue_with_line(self, line, _filepath, _line_number):
|
||||
# Detect leftover git conflict markers.
|
||||
if line.startswith(b'<<<<<<< ') or line.startswith(b'>>>>>>> '):
|
||||
return True
|
||||
if line.startswith(b'||||||| '): # from merge.conflictStyle=diff3
|
||||
return True
|
||||
if line.rstrip(b'\r\n') == b'=======' and \
|
||||
not _filepath.endswith('.md'):
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def this_location():
|
||||
frame = inspect.currentframe()
|
||||
assert frame is not None
|
||||
info = inspect.getframeinfo(frame)
|
||||
return os.path.basename(info.filename), info.lineno
|
||||
THIS_FILE_BASE_NAME, LINE_NUMBER_BEFORE_LICENSE_ISSUE_TRACKER = this_location()
|
||||
|
||||
class LicenseIssueTracker(LineIssueTracker):
|
||||
"""Check copyright statements and license indications.
|
||||
|
||||
This class only checks that statements are correct if present. It does
|
||||
not enforce the presence of statements in each file.
|
||||
"""
|
||||
|
||||
heading = "License issue:"
|
||||
|
||||
LICENSE_EXEMPTION_RE_LIST = [
|
||||
# Third-party code, other than whitelisted third-party modules,
|
||||
# may be under a different license.
|
||||
r'3rdparty/(?!(p256-m)/.*)',
|
||||
# Documentation explaining the license may have accidental
|
||||
# false positives.
|
||||
r'(ChangeLog|LICENSE|[-0-9A-Z_a-z]+\.md)\Z',
|
||||
# Files imported from TF-M, and not used except in test builds,
|
||||
# may be under a different license.
|
||||
r'configs/ext/crypto_config_profile_medium\.h\Z',
|
||||
r'configs/ext/tfm_mbedcrypto_config_profile_medium\.h\Z',
|
||||
r'configs/ext/README\.md\Z',
|
||||
# Third-party file.
|
||||
r'dco\.txt\Z',
|
||||
]
|
||||
path_exemptions = re.compile('|'.join(BINARY_FILE_PATH_RE_LIST +
|
||||
LICENSE_EXEMPTION_RE_LIST))
|
||||
|
||||
COPYRIGHT_HOLDER = rb'The Mbed TLS Contributors'
|
||||
# Catch "Copyright foo", "Copyright (C) foo", "Copyright © foo", etc.
|
||||
COPYRIGHT_RE = re.compile(rb'.*\bcopyright\s+((?:\w|\s|[()]|[^ -~])*\w)', re.I)
|
||||
|
||||
SPDX_HEADER_KEY = b'SPDX-License-Identifier'
|
||||
LICENSE_IDENTIFIER = b'Apache-2.0 OR GPL-2.0-or-later'
|
||||
SPDX_RE = re.compile(br'.*?(' +
|
||||
re.escape(SPDX_HEADER_KEY) +
|
||||
br')(:\s*(.*?)\W*\Z|.*)', re.I)
|
||||
|
||||
LICENSE_MENTION_RE = re.compile(rb'.*(?:' + rb'|'.join([
|
||||
rb'Apache License',
|
||||
rb'General Public License',
|
||||
]) + rb')', re.I)
|
||||
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
# Record what problem was caused. We can't easily report it due to
|
||||
# the structure of the script. To be fixed after
|
||||
# https://github.com/Mbed-TLS/mbedtls/pull/2506
|
||||
self.problem = None
|
||||
|
||||
def issue_with_line(self, line, filepath, line_number):
|
||||
#pylint: disable=too-many-return-statements
|
||||
|
||||
# Use endswith() rather than the more correct os.path.basename()
|
||||
# because experimentally, it makes a significant difference to
|
||||
# the running time.
|
||||
if filepath.endswith(THIS_FILE_BASE_NAME) and \
|
||||
line_number > LINE_NUMBER_BEFORE_LICENSE_ISSUE_TRACKER:
|
||||
# Avoid false positives from the code in this class.
|
||||
# Also skip the rest of this file, which is highly unlikely to
|
||||
# contain any problematic statements since we put those near the
|
||||
# top of files.
|
||||
return False
|
||||
|
||||
m = self.COPYRIGHT_RE.match(line)
|
||||
if m and m.group(1) != self.COPYRIGHT_HOLDER:
|
||||
self.problem = 'Invalid copyright line'
|
||||
return True
|
||||
|
||||
m = self.SPDX_RE.match(line)
|
||||
if m:
|
||||
if m.group(1) != self.SPDX_HEADER_KEY:
|
||||
self.problem = 'Misspelled ' + self.SPDX_HEADER_KEY.decode()
|
||||
return True
|
||||
if not m.group(3):
|
||||
self.problem = 'Improperly formatted SPDX license identifier'
|
||||
return True
|
||||
if m.group(3) != self.LICENSE_IDENTIFIER:
|
||||
self.problem = 'Wrong SPDX license identifier'
|
||||
return True
|
||||
|
||||
m = self.LICENSE_MENTION_RE.match(line)
|
||||
if m:
|
||||
self.problem = 'Suspicious license mention'
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
class IntegrityChecker:
|
||||
"""Sanity-check files under the current directory."""
|
||||
|
||||
def __init__(self, log_file):
|
||||
"""Instantiate the sanity checker.
|
||||
Check files under the current directory.
|
||||
Write a report of issues to log_file."""
|
||||
build_tree.check_repo_path()
|
||||
self.logger = None
|
||||
self.setup_logger(log_file)
|
||||
self.issues_to_check = [
|
||||
ShebangIssueTracker(),
|
||||
EndOfFileNewlineIssueTracker(),
|
||||
Utf8BomIssueTracker(),
|
||||
UnicodeIssueTracker(),
|
||||
UnixLineEndingIssueTracker(),
|
||||
WindowsLineEndingIssueTracker(),
|
||||
TrailingWhitespaceIssueTracker(),
|
||||
TabIssueTracker(),
|
||||
MergeArtifactIssueTracker(),
|
||||
LicenseIssueTracker(),
|
||||
]
|
||||
|
||||
def setup_logger(self, log_file, level=logging.INFO):
|
||||
"""Log to log_file if provided, or to stderr if None."""
|
||||
self.logger = logging.getLogger()
|
||||
self.logger.setLevel(level)
|
||||
if log_file:
|
||||
handler = logging.FileHandler(log_file)
|
||||
self.logger.addHandler(handler)
|
||||
else:
|
||||
console = logging.StreamHandler()
|
||||
self.logger.addHandler(console)
|
||||
|
||||
@staticmethod
|
||||
def collect_files():
|
||||
"""Return the list of files to check.
|
||||
|
||||
These are the regular files commited into Git.
|
||||
"""
|
||||
bytes_output = subprocess.check_output(['git', 'ls-files', '-z'])
|
||||
bytes_filepaths = bytes_output.split(b'\0')[:-1]
|
||||
ascii_filepaths = map(lambda fp: fp.decode('ascii'), bytes_filepaths)
|
||||
# Filter out directories. Normally Git doesn't list directories
|
||||
# (it only knows about the files inside them), but there is
|
||||
# at least one case where 'git ls-files' includes a directory:
|
||||
# submodules. Just skip submodules (and any other directories).
|
||||
ascii_filepaths = [fp for fp in ascii_filepaths
|
||||
if os.path.isfile(fp)]
|
||||
# Prepend './' to files in the top-level directory so that
|
||||
# something like `'/Makefile' in fp` matches in the top-level
|
||||
# directory as well as in subdirectories.
|
||||
return [fp if os.path.dirname(fp) else os.path.join(os.curdir, fp)
|
||||
for fp in ascii_filepaths]
|
||||
|
||||
def check_files(self):
|
||||
"""Check all files for all issues."""
|
||||
for issue_to_check in self.issues_to_check:
|
||||
for filepath in self.collect_files():
|
||||
if issue_to_check.should_check_file(filepath):
|
||||
issue_to_check.check_file_for_issue(filepath)
|
||||
|
||||
def output_issues(self):
|
||||
"""Log the issues found and their locations.
|
||||
|
||||
Return 1 if there were issues, 0 otherwise.
|
||||
"""
|
||||
integrity_return_code = 0
|
||||
for issue_to_check in self.issues_to_check:
|
||||
if issue_to_check.files_with_issues:
|
||||
integrity_return_code = 1
|
||||
issue_to_check.output_file_issues(self.logger)
|
||||
return integrity_return_code
|
||||
|
||||
|
||||
def run_main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument(
|
||||
"-l", "--log_file", type=str, help="path to optional output log",
|
||||
)
|
||||
check_args = parser.parse_args()
|
||||
integrity_check = IntegrityChecker(check_args.log_file)
|
||||
integrity_check.check_files()
|
||||
return_code = integrity_check.output_issues()
|
||||
sys.exit(return_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_main()
|
||||
Reference in New Issue
Block a user