blob: dac29edc5a0a5ca95ce254dc00a4779872b27a64 [file]
/*
* lsfirewirephy.c - list the PHYs on a bus
*
* Copyright 2010-2011 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 <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 <sys/types.h>
#include <sys/stat.h>
#include <linux/firewire-cdev.h>
#include <linux/firewire-constants.h>
#include "config.h"
#define ptr_to_u64(p) ((uintptr_t)(p))
#define PHY_REMOTE_ACCESS_PAGED(phy_id, page, port, reg) \
(((phy_id) << 24) | (5 << 18) | ((page) << 15) | ((port) << 11) | ((reg) << 8))
#define PHY_REMOTE_REPLY_PAGED(phy_id, page, port, reg, data) \
(((phy_id) << 24) | (7 << 18) | ((page) << 15) | ((port) << 11) | ((reg) << 8) | (data))
typedef __u8 u8;
typedef __u32 u32;
typedef __u64 u64;
typedef u32 u24;
static const struct vendor {
u24 oui;
const char *name;
const struct phy {
u24 id;
const char *name;
u24 mask;
} *phys;
} vendors[] = {
{
.oui = 0x00000e,
.name = "Fujitsu",
.phys = (const struct phy[]) {
{ 0x086613, "MB86613" },
{}
}
},
{
.oui = 0x00004c,
.name = "NEC",
.phys = (const struct phy[]) {
{ 0x000201, "PD7286x" },
{ 0x050160, "PD7287x" },
{}
}
},
{
.oui = 0x00053d,
.name = "Agere (LSI)",
.phys = (const struct phy[]) {
{ 0x053300, "FW533E", 0xffff00 },
{ 0x064300, "FW643(E)", 0xffff00 },
{ 0x084300, "FW843", 0xffff00 },
{}
}
},
{
.oui = 0x000cc2,
.name = "ControlNet India (O2Micro)",
.phys = (const struct phy[]) {
{ 0x401104, "OZxxx" },
{}
}
},
{
.oui = 0x001018,
.name = "Broadcom",
.phys = (const struct phy[]) {
{}
}
},
{
/*
* This OUI actually belongs to System S.p.A.;
* VIA's OUI is 0x004063.
*/
.oui = 0x001163,
.name = "VIA",
.phys = (const struct phy[]) {
{ 0x306001, "VT63xx" },
{}
}
},
{
.oui = 0x001454,
.name = "Symwave",
.phys = (const struct phy[]) {
{ 0x003181, "SW3080" },
{}
}
},
{
.oui = 0x001b8c,
.name = "JMicron",
.phys = (const struct phy[]) {
{ 0x038100, "JMB38x" },
{}
}
},
{
.oui = 0x00601d,
.name = "Lucent (LSI)",
.phys = (const struct phy[]) {
{ 0x032200, "FW322", 0xffff00 },
{ 0x032300, "FW323", 0xffff00 },
{ 0x080200, "FW802", 0xffff00 },
{}
}
},
{
.oui = 0x006037,
.name = "Philips (NXP)",
.phys = (const struct phy[]) {
{ 0x412801, "PDI1394P25" },
{ 0x422001, "PDI1394P23" },
{ 0x423900, "PDI1394P24", 0xffff0f },
{ 0x431000, "PDI1394P21" },
{ 0x431100, "PDI1394P22" },
{}
}
},
{
.oui = 0x00c02d,
.name = "Fujifilm",
.phys = (const struct phy[]) {
{ 0x303562, "MD8405B" },
{ 0x303565, "MD8405E" },
{}
}
},
{
.oui = 0x080028,
.name = "Texas Instruments",
.phys = (const struct phy[]) {
{ 0x42308a, "TSB41LV02A" },
{ 0x424296, "TSB41AB1/2" },
{ 0x424499, "TSB43AB22(A)" },
{ 0x424729, "XIO2200A" },
{ 0x434195, "TSB41AB3" },
{ 0x434615, "TSB43CB43A" },
{ 0x46318a, "TSB41LV06A" },
{ 0x831304, "TSB81BA3(A)" },
{ 0x831306, "TSB81BA3D" },
{ 0x831307, "TSB81BA3E/XIO2213" },
{ 0x833005, "TSB41BA3D" },
{}
}
},
{
.oui = 0x10005a,
.name = "IBM",
.phys = (const struct phy[]) {
{ 0x218600, "IBM21S860", 0xfffff0 },
{ 0x218610, "IBM21S861", 0xfffff0 },
{ 0x218620, "IBM21S862", 0xfffff0 },
{}
}
},
{}
};
static char *device_file_name;
static int list_phy_id = -1;
static int fd;
static bool any_unknown_phys;
struct fw_cdev_get_info get_info;
struct fw_cdev_event_bus_reset bus_reset;
static void help(void)
{
fputs("Usage: lsfirewirephy [options] [devicenode [phyid]]\n"
"Options:\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 parse_parameters(int argc, char *argv[])
{
static const char short_options[] = "hV";
static const struct option long_options[] = {
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{}
};
int c;
char *endptr;
while ((c = getopt_long(argc, argv, short_options, long_options, NULL)) != -1) {
switch (c) {
case 'h':
help();
exit(EXIT_SUCCESS);
case 'V':
puts("lsfirewirephy version " PACKAGE_VERSION);
exit(EXIT_SUCCESS);
default:
syntax_error:
help();
exit(EXIT_FAILURE);
}
}
if (optind < argc) {
device_file_name = strdup(argv[optind++]);
if (!device_file_name) {
fputs("out of memory\n", stderr);
exit(EXIT_FAILURE);
}
if (optind < argc) {
list_phy_id = strtol(argv[optind], &endptr, 0);
if (argv[optind][0] == '\0' || *endptr != '\0')
goto syntax_error;
if (list_phy_id < 0 || list_phy_id >= 63) {
fputs("phy-id must be between 0 and 62\n", stderr);
exit(EXIT_FAILURE);
}
++optind;
if (optind < argc)
goto syntax_error;
}
}
}
static struct dirent **fw_dirents;
static int fw_dirent_count;
static int fw_dirent_index;
static int fw_filter(const struct dirent *dirent)
{
return dirent->d_name[0] == 'f' &&
dirent->d_name[1] == 'w' &&
isdigit(dirent->d_name[2]);
}
static void init_enumerated_fw_devs(void)
{
fw_dirent_count = scandir("/dev", &fw_dirents, fw_filter, versionsort);
if (fw_dirent_count < 0) {
perror("cannot read /dev");
exit(EXIT_FAILURE);
}
fw_dirent_index = 0;
}
static void cleanup_enumerated_fw_devs(void)
{
int i;
for (i = 0; i < fw_dirent_count; ++i)
free(fw_dirents[i]);
free(fw_dirents);
fw_dirents = NULL;
fw_dirent_count = 0;
}
static bool has_enumerated_fw_dev(void)
{
free(device_file_name);
device_file_name = NULL;
if (fw_dirent_index < fw_dirent_count) {
if (asprintf(&device_file_name, "/dev/%s",
fw_dirents[fw_dirent_index]->d_name) < 0) {
perror("asprintf failed");
exit(EXIT_FAILURE);
}
return true;
} else {
cleanup_enumerated_fw_devs();
return false;
}
}
static void next_enumerated_fw_dev(void)
{
++fw_dirent_index;
}
static bool open_device(bool force)
{
fd = open(device_file_name, O_RDWR);
if (fd == -1) {
if (!force && errno == ENODEV)
return false;
perror(device_file_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) {
perror("GET_INFO ioctl failed");
exit(EXIT_FAILURE);
}
if (get_info.version < 4) {
fputs("this kernel is too old\n", stderr);
exit(EXIT_FAILURE);
}
return true;
}
static void enable_phy_packets(void)
{
struct fw_cdev_receive_phy_packets receive_phy_packets;
receive_phy_packets.closure = 0;
if (ioctl(fd, FW_CDEV_IOC_RECEIVE_PHY_PACKETS, &receive_phy_packets) < 0) {
perror("RECEIVE_PHY_PACKETS ioctl failed");
exit(EXIT_FAILURE);
}
}
static bool device_is_local_node(void)
{
return bus_reset.node_id == bus_reset.local_node_id;
}
static void check_local_node(void)
{
if (!device_is_local_node()) {
fprintf(stderr, "%s: not a local node\n", device_file_name);
exit(EXIT_FAILURE);
}
}
static const struct vendor *search_vendor(u24 oui)
{
const struct vendor *vendor;
for (vendor = vendors; vendor->name; ++vendor)
if (vendor->oui== oui)
return vendor;
return NULL;
}
static const struct phy *search_phy(const struct vendor *vendor, u24 id)
{
const struct phy *phy;
u24 mask;
for (phy = vendor->phys; phy->name; ++phy) {
mask = phy->mask ?: 0xffffff;
if ((phy->id & mask) == (id & mask))
return phy;
}
return NULL;
}
static void list_phy(void)
{
struct fw_cdev_send_phy_packet send_phy_packet;
unsigned int reg, regs_read;
int ready, r;
struct pollfd pfd;
u8 buf[256];
struct fw_cdev_event_common *event;
u8 reg_values[6];
u24 oui, id;
const struct vendor *vendor;
const struct phy *phy;
send_phy_packet.closure = 0;
send_phy_packet.generation = bus_reset.generation;
for (reg = 2; reg <= 7; ++reg) {
send_phy_packet.data[0] = PHY_REMOTE_ACCESS_PAGED(list_phy_id, 1, 0, reg);
send_phy_packet.data[1] = ~send_phy_packet.data[0];
if (ioctl(fd, FW_CDEV_IOC_SEND_PHY_PACKET, &send_phy_packet) < 0) {
perror("SEND_PHY_PACKET ioctl failed");
exit(EXIT_FAILURE);
}
}
pfd.fd = fd;
pfd.events = POLLIN;
regs_read = 0;
while (regs_read != 0xfc) {
ready = poll(&pfd, 1, 123);
if (ready < 0) {
perror("poll failed");
exit(EXIT_FAILURE);
}
if (!ready) {
fputs("timeout\n", stderr);
return; /* try next PHY */
}
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) {
/* TODO: retry */
fputs("bus reset\n", stderr);
exit(EXIT_FAILURE);
}
if (event->type == FW_CDEV_EVENT_PHY_PACKET_SENT) {
struct fw_cdev_event_phy_packet *phy_packet = (void *)buf;
if (phy_packet->rcode != RCODE_COMPLETE) {
fprintf(stderr, "PHY packet failed: rcode %u\n",
(unsigned int)phy_packet->rcode);
exit(EXIT_FAILURE);
}
} else if (event->type == FW_CDEV_EVENT_PHY_PACKET_RECEIVED) {
struct fw_cdev_event_phy_packet *phy_packet = (void *)buf;
if (phy_packet->length == 8 &&
(phy_packet->data[0] & 0xffff8000)
== PHY_REMOTE_REPLY_PAGED(list_phy_id, 1, 0, 0, 0)) {
reg = (phy_packet->data[0] >> 8) & 7;
if (reg >= 2) {
reg_values[reg - 2] = phy_packet->data[0] & 0xff;
regs_read |= 1 << reg;
}
}
}
}
oui = (reg_values[0] << 16) | (reg_values[1] << 8) | reg_values[2];
id = (reg_values[3] << 16) | (reg_values[4] << 8) | reg_values[5];
vendor = search_vendor(oui);
phy = vendor ? search_phy(vendor, id) : NULL;
printf("bus %u, node %d: %06x:%06x ", get_info.card, list_phy_id, oui, id);
if (vendor)
printf("%s %s\n", vendor->name, phy ? phy->name : "(unknown)");
else
printf("%s\n", "(unknown)");
if (!phy)
any_unknown_phys = true;
}
static void list_one_phy(void)
{
open_device(true);
check_local_node();
enable_phy_packets();
list_phy();
close(fd);
}
static void list_device(void)
{
unsigned int list_card;
open_device(true);
list_phy_id = bus_reset.node_id & 0x3f;
list_card = get_info.card;
if (!device_is_local_node()) {
close(fd);
for (init_enumerated_fw_devs();
has_enumerated_fw_dev();
next_enumerated_fw_dev()) {
if (!open_device(false))
continue;
if (get_info.card == list_card &&
device_is_local_node())
goto found;
close(fd);
}
fprintf(stderr, "local node for card %u not found\n", list_card);
exit(EXIT_FAILURE);
}
found:
enable_phy_packets();
list_phy();
close(fd);
}
static void list_all_buses(void)
{
for (init_enumerated_fw_devs();
has_enumerated_fw_dev();
next_enumerated_fw_dev()) {
if (!open_device(false))
continue;
if (device_is_local_node()) {
int root_phy_id = bus_reset.root_node_id & 0x3f;
enable_phy_packets();
for (list_phy_id = 0; list_phy_id <= root_phy_id; ++list_phy_id)
list_phy();
}
close(fd);
}
}
int main(int argc, char *argv[])
{
parse_parameters(argc, argv);
if (device_file_name)
if (list_phy_id >= 0)
list_one_phy();
else
list_device();
else
list_all_buses();
if (any_unknown_phys)
fputs(" Please check this web page for updated PHY IDs:\n"
" http://ieee1394.docs.kernel.org/en/latest/phy.html\n", stderr);
return 0;
}