diff options
author | Simon Glass | 2015-03-05 12:25:29 -0700 |
---|---|---|
committer | Simon Glass | 2015-04-18 11:09:45 -0600 |
commit | d2cb9b2b00697eea55068f01ea0890a23313bf51 (patch) | |
tree | a6bbb47ba5fe4c805afc7a633bbd1cade104bb28 | |
parent | 36d0d3b4b4974f4183609ac8b4d77a1f46acba55 (diff) |
dm: sandbox: Add a emulated PCI device as an example
This device sits on the sandbox PCI bus and provides a case-swapping
service for sandbox. It illustrates the use of both PCI I/O and PCI
memory accesses.
Signed-off-by: Simon Glass <sjg@chromium.org>
-rw-r--r-- | drivers/misc/swap_case.c | 285 |
1 files changed, 285 insertions, 0 deletions
diff --git a/drivers/misc/swap_case.c b/drivers/misc/swap_case.c new file mode 100644 index 00000000000..f6028ba3328 --- /dev/null +++ b/drivers/misc/swap_case.c @@ -0,0 +1,285 @@ +/* + * PCI emulation device which swaps the case of text + * + * Copyright (c) 2014 Google, Inc + * Written by Simon Glass <sjg@chromium.org> + * + * SPDX-License-Identifier: GPL-2.0+ + */ + +#include <common.h> +#include <dm.h> +#include <pci.h> +#include <asm/test.h> +#include <linux/ctype.h> + +/** + * struct swap_case_platdata - platform data for this device + * + * @command: Current PCI command value + * @bar: Current base address values + */ +struct swap_case_platdata { + u16 command; + u32 bar[2]; +}; + +#define offset_to_barnum(offset) \ + (((offset) - PCI_BASE_ADDRESS_0) / sizeof(u32)) + +enum { + MEM_TEXT_SIZE = 0x100, +}; + +enum swap_case_op { + OP_TO_LOWER, + OP_TO_UPPER, + OP_SWAP, +}; + +static struct pci_bar { + int type; + u32 size; +} barinfo[] = { + { PCI_BASE_ADDRESS_SPACE_IO, 1 }, + { PCI_BASE_ADDRESS_MEM_TYPE_32, MEM_TEXT_SIZE }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, + { 0, 0 }, +}; + +struct swap_case_priv { + enum swap_case_op op; + char mem_text[MEM_TEXT_SIZE]; +}; + +static int sandbox_swap_case_get_devfn(struct udevice *dev) +{ + struct pci_child_platdata *plat = dev_get_parent_platdata(dev); + + return plat->devfn; +} + +static int sandbox_swap_case_read_config(struct udevice *emul, uint offset, + ulong *valuep, enum pci_size_t size) +{ + struct swap_case_platdata *plat = dev_get_platdata(emul); + + switch (offset) { + case PCI_COMMAND: + *valuep = plat->command; + break; + case PCI_HEADER_TYPE: + *valuep = 0; + break; + case PCI_VENDOR_ID: + *valuep = SANDBOX_PCI_VENDOR_ID; + break; + case PCI_DEVICE_ID: + *valuep = SANDBOX_PCI_DEVICE_ID; + break; + case PCI_CLASS_DEVICE: + if (size == PCI_SIZE_8) { + *valuep = SANDBOX_PCI_CLASS_SUB_CODE; + } else { + *valuep = (SANDBOX_PCI_CLASS_CODE << 8) | + SANDBOX_PCI_CLASS_SUB_CODE; + } + break; + case PCI_CLASS_CODE: + *valuep = SANDBOX_PCI_CLASS_CODE; + break; + case PCI_BASE_ADDRESS_0: + case PCI_BASE_ADDRESS_1: + case PCI_BASE_ADDRESS_2: + case PCI_BASE_ADDRESS_3: + case PCI_BASE_ADDRESS_4: + case PCI_BASE_ADDRESS_5: { + int barnum; + u32 *bar, result; + + barnum = offset_to_barnum(offset); + bar = &plat->bar[barnum]; + + result = *bar; + if (*bar == 0xffffffff) { + if (barinfo[barnum].type) { + result = (~(barinfo[barnum].size - 1) & + PCI_BASE_ADDRESS_IO_MASK) | + PCI_BASE_ADDRESS_SPACE_IO; + } else { + result = (~(barinfo[barnum].size - 1) & + PCI_BASE_ADDRESS_MEM_MASK) | + PCI_BASE_ADDRESS_MEM_TYPE_32; + } + } + debug("r bar %d=%x\n", barnum, result); + *valuep = result; + break; + } + } + + return 0; +} + +static int sandbox_swap_case_write_config(struct udevice *emul, uint offset, + ulong value, enum pci_size_t size) +{ + struct swap_case_platdata *plat = dev_get_platdata(emul); + + switch (offset) { + case PCI_COMMAND: + plat->command = value; + break; + case PCI_BASE_ADDRESS_0: + case PCI_BASE_ADDRESS_1: { + int barnum; + u32 *bar; + + barnum = offset_to_barnum(offset); + bar = &plat->bar[barnum]; + + debug("w bar %d=%lx\n", barnum, value); + *bar = value; + break; + } + } + + return 0; +} + +static int sandbox_swap_case_find_bar(struct udevice *emul, unsigned int addr, + int *barnump, unsigned int *offsetp) +{ + struct swap_case_platdata *plat = dev_get_platdata(emul); + int barnum; + + for (barnum = 0; barnum < ARRAY_SIZE(barinfo); barnum++) { + unsigned int size = barinfo[barnum].size; + + if (addr >= plat->bar[barnum] && + addr < plat->bar[barnum] + size) { + *barnump = barnum; + *offsetp = addr - plat->bar[barnum]; + return 0; + } + } + *barnump = -1; + + return -ENOENT; +} + +static void sandbox_swap_case_do_op(enum swap_case_op op, char *str, int len) +{ + for (; len > 0; len--, str++) { + switch (op) { + case OP_TO_UPPER: + *str = toupper(*str); + break; + case OP_TO_LOWER: + *str = tolower(*str); + break; + case OP_SWAP: + if (isupper(*str)) + *str = tolower(*str); + else + *str = toupper(*str); + break; + } + } +} + +int sandbox_swap_case_read_io(struct udevice *dev, unsigned int addr, + ulong *valuep, enum pci_size_t size) +{ + struct swap_case_priv *priv = dev_get_priv(dev); + unsigned int offset; + int barnum; + int ret; + + ret = sandbox_swap_case_find_bar(dev, addr, &barnum, &offset); + if (ret) + return ret; + + if (barnum == 0 && offset == 0) + *valuep = (*valuep & ~0xff) | priv->op; + + return 0; +} + +int sandbox_swap_case_write_io(struct udevice *dev, unsigned int addr, + ulong value, enum pci_size_t size) +{ + struct swap_case_priv *priv = dev_get_priv(dev); + unsigned int offset; + int barnum; + int ret; + + ret = sandbox_swap_case_find_bar(dev, addr, &barnum, &offset); + if (ret) + return ret; + if (barnum == 0 && offset == 0) + priv->op = value; + + return 0; +} + +static int sandbox_swap_case_map_physmem(struct udevice *dev, + phys_addr_t addr, unsigned long *lenp, void **ptrp) +{ + struct swap_case_priv *priv = dev_get_priv(dev); + unsigned int offset, avail; + int barnum; + int ret; + + ret = sandbox_swap_case_find_bar(dev, addr, &barnum, &offset); + if (ret) + return ret; + if (barnum == 1) { + *ptrp = priv->mem_text + offset; + avail = barinfo[1].size - offset; + if (avail > barinfo[1].size) + *lenp = 0; + else + *lenp = min(*lenp, (ulong)avail); + + return 0; + } + + return -ENOENT; +} + +static int sandbox_swap_case_unmap_physmem(struct udevice *dev, + const void *vaddr, unsigned long len) +{ + struct swap_case_priv *priv = dev_get_priv(dev); + + sandbox_swap_case_do_op(priv->op, (void *)vaddr, len); + + return 0; +} + +struct dm_pci_emul_ops sandbox_swap_case_emul_ops = { + .get_devfn = sandbox_swap_case_get_devfn, + .read_config = sandbox_swap_case_read_config, + .write_config = sandbox_swap_case_write_config, + .read_io = sandbox_swap_case_read_io, + .write_io = sandbox_swap_case_write_io, + .map_physmem = sandbox_swap_case_map_physmem, + .unmap_physmem = sandbox_swap_case_unmap_physmem, +}; + +static const struct udevice_id sandbox_swap_case_ids[] = { + { .compatible = "sandbox,swap-case" }, + { } +}; + +U_BOOT_DRIVER(sandbox_swap_case_emul) = { + .name = "sandbox_swap_case_emul", + .id = UCLASS_PCI_EMUL, + .of_match = sandbox_swap_case_ids, + .ops = &sandbox_swap_case_emul_ops, + .priv_auto_alloc_size = sizeof(struct swap_case_priv), + .platdata_auto_alloc_size = sizeof(struct swap_case_platdata), +}; |