| /* Copyright (c) 2019 Google Inc |
| * Aditya Basu <mitthu@google.com> |
| * See LICENSE for details. |
| |
| * For kernel space |
| * ---------------- |
| * uintptr_t uva2kva(struct proc *p, void *uva, size_t len, int prot) |
| * prot is e.g. PROT_WRITE (writable by userspace). |
| * returns a KVA, which you can convert to a phys addr with PADDR(). |
| * |
| * TODO: |
| * - Bypass DMA re-mapping if iommu is not turned on (in #cbdma/iommu). |
| */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <sys/mman.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <fcntl.h> |
| #include <unistd.h> |
| #include <parlib/assert.h> |
| |
| #define CBDMA_DESC_CTRL_INTR_ON_COMPLETION 0x00000001 |
| #define CBDMA_DESC_CTRL_WRITE_CHANCMP_ON_COMPLETION 0x00000008 |
| #define CBDMA_DESC_CTRL_NULL_DESC 0x20 |
| |
| #define BUFFERSIZE 20 |
| |
| /* Descriptor structue as defined in the programmer's guide. |
| * It describes a single DMA transfer |
| */ |
| struct desc { |
| uint32_t xfer_size; |
| uint32_t descriptor_control; |
| uint64_t src_addr; |
| uint64_t dest_addr; |
| uint64_t next_desc_addr; |
| uint64_t next_source_address; |
| uint64_t next_destination_address; |
| uint64_t reserved0; |
| uint64_t reserved1; |
| } __attribute__((packed)); |
| |
| /* describe a DMA */ |
| struct ucbdma { |
| struct desc desc; |
| uint64_t status; |
| uint16_t ndesc; |
| }; |
| |
| static void *map_page(void) |
| { |
| void *region; |
| size_t pagesize = getpagesize(); |
| |
| printf("[user] page size: %zu bytes\n", pagesize); |
| |
| region = mmap(0, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC, |
| MAP_ANON | MAP_PRIVATE, 0, 0); |
| |
| if (region == MAP_FAILED) |
| panic("cannot mmap"); |
| |
| return region; |
| } |
| |
| static void unmap_page(void *region) |
| { |
| int err; |
| size_t pagesize = getpagesize(); |
| |
| err = munmap(region, pagesize); |
| if (err) |
| panic("cannot munmap"); |
| } |
| |
| static void issue_dma(struct ucbdma *ptr) |
| { |
| int fd = open("#cbdma/ucopy", O_RDWR); |
| |
| if (fd < 0) |
| panic("open failed: #cbdma/ucopy"); |
| |
| printf("[user] ucbdma ptr: %p\n", ptr); |
| write(fd, ptr, sizeof(struct ucbdma *)); |
| |
| close(fd); |
| } |
| |
| static void fill_buffer(char *buffer, char c, int size) |
| { |
| memset(buffer, c, size); |
| buffer[size-1] = '\0'; |
| } |
| |
| static void dump_ucbdma(struct ucbdma *ucbdma) |
| { |
| printf("[user] ucbdma: %p, size: %d (or 0x%x)\n", ucbdma, |
| sizeof(struct ucbdma), sizeof(struct ucbdma)); |
| printf("[user] \tdesc->xref_size: %d\n", ucbdma->desc.xfer_size); |
| printf("[user] \tdesc->src_addr: %p\n", ucbdma->desc.src_addr); |
| printf("[user] \tdesc->dest_addr: %p\n", ucbdma->desc.dest_addr); |
| printf("[user] \tdesc->next_desc_addr: %p\n", |
| ucbdma->desc.next_desc_addr); |
| printf("[user] \tndesc: %d\n", ucbdma->ndesc); |
| printf("[user] \tstatus: 0x%llx\n", ucbdma->status); |
| } |
| |
| static void attach_device(char *pcistr) |
| { |
| char buf[1024]; |
| int fd = open("#iommu/attach", O_RDWR); |
| |
| if (fd < 0) |
| panic("open failed: #iommu/attach"); |
| |
| sprintf(buf, "%s %d\n", pcistr, getpid()); |
| write(fd, buf, strlen(buf)); |
| |
| close(fd); |
| |
| system("cat \\#iommu/mappings"); |
| } |
| |
| static void detach_device(char *pcistr) |
| { |
| int fd = open("#iommu/detach", O_RDWR); |
| |
| if (fd < 0) |
| panic("open failed: #iommu/detach"); |
| |
| write(fd, pcistr, strlen(pcistr)); |
| |
| close(fd); |
| } |
| |
| int main(int argc, char **argv) |
| { |
| char *region; |
| struct ucbdma *ucbdma; |
| char *src, *dst; |
| char *pcistr; |
| |
| if (argc < 2) { |
| printf("example:\n"); |
| printf("\tucbdma 00:04.7\n"); |
| exit(1); |
| } |
| pcistr = argv[1]; |
| |
| attach_device(pcistr); |
| |
| /* map page for placing ucbdma */ |
| region = map_page(); |
| |
| /* setup src and dst buffers; 100 is random padding */ |
| src = region + sizeof(struct ucbdma) + 100; |
| dst = region + sizeof(struct ucbdma) + 100 + BUFFERSIZE; |
| fill_buffer(src, '1', BUFFERSIZE); |
| fill_buffer(dst, '0', BUFFERSIZE); |
| printf("[user] src: %s\n", src); |
| printf("[user] dst: %s\n", dst); |
| |
| /* setup ucbdma*/ |
| ucbdma = (struct ucbdma *) region; |
| ucbdma->status = 0; |
| ucbdma->desc.descriptor_control |
| = CBDMA_DESC_CTRL_INTR_ON_COMPLETION |
| | CBDMA_DESC_CTRL_WRITE_CHANCMP_ON_COMPLETION; |
| ucbdma->desc.xfer_size = BUFFERSIZE; |
| ucbdma->desc.src_addr = (uint64_t) src; |
| ucbdma->desc.dest_addr = (uint64_t) dst; |
| ucbdma->desc.next_desc_addr = (uint64_t) &ucbdma->desc; |
| ucbdma->ndesc = 1; |
| |
| dump_ucbdma(ucbdma); |
| issue_dma(ucbdma); |
| dump_ucbdma(ucbdma); |
| |
| printf("[user] channel_status: %llx\n", ucbdma->status); |
| printf("[user] src: %s\n", src); |
| printf("[user] dst: %s\n", dst); |
| |
| /* cleanup */ |
| unmap_page(region); |
| |
| detach_device(pcistr); |
| |
| return 0; |
| } |