| /* |
| * 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; |
| } |