| /* |
| * This is a scriptable tool to use mmap and write to registers. |
| */ |
| |
| #define _POSIX_SOURCE /* for fileno! */ |
| #define _BSD_SOURCE /* for usleep! */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <memory.h> |
| #include <errno.h> |
| #include <inttypes.h> |
| #include <sys/mman.h> |
| #include <sys/types.h> |
| #include <sys/stat.h> |
| #include <linux/limits.h> |
| #include <fcntl.h> |
| |
| #define MAXSLOTS 10 |
| #define MAXARGS 10 |
| |
| struct slot { |
| char path[PATH_MAX]; |
| int fd; |
| off_t fileLength; |
| void* map; |
| uint64_t addr; |
| uint64_t length; |
| }; |
| |
| static struct slot slots[MAXSLOTS]; |
| static char* posPrefix = ""; |
| |
| void usage(const char* prog) |
| { |
| fprintf(stderr, "Usage: %s [-q)uiet] [command-file ...]\n", prog); |
| fprintf(stderr, "\twhere command-file or stdin contains:\n"); |
| fprintf(stderr, "\t\tfile 0 /sys/bus/pci/devices/0000:01:00.0/resource0 " |
| "0 0x10000\n"); |
| fprintf(stderr, "\t\tfile 2 /sys/bus/pci/devices/0000:01:00.0/resource2 " |
| "0 0x10000\n"); |
| fprintf(stderr, "\t\tfile 4 /sys/bus/pci/devices/0000:01:00.0/resource4 " |
| "0 0x10000\n"); |
| fprintf(stderr, "\t\tread 2 16 4 " |
| "# read file 2, addr 16, length 4\n"); |
| fprintf(stderr, "\t\twrite 4 18 4 0xffff " |
| "# write file 4, addr 18, length 4, value 0xffff\n"); |
| fprintf(stderr, "\t\tdump 4 18 4 100 " |
| "# dump file 4, addr 18, length 4, 100 values\n"); |
| fprintf(stderr, "\t\tclose 0 " |
| "# close a file\n"); |
| } |
| |
| int asUnsigned(uint64_t* ip, const char* str) |
| { |
| char* end; |
| |
| uint64_t value = strtoull(str, &end, 0); |
| if (end == str || *end != '\0') { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "failed to parse '%s' as unsigned (eg 0x10 or 16)\n", str); |
| return -1; |
| } |
| *ip = value; |
| return 0; |
| } |
| |
| int asFileAddr(uint64_t* ip, const char* str, int slot) |
| { |
| if (asUnsigned(ip, str) < 0) { |
| return -1; |
| } |
| if (*ip >= (uint64_t) slots[slot].fileLength) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "address '%s' exceeds bounds of file\n", str); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int asFileLength(uint64_t* ip, const char* str, int slot, uint64_t fileAddr) |
| { |
| if (asUnsigned(ip, str) < 0) { |
| return -1; |
| } |
| if (fileAddr + *ip >= (uint64_t) slots[slot].fileLength) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "length '%s' exceeds bounds of file\n", str); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int asAddr(uint64_t* ip, const char* str, int slot) |
| { |
| if (asUnsigned(ip, str) < 0) { |
| return -1; |
| } |
| if (*ip >= (uint64_t) slots[slot].length) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "address '%s' out of range 0x%" PRIx64 "..0x%" PRIx64 "\n", |
| str, slots[slot].addr, slots[slot].addr + slots[slot].length-1); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int asLength(uint64_t* ip, const char* str, int slot, uint64_t addr) |
| { |
| if (asUnsigned(ip, str) < 0) { |
| return -1; |
| } |
| if (addr + *ip >= addr + slots[slot].length) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "address '%s' out of range 0..0x%" PRIx64 "\n", |
| str, slots[slot].length-1); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int asWordLen(int *ip, const char* str) |
| { |
| uint64_t value; |
| if (asUnsigned(&value, str) < 0) { |
| return -1; |
| } |
| if (value != 1 && value != 2 && value != 4 && value != 8) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "length '%s' must be 1, 2, 4 or 8\n", str); |
| return -1; |
| } |
| *ip = value; |
| return 0; |
| } |
| |
| int asSlot(int *ip, const char* str, int wantOpen) |
| { |
| uint64_t value; |
| if (asUnsigned(&value, str) < 0) { |
| return -1; |
| } |
| if (value >= MAXSLOTS) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "slot '%s' is out of range 0-%d or is not open\n", |
| str, MAXSLOTS-1); |
| return -1; |
| } |
| if (wantOpen && slots[value].map == NULL) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "slot '%s' is not open\n", str); |
| return -1; |
| } else if (!wantOpen && slots[value].map != NULL) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "slot '%s' is already open\n", str); |
| return -1; |
| } |
| *ip = value; |
| return 0; |
| } |
| |
| int do_open(char* path, int slot, uint64_t fileAddr, uint64_t length) |
| { |
| int fd = open(path, O_RDWR); |
| if (fd < 0) { |
| perror(path); |
| return -1; |
| } |
| |
| struct stat stats; |
| if (fstat(fd, &stats) < 0) { |
| perror("fstat"); |
| close(fd); |
| return -1; |
| } |
| uint64_t fileLength = stats.st_size; |
| |
| if (fileAddr + length > fileLength) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "mapped range (0x%" PRIx64 ",0x%" PRIx64 ") " |
| "is outside of size of file (0x%" PRIx64 ")\n", |
| fileAddr, length, fileLength); |
| close(fd); |
| return -1; |
| } |
| |
| void* mm = mmap(NULL, length, PROT_READ|PROT_WRITE, MAP_SHARED, fd, fileAddr); |
| if (mm == NULL) { |
| perror("mmap"); |
| close(fd); |
| return -1; |
| } |
| strncpy(slots[slot].path, path, sizeof slots[slot].path); |
| slots[slot].fd = fd; |
| slots[slot].fileLength = fileLength; |
| slots[slot].map = mm; |
| slots[slot].addr = fileAddr; |
| slots[slot].length = length; |
| return 0; |
| } |
| |
| void do_close(int slot) { |
| munmap(slots[slot].map, slots[slot].length); |
| close(slots[slot].fd); |
| memset(&slots[slot], 0, sizeof (slots[slot])); |
| slots[slot].fd = -1; |
| } |
| |
| int do_read_helper(int slot, uint64_t addr, int wordlen, uint64_t* valueP) |
| { |
| void* vp = slots[slot].map + addr; |
| uint8_t u8; |
| uint16_t u16; |
| uint32_t u32; |
| uint64_t u64; |
| void *dp; |
| |
| switch (wordlen) { |
| case sizeof(u64): dp = &u64; break; |
| case sizeof(u32): dp = &u32; break; |
| case sizeof(u16): dp = &u16; break; |
| case sizeof(u8): dp = &u8; break; |
| default: |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "Can't find datatype for wordlen '%d'\n", wordlen); |
| return -1; |
| } |
| |
| memcpy(dp, vp, wordlen); |
| |
| switch (wordlen) { |
| case sizeof(u64): *valueP = u64; break; |
| case sizeof(u32): *valueP = u32; break; |
| case sizeof(u16): *valueP = u16; break; |
| case sizeof(u8): *valueP = u8; break; |
| default: |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "Can't find datatype for wordlen '%d'\n", wordlen); |
| return -1; |
| } |
| return 0; |
| } |
| |
| int do_read(int slot, uint64_t addr, int wordlen) |
| { |
| uint64_t value; |
| |
| if (do_read_helper(slot, addr, wordlen, &value) < 0) { |
| return -1; |
| } |
| printf("0x%0*" PRIx64 "\n", 2*wordlen, value); |
| return 0; |
| } |
| |
| void do_write(int slot, uint64_t addr, int wordlen, uint64_t value) |
| { |
| void* vp = slots[slot].map + addr; |
| memcpy(vp, &value, wordlen); |
| } |
| |
| int do_hexdump(int slot, uint64_t addr, int wordlen, uint64_t count) |
| { |
| uint64_t perline = 16/wordlen; |
| int err = 0; |
| |
| for (uint64_t i = 0; i < count; i += perline) { |
| printf("%08" PRIx64 ":", addr + i * wordlen); |
| for (uint64_t j = 0; j < perline; j++) { |
| if (i + j >= count) { |
| break; |
| } |
| uint64_t value = 0; |
| if (do_read_helper(slot, addr + (i + j) * wordlen, wordlen, &value) < 0) { |
| err = -1; |
| } |
| printf(" 0x%0*" PRIx64, 2*wordlen, value); |
| } |
| printf("\n"); |
| } |
| return err; |
| } |
| |
| // loop waiting for a register to have a value |
| int wait_for_bits(uint32_t* creg, uint32_t mask, uint32_t want, |
| int usec_delay, int max_tries) |
| { |
| uint32_t got = 0; |
| |
| for (int i = 0; i < max_tries; i++) { |
| got = *creg; |
| if ((got & mask) == want) { |
| return 0; |
| } |
| usleep(usec_delay); |
| } |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "timeout waiting for bits: " |
| "mask=0x%08x want=0x%08x got=0x%08x\n", mask, want, got); |
| return -1; |
| } |
| |
| // Alleycat XSMI Management Register |
| |
| #define CMD_ADDR_THEN_WRITE 5 |
| #define CMD_ADDR_THEN_READ 7 |
| |
| #define CMD_BUSY (1 << 30) |
| |
| // read from device on MDIO bus attached to a PCI device |
| int do_mread(int slot, uint64_t addr_reg, uint64_t cmd_reg, |
| uint64_t port, uint64_t dev, uint64_t reg, uint64_t count) |
| { |
| uint32_t* areg = slots[slot].map + addr_reg; |
| uint32_t* creg = slots[slot].map + cmd_reg; |
| uint32_t value; |
| |
| for (uint64_t i = 0; i < count; i++) { |
| uint32_t rreg = reg + i; |
| |
| if (wait_for_bits(creg, CMD_BUSY, 0, 1000, 100) < 0) { |
| return -1; |
| } |
| |
| // remote register we want to read |
| *areg = rreg; |
| |
| // issue command |
| *creg = (CMD_ADDR_THEN_READ << 26) | (dev << 21) | (port << 16); |
| |
| if (wait_for_bits(creg, CMD_BUSY, 0, 1000, 100) < 0) { |
| return -1; |
| } |
| |
| // get result |
| value = *creg; |
| |
| printf("%" PRIx64 ".%" PRIx64 ".%04" PRIx32 ": %04x %04x\n", |
| port, dev, rreg, value >> 16, value & 0xffff); |
| } |
| return 0; |
| } |
| |
| // write to device on MDIO bus attached to a PCI device |
| int do_mwrite(int slot, uint64_t addr_reg, uint64_t cmd_reg, |
| uint64_t port, uint64_t dev, uint64_t reg, uint64_t newVal) |
| { |
| uint32_t* areg = slots[slot].map + addr_reg; |
| uint32_t* creg = slots[slot].map + cmd_reg; |
| uint32_t value = newVal; |
| |
| if (wait_for_bits(creg, CMD_BUSY, 0, 1000, 100) < 0) { |
| return -1; |
| } |
| |
| // remote register we want to read |
| *areg = reg; |
| |
| // issue command |
| *creg = (CMD_ADDR_THEN_WRITE << 26) | (dev << 21) | (port << 16) | |
| (value & 0xffff); |
| |
| if (wait_for_bits(creg, CMD_BUSY, 0, 1000, 100) < 0) { |
| return -1; |
| } |
| |
| // get result |
| value = *creg; |
| |
| printf("%" PRIx64 ".%" PRIx64 ".%04" PRIx64 ": %04x %04x\n", |
| port, dev, reg, value >> 16, value & 0xffff); |
| return 0; |
| } |
| |
| int cmd_open(int ac, char* av[]) |
| { |
| char* usage = "open slot file offset length"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 5) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| char* path = av[2]; |
| uint64_t offset; |
| uint64_t len; |
| |
| if (asSlot(&slot, av[1], 0) < 0 || |
| asUnsigned(&offset, av[3]) < 0 || |
| asUnsigned(&len, av[4]) < 0) { |
| return -1; |
| } |
| if (do_open(path, slot, offset, len) < 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| int cmd_close(int ac, char* av[]) |
| { |
| char* usage = "close slot"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 2) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| |
| if (asSlot(&slot, av[1], 1) < 0) { |
| return -1; |
| } |
| do_close(slot); |
| return 0; |
| } |
| |
| int cmd_read(int ac, char* av[]) |
| { |
| char* usage = "read slot addr wordlen"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 4) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| uint64_t addr; |
| int wordlen; |
| |
| if (asSlot(&slot, av[1], 1) < 0 || |
| asAddr(&addr, av[2], slot) < 0 || |
| asWordLen(&wordlen, av[3]) < 0) { |
| return -1; |
| } |
| if (do_read(slot, addr, wordlen) < 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| int cmd_write(int ac, char* av[]) |
| { |
| char* usage = "write slot addr wordlen value"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 5) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| uint64_t addr; |
| int wordlen; |
| uint64_t value; |
| |
| if (asSlot(&slot, av[1], 1) < 0 || |
| asAddr(&addr, av[2], slot) < 0 || |
| asWordLen(&wordlen, av[3]) < 0 || |
| asUnsigned(&value, av[4]) < 0) { |
| return -1; |
| } |
| do_write(slot, addr, wordlen, value); |
| return 0; |
| } |
| |
| int cmd_dump(int ac, char* av[]) |
| { |
| char* usage = "dump slot addr wordlen count"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 5) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| uint64_t addr; |
| int wordlen; |
| uint64_t count; |
| |
| if (asSlot(&slot, av[1], 1) < 0 || |
| asUnsigned(&addr, av[2]) < 0 || |
| asWordLen(&wordlen, av[3]) < 0 || |
| asUnsigned(&count, av[4]) < 0) { |
| return -1; |
| } |
| if (do_hexdump(slot, addr, wordlen, count) < 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| int cmd_msleep(int ac, char* av[]) |
| { |
| char* usage = "msleep msecs"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 2) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| uint64_t msecs; |
| |
| if (asUnsigned(&msecs, av[1]) < 0) { |
| return -1; |
| } |
| usleep(msecs*1000); |
| return 0; |
| } |
| |
| int cmd_echo(int ac, char* av[]) |
| { |
| char* usage = "echo text ..."; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| |
| for (int i = 1; i < ac; i++) { |
| printf("%s ", av[i]); |
| } |
| printf("\n"); |
| return 0; |
| } |
| |
| // read registers on mdio devices via PCI indirection |
| int cmd_mread(int ac, char* av[]) |
| { |
| char* usage = "mread slot addr_reg cmd_reg port dev reg count"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 8) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| uint64_t addr_reg; |
| uint64_t cmd_reg; |
| uint64_t port; |
| uint64_t dev; |
| uint64_t reg; |
| uint64_t count; |
| |
| if (asSlot(&slot, av[1], 1) < 0 || |
| asAddr(&addr_reg, av[2], slot) < 0 || |
| asAddr(&cmd_reg, av[3], slot) < 0 || |
| asUnsigned(&port, av[4]) < 0 || |
| asUnsigned(&dev, av[5]) < 0 || |
| asUnsigned(®, av[6]) < 0 || |
| asUnsigned(&count, av[7]) < 0) { |
| return -1; |
| } |
| if (do_mread(slot, addr_reg, cmd_reg, port, dev, reg, count) < 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| // write registers on mdio devices via PCI indirection |
| int cmd_mwrite(int ac, char* av[]) |
| { |
| char* usage = "mwrite slot addr_reg cmd_reg port dev reg value"; |
| |
| if (ac > 1 && strcmp(av[1], "help") == 0) { |
| printf("\t%s\n", usage); |
| return 0; |
| } |
| if (ac != 8) { |
| fprintf(stderr, "Usage: %s\n", usage); |
| return -1; |
| } |
| |
| int slot; |
| uint64_t addr_reg; |
| uint64_t cmd_reg; |
| uint64_t port; |
| uint64_t dev; |
| uint64_t reg; |
| uint64_t value; |
| |
| if (asSlot(&slot, av[1], 1) < 0 || |
| asAddr(&addr_reg, av[2], slot) < 0 || |
| asAddr(&cmd_reg, av[3], slot) < 0 || |
| asUnsigned(&port, av[4]) < 0 || |
| asUnsigned(&dev, av[5]) < 0 || |
| asUnsigned(®, av[6]) < 0 || |
| asUnsigned(&value, av[7]) < 0) { |
| return -1; |
| } |
| if (do_mwrite(slot, addr_reg, cmd_reg, port, dev, reg, value) < 0) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| typedef int (*cmdFunc)(int ac, char* av[]); |
| |
| struct commands { |
| char* name; |
| cmdFunc func; |
| }; |
| |
| struct commands cmds[] = { |
| { "open", cmd_open, }, |
| { "close", cmd_close, }, |
| { "read", cmd_read, }, |
| { "write", cmd_write, }, |
| { "dump", cmd_dump, }, |
| { "msleep", cmd_msleep, }, |
| { "echo", cmd_echo, }, |
| { "mread", cmd_mread, }, |
| { "mwrite", cmd_mwrite, }, |
| { NULL, NULL }, |
| }; |
| |
| int processFile(const char* file, int quiet) |
| { |
| const char* fpName; |
| FILE* fp; |
| char* delim= " \t\n"; |
| |
| if (file == NULL || strcmp(file, "-") == 0) { |
| fpName = "stdin"; |
| fp = stdin; |
| } else { |
| fpName = file; |
| fp = fopen(fpName, "r"); |
| if (fp == NULL) { |
| perror(fpName); |
| return -1; |
| } |
| } |
| |
| int isTTY = isatty(fileno(fp)); |
| |
| int lineno = 0; |
| int err = 0; |
| for (;;) { |
| char line[1024] = { 0 }; |
| if (isTTY) { |
| printf("mmap>> "); |
| } |
| if (fgets(line, sizeof line, fp) == NULL) { |
| break; // EOF |
| } |
| |
| lineno++; |
| char fileLine[128]; |
| sprintf(fileLine, "%s:%d: ", fpName, lineno); |
| posPrefix = fileLine; |
| |
| int len = strlen(line); |
| if (len == 0) { |
| continue; |
| } |
| if (line[len-1] != '\n') { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "line too long\n"); |
| err++; |
| continue; |
| } |
| if (line[0] == '#') { |
| continue; // skip comments |
| } |
| |
| if (!quiet) { |
| printf("# %s", line); |
| } |
| |
| int ac = 0; |
| char* av[MAXARGS]; |
| char** avP = av; |
| |
| *avP = strtok(line, delim); |
| while (*avP != NULL) { |
| if (ac >= MAXARGS) { |
| break; |
| } |
| ac++; |
| avP++; |
| *avP = strtok(NULL, delim); |
| } |
| if (ac > MAXARGS) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "too many arguments\n"); |
| err++; |
| continue; |
| } |
| if (ac == 0) { |
| continue; // skip whitespace only |
| } |
| |
| if (strcmp(av[0], "help") == 0) { |
| char* args[] = { "", "help" }; |
| for (struct commands* cp = cmds; cp->func != NULL; cp++) { |
| cp->func(2, args); |
| } |
| continue; |
| } |
| |
| int match = 0; |
| for (struct commands* cp = cmds; cp->func != NULL; cp++) { |
| if (strcmp(av[0], cp->name) == 0) { |
| if (cp->func(ac, av) < 0) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "command '%s' failed\n", av[0]); |
| err++; |
| } |
| match = 1; |
| break; |
| } |
| } |
| if (!match) { |
| fprintf(stderr, "%s", posPrefix); |
| fprintf(stderr, "unknown command '%s', try help\n", av[0]); |
| err++; |
| continue; |
| } |
| } |
| return err ? -1 : 0; |
| } |
| |
| int main(int argc, char* argv[]) |
| { |
| int err = 0; |
| int quiet = 0; |
| |
| int opt; |
| while ((opt = getopt(argc, argv, "q")) != -1) { |
| switch (opt) { |
| case 'q': |
| quiet = 1; |
| break; |
| default: |
| usage(argv[0]); |
| exit(1); |
| } |
| } |
| |
| setbuf(stdout, NULL); // unbuffered |
| |
| if (optind == argc) { |
| if (processFile(NULL, quiet) < 0) { |
| err++; |
| } |
| } else { |
| for (int i = optind; i < argc; i++) { |
| if (processFile(argv[i], quiet) < 0) { |
| err++; |
| } |
| } |
| } |
| exit(err ? 1 : 0); |
| } |