blob: 9d04dfd1974c739b91ad3e0967648f9ba98fbb26 [file]
/*
* firewire-phy-command.c - send PHY packets
*
* Copyright 2011 Clemens Ladisch <clemens@ladisch.de>
*
* licensed under the terms of the GNU General Public License, version 2
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <getopt.h>
#include <fcntl.h>
#include <poll.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/firewire-cdev.h>
#include <linux/firewire-constants.h>
#include "config.h"
#ifndef FW_CDEV_IOC_SEND_PHY_PACKET
#error kernel headers too old
#endif
#define ARRAY_SIZE(a) (sizeof(a) / sizeof *(a))
#define ptr_to_u64(p) ((uintptr_t)(p))
typedef __u8 u8;
typedef __u32 u32;
typedef __u64 u64;
struct node {
struct node *next;
char *name;
int fd;
u32 card;
u32 id;
u32 generation;
bool is_local;
};
static struct node *nodes;
static struct node *local_node;
static u32 param_node_id;
static u32 ping_time;
static u32 self_ids[3];
static void help(void)
{
fputs("Usage: firewire-phy-command [options] command [parameters]\n"
"Commands:\n"
" config [root <node>] [gapcount <value>]\n"
" ping <node>\n"
" read <node> [<page> <port>] <register>\n"
" nop|disable|suspend|clear|enable|resume <node> <port>\n"
" resume\n"
" linkon <node>\n"
" reset\n"
"Options:\n"
" -b, --bus=node bus to send packet on\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 int fw_filter(const struct dirent *dirent)
{
unsigned int i;
if (dirent->d_name[0] != 'f' ||
dirent->d_name[1] != 'w')
return false;
i = 2;
do {
if (!isdigit(dirent->d_name[i]))
return false;
} while (dirent->d_name[++i]);
return true;
}
static void open_all_nodes(void)
{
struct dirent **ents;
int count, i;
struct node *node;
struct fw_cdev_get_info get_info;
struct fw_cdev_event_bus_reset bus_reset;
bool eacces = false;
count = scandir("/dev", &ents, fw_filter, versionsort);
if (count < 0) {
perror("cannot read /dev");
exit(EXIT_FAILURE);
}
for (i = count - 1; i >= 0; --i) {
node = malloc(sizeof(*node));
if (!node) {
fputs("out of memory\n", stderr);
exit(EXIT_FAILURE);
}
if (asprintf(&node->name, "/dev/%s", ents[i]->d_name) < 0) {
perror("asprintf failed");
exit(EXIT_FAILURE);
}
node->fd = open(node->name, O_RDWR);
if (node->fd == -1) {
if (errno == EACCES)
eacces = true;
free(node->name);
free(node);
continue;
}
get_info.version = 4;
get_info.rom_length = 0;
get_info.rom = 0;
get_info.bus_reset = ptr_to_u64(&bus_reset);
get_info.bus_reset_closure = 0;
if (ioctl(node->fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) {
close(node->fd);
free(node->name);
free(node);
continue;
}
node->card = get_info.card;
node->id = bus_reset.node_id;
node->generation = bus_reset.generation;
node->is_local = bus_reset.node_id == bus_reset.local_node_id;
node->next = nodes;
nodes = node;
free(ents[i]);
}
free(ents);
if (!nodes) {
if (eacces) {
errno = EACCES;
perror("/dev/fw*");
} else {
fputs("no fw devices found\n", stderr);
}
exit(EXIT_FAILURE);
}
}
static void close_all_nodes(void)
{
struct node *node, *next;
for (node = nodes; node; node = next) {
close(node->fd);
free(node->name);
next = node->next;
free(node);
}
}
static void find_local_node(const char *bus_name)
{
int bus_card;
char *endptr;
int fd;
struct fw_cdev_get_info get_info;
struct node *node;
if (bus_name) {
bus_card = strtol(bus_name, &endptr, 0);
if (!*endptr) {
if (bus_card < 0) {
fputs("invalid bus number\n", stderr);
exit(EXIT_FAILURE);
}
} else {
bus_card = -1;
for (node = nodes; node; node = node->next)
if (!strcmp(node->name, bus_name)) {
bus_card = node->card;
break;
}
if (bus_card < 0) {
fd = open(bus_name, O_RDWR);
if (fd == -1) {
perror(bus_name);
exit(EXIT_FAILURE);
}
get_info.version = 4;
get_info.rom_length = 0;
get_info.rom = 0;
get_info.bus_reset = 0;
get_info.bus_reset_closure = 0;
if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) {
fprintf(stderr, "%s: not a fw device\n", bus_name);
exit(EXIT_FAILURE);
}
close(fd);
bus_card = get_info.card;
}
}
} else {
bus_card = -1;
}
for (node = nodes; node; node = node->next) {
if (node->is_local &&
(bus_card == -1 || node->card == bus_card)) {
local_node = node;
return;
}
}
if (bus_card == -1)
fputs("local node not found\n", stderr);
else
fprintf(stderr, "local node for card %d not found\n", bus_card);
exit(EXIT_FAILURE);
}
static void find_param_node(const char *name)
{
int id;
char *endptr;
struct node *node;
int fd;
struct fw_cdev_get_info get_info;
struct fw_cdev_event_bus_reset bus_reset;
char card_str[16];
id = strtol(name, &endptr, 0);
if (!*endptr) {
if (id < 0 || id > 63) {
fputs("invalid node id\n", stderr);
exit(EXIT_FAILURE);
}
param_node_id = id;
return;
}
for (node = nodes; node; node = node->next)
if (!strcmp(node->name, name)) {
param_node_id = node->id & 0x3f;
sprintf(card_str, "%u", node->card);
find_local_node(card_str);
return;
}
fd = open(name, O_RDWR);
if (fd == -1) {
perror(name);
exit(EXIT_FAILURE);
}
get_info.version = 4;
get_info.rom_length = 0;
get_info.rom = 0;
get_info.bus_reset = ptr_to_u64(&bus_reset);
get_info.bus_reset_closure = 0;
if (ioctl(fd, FW_CDEV_IOC_GET_INFO, &get_info) < 0) {
fprintf(stderr, "%s: not a fw device\n", name);
exit(EXIT_FAILURE);
}
close(fd);
param_node_id = bus_reset.node_id & 0x3f;
sprintf(card_str, "%u", get_info.card);
find_local_node(card_str);
}
static u32 _send_packet(u32 quadlet0, u32 quadlet1, u32 response_mask, u32 response_bits)
{
struct fw_cdev_receive_phy_packets receive_phy_packets;
struct fw_cdev_send_phy_packet send_phy_packet;
struct pollfd pollfd;
int poll_result;
ssize_t bytes;
union fw_cdev_event event;
bool wait_for_sent = true;
bool wait_for_response = response_mask != 0;
unsigned int self_id_index = 0;
u32 response = 0;
if (wait_for_response) {
receive_phy_packets.closure = 0;
if (ioctl(local_node->fd, FW_CDEV_IOC_RECEIVE_PHY_PACKETS, &receive_phy_packets) < 0) {
perror("RECEIVE_PHY_PACKETS ioctl failed");
exit(EXIT_FAILURE);
}
}
send_phy_packet.closure = 0;
send_phy_packet.data[0] = quadlet0;
send_phy_packet.data[1] = quadlet1;
send_phy_packet.generation = local_node->generation;
if (ioctl(local_node->fd, FW_CDEV_IOC_SEND_PHY_PACKET, &send_phy_packet) < 0) {
perror("SEND_PHY_PACKET ioctl failed");
exit(EXIT_FAILURE);
}
pollfd.fd = local_node->fd;
pollfd.events = POLLIN;
while (wait_for_sent || wait_for_response) {
poll_result = poll(&pollfd, 1, 100);
if (poll_result < 0) {
perror("poll failed");
exit(EXIT_FAILURE);
}
if (poll_result == 0) {
fputs("timeout\n", stderr);
exit(EXIT_FAILURE);
}
bytes = read(local_node->fd, &event, sizeof(event));
if (bytes < sizeof(struct fw_cdev_event_common)) {
fputs("short read\n", stderr);
exit(EXIT_FAILURE);
}
switch (event.common.type) {
case FW_CDEV_EVENT_BUS_RESET:
fputs("bus reset\n", stderr);
exit(EXIT_FAILURE);
case FW_CDEV_EVENT_PHY_PACKET_SENT:
wait_for_sent = false;
if (event.phy_packet.length >= 4)
ping_time = event.phy_packet.data[0];
break;
case FW_CDEV_EVENT_PHY_PACKET_RECEIVED:
if ((event.phy_packet.data[0] & response_mask) == response_bits) {
response = event.phy_packet.data[0];
if ((response & 0xc0000000) == 0x80000000) {
self_ids[self_id_index++] = response;
if (self_id_index >= ARRAY_SIZE(self_ids) ||
!(response & 1))
wait_for_response = false;
} else {
wait_for_response = false;
}
}
break;
}
}
return response;
}
static u32 send_packet(u32 quadlet, u32 response_mask, u32 response_bits)
{
return _send_packet(quadlet, ~quadlet, response_mask, response_bits);
}
static void command_config(char *args[])
{
bool new_root = false;
int gap_count = -1;
char *endptr;
u32 packet;
if (!args[0]) {
fputs("missing configuration parameter\n", stderr);
syntax_error:
help();
exit(EXIT_FAILURE);
}
do {
if (!strcmp(args[0], "root")) {
if (!args[1]) {
fputs("no root node specified\n", stderr);
goto syntax_error;
}
find_param_node(args[1]);
new_root = true;
} else if (!strcmp(args[0], "gapcount")) {
if (!args[1]) {
fputs("no gap count specified\n", stderr);
goto syntax_error;
}
gap_count = strtol(args[1], &endptr, 0);
if (*endptr) {
fputs("gap count is not a number\n", stderr);
exit(EXIT_FAILURE);
} else if (gap_count < 0 || gap_count > 63) {
fputs("gap count out of range\n", stderr);
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "unknown configuration parameter `%s'\n", args[0]);
goto syntax_error;
}
args += 2;
} while (args[0]);
packet = 0;
if (new_root)
packet |= (1 << 23) | (param_node_id << 24);
if (gap_count >= 0)
packet |= (1 << 22) | (gap_count << 16);
send_packet(packet, 0, 0);
}
static void print_port(u32 self_id, unsigned int shift)
{
static const char *const port[] = {
[0] = "",
[1] = "-",
[2] = "p",
[3] = "c",
};
fputs(port[(self_id >> shift) & 3], stdout);
}
static void command_ping(char *args[])
{
static const char *const speed[] = {
[0] = "S100",
[1] = "S200",
[2] = "S400",
[3] = "beta",
};
static const char *const power[] = {
[0] = "+0W",
[1] = "+15W",
[2] = "+30W",
[3] = "+45W",
[4] = "-3W",
[5] = " ?W",
[6] = "-3..-6W",
[7] = "-3..-10W",
};
unsigned int i;
if (!args[0]) {
fputs("missing destination node\n", stderr);
syntax_error:
help();
exit(EXIT_FAILURE);
}
find_param_node(args[0]);
if (args[1]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[1]);
goto syntax_error;
}
send_packet((0 << 18) | (param_node_id << 24),
0xff000000,
(2 << 30) | (param_node_id << 24));
printf("time: %u ticks (%llu ns)",
ping_time, (ping_time * 1000000uLL + 12288u) / 24576u);
printf(", selfID: phy %u %s gc=%u %s %s%s%s [",
(self_ids[0] >> 24) & 0x3f,
speed[(self_ids[0] >> 14) & 3],
(self_ids[0] >> 16) & 0x3f,
power[(self_ids[0] >> 8) & 7],
self_ids[0] & (1 << 22) ? "L" : "",
self_ids[0] & (1 << 11) ? "c" : "",
self_ids[0] & (1 << 1) ? "i" : "");
print_port(self_ids[0], 6);
print_port(self_ids[0], 4);
print_port(self_ids[0], 2);
if (self_ids[0] & 1) {
for (i = 1; i < ARRAY_SIZE(self_ids); ++i) {
print_port(self_ids[i], 16);
print_port(self_ids[i], 14);
print_port(self_ids[i], 12);
print_port(self_ids[i], 10);
print_port(self_ids[i], 8);
print_port(self_ids[i], 6);
print_port(self_ids[i], 4);
print_port(self_ids[i], 2);
if (!(self_ids[i] & 1))
break;
}
}
puts("]");
}
static void command_read(char *args[])
{
unsigned int page, port, reg;
char *endptr;
u32 packet;
u32 response;
if (!args[0]) {
fputs("missing destination node\n", stderr);
syntax_error:
help();
exit(EXIT_FAILURE);
}
find_param_node(args[0]);
if (!args[1]) {
fputs("missing register number\n", stderr);
goto syntax_error;
}
if (args[2]) {
if (!args[3]) {
fputs("missing register number\n", stderr);
goto syntax_error;
}
page = strtol(args[1], &endptr, 0);
if (*endptr) {
fputs("invalid page number\n", stderr);
exit(EXIT_FAILURE);
}
if (page > 7) {
fputs("page number out of range\n", stderr);
exit(EXIT_FAILURE);
}
port = strtol(args[2], &endptr, 0);
if (*endptr) {
fputs("invalid port number\n", stderr);
exit(EXIT_FAILURE);
}
if (port > 15) {
fputs("port number out of range\n", stderr);
exit(EXIT_FAILURE);
}
reg = strtol(args[3], &endptr, 0);
if (*endptr) {
fputs("invalid register number\n", stderr);
exit(EXIT_FAILURE);
}
if (reg < 8 || reg > 15) {
fputs("register number out of range\n", stderr);
exit(EXIT_FAILURE);
}
if (args[4]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[4]);
goto syntax_error;
}
} else {
page = 0;
port = 0;
reg = strtol(args[1], &endptr, 0);
if (*endptr) {
fputs("invalid register number\n", stderr);
exit(EXIT_FAILURE);
}
if (reg > 7) {
fputs("register number out of range\n", stderr);
exit(EXIT_FAILURE);
}
if (args[2]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[2]);
goto syntax_error;
}
}
packet = reg < 8 ? 1 << 18 : 5 << 18;
packet |= page << 15;
packet |= port << 11;
packet |= (reg & 7) << 8;
packet |= param_node_id << 24;
response = send_packet(packet, 0xffffff00, packet | (2 << 18));
printf("value: 0x%02x\n", response & 0xff);
}
static void command_remote_cmd(char *args[], u32 cmd)
{
unsigned int port;
char *endptr;
u32 response;
if (!args[0]) {
fputs("missing destination node\n", stderr);
syntax_error:
help();
exit(EXIT_FAILURE);
}
find_param_node(args[0]);
if (!args[1]) {
fputs("missing port number\n", stderr);
goto syntax_error;
}
port = strtol(args[1], &endptr, 0);
if (*endptr) {
fputs("invalid port number\n", stderr);
exit(EXIT_FAILURE);
}
if (port > 15) {
fputs("port number out of range\n", stderr);
exit(EXIT_FAILURE);
}
if (args[2]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[2]);
goto syntax_error;
}
response = send_packet((0x8 << 18) | cmd | (port << 11) | (param_node_id << 24),
0xff3ff807,
(0xa << 18) | cmd | (port << 11) | (param_node_id << 24));
if (!(response & (1 << 3)))
puts("command rejected");
else if (!(response & 0x1f0))
fputs("port status: ok\n", stdout);
else
printf("port status:%s%s%s%s%s\n",
response & (1 << 4) ? " disabled" : "",
response & (1 << 5) ? " bias" : "",
response & (1 << 6) ? " connected" : "",
response & (1 << 7) ? " fault" : "",
response & (1 << 8) ? " standby_fault" : "");
}
static void command_nop(char *args[])
{
command_remote_cmd(args, 0);
}
static void command_disable(char *args[])
{
command_remote_cmd(args, 1);
}
static void command_suspend(char *args[])
{
command_remote_cmd(args, 2);
}
static void command_clear(char *args[])
{
command_remote_cmd(args, 4);
}
static void command_enable(char *args[])
{
command_remote_cmd(args, 5);
}
static void command_resume(char *args[])
{
if (args[0])
command_remote_cmd(args, 6);
else
send_packet((0xf << 18) | (local_node->id << 24), 0, 0);
}
static void command_standby(char *args[])
{
command_remote_cmd(args, 7 | (1 << 15));
}
static void command_restore(char *args[])
{
command_remote_cmd(args, 7 | (2 << 15));
}
static void command_linkon(char *args[])
{
if (!args[0]) {
fputs("missing destination node\n", stderr);
syntax_error:
help();
exit(EXIT_FAILURE);
}
find_param_node(args[0]);
if (args[1]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[1]);
goto syntax_error;
}
send_packet((1 << 30) | (param_node_id << 24), 0, 0);
}
static void command_versaphy(char *args[])
{
u32 q0, q1;
char *endptr;
if (!args[0] || !args[1]) {
fputs("missing data\n", stderr);
syntax_error:
help();
exit(EXIT_FAILURE);
}
q0 = strtol(args[0], &endptr, 16);
if (*endptr) {
fputs("invalid data quadlet\n", stderr);
exit(EXIT_FAILURE);
}
q1 = strtol(args[1], &endptr, 16);
if (*endptr) {
fputs("invalid data quadlet\n", stderr);
exit(EXIT_FAILURE);
}
if ((q0 & 0xc0000000) != 0xc0000000) {
fputs("not a VersaPHY packet\n", stderr);
exit(EXIT_FAILURE);
}
if (args[2]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[2]);
goto syntax_error;
}
_send_packet(q0, q1, 0, 0);
}
static void command_reset(char *args[])
{
struct fw_cdev_initiate_bus_reset initiate_bus_reset;
if (args[0]) {
fprintf(stderr, "unexpected parameter `%s'\n", args[0]);
help();
exit(EXIT_FAILURE);
}
initiate_bus_reset.type = FW_CDEV_SHORT_RESET;
if (ioctl(local_node->fd, FW_CDEV_IOC_INITIATE_BUS_RESET, &initiate_bus_reset) < 0) {
perror("INITIATE_BUS_RESET ioctl failed");
exit(EXIT_FAILURE);
}
}
int main(int argc, char *argv[])
{
static const char short_options[] = "b:hV";
static const struct option long_options[] = {
{ "bus", 1, NULL, 'b' },
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{}
};
static const struct {
const char *name;
void (*fn)(char *args[]);
} commands[] = {
{ "config", command_config },
{ "ping", command_ping },
{ "read", command_read },
{ "nop", command_nop },
{ "disable", command_disable },
{ "suspend", command_suspend },
{ "clear", command_clear },
{ "enable", command_enable },
{ "resume", command_resume },
{ "standby", command_standby },
{ "restore", command_restore },
{ "linkon", command_linkon },
{ "link-on", command_linkon },
{ "link_on", command_linkon },
{ "versaphy", command_versaphy },
{ "reset", command_reset },
};
const char *bus_name = NULL;
unsigned int i;
int c;
while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
switch (c) {
case 'b':
bus_name = optarg;
break;
case 'h':
help();
return 0;
case 'V':
puts("firewire-phy-command version " PACKAGE_VERSION);
return 0;
default:
syntax_error:
help();
return 1;
}
}
if (optind >= argc) {
fputs("missing command\n", stderr);
goto syntax_error;
}
for (i = 0; i < ARRAY_SIZE(commands); ++i)
if (!strcmp(commands[i].name, argv[optind])) {
open_all_nodes();
find_local_node(bus_name);
commands[i].fn(argv + optind + 1);
close_all_nodes();
return 0;
}
fprintf(stderr, "unknown command `%s'\n", argv[optind]);
return 1;
}