blob: 2efb750857568a23c6ec53d65c891344e02348a0 [file] [log] [blame] [edit]
/* Copyright (c) 2019 Google Inc
* Aditya Basu <mitthu@google.com>
* Barret Rhoden <brho@google.com>
* See LICENSE for details.
*/
#include <parlib/stdio.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <string.h>
#include <inttypes.h>
#include <fcntl.h>
#include <unistd.h>
#define BUFFERSIZE 20
#define error_exit(s) \
do { \
perror((s)); \
exit(-1); \
} while (1)
/* Descriptor structue as defined in the programmer's guide.
* It describes a single DMA transfer
*/
struct ucbdma {
uint64_t dst_addr;
uint64_t src_addr;
uint32_t xfer_size;
char bdf_str[10];
} __attribute__((packed));
static void *map_page(void)
{
void *region;
size_t pagesize = getpagesize();
region = mmap(0, pagesize, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANON | MAP_PRIVATE, 0, 0);
if (region == MAP_FAILED)
error_exit("cannot mmap");
return region;
}
static void unmap_page(void *region)
{
int err;
size_t pagesize = getpagesize();
err = munmap(region, pagesize);
if (err)
error_exit("cannot munmap");
}
static void issue_dma(struct ucbdma *ptr)
{
int fd = open("#cbdma/ucopy", O_RDWR);
if (fd < 0)
error_exit("open failed: #cbdma/ucopy");
printf("[user] ucbdma ptr: %p\n", ptr);
if (write(fd, ptr, sizeof(struct ucbdma)) < 0)
error_exit("write 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] \txref_size: %d\n", ucbdma->xfer_size);
printf("[user] \tsrc_addr: %p\n", ucbdma->src_addr);
printf("[user] \tdst_addr: %p\n", ucbdma->dst_addr);
}
static void attach_device(char *pcistr)
{
char buf[1024];
int fd = open("#iommu/attach", O_RDWR);
if (fd < 0)
error_exit("open failed: #iommu/attach");
sprintf(buf, "%s %d\n", pcistr, getpid());
if (write(fd, buf, strlen(buf)) < 0)
error_exit("attach");
close(fd);
system("cat \\#iommu/mappings");
}
static void detach_device(char *pcistr)
{
char buf[1024];
int fd = open("#iommu/detach", O_RDWR);
if (fd < 0)
error_exit("open failed: #iommu/detach");
sprintf(buf, "%s %d\n", pcistr, getpid());
if (write(fd, buf, strlen(buf)) < 0)
error_exit("dettach");
close(fd);
}
static void showmapping(pid_t pid, char *dst)
{
/* One could imagine typeof-based macros that create a string of the
* right size and snprintf variables with %d, %p, whatever... */
char pid_s[20];
char addr_s[20];
char *argv[] = { "m", "showmapping", pid_s, addr_s, NULL };
snprintf(pid_s, sizeof(pid_s), "%d", pid);
snprintf(addr_s, sizeof(addr_s), "%p", dst);
run_and_wait(argv[0], sizeof(argv), argv);
}
int main(int argc, char *argv[])
{
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);
/* setup src and dst buffers; 100 is random padding */
src = map_page();
dst = map_page();
printf("[user] mmaped src %p\n", src);
printf("[user] mmaped dst %p\n", dst);
/* No need to fill dst, it is all zeros (\0, not '0') from the OS */
fill_buffer(src, '1', BUFFERSIZE);
printf("[user] src: %s\n", src);
printf("[user] dst: %s\n", dst);
/* setup ucbdma*/
ucbdma = malloc(sizeof(struct ucbdma));
ucbdma->xfer_size = BUFFERSIZE;
ucbdma->src_addr = (uint64_t) src;
ucbdma->dst_addr = (uint64_t) dst;
memcpy(&ucbdma->bdf_str, pcistr, sizeof(ucbdma->bdf_str));
issue_dma(ucbdma);
printf("[user] src: %s\n", src);
printf("[user] dst: %s\n", dst);
/* Force an IOTLB flush by mmaping/munmapping an arbitrary page */
unmap_page(map_page());
/* Ideally, we'd see the dirty bit set in the PTE. But we probably
* won't. The user would have to dirty the page to tell the OS it was
* dirtied, which is really nasty. */
printf("[user] Asking the kernel to show the PTE for %p\n", dst);
showmapping(getpid(), dst);
/* cleanup */
unmap_page(src);
unmap_page(dst);
detach_device(pcistr);
return 0;
}