/***************************************************************************** * drivers/virt/qemu_edu.c * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include /***************************************************************************** * Pre-processor Definitions *****************************************************************************/ /* Registers defined for device. Size 4 for < 0x80. Size 8 for >= 0x80. */ #define EDU_REG_ID 0x00 /* Identification */ #define EDU_REG_LIVE 0x04 /* Liveness Check */ #define EDU_REG_FAC 0x08 /* Factorial Computation */ #define EDU_REG_STATUS 0x20 /* Status */ #define EDU_REG_INT_STATUS 0x24 /* Interupt Status */ #define EDU_REG_INT_RAISE 0x60 /* Raise an interrupt */ #define EDU_REG_INT_ACK 0x64 /* Acknowledge interrupt */ #define EDU_REG_DMA_SOURCE 0x80 /* Source address for DMA transfer */ #define EDU_REG_DMA_DEST 0x88 /* Destination address for DMA transfer */ #define EDU_REG_DMA_COUNT 0x90 /* Size of area to transfer with DMA */ #define EDU_REG_DMA_CMD 0x98 /* Control DMA tranfer */ #define EDU_CONTROL_BAR_ID 0 #define EDU_CONTROL_BAR_OFFSET PCI_HEADER_NORM_BAR0 /* One 4096 bytes long buffer at offset 0x40000 is available in the * EDU device */ #define QEMU_EDU_DMABUF_OFFSET 0x40000 /***************************************************************************** * Private Types *****************************************************************************/ struct qemu_edu_priv_s { uintptr_t base_addr; sem_t isr_done; uint32_t test_result; }; /***************************************************************************** * Private Functions Definitions *****************************************************************************/ static void qemu_edu_write_reg32(uintptr_t addr, uint32_t val); static uint32_t qemu_edu_read_reg32(uintptr_t addr); static void qemu_edu_write_reg64(uintptr_t addr, uint64_t val); static void qemu_edu_test_poll(FAR struct pci_dev_s *dev, uintptr_t base_addr); static void qemu_edu_test_intx(FAR struct pci_dev_s *dev, struct qemu_edu_priv_s *drv_priv); static int qemu_edu_interrupt(int irq, void *context, FAR void *arg); static int qemu_edu_probe(FAR struct pci_bus_s *bus, FAR const struct pci_dev_type_s *type, uint16_t bdf); /***************************************************************************** * Public Data *****************************************************************************/ const struct pci_dev_type_s g_pci_type_qemu_edu = { .vendor = 0x1234, .device = 0x11e8, .class_rev = PCI_ID_ANY, .name = "Qemu PCI EDU device", .probe = qemu_edu_probe }; /***************************************************************************** * Private Functions *****************************************************************************/ /***************************************************************************** * Name: qemu_edu_write_reg32 * * Description: * Provide a write interface for 32bit mapped registers * * Input Parameters: * addr - Register address * val - Value to assign to register * *****************************************************************************/ static void qemu_edu_write_reg32(uintptr_t addr, uint32_t val) { *(volatile uint32_t *)addr = val; } /***************************************************************************** * Name: qemu_edu_read_reg32 * * Description: * Provide a read interface for 32bit mapped registers * * Returned Value: * Register value * *****************************************************************************/ static uint32_t qemu_edu_read_reg32(uintptr_t addr) { return *(volatile uint32_t *)addr; } /***************************************************************************** * Name: qemu_edu_write_reg64 * * Description: * Provide a write interface for 64bit mapped registers * * Input Parameters: * addr - Register address * val - Value to assign to register * *****************************************************************************/ static void qemu_edu_write_reg64(uintptr_t addr, uint64_t val) { *(volatile uint64_t *)addr = val; } /***************************************************************************** * Name: qemu_edu_test_poll * * Description: * Performs basic functional test of PCI device and MMIO using polling * of mapped register interfaces. * * Input Parameters: * bus - An PCI device * base_addr - Base address of device register space * *****************************************************************************/ static void qemu_edu_test_poll(FAR struct pci_dev_s *dev, uintptr_t base_addr) { uint32_t test_value; uint32_t test_read; pciinfo("Identification: 0x%08xu\n", qemu_edu_read_reg32(base_addr + EDU_REG_ID)); /* Test Live Check */ test_value = 0xdeadbeef; qemu_edu_write_reg32(base_addr + EDU_REG_LIVE, test_value); test_read = qemu_edu_read_reg32(base_addr + EDU_REG_LIVE); pciinfo("Live Check: Wrote: 0x%08x Read: 0x%08x Error Bits 0x%08x\n", test_value, test_read, test_read ^ ~test_value); pciinfo("TEST %s\n", ((test_read ^ ~test_value) == 0) ? "PASS" : "FAIL"); /* Test Factorial */ test_value = 10; qemu_edu_write_reg32(base_addr + EDU_REG_STATUS, 0); qemu_edu_write_reg32(base_addr + EDU_REG_FAC, test_value); while (qemu_edu_read_reg32(base_addr + EDU_REG_STATUS) & 0x01) { pciinfo("Waiting to compute factorial..."); usleep(10000); } test_read = qemu_edu_read_reg32(base_addr + EDU_REG_FAC); pciinfo("Computed factorial of %d as %d\n", test_value, test_read); pciinfo("TEST %s\n", (test_read == 3628800) ? "PASS" : "FAIL"); } /***************************************************************************** * Name: qemu_edu_test_intx * * Description: * Performs basic functional test of PCI device and MMIO using INTx * * Input Parameters: * bus - An PCI device * drv_priv - Struct containing internal state of driver * *****************************************************************************/ static void qemu_edu_test_intx(FAR struct pci_dev_s *dev, FAR struct qemu_edu_priv_s *drv_priv) { uintptr_t base_addr = drv_priv->base_addr; uint32_t test_value; pciinfo("Identification: 0x%08xu\n", qemu_edu_read_reg32(base_addr + EDU_REG_ID)); /* Test Read/Write */ test_value = 0xdeadbeef; pciinfo("Triggering interrupt with value 0x%08x\n", test_value); qemu_edu_write_reg32(base_addr + EDU_REG_INT_RAISE, test_value); sem_wait(&drv_priv->isr_done); pciinfo("TEST %s\n", (drv_priv->test_result == test_value) ? "PASS" : "FAIL"); /* Test Factorial */ test_value = 5; pciinfo("Computing factorial of %d\n", test_value); qemu_edu_write_reg32(base_addr + EDU_REG_STATUS, 0x80); qemu_edu_write_reg32(base_addr + EDU_REG_FAC, test_value); sem_wait(&drv_priv->isr_done); pciinfo("TEST %s\n", (drv_priv->test_result == 120) ? "PASS" : "FAIL"); /* Test ISR Status Cleanup */ qemu_edu_write_reg32(base_addr + EDU_REG_INT_RAISE, test_value); sem_wait(&drv_priv->isr_done); pciinfo("TEST %s\n", (drv_priv->test_result == test_value) ? "PASS" : "FAIL"); } /***************************************************************************** * Name: qemu_edu_test_dma * * Description: * Performs dma functional test of PCI device * * Input Parameters: * bus - An PCI device * drv_priv - Struct containing internal state of driver * *****************************************************************************/ static void qemu_edu_test_dma(FAR struct pci_dev_s *dev, FAR struct qemu_edu_priv_s *drv_priv) { uintptr_t base_addr = drv_priv->base_addr; FAR void *test_block; size_t block_size = 2048; int i; uint32_t psrand; uint32_t tx_checksum; uint32_t rx_checksum; uint32_t dev_addr = QEMU_EDU_DMABUF_OFFSET; pciinfo("Identification: 0x%08xu\n", qemu_edu_read_reg32(base_addr + EDU_REG_ID)); test_block = kmm_malloc(block_size); for (i = 0; i < block_size; i++) { *((uint8_t *)test_block + i) = i & 0xff; } tx_checksum = 0; psrand = 0x0011223344; for (i = 0; i < (block_size / 4); i++) { /* Fill the memory block with "random" data */ psrand ^= psrand << 13; psrand ^= psrand >> 17; psrand ^= psrand << 5; *((uint32_t *)test_block + i) = psrand; tx_checksum += psrand; } pciinfo("Test block checksum 0x%08x\n", tx_checksum); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_SOURCE, (uint64_t)test_block); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_DEST, (uint64_t)dev_addr); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_COUNT, (uint64_t)block_size); qemu_edu_write_reg32(base_addr + EDU_REG_STATUS, 0x00); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_CMD, 0x01 | 0x04); sem_wait(&drv_priv->isr_done); pciinfo("DMA transfer to device complete.\n"); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_DEST, (uint64_t)test_block); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_SOURCE, (uint64_t)dev_addr); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_COUNT, (uint64_t)block_size); qemu_edu_write_reg32(base_addr + EDU_REG_STATUS, 0x00); qemu_edu_write_reg64(base_addr + EDU_REG_DMA_CMD, 0x01 | 0x02 | 0x04); sem_wait(&drv_priv->isr_done); pciinfo("DMA transfer from device complete.\n"); rx_checksum = 0; for (i = 0; i < block_size / 4; i++) { rx_checksum += *((uint32_t *)test_block + i); } pciinfo("Received block checksum 0x%08x\n", rx_checksum); pciinfo("TEST %s\n", (rx_checksum == tx_checksum) ? "PASS" : "FAIL"); } /***************************************************************************** * Name: qemu_edu_interrupt * * Description: * EDU interrupt handler * *****************************************************************************/ static int qemu_edu_interrupt(int irq, FAR void *context, FAR void *arg) { FAR struct qemu_edu_priv_s *drv_priv = (struct qemu_edu_priv_s *)arg; uintptr_t base_addr; uint32_t status; base_addr = drv_priv->base_addr; status = qemu_edu_read_reg32(base_addr + EDU_REG_INT_STATUS); qemu_edu_write_reg32(base_addr + EDU_REG_INT_ACK, ~0U); switch (status) { /* Factorial triggered */ case 0x1: { drv_priv->test_result = qemu_edu_read_reg32(base_addr + EDU_REG_FAC); pciinfo("Computed factorial: %d\n", drv_priv->test_result); break; } /* DMA triggered */ case 0x100: { pciinfo("DMA transfer complete\n"); break; } /* Generic write */ default: { drv_priv->test_result = status; pciinfo("Received value: 0x%08x\n", status); break; } } sem_post(&drv_priv->isr_done); return OK; } /***************************************************************************** * Name: qemu_edu_probe * * Description: * Initialize device * *****************************************************************************/ static int qemu_edu_probe(FAR struct pci_bus_s *bus, FAR const struct pci_dev_type_s *type, uint16_t bdf) { struct qemu_edu_priv_s drv_priv; struct pci_dev_s dev; uint32_t bar; uintptr_t bar_addr; uint8_t irq; /* Get dev */ dev.bus = bus; dev.type = type; dev.bdf = bdf; pci_enable_bus_master(&dev); pciinfo("Enabled bus mastering\n"); pci_enable_io(&dev, PCI_SYS_RES_MEM); pciinfo("Enabled memory resources\n"); if (pci_bar_valid(&dev, EDU_CONTROL_BAR_ID) != OK) { pcierr("Control BAR is not valid\n"); DEBUGPANIC(); return -EINVAL; } bar_addr = pci_bar_addr(&dev, EDU_CONTROL_BAR_ID); bar = bus->ops->pci_cfg_read(&dev, EDU_CONTROL_BAR_OFFSET, 4); if ((bar & PCI_BAR_LAYOUT_MASK) != PCI_BAR_LAYOUT_MEM) { pcierr("Control bar expected to be MMIO\n"); DEBUGPANIC(); return -EINVAL; } if (bus->ops->pci_map_bar(bar_addr, pci_bar_size(&dev, EDU_CONTROL_BAR_ID)) != OK) { pcierr("Failed to map address space\n"); DEBUGPANIC(); return -EINVAL; } pciinfo("Device Initialized\n"); /* Run Poll Tests */ qemu_edu_test_poll(&dev, bar_addr); /* Run IRQ Tests */ drv_priv.base_addr = bar_addr; sem_init(&drv_priv.isr_done, 0, 0); sem_setprotocol(&drv_priv.isr_done, SEM_PRIO_NONE); irq = IRQ0 + bus->ops->pci_cfg_read(&dev, PCI_HEADER_NORM_INT_LINE, 1); pciinfo("Attaching IRQ %d to %p\n", irq, qemu_edu_interrupt); irq_attach(irq, (xcpt_t)qemu_edu_interrupt, (void *)&drv_priv); up_enable_irq(irq); qemu_edu_test_intx(&dev, &drv_priv); qemu_edu_test_dma(&dev, &drv_priv); up_disable_irq(irq); irq_detach(irq); sem_destroy(&drv_priv.isr_done); /* Run MSI Tests */ /* Really should be cleaning up the mapped memory */ return OK; }