aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorClemens Ladisch <clemens@ladisch.de>2010-08-07 22:42:51 +0200
committerClemens Ladisch <clemens@ladisch.de>2012-05-18 13:40:55 +0200
commit58540b8c1e2e1129980a4945abafff48fc8e6a2c (patch)
tree744035665e1793c1a10739561611a752d7c5d627
parentb4617b712d3046b8bbbce5c1bc535263f277daec (diff)
downloadlinux-firewire-utils-58540b8c1e2e1129980a4945abafff48fc8e6a2c.tar.gz
add firewire-request
-rw-r--r--.gitignore6
-rw-r--r--README.in3
-rw-r--r--TODO1
-rw-r--r--configure.ac18
-rw-r--r--src/Makefile.am3
-rw-r--r--src/firewire-request.8.in140
-rw-r--r--src/firewire-request.c768
-rw-r--r--src/lsfirewire.8.in2
8 files changed, 938 insertions, 3 deletions
diff --git a/.gitignore b/.gitignore
index 89e27d6..cff82f7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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
diff --git a/README.in b/README.in
index 8dfa67a..4ecf701 100644
--- a/README.in
+++ b/README.in
@@ -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
diff --git a/TODO b/TODO
index c71825e..02c5b26 100644
--- a/TODO
+++ b/TODO
@@ -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)