diff options
author | Clemens Ladisch <clemens@ladisch.de> | 2010-08-07 22:42:51 +0200 |
---|---|---|
committer | Clemens Ladisch <clemens@ladisch.de> | 2012-05-18 13:40:55 +0200 |
commit | 58540b8c1e2e1129980a4945abafff48fc8e6a2c (patch) | |
tree | 744035665e1793c1a10739561611a752d7c5d627 | |
parent | b4617b712d3046b8bbbce5c1bc535263f277daec (diff) | |
download | linux-firewire-utils-58540b8c1e2e1129980a4945abafff48fc8e6a2c.tar.gz |
add firewire-request
-rw-r--r-- | .gitignore | 6 | ||||
-rw-r--r-- | README.in | 3 | ||||
-rw-r--r-- | TODO | 1 | ||||
-rw-r--r-- | configure.ac | 18 | ||||
-rw-r--r-- | src/Makefile.am | 3 | ||||
-rw-r--r-- | src/firewire-request.8.in | 140 | ||||
-rw-r--r-- | src/firewire-request.c | 768 | ||||
-rw-r--r-- | src/lsfirewire.8.in | 2 |
8 files changed, 938 insertions, 3 deletions
@@ -3,6 +3,7 @@ aclocal.m4 autom4te.cache # generated by automake Makefile.in +depcomp install-sh missing # generated by autoconf @@ -14,5 +15,10 @@ config.log config.status src/lsfirewire src/lsfirewire.8 +src/firewire-request.8 +# generated by make +.deps +*.o +src/firewire-request # generated by make dist jujuutils-*.tar.gz @@ -5,7 +5,8 @@ What's this? ------------ The @PACKAGE_NAME@ package contains Linux FireWire utilities for -listing devices (lsfirewire). +listing devices (lsfirewire) and for querying and configuring devices +(firewire-request). Installation @@ -1,6 +1,5 @@ tools for: * printing config ROM (crpp) -* making fw requests (like fwhack, but more user-friendly like setpci) * bus manager: - initialize BUS_TIME - set PRIORITY_BUDGET diff --git a/configure.ac b/configure.ac index cc38cf6..159c9f1 100644 --- a/configure.ac +++ b/configure.ac @@ -7,10 +7,28 @@ AM_INIT_AUTOMAKE([foreign no-define silent-rules]) AS_IF([test "${enable_silent_rules+set}" != set], [AM_DEFAULT_VERBOSITY=0]) +AC_PROG_CC + +AC_CHECK_HEADERS_ONCE([ +asm/byteorder.h +linux/firewire-cdev.h +linux/firewire-constants.h +]) + +AS_IF([test "$ac_cv_header_asm_byteorder_h" != yes], + [AC_MSG_ERROR([Linux kernel headers not found])]) + +JUJU=1 +test "$ac_cv_header_linux_firewire_cdev_h" = yes || JUJU= +test "$ac_cv_header_linux_firewire_constants_h" = yes || JUJU= +AS_IF([test -z "$JUJU"], + [AC_MSG_ERROR([required Linux FireWire headers not found])]) + AC_OUTPUT([ README Makefile src/Makefile src/lsfirewire src/lsfirewire.8 +src/firewire-request.8 ]) diff --git a/src/Makefile.am b/src/Makefile.am index c126a26..c411faf 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -1,2 +1,3 @@ bin_SCRIPTS = lsfirewire -man_MANS = lsfirewire.8 +bin_PROGRAMS = firewire-request +man_MANS = lsfirewire.8 firewire-request.8 diff --git a/src/firewire-request.8.in b/src/firewire-request.8.in new file mode 100644 index 0000000..6fc551c --- /dev/null +++ b/src/firewire-request.8.in @@ -0,0 +1,140 @@ +.TH firewire\-request 8 "7 Aug 2010" "@PACKAGE_STRING@" +.IX firewire\-request +.SH NAME +firewire\-request \- query and configure FireWire devices +.SH SYNOPSIS +.B firewire\-request +.RI [ options ] +.I device +.I command +.RI [ parameters ] +.SH DESCRIPTION +.B firewire\-request +is a utility to query and configure FireWire devices. +.PP +The +.I device +parameter specifies the device file +.RB ( /dev/fw *) +of the device that is to be accessed. +.PP +All numbers must be specified in hexadecimal notation. +. +When specifying data blocks, you can separate bytes or quadlets with spaces, +but then you have to remember to quote them in the shell +so that all bytes are recognized as a single parameter. +.PP +In the following commands, +.I address +is either a 48-bit address or a register name. +.PP +The following commands are available: +.TP +\fBfirewire\-request\fP \fIdevice\fP \fBread\fP \fIaddress\fP [\fIlength\fP] +Sends a read request to the device +and prints the value returned by the device, if successful. +.IP +If +.I length +is not specified, it is derived from +.IR address : +for a named register, the register's length is used; +for a numerical address, a default length of one quadlet (4\~bytes) is used. +.TP +\fBfirewire\-request\fP \fIdevice\fP \fBwrite\fP|\fBbroadcast\fP \fIaddress\fP \fIdata\fP +Sends a write request to the device. +.IP +Broadcasts are allowed only for a +.I device +that corresponds to a local controller, +and are sent to all the other devices on the bus. +.TP +\fBfirewire\-request\fP \fIdevice locktype address data \fP[\fIdata2\fP] +Executes a lock transaction (an atomic change) on the device. +.IP +.I locktype +can be any of the following: +.RS +.TP +.B mask_swap +Changes the bits set in +.I data +to the value in +.IR data2 . +.TP +.B compare_swap +If the register has the same value as +.IR data , +set it to +.IR data2 . +.TP +.BR add [ _big ] +Increase the register by +.IR data . +.TP +.B add_little +Increase the little-endian register by +.IR data . +.TP +.BR bounded_add [ _big ] +If the register has not the the same values as +.IR data , +increase it by +.IR data2 . +.TP +.BR wrap_add [ _big ] +If the register has not the the same values as +.IR data , +increase it by +.IR data2 , +else set it to +.IR data2 . +.RE +.IP +All transaction types except +.BR add / add_big / add_little +require two parameters. +.IP +.B firewire\-request +will print the value returned by the device, +which is the old register value at the beginning of the transaction. +.TP +\fBfirewire\-request\fP \fIdevice\fP \fBfcp\fP \fIdata\fP +Sends the message +.I data +to the device's FCP command register, +and prints the response returned by the device +to the local FCP response register. +.TP +\fBfirewire\-request\fP \fIdevice\fP \fBreset\fP|\fBlong_reset\fP +Issues a bus reset on the bus connected to +.IR device . +.SH OPTIONS +.TP +.B \-D, \-\-dump\-register\-names +Print register names that can be used as +.IR address , +and exit. +.TP +.B \-v, \-\-verbose +When used together with +.BR \-\-dump\-register\-names , +print the complete list of register names. +.TP +.B \-h, \-\-help +Print a summary of the command-line options and exit. +.TP +.B \-V, \-\-version +Print the version number of +.B firewire\-request +on the standard output and exit. +.SH NOTES +Whether you can access a device depends on the permissions set for its device file. +. +Usually, most devices require root privileges. +.SH BUGS +Report bugs to <@PACKAGE_BUGREPORT@>. +.br +@PACKAGE_NAME@ home page: <@PACKAGE_URL@>. +.SH SEE ALSO +.BR lsfirewire (8) diff --git a/src/firewire-request.c b/src/firewire-request.c new file mode 100644 index 0000000..4999105 --- /dev/null +++ b/src/firewire-request.c @@ -0,0 +1,768 @@ +/* + * firewire-request.c - send requests to FireWire devices + * + * Copyright 2010 Clemens Ladisch <clemens@ladisch.de> + * + * licensed under the terms of version 2 of the GNU General Public License + */ + +#define _GNU_SOURCE +#include <stdio.h> +#include <stdlib.h> +#include <stdbool.h> +#include <string.h> +#include <ctype.h> +#include <dirent.h> +#include <getopt.h> +#include <unistd.h> +#include <poll.h> +#include <fcntl.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <linux/firewire-cdev.h> +#include <linux/firewire-constants.h> +#include <asm/byteorder.h> + +#define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a)) + +typedef __u8 u8; +typedef __u32 u32; +typedef __u64 u64; + +typedef void (*command_func)(void); + +struct data { + unsigned int length; + u8 *data; +}; + +static bool verbose; +static const char *device_name; +static u64 address; +static unsigned int register_length; +static unsigned int read_length; +static struct data data; +static struct data data2; +static int fd; +static u32 generation; + +static void open_device(void) +{ + struct fw_cdev_get_info get_info; + struct fw_cdev_event_bus_reset bus_reset; + + fd = open(device_name, O_RDWR); + if (fd == -1) { + perror(device_name); + exit(EXIT_FAILURE); + } + + get_info.version = 2; + get_info.rom_length = 0; + get_info.rom = 0; + get_info.bus_reset = (u64)&bus_reset; + get_info.bus_reset_closure = 0; + if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) { + perror("GET_INFO ioctl failed"); + exit(EXIT_FAILURE); + } + generation = bus_reset.generation; +} + +static struct fw_cdev_event_response *wait_for_response(void) +{ + static u8 buf[sizeof(struct fw_cdev_event_response) + 16384]; + struct pollfd pfd; + int ready, r; + struct fw_cdev_event_common *event; + struct fw_cdev_event_bus_reset *event_bus_reset; + + pfd.fd = fd; + pfd.events = POLLIN; + for (;;) { + ready = poll(&pfd, 1, 123); + if (ready < 0) { + perror("poll failed"); + exit(EXIT_FAILURE); + } + if (!ready) { + fputs("timeout (no ack)\n", stderr); + exit(EXIT_FAILURE); + } + r = read(fd, buf, sizeof buf); + if (r < sizeof(struct fw_cdev_event_common)) { + fputs("short read\n", stderr); + exit(EXIT_FAILURE); + } + event = (void *)buf; + if (event->type == FW_CDEV_EVENT_RESPONSE) + return (struct fw_cdev_event_response *)buf; + if (event->type == FW_CDEV_EVENT_BUS_RESET) { + event_bus_reset = (void *)buf; + generation = event_bus_reset->generation; + } + } +} + +static void print_rcode(u32 rcode) +{ + const char *s; + + switch (rcode) { + case RCODE_CONFLICT_ERROR: s = "conflict error"; break; + case RCODE_DATA_ERROR: s = "data error"; break; + case RCODE_TYPE_ERROR: s = "type error"; break; + case RCODE_ADDRESS_ERROR: s = "address error"; break; + case RCODE_SEND_ERROR: s = "send error"; break; + case RCODE_CANCELLED: s = "error: cancelled"; break; + case RCODE_BUSY: s = "error: busy"; break; + case RCODE_GENERATION: s = "error: bus reset"; break; + case RCODE_NO_ACK: s = "error: no ack"; break; + default: s = "unknown error"; break; + } + fprintf(stderr, "%s\n", s); +} + +static void print_data(const char *prefix, const void *p, unsigned int length, bool allow_value) +{ + const u8 *data = p; + unsigned int line, col; + + if (allow_value) { + const u32 *data = p; + if (length == 4) { + printf("%s%08x\n", prefix, __be32_to_cpu(*data)); + return; + } + if (length == 8) { + printf("%s%08x%08x\n", prefix, __be32_to_cpu(data[0]), __be32_to_cpu(data[1])); + return; + } + } + + for (line = 0; line < ((length + 15) & ~15); line += 16) { + printf("%s%03x:", prefix, line); + for (col = line; col < line + 16 && col < length; ++col) + printf(" %02x", data[col]); + printf("%*s", 1 + (line + 16 - col) * 3, ""); + for (col = line; col < line + 16 && col < length; ++col) + if (data[col] >= 32 && data[col] < 127) + putchar(data[col]); + else + putchar('.'); + putchar('\n'); + } +} + +static void do_read(void) +{ + struct fw_cdev_send_request send_request; + struct fw_cdev_event_response *response; + + do { + if (read_length == 4 && !(address & 3)) + send_request.tcode = TCODE_READ_QUADLET_REQUEST; + else + send_request.tcode = TCODE_READ_BLOCK_REQUEST; + send_request.length = read_length; + send_request.offset = address; + send_request.closure = 0; + send_request.data = 0; + send_request.generation = generation; + if (ioctl(fd, FW_CDEV_IOC_SEND_REQUEST, &send_request) < 0) { + perror("SEND_REQUEST ioctl failed"); + exit(EXIT_FAILURE); + } + response = wait_for_response(); + } while (response->rcode == RCODE_GENERATION); + if (response->rcode != RCODE_COMPLETE) + print_rcode(response->rcode); + else + print_data("result: ", response->data, response->length, response->length == read_length); +} + +static void do_write_request(int request) +{ + struct fw_cdev_send_request send_request; + struct fw_cdev_event_response *response; + + do { + if (data.length == 4 && !(address & 3)) + send_request.tcode = TCODE_WRITE_QUADLET_REQUEST; + else + send_request.tcode = TCODE_WRITE_BLOCK_REQUEST; + send_request.length = data.length; + send_request.offset = address; + send_request.closure = 0; + send_request.data = (u64)data.data; + send_request.generation = generation; + if (ioctl(fd, request, &send_request) < 0) { + perror("SEND_REQUEST ioctl failed"); + exit(EXIT_FAILURE); + } + response = wait_for_response(); + } while (response->rcode == RCODE_GENERATION); + if (response->rcode != RCODE_COMPLETE) + print_rcode(response->rcode); +} + +static void do_write(void) +{ + do_write_request(FW_CDEV_IOC_SEND_REQUEST); +} + +static void do_broadcast(void) +{ + do_write_request(FW_CDEV_IOC_SEND_BROADCAST_REQUEST); +} + +static void do_lock_request(u32 tcode) +{ + bool has_data2; + u8 *buf; + struct fw_cdev_send_request send_request; + struct fw_cdev_event_response *response; + + has_data2 = tcode != TCODE_LOCK_FETCH_ADD && tcode != TCODE_LOCK_LITTLE_ADD; + if ((data.length != 4 && data.length != 8) || + (has_data2 && (data2.length != 4 && data2.length != 8))) { + fputs("data size must be 32 or 64 bits\n", stderr); + exit(EXIT_FAILURE); + } + if (has_data2 && data.length != data2.length) { + fputs("both data blocks must have the same size\n", stderr); + exit(EXIT_FAILURE); + } + if (has_data2) { + buf = malloc(data.length * 2); + if (!buf) { + fputs("out of memory\n", stderr); + exit(EXIT_FAILURE); + } + memcpy(buf, data.data, data.length); + memcpy(buf + data.length, data2.data, data2.length); + } else + buf = data.data; + do { + send_request.tcode = tcode;; + send_request.length = has_data2 ? data.length * 2 : data.length; + send_request.offset = address; + send_request.closure = 0; + send_request.data = (u64)buf; + send_request.generation = generation; + if (ioctl(fd, FW_CDEV_IOC_SEND_REQUEST, &send_request) < 0) { + perror("SEND_REQUEST ioctl failed"); + exit(EXIT_FAILURE); + } + response = wait_for_response(); + } while (response->rcode == RCODE_GENERATION); + if (response->rcode != RCODE_COMPLETE) + print_rcode(response->rcode); + else + print_data("old: ", response->data, response->length, true); +} + +static void do_mask_swap(void) +{ + do_lock_request(TCODE_LOCK_MASK_SWAP); +} + +static void do_compare_swap(void) +{ + do_lock_request(TCODE_LOCK_COMPARE_SWAP); +} + +static void do_add_big(void) +{ + do_lock_request(TCODE_LOCK_FETCH_ADD); +} + +static void do_add_little(void) +{ + do_lock_request(TCODE_LOCK_LITTLE_ADD); +} + +static void do_bounded_add(void) +{ + do_lock_request(TCODE_LOCK_BOUNDED_ADD); +} + +static void do_wrap_add(void) +{ + do_lock_request(TCODE_LOCK_WRAP_ADD); +} + +static void send_response(u32 handle, u32 rcode) +{ + struct fw_cdev_send_response send_response; + + send_response.rcode = rcode; + send_response.length = 0; + send_response.data = 0; + send_response.handle = handle; + if (ioctl(fd, FW_CDEV_IOC_SEND_RESPONSE, &send_response) < 0) { + perror("SEND_RESPONSE ioctl failed"); + exit(EXIT_FAILURE); + } +} + +static void do_fcp(void) +{ + static u8 buf[sizeof(struct fw_cdev_event_response) + 0x200]; + struct fw_cdev_allocate allocate; + struct fw_cdev_send_request send_request; + bool ack_received, response_received; + struct pollfd pfd; + int ready, r; + struct fw_cdev_event_common *event; + + allocate.offset = 0xfffff0000d00uLL; /* FCP response */ + allocate.closure = 0; + allocate.length = 0x200; + if (ioctl(fd, FW_CDEV_IOC_ALLOCATE, &allocate) < 0) { + perror("ALLOCATE ioctl failed"); + exit(EXIT_FAILURE); + } + + send_request.tcode = data.length == 4 ? TCODE_WRITE_QUADLET_REQUEST : TCODE_WRITE_BLOCK_REQUEST; + send_request.length = data.length; + send_request.offset = 0xfffff0000b00uLL; /* FCP command */ + send_request.closure = 0; + send_request.data = (u64)data.data; + send_request.generation = generation; + if (ioctl(fd, FW_CDEV_IOC_SEND_REQUEST, &send_request) < 0) { + perror("SEND_REQUEST ioctl failed"); + exit(EXIT_FAILURE); + } + + ack_received = false; + response_received = false; + pfd.fd = fd; + pfd.events = POLLIN; + while (!ack_received && !response_received) { + ready = poll(&pfd, 1, 123); + if (ready < 0) { + perror("poll failed"); + exit(EXIT_FAILURE); + } + if (!ready) { + fprintf(stderr, "timeout (%s)\n", !ack_received ? "no ack" : "no response"); + exit(EXIT_FAILURE); + } + r = read(fd, buf, sizeof buf); + if (r < sizeof(struct fw_cdev_event_common)) { + fputs("short read\n", stderr); + exit(EXIT_FAILURE); + } + event = (void *)buf; + if (event->type == FW_CDEV_EVENT_BUS_RESET) { + fputs("bus reset\n", stderr); + exit(EXIT_FAILURE); + } + if (event->type == FW_CDEV_EVENT_RESPONSE) { + struct fw_cdev_event_response *response = (void *)buf; + if (response->rcode != RCODE_COMPLETE) { + print_rcode(response->rcode); + return; + } + ack_received = true; + } else if (event->type == FW_CDEV_EVENT_REQUEST) { + struct fw_cdev_event_request *request = (void *)buf; + send_response(request->handle, RCODE_COMPLETE); + print_data("response: ", request->data, request->length, false); + } + } +} + +static void do_bus_reset(u32 type) +{ + struct fw_cdev_initiate_bus_reset reset; + + reset.type = type; + if (ioctl(fd, FW_CDEV_IOC_INITIATE_BUS_RESET, &reset) < 0) { + perror("INITIATE_BUS_RESET ioctl failed"); + exit(EXIT_FAILURE); + } +} + +static void do_reset(void) +{ + do_bus_reset(FW_CDEV_SHORT_RESET); +} + +static void do_long_reset(void) +{ + do_bus_reset(FW_CDEV_LONG_RESET); +} + +static const struct command { + const char *name; + command_func function; + bool has_addr; + bool has_length; + bool has_data; + bool has_data2; +} commands[] = { + { "read", do_read, .has_addr = true, .has_length = true }, + { "write", do_write, .has_addr = true, .has_data = true }, + { "broadcast", do_broadcast, .has_addr = true, .has_data = true }, + { "mask_swap", do_mask_swap, .has_addr = true, .has_data = true, .has_data2 = true }, + { "compare_swap", do_compare_swap, .has_addr = true, .has_data = true, .has_data2 = true }, + { "add", do_add_big, .has_addr = true, .has_data = true }, + { "add_big", do_add_big, .has_addr = true, .has_data = true }, + { "add_little", do_add_little, .has_addr = true, .has_data = true }, + { "bounded_add", do_bounded_add, .has_addr = true, .has_data = true, .has_data2 = true }, + { "bounded_add_big", do_bounded_add, .has_addr = true, .has_data = true, .has_data2 = true }, + { "wrap_add", do_wrap_add, .has_addr = true, .has_data = true, .has_data2 = true }, + { "wrap_add_big", do_wrap_add, .has_addr = true, .has_data = true, .has_data2 = true }, + { "fcp", do_fcp, .has_data = true }, + { "reset", do_reset }, + { "long_reset", do_long_reset }, +}; + +static const struct register_name { + u64 address; + unsigned int size; + const char *name; + bool hide; +#define HIDDEN true +} register_names[] = { + { 0xfffff0000000, 4, "state_clear" }, + { 0xfffff0000004, 4, "state_set" }, + { 0xfffff0000008, 4, "node_ids" }, + { 0xfffff000000c, 4, "reset_start" }, + { 0xfffff0000018, 8, "split_timeout" }, + { 0xfffff0000018, 4, "split_timeout_hi" }, + { 0xfffff000001c, 4, "split_timeout_lo" }, + { 0xfffff0000020, 8, "argument", HIDDEN }, + { 0xfffff0000020, 4, "argument_hi", HIDDEN }, + { 0xfffff0000024, 4, "argument_lo", HIDDEN }, + { 0xfffff0000028, 4, "test_start", HIDDEN }, + { 0xfffff000002c, 4, "test_status", HIDDEN }, + { 0xfffff0000050, 4, "interrupt_target", HIDDEN }, + { 0xfffff0000054, 4, "interrupt_mask", HIDDEN }, + { 0xfffff0000080, 64, "message_request" }, + { 0xfffff00000c0, 64, "message_response" }, + { 0xfffff0000180, 128, "error_log_buffer", HIDDEN }, + { 0xfffff0000200, 4, "cycle_time" }, + { 0xfffff0000204, 4, "bus_time" }, + { 0xfffff0000208, 4, "power_fail_imminent", HIDDEN }, + { 0xfffff000020c, 4, "power_source", HIDDEN }, + { 0xfffff0000210, 4, "busy_timeout" }, + { 0xfffff0000214, 4, "quarantine", HIDDEN }, + { 0xfffff0000218, 4, "priority_budget" }, + { 0xfffff000021c, 4, "bus_manager_id" }, + { 0xfffff0000220, 4, "bandwidth_available" }, + { 0xfffff0000224, 8, "channels_available" }, + { 0xfffff0000224, 4, "channels_available_hi" }, + { 0xfffff0000228, 4, "channels_available_lo" }, + { 0xfffff000022c, 4, "maint_control", HIDDEN }, + { 0xfffff0000230, 4, "maint_utility" }, + { 0xfffff0000234, 4, "broadcast_channel" }, + { 0xfffff0000400, 0x400, "config_rom" }, + { 0xfffff0000900, 4, "output_master_plug" }, + { 0xfffff0000904, 4, "output_plug0" }, + { 0xfffff0000908, 4, "output_plug1", HIDDEN }, + { 0xfffff000090c, 4, "output_plug2", HIDDEN }, + { 0xfffff0000910, 4, "output_plug3", HIDDEN }, + { 0xfffff0000914, 4, "output_plug4", HIDDEN }, + { 0xfffff0000918, 4, "output_plug5", HIDDEN }, + { 0xfffff000091c, 4, "output_plug6", HIDDEN }, + { 0xfffff0000920, 4, "output_plug7", HIDDEN }, + { 0xfffff0000924, 4, "output_plug8", HIDDEN }, + { 0xfffff0000928, 4, "output_plug9", HIDDEN }, + { 0xfffff000092c, 4, "output_plug10", HIDDEN }, + { 0xfffff0000930, 4, "output_plug11", HIDDEN }, + { 0xfffff0000934, 4, "output_plug12", HIDDEN }, + { 0xfffff0000938, 4, "output_plug13", HIDDEN }, + { 0xfffff000093c, 4, "output_plug14", HIDDEN }, + { 0xfffff0000940, 4, "output_plug15", HIDDEN }, + { 0xfffff0000944, 4, "output_plug16", HIDDEN }, + { 0xfffff0000948, 4, "output_plug17", HIDDEN }, + { 0xfffff000094c, 4, "output_plug18", HIDDEN }, + { 0xfffff0000950, 4, "output_plug19", HIDDEN }, + { 0xfffff0000954, 4, "output_plug20", HIDDEN }, + { 0xfffff0000958, 4, "output_plug21", HIDDEN }, + { 0xfffff000095c, 4, "output_plug22", HIDDEN }, + { 0xfffff0000960, 4, "output_plug23", HIDDEN }, + { 0xfffff0000964, 4, "output_plug24", HIDDEN }, + { 0xfffff0000968, 4, "output_plug25", HIDDEN }, + { 0xfffff000096c, 4, "output_plug26", HIDDEN }, + { 0xfffff0000970, 4, "output_plug27", HIDDEN }, + { 0xfffff0000974, 4, "output_plug28", HIDDEN }, + { 0xfffff0000978, 4, "output_plug29", HIDDEN }, + { 0xfffff000097c, 4, "output_plug30" }, + { 0xfffff0000980, 4, "input_master_plug" }, + { 0xfffff0000984, 4, "input_plug0" }, + { 0xfffff0000988, 4, "input_plug1", HIDDEN }, + { 0xfffff000098c, 4, "input_plug2", HIDDEN }, + { 0xfffff0000990, 4, "input_plug3", HIDDEN }, + { 0xfffff0000994, 4, "input_plug4", HIDDEN }, + { 0xfffff0000998, 4, "input_plug5", HIDDEN }, + { 0xfffff000099c, 4, "input_plug6", HIDDEN }, + { 0xfffff00009a0, 4, "input_plug7", HIDDEN }, + { 0xfffff00009a4, 4, "input_plug8", HIDDEN }, + { 0xfffff00009a8, 4, "input_plug9", HIDDEN }, + { 0xfffff00009ac, 4, "input_plug10", HIDDEN }, + { 0xfffff00009b0, 4, "input_plug11", HIDDEN }, + { 0xfffff00009b4, 4, "input_plug12", HIDDEN }, + { 0xfffff00009b8, 4, "input_plug13", HIDDEN }, + { 0xfffff00009bc, 4, "input_plug14", HIDDEN }, + { 0xfffff00009c0, 4, "input_plug15", HIDDEN }, + { 0xfffff00009c4, 4, "input_plug16", HIDDEN }, + { 0xfffff00009c8, 4, "input_plug17", HIDDEN }, + { 0xfffff00009cc, 4, "input_plug18", HIDDEN }, + { 0xfffff00009d0, 4, "input_plug19", HIDDEN }, + { 0xfffff00009d4, 4, "input_plug20", HIDDEN }, + { 0xfffff00009d8, 4, "input_plug21", HIDDEN }, + { 0xfffff00009dc, 4, "input_plug22", HIDDEN }, + { 0xfffff00009e0, 4, "input_plug23", HIDDEN }, + { 0xfffff00009e4, 4, "input_plug24", HIDDEN }, + { 0xfffff00009e8, 4, "input_plug25", HIDDEN }, + { 0xfffff00009ec, 4, "input_plug26", HIDDEN }, + { 0xfffff00009f0, 4, "input_plug27", HIDDEN }, + { 0xfffff00009f4, 4, "input_plug28", HIDDEN }, + { 0xfffff00009f8, 4, "input_plug29", HIDDEN }, + { 0xfffff00009fc, 4, "input_plug30" }, + { 0xfffff0000b00, 0x200, "fcp_command" }, + { 0xfffff0000d00, 0x200, "fcp_response" }, + { 0xfffff0001000, 0x400, "topology_map" }, + { 0xfffff0001c00, 0x200, "virtual_id_map", HIDDEN }, + { 0xfffff0001e00, 0x100, "route_map", HIDDEN }, + { 0xfffff0001f00, 8, "clan_eui_64", HIDDEN }, + { 0xfffff0001f08, 4, "clan_info", HIDDEN }, + { 0xfffff0002000, 0x1000, "speed_map", HIDDEN }, +}; + +static void parse_address(const char *s) +{ + char *endptr; + unsigned int i; + + address = strtoull(s, &endptr, 16); + if (*endptr != '\0') { + for (i = 0; i < ARRAY_SIZE(register_names); ++i) + if (!strcasecmp(s, register_names[i].name)) { + address = register_names[i].address; + register_length = register_names[i].size; + return; + } + fprintf(stderr, "invalid address: `%s'\n", s); + exit(EXIT_FAILURE); + } +} + +static void parse_read_length(const char *s) +{ + char *endptr; + unsigned long int l; + + l = strtoul(s, &endptr, 16); + if (*endptr != '\0') { + fprintf(stderr, "invalid length: `%s'\n", s); + exit(EXIT_FAILURE); + } + read_length = l; +} + +static void parse_data(const char *s, struct data *data) +{ + unsigned int len; + bool high_nibble = true; + u8 val; + + while (isspace(*s)) + ++s; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + s += 2; + + len = strlen(s); + data->data = malloc(len / 2 + 1); + if (!data->data) { + fputs("out of memory\n", stderr); + exit(EXIT_FAILURE); + } + + for (len = 0; *s != '\0'; ++s) { + if (isspace(*s) || *s == '_') + continue; + if ('0' <= *s && *s <= '9') + val = *s - '0'; + else if ('A' <= *s && *s <= 'F') + val = *s - 'A' + 10; + else if ('a' <= *s && *s <= 'f') + val = *s - 'a' + 10; + else { + fprintf(stderr, "invalid character in data: `%c'\n", *s); + exit(EXIT_FAILURE); + } + if (high_nibble) { + data->data[len] = val << 4; + high_nibble = false; + } else { + data->data[len++] |= val; + high_nibble = true; + } + } + if (!high_nibble) { + fputs("data ends in a half byte\n", stderr); + exit(EXIT_FAILURE); + } + if (register_length && register_length <= 8 && register_length != len) { + fprintf(stderr, "data for this register must have %u bits\n", register_length * 8); + exit(EXIT_FAILURE); + } + data->length = len; +} + +static void help(void) +{ + fputs("firewire-request <dev> read <addr> [<length>]\n" + "firewire-request <dev> write <addr> <data>\n" + "firewire-request <dev> <locktype> <addr> <data> [<data>]\n" + "firewire-request <dev> broadcast <addr> <data>\n" + "firewire-request <dev> fcp <data>\n" + "firewire-request <dev> reset|long_reset\n" + "\n" + "<dev> is device node (/dev/fwX)\n" + "<addr> is address in hex or register name\n" + "<length> is byte length in hex, default from register or 4\n" + "<data> is data bytes in hex (spaces must be quoted)\n" + "<locktype> is mask_swap|compare_swap|add_big|add_little|bounded_add|wrap_add\n" + "\n" + "Options:\n" + " -D,--dump-register-names show known register names and exit\n" + " -v,--verbose more information\n" + " -h,--help show this message and exit\n" + " -V,--version show version number and exit\n" + "\n" + "Report bugs to <" PACKAGE_BUGREPORT ">.\n" + PACKAGE_NAME " home page: <" PACKAGE_URL ">.\n", + stderr); +} + +static void help_registers(void) +{ + unsigned int i; + + puts("address length name"); + for (i = 0; i < ARRAY_SIZE(register_names); ++i) { + bool hi_lo; + if (register_names[i].hide && !verbose) + continue; + hi_lo = !verbose && i < ARRAY_SIZE(register_names) - 2 && + register_names[i].address == register_names[i + 1].address; + printf("%012Lx %4x %s%s\n", register_names[i].address, + register_names[i].size, register_names[i].name, + hi_lo ? "[_hi|_lo]" : ""); + if (hi_lo) + i += 2; + } +} + +static command_func parse_parameters(int argc, char *argv[]) +{ + static const char short_options[] = "vDh"; + static const struct option long_options[] = { + { "dump-register-names", 0, NULL, 'D' }, + { "verbose", 0, NULL, 'v' }, + { "help", 0, NULL, 'h' }, + { "version", 0, NULL, 'V' }, + {} + }; + int c; + bool show_regs = false, show_help = false, show_version = false; + const struct command *command; + + while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) { + switch (c) { + case 'D': + show_regs = true; + break; + case 'v': + verbose = true; + break; + case 'h': + show_help = true; + break; + case 'V': + show_version = true; + break; + default: + syntax_error: + help(); + exit(EXIT_FAILURE); + } + } + + if (show_regs) + help_registers(); + if (show_help) + help(); + if (show_version) + puts("firewire-request version " PACKAGE_VERSION); + if (show_regs || show_help || show_version) + exit(EXIT_SUCCESS); + + if (optind >= argc) + goto syntax_error; + device_name = argv[optind++]; + + if (optind >= argc) + goto syntax_error; + for (c = 0; c < ARRAY_SIZE(commands); ++c) + if (!strcasecmp(argv[optind], commands[c].name)) { + command = &commands[c]; + goto command_found; + } + fprintf(stderr, "unknown command: `%s'\n", argv[optind]); + goto syntax_error; +command_found: + ++optind; + + if (command->has_addr) { + if (optind >= argc) + goto syntax_error; + parse_address(argv[optind++]); + } + + if (command->has_length) { + if (optind < argc) + parse_read_length(argv[optind++]); + else if (register_length) + read_length = register_length; + else + read_length = 4; + } + + if (command->has_data) { + if (optind >= argc) + goto syntax_error; + parse_data(argv[optind++], &data); + } + + if (command->has_data2) { + if (optind >= argc) + goto syntax_error; + parse_data(argv[optind++], &data2); + } + + if (optind < argc) { + fprintf(stderr, "superfluous parameter: `%s'\n", argv[optind]); + goto syntax_error; + } + + return command->function; +} + +int main(int argc, char *argv[]) +{ + command_func fn; + + fn = parse_parameters(argc, argv); + open_device(); + fn(); + close(fd); + return 0; +} diff --git a/src/lsfirewire.8.in b/src/lsfirewire.8.in index 0f68a13..fd8fd80 100644 --- a/src/lsfirewire.8.in +++ b/src/lsfirewire.8.in @@ -28,3 +28,5 @@ on the standard output and exit. Report bugs to <@PACKAGE_BUGREPORT@>. .br @PACKAGE_NAME@ home page: <@PACKAGE_URL@>. +.SH SEE ALSO +.BR firewire\-request (8) |