summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKristian Høgsberg <krh@redhat.com>2006-10-23 22:41:42 -0400
committerKristian Høgsberg <krh@redhat.com>2006-10-23 22:41:42 -0400
commit3bf76d9d76687b2bbbfa5c5d1a5621ae7faa3b4e (patch)
treed071b78b2dbb1bd40617f4bc71c4f1f02319b69a
Pull over nosy from mercurial repo.
-rw-r--r--Makefile46
-rw-r--r--list.h62
-rw-r--r--nosy-dump.c1032
-rw-r--r--nosy-user.h51
-rw-r--r--nosy.c708
-rw-r--r--nosy.h238
6 files changed, 2137 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1f5701a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,46 @@
+version = 0.3
+prefix = /usr
+
+CC = gcc
+
+MODULESROOT := /lib/modules/$(shell uname -r)
+kdir := $(MODULESROOT)/build
+
+dist-files = Makefile nosy-dump.c list.h nosy-user.h nosy.c nosy.h
+
+all : nosy.ko nosy-dump
+
+CFLAGS += -DVERSION=\"$(version)\"
+
+nosy.ko : nosy.c
+ $(MAKE) -C $(kdir) SUBDIRS=$(PWD) modules
+
+obj-m := nosy.o
+
+%.mod.c : %.o
+ $(kdir)/scripts/modpost $(kdir)/vmlinux $<
+
+
+nosy-dump : CFLAGS = -Wall -O2 -g
+nosy-dump : CPPFLAGS = -DVERSION=\"$(version)\"
+nosy-dump : LDFLAGS = -g
+nosy-dump : LDLIBS = -lpopt
+
+nosy-dump : nosy-dump.o
+
+clean :
+ rm -rf nosy.ko *.o *.mod.c nosy-dump .*.cmd
+
+install :
+ $(MAKE) -C $(kdir) M=$(PWD) modules_install
+ install nosy-dump $(prefix)/bin/nosy-dump
+
+dist :
+ svnversion . | egrep ^[0-9]+$$
+ rm -rf nosy-$(version)
+ mkdir nosy-$(version)
+ cp $(dist-files) nosy-$(version)
+ echo SVN Revision $(shell svnversion .) > nosy-$(version)/REVISION
+ tar cfz nosy-$(version).tar.gz nosy-$(version)
+
+.PHONY: clean dist install
diff --git a/list.h b/list.h
new file mode 100644
index 0000000..fa119dd
--- /dev/null
+++ b/list.h
@@ -0,0 +1,62 @@
+struct list {
+ struct list *next, *prev;
+};
+
+static inline void
+list_init(struct list *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static inline int
+list_empty(struct list *list)
+{
+ return list->next == list;
+}
+
+static inline void
+list_insert(struct list *link, struct list *new_link)
+{
+ new_link->prev = link->prev;
+ new_link->next = link;
+ new_link->prev->next = new_link;
+ new_link->next->prev = new_link;
+}
+
+static inline void
+list_append(struct list *list, struct list *new_link)
+{
+ list_insert((struct list *)list, new_link);
+}
+
+static inline void
+list_prepend(struct list *list, struct list *new_link)
+{
+ list_insert(list->next, new_link);
+}
+
+static inline void
+list_remove(struct list *link)
+{
+ link->prev->next = link->next;
+ link->next->prev = link->prev;
+}
+
+#define list_entry(link, type, member) \
+ ((type *)((char *)(link)-(unsigned long)(&((type *)0)->member)))
+
+#define list_head(list, type, member) \
+ list_entry((list)->next, type, member)
+
+#define list_tail(list, type, member) \
+ list_entry((list)->prev, type, member)
+
+#define list_next(elm, member) \
+ list_entry((elm)->member.next, typeof(*elm), member)
+
+#define list_for_each_entry(pos, list, member) \
+ for (pos = list_head(list, typeof(*pos), member); \
+ &pos->member != (list); \
+ pos = list_next(pos, member))
+
diff --git a/nosy-dump.c b/nosy-dump.c
new file mode 100644
index 0000000..c68dccc
--- /dev/null
+++ b/nosy-dump.c
@@ -0,0 +1,1032 @@
+/* -*- mode: c; c-basic-offset: 2 -*- */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <endian.h>
+#include <popt.h>
+#include <byteswap.h>
+
+#include "nosy-user.h"
+#include "list.h"
+
+#define array_length(array) (sizeof(array) / sizeof(array[0]))
+
+
+static void
+print_packet(unsigned long *data, size_t length);
+
+static char *option_nosy_device = "/dev/nosy";
+static char *option_view = "packet";
+static int option_hex;
+static int option_version;
+
+#define SPEED_100 0x0
+#define SPEED_200 0x1
+#define SPEED_400 0x2
+
+enum {
+ VIEW_TRANSACTION,
+ VIEW_PACKET,
+ VIEW_STATS
+};
+
+static const struct poptOption options[] = {
+ {
+ longName: "device",
+ shortName: 'd',
+ argInfo: POPT_ARG_STRING,
+ arg: &option_nosy_device,
+ descrip: "Path to nosy device.",
+ argDescrip: "DEVICE"
+ },
+ {
+ longName: "view",
+ shortName: 'v',
+ argInfo: POPT_ARG_STRING,
+ arg: &option_view,
+ descrip: "Specify view of bus traffic: packet, transaction or stats.",
+ argDescrip: "VIEW"
+ },
+ {
+ longName: "hex",
+ shortName: 'x',
+ argInfo: POPT_ARG_NONE,
+ arg: &option_hex,
+ descrip: "Print each packet in hex.",
+ argDescrip: "HEX"
+ },
+ {
+ longName: "version",
+ argInfo: POPT_ARG_NONE,
+ arg: &option_version,
+ descrip: "Specify print version info.",
+ },
+ POPT_AUTOHELP
+ POPT_TABLEEND
+};
+
+struct phy_packet {
+ unsigned long timestamp;
+
+ union {
+ struct {
+ unsigned int zero : 24;
+ unsigned int phy_id : 6;
+ unsigned int identifier : 2;
+ } common, link_on;
+ struct {
+ unsigned int zero : 16;
+ unsigned int gap_count : 6;
+ unsigned int set_gap_count : 1;
+ unsigned int set_root : 1;
+ unsigned int root_id : 6;
+ unsigned int identifier : 2;
+ } phy_config;
+ struct {
+ unsigned int more_packets : 1;
+ unsigned int initiated_reset : 1;
+ unsigned int port2 : 2;
+ unsigned int port1 : 2;
+ unsigned int port0 : 2;
+ unsigned int power_class : 3;
+ unsigned int contender : 1;
+ unsigned int phy_delay : 2;
+ unsigned int phy_speed : 2;
+ unsigned int gap_count : 6;
+ unsigned int link_active : 1;
+ unsigned int extended : 1;
+ unsigned int phy_id : 6;
+ unsigned int identifier : 2;
+ } self_id;
+
+ struct {
+ unsigned int more_packets : 1;
+ unsigned int reserved1 : 1;
+ unsigned int porth : 2;
+ unsigned int portg : 2;
+ unsigned int portf : 2;
+ unsigned int porte : 2;
+ unsigned int portd : 2;
+ unsigned int portc : 2;
+ unsigned int portb : 2;
+ unsigned int porta : 2;
+ unsigned int reserved0 : 2;
+ unsigned int sequence : 3;
+ unsigned int extended : 1;
+ unsigned int phy_id : 6;
+ unsigned int identifier : 2;
+ } ext_self_id;
+ };
+
+ unsigned long inverted;
+ unsigned long ack;
+};
+
+#define PHY_PACKET_CONFIGURATION 0x00
+#define PHY_PACKET_LINK_ON 0x01
+#define PHY_PACKET_SELF_ID 0x02
+
+struct link_packet {
+ unsigned long timestamp;
+
+ union {
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int offset_high : 16;
+ unsigned int source : 16;
+
+ unsigned long offset_low;
+ } common;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int offset_high : 16;
+ unsigned int source : 16;
+
+ unsigned long offset_low;
+
+ unsigned long crc;
+ } read_quadlet;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int reserved0 : 12;
+ unsigned int rcode : 4;
+ unsigned int source : 16;
+
+ unsigned long reserved1;
+
+ unsigned long data;
+
+ unsigned long crc;
+ } read_quadlet_response;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int offset_high : 16;
+ unsigned int source : 16;
+
+ unsigned long offset_low;
+
+ unsigned int extended_tcode : 16;
+ unsigned int data_length : 16;
+
+ unsigned long crc;
+ } read_block;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int reserved0 : 12;
+ unsigned int rcode : 4;
+ unsigned int source : 16;
+
+ unsigned long reserved1;
+
+ unsigned int extended_tcode : 16;
+ unsigned int data_length : 16;
+
+ unsigned long crc;
+
+ unsigned long data[0];
+
+ /* crc and ack follows. */
+
+ } read_block_response;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int offset_high : 16;
+ unsigned int source : 16;
+
+ unsigned long offset_low;
+
+ unsigned long data;
+
+ unsigned long crc;
+
+ } write_quadlet;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int offset_high : 16;
+ unsigned int source : 16;
+
+ unsigned int offset_low : 32;
+
+ unsigned int extended_tcode : 16;
+ unsigned int data_length : 16;
+
+ unsigned long crc;
+ unsigned long data[0];
+
+ /* crc and ack follows. */
+
+ } write_block;
+
+ struct {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+
+ unsigned int reserved0 : 12;
+ unsigned int rcode : 4;
+ unsigned int source : 16;
+
+ unsigned long reserved1;
+
+ unsigned long crc;
+ } write_response;
+
+ };
+
+};
+
+struct subaction {
+ unsigned long ack;
+ size_t length;
+ struct list link;
+ struct link_packet packet;
+};
+
+
+struct subaction *
+subaction_create(unsigned long *data, size_t length)
+{
+ struct subaction *sa;
+
+ /* we put the ack in the subaction struct for easy access. */
+ sa = malloc(sizeof *sa - sizeof sa->packet + length);
+ sa->ack = data[length / 4 - 1];
+ sa->length = length;
+ memcpy (&sa->packet, data, length);
+
+ return sa;
+}
+
+void
+subaction_destroy(struct subaction *sa)
+{
+ free(sa);
+}
+
+struct link_transaction {
+ int request_node, response_node, tlabel;
+ struct list request_list, response_list;
+ struct list link;
+};
+
+struct list pending_transaction_list =
+ { &pending_transaction_list, &pending_transaction_list };
+
+struct link_transaction *
+link_transaction_lookup(int request_node, int response_node, int tlabel)
+{
+ struct link_transaction *t;
+
+ list_for_each_entry(t, &pending_transaction_list, link) {
+ if (t->request_node == request_node &&
+ t->response_node == response_node &&
+ t->tlabel == tlabel)
+ return t;
+ }
+
+ t = malloc(sizeof *t);
+ t->request_node = request_node;
+ t->response_node = response_node;
+ t->tlabel = tlabel;
+ list_init(&t->request_list);
+ list_init(&t->response_list);
+
+ list_append(&pending_transaction_list, &t->link);
+
+ return t;
+}
+
+void
+link_transaction_destroy(struct link_transaction *t)
+{
+ while (!list_empty(&t->request_list)) {
+ struct subaction *sa = list_head(&t->request_list, struct subaction, link);
+ list_remove(&sa->link);
+ subaction_destroy(sa);
+ }
+
+ while (!list_empty(&t->response_list)) {
+ struct subaction *sa = list_head(&t->response_list, struct subaction, link);
+ list_remove(&sa->link);
+ subaction_destroy(sa);
+ }
+
+ free(t);
+}
+
+void
+handle_transaction(struct link_transaction *t)
+{
+ struct subaction *sa;
+
+ printf("completed transaction (%04x->%04x, tlabel=%x)\n",
+ t->request_node, t->response_node, t->tlabel);
+ list_for_each_entry(sa, &t->request_list, link)
+ print_packet((unsigned long *) &sa->packet, sa->length);
+ list_for_each_entry(sa, &t->response_list, link)
+ print_packet((unsigned long *) &sa->packet, sa->length);
+ printf("\n");
+
+ link_transaction_destroy(t);
+}
+
+void
+clear_pending_transaction_list(void)
+{
+ struct link_transaction *t;
+
+ while (!list_empty(&pending_transaction_list)) {
+ t = list_head(&pending_transaction_list, struct link_transaction, link);
+ list_remove(&t->link);
+ link_transaction_destroy(t);
+ /* print unfinished transactions */
+ }
+}
+
+static const char * const tcode_names[] = {
+ "write_quadlet_request",
+ "write_block_request",
+ "write_response",
+ "reserved",
+ "read_quadlet_request",
+ "read_block_request",
+ "read_quadlet_response",
+ "read_block_response",
+ "cycle_start",
+ "lock_request",
+ "iso_data",
+ "lock_response"
+};
+
+static const char * const ack_names[] = {
+ "no ack",
+ "ack_complete",
+ "ack_pending",
+ "reserved (0x03)",
+ "ack_busy_x",
+ "ack_busy_a",
+ "ack_busy_b",
+ "reserved (0x07)",
+ "reserved (0x08)",
+ "reserved (0x09)",
+ "reserved (0x0a)",
+ "reserved (0x0b)",
+ "reserved (0x0c)",
+ "ack_data_error",
+ "ack_type_error",
+ "reserved (0x0f)",
+};
+
+static const char * const rcode_names[] = {
+ "complete",
+ "reserved (0x01)",
+ "reserved (0x02)",
+ "reserved (0x03)",
+ "conflict_error",
+ "data_error",
+ "type_error",
+ "address_error",
+};
+
+static const char * const retry_names[] = {
+ "retry_1",
+ "retry_x",
+ "retry_a",
+ "retry_b",
+};
+
+enum {
+ PACKET_RESERVED,
+ PACKET_REQUEST,
+ PACKET_RESPONSE,
+ PACKET_OTHER,
+};
+
+struct packet_info {
+ const char *name;
+ int type;
+ int response_tcode;
+ struct packet_field *fields;
+ int field_count;
+};
+
+struct packet_field {
+ const char *name; /* Short name for field. */
+ int offset; /* Location of field, specified in bits.
+ * Negative means from end of packet */
+ int width; /* Width of field, 0 means use data_length. */
+ const char * const *value_names;
+};
+
+#define COMMON_REQUEST_FIELDS \
+ { "dest", 0, 16 }, \
+ { "tl", 16, 6 }, \
+ { "rt", 22, 2, retry_names }, \
+ { "tcode", 24, 4, tcode_names }, \
+ { "pri", 28, 4 }, \
+ { "src", 32, 16 }, \
+ { "offs", 48, 48 }
+
+#define COMMON_RESPONSE_FIELDS \
+ { "dest", 0, 16 }, \
+ { "tl", 16, 6 }, \
+ { "rt", 22, 2, retry_names }, \
+ { "tcode", 24, 4, tcode_names }, \
+ { "pri", 28, 4 }, \
+ { "src", 32, 16 }, \
+ { "rcode", 48, 4, rcode_names } \
+
+struct packet_field read_quadlet_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "crc", 96, 32 },
+ { "ack", 156, 4, ack_names }
+};
+
+struct packet_field read_quadlet_response_fields[] = {
+ COMMON_RESPONSE_FIELDS,
+ { "data", 96, 32 },
+ { "crc", 128, 32 },
+ { "ack", 188, 4, ack_names }
+};
+
+struct packet_field read_block_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "data_length", 96, 16 },
+ { "extended_tcode", 112, 16 },
+ { "crc", 128, 32 },
+ { "ack", 188, 4, ack_names },
+};
+
+struct packet_field read_block_response_fields[] = {
+ COMMON_RESPONSE_FIELDS,
+ { "data_length", 96, 16 },
+ { "extended_tcode", 112, 16 },
+ { "crc", 128, 32 },
+ { "data", 160, 0 },
+ { "crc", -64, 32 },
+ { "ack", -4, 4, ack_names }
+};
+
+struct packet_field write_quadlet_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "data", 96, 32 },
+ { "ack", -4, 4, ack_names }
+};
+
+struct packet_field write_block_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "data_length", 96, 16 },
+ { "extended_tcode", 112, 16 },
+ { "crc", 128, 32 },
+ { "data", 160, 0 },
+ { "crc", -64, 32 },
+ { "ack", -4, 4, ack_names }
+};
+
+struct packet_field write_response_fields[] = {
+ COMMON_RESPONSE_FIELDS,
+ { "reserved", 64, 32 },
+ { "ack", -4, 4, ack_names }
+};
+
+struct packet_field cycle_start_fields[] = {
+ { "tcode", 24, 4, tcode_names },
+};
+
+
+static struct packet_info packet_info[] = {
+ {
+ .name = "write_quadlet_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_WRITE_RESPONSE,
+ .fields = write_quadlet_request_fields,
+ .field_count = array_length(write_quadlet_request_fields)
+ },
+ {
+ .name = "write_block_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_WRITE_RESPONSE,
+ .fields = write_block_request_fields,
+ .field_count = array_length(write_block_request_fields)
+ },
+ {
+ .name = "write_response",
+ .type = PACKET_RESPONSE,
+ .fields = write_response_fields,
+ .field_count = array_length(write_response_fields)
+ },
+ {
+ .name = "reserved",
+ .type = PACKET_RESERVED,
+ },
+ {
+ .name = "read_quadlet_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_READ_QUADLET_RESPONSE,
+ .fields = read_quadlet_request_fields,
+ .field_count = array_length(read_quadlet_request_fields)
+ },
+ {
+ .name = "read_block_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_READ_BLOCK_RESPONSE,
+ .fields = read_block_request_fields,
+ .field_count = array_length(read_block_request_fields)
+ },
+ {
+ .name = "read_quadlet_response",
+ .type = PACKET_RESPONSE,
+ .fields = read_quadlet_response_fields,
+ .field_count = array_length(read_quadlet_response_fields)
+ },
+ {
+ .name = "read_block_response",
+ .type = PACKET_RESPONSE,
+ .fields = read_block_response_fields,
+ .field_count = array_length(read_block_response_fields)
+ },
+ {
+ .name = "cycle_start",
+ .type = PACKET_OTHER,
+ .fields = cycle_start_fields,
+ .field_count = array_length(cycle_start_fields)
+ },
+ {
+ .name = "lock_request",
+ .type = PACKET_REQUEST,
+ },
+ {
+ .name = "iso_data",
+ .type = PACKET_OTHER
+ },
+ {
+ .name = "lock_response",
+ .type = PACKET_RESPONSE
+ }
+};
+
+void
+handle_packet(unsigned long *data, size_t length)
+{
+ if (length == 0) {
+ printf("bus reset\n");
+ clear_pending_transaction_list();
+ }
+ else if (length > sizeof(struct phy_packet)) {
+ struct link_packet *p = (struct link_packet *) data;
+ struct subaction *sa, *prev;
+ struct link_transaction *t;
+
+ switch (packet_info[p->common.tcode].type) {
+ case PACKET_REQUEST:
+ t = link_transaction_lookup(p->common.source, p->common.destination,
+ p->common.tlabel);
+ sa = subaction_create(data, length);
+
+ if (!list_empty(&t->request_list)) {
+ prev = list_tail(&t->request_list, struct subaction, link);
+
+ if (!ACK_BUSY(prev->ack)) {
+ /* error, we should only see ack_busy_* before the
+ * ack_pending/ack_complete -- this is an ack_pending
+ * instead (ack_complete would have finished the
+ * transaction). */
+ }
+
+ if (prev->packet.common.tcode != sa->packet.common.tcode ||
+ prev->packet.common.tlabel != sa->packet.common.tlabel)
+ /* memcmp() ? */
+ /* error, these should match for retries. */;
+ }
+
+ list_append(&t->request_list, &sa->link);
+
+ switch (sa->ack) {
+ case ACK_COMPLETE:
+ if (p->common.tcode != TCODE_WRITE_QUADLET &&
+ p->common.tcode != TCODE_WRITE_BLOCK)
+ /* error, unified transactions only allowed for write */;
+ list_remove(&t->link);
+ handle_transaction(t);
+ break;
+
+ case ACK_NO_ACK:
+ case ACK_DATA_ERROR:
+ case ACK_TYPE_ERROR:
+ list_remove(&t->link);
+ handle_transaction(t);
+ break;
+
+ case ACK_PENDING:
+ /* request subaction phase over, wait for response. */
+ break;
+
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ /* ok, wait for retry. */
+ /* check that retry protocol is respected. */
+ break;
+ }
+ break;
+
+ case PACKET_RESPONSE:
+ t = link_transaction_lookup(p->common.destination, p->common.source,
+ p->common.tlabel);
+ if (list_empty(&t->request_list)) {
+ /* unsolicited response */
+ }
+
+ sa = subaction_create(data, length);
+
+ if (!list_empty(&t->response_list)) {
+ prev = list_tail(&t->response_list, struct subaction, link);
+
+ if (!ACK_BUSY(prev->ack))
+ /* error, we should only see ack_busy_* before the
+ * ack_pending/ack_complete */;
+
+ if (prev->packet.common.tcode != sa->packet.common.tcode ||
+ prev->packet.common.tlabel != sa->packet.common.tlabel)
+ /* use memcmp() instead? */
+ /* error, these should match for retries. */;
+ }
+ else {
+ prev = list_tail(&t->request_list, struct subaction, link);
+ if (prev->ack != ACK_PENDING) {
+ /* error, should not get response unless last request got
+ * ack_pending. */
+ }
+
+ if (packet_info[prev->packet.common.tcode].response_tcode !=
+ sa->packet.common.tcode) {
+ /* error, tcode mismatch */
+ }
+ }
+
+ list_append(&t->response_list, &sa->link);
+
+ switch (sa->ack) {
+ case ACK_COMPLETE:
+ case ACK_NO_ACK:
+ case ACK_DATA_ERROR:
+ case ACK_TYPE_ERROR:
+ list_remove(&t->link);
+ handle_transaction(t);
+ /* transaction complete, remove t from pending list. */
+ break;
+
+ case ACK_PENDING:
+ /* error for responses. */
+ break;
+
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ /* no problem, wait for next retry */
+ break;
+ }
+
+ break;
+
+ case PACKET_OTHER:
+ case PACKET_RESERVED:
+ return;
+ }
+ }
+}
+
+unsigned int get_bits(struct link_packet *packet, int offset, int width)
+{
+ unsigned long *data = (unsigned long *) packet;
+ unsigned long index, shift, mask;
+
+ index = offset / 32 + 1;
+ shift = 32 - (offset & 31) - width;
+ mask = width == 32 ? ~0 : (1 << width) - 1;
+
+ return (data[index] >> shift) & mask;
+}
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define byte_index(i) (i)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define byte_index(i) ((i) ^ 3)
+#else
+#error unsupported byte order.
+#endif
+
+void dump_data(unsigned char *data, int length)
+{
+ int i;
+
+ for (i = 0; i < length; i++)
+ printf("%s%02hhx",
+ (i % 4 == 0 && i != 0) ? " " : "",
+ data[byte_index(i)]);
+}
+
+void decode_link_packet(struct link_packet *packet, size_t length)
+{
+ struct packet_info *pi;
+ int i;
+
+ pi = &packet_info[packet->common.tcode];
+
+ for (i = 0; i < pi->field_count; i++) {
+ struct packet_field *f = &pi->fields[i];
+ int offset;
+
+ if (f->offset < 0)
+ offset = length * 8 + f->offset - 32;
+ else
+ offset = f->offset;
+
+ if (f->value_names != NULL) {
+ unsigned long bits;
+
+ bits = get_bits(packet, offset, f->width);
+ printf("%s", f->value_names[bits]);
+ }
+ else if (f->width == 0) {
+ /* The way I'd like to do this instead is to have a way to
+ * specify which of the other fields designate the length of the
+ * data field. */
+ printf("%s=[", f->name);
+ dump_data((unsigned char *) packet + (offset / 8 + 4),
+ packet->write_block.data_length);
+ printf("] ");
+ }
+ else {
+ unsigned long long bits;
+ int high_width, low_width;
+
+ if ((offset & ~31) != ((offset + f->width - 1) & ~31)) {
+ /* Bit field spans quadlet boundary. */
+ high_width = ((offset + 31) & ~31) - offset;
+ low_width = f->width - high_width;
+
+ bits = get_bits(packet, offset, high_width);
+ bits = (bits << low_width) |
+ get_bits(packet, offset + high_width, low_width);
+ }
+ else
+ bits = get_bits(packet, offset, f->width);
+
+ printf("%s=0x%llx", f->name, bits);
+ }
+
+ if (i < pi->field_count - 1)
+ printf(", ");
+ }
+}
+
+static void
+print_packet(unsigned long *data, size_t length)
+{
+ int i;
+
+ printf("%6lu ", data[0]);
+
+ if (length == 4)
+ printf("bus reset");
+ else if (length < sizeof(struct phy_packet)) {
+ printf("short packet: ");
+ for (i = 1; i < length / 4; i++)
+ printf("%s%08lx", i == 0 ? "[" : " ", data[i]);
+ printf("]");
+
+ }
+ else if (length == sizeof(struct phy_packet)) {
+ struct phy_packet *pp = (struct phy_packet *) data;
+
+ /* phy packet are 3 quadlets: the 1 quadlet payload,
+ * the bitwise inverse of the payload and the snoop
+ * mode ack */
+
+ switch (pp->common.identifier) {
+ case PHY_PACKET_CONFIGURATION:
+ if (!pp->phy_config.set_root && !pp->phy_config.set_gap_count) {
+ printf("ext phy config: phy_id=%02x", pp->phy_config.root_id);
+ }
+ else {
+ printf("phy config:");
+ if (pp->phy_config.set_root)
+ printf(" set_root_id=%02x", pp->phy_config.root_id);
+ if (pp->phy_config.set_gap_count)
+ printf(" set_gap_count=%d", pp->phy_config.gap_count);
+ }
+ break;
+
+ case PHY_PACKET_LINK_ON:
+ printf("link-on packet, phy_id=%02x", pp->link_on.phy_id);
+ break;
+
+ case PHY_PACKET_SELF_ID:
+ if (pp->self_id.extended) {
+ printf("extended self id: phy_id=%02x, seq=%d",
+ pp->ext_self_id.phy_id, pp->ext_self_id.sequence);
+ }
+ else {
+ static const char * const speed_names[] = {
+ "S100", "S200", "S400", "BETA"
+ };
+ printf("self id: phy_id=%02x, link %s, gap_count=%d, speed=%s%s%s",
+ pp->self_id.phy_id,
+ (pp->self_id.link_active ? "active" : "not active"),
+ pp->self_id.gap_count,
+ speed_names[pp->self_id.phy_speed],
+ (pp->self_id.contender ? ", irm contender" : ""),
+ (pp->self_id.initiated_reset ? ", initiator" : ""));
+
+ }
+ break;
+ default:
+ printf("unknown phy packet: ");
+ for (i = 1; i < length / 4; i++)
+ printf("%s%08lx", i == 0 ? "[" : " ", data[i]);
+ printf("]");
+ break;
+ }
+ }
+ else {
+ struct link_packet *packet = (struct link_packet *) data;
+
+ decode_link_packet(packet, length);
+ }
+
+ if (option_hex) {
+ printf(" [");
+ dump_data((unsigned char *) data + 4, length - 4);
+ printf("]");
+ }
+
+ printf("\n");
+}
+
+#define HIDE_CURSOR "\033[?25l"
+#define SHOW_CURSOR "\033[?25h"
+#define CLEAR "\033[H\033[2J"
+
+static void
+print_stats(unsigned long *data, size_t length)
+{
+ static int bus_reset_count, short_packet_count, phy_packet_count;
+ static int tcode_count[16];
+ static struct timeval last_update;
+ struct timeval now;
+ int i;
+
+ if (length == 0)
+ bus_reset_count++;
+ else if (length < sizeof(struct phy_packet))
+ short_packet_count++;
+ else if (length == sizeof(struct phy_packet))
+ phy_packet_count++;
+ else {
+ struct link_packet *packet = (struct link_packet *) data;
+ tcode_count[packet->common.tcode]++;
+ }
+
+ gettimeofday(&now, NULL);
+ if (now.tv_sec <= last_update.tv_sec &&
+ now.tv_usec < last_update.tv_usec + 500000)
+ return;
+
+ last_update = now;
+ printf(CLEAR HIDE_CURSOR
+ " bus resets : %8d\n"
+ " short packets : %8d\n"
+ " phy packets : %8d\n",
+ bus_reset_count, short_packet_count, phy_packet_count);
+
+ for (i = 0; i < array_length(packet_info); i++)
+ if (packet_info[i].type != PACKET_RESERVED)
+ printf(" %-24s: %8d\n", packet_info[i].name, tcode_count[i]);
+ printf(SHOW_CURSOR "\n");
+}
+
+int main(int argc, const char *argv[])
+{
+ int fd;
+
+ poptContext con;
+ int retval;
+ int view;
+
+ con = poptGetContext(NULL, argc, argv, options, 0);
+ retval = poptGetNextOpt(con);
+ if (retval < -1) {
+ poptPrintUsage(con, stdout, 0);
+ return -1;
+ }
+
+ if (option_version) {
+ printf("dump tool for nosy sniffer, version %s\n", VERSION);
+ return 0;
+ }
+
+ if (__BYTE_ORDER != __LITTLE_ENDIAN)
+ fprintf(stderr, "warning: nosy has only been tested on little "
+ "endian machines\n");
+
+ fd = open(option_nosy_device, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Could not open %s, %m\n", option_nosy_device);
+ return -1;
+ }
+
+ if (strcmp(option_view, "transaction") == 0)
+ view = VIEW_TRANSACTION;
+ else if (strcmp(option_view, "stats") == 0)
+ view = VIEW_STATS;
+ else
+ view = VIEW_PACKET;
+
+ setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+
+ if (1) {
+ unsigned long buf[1024];
+ int length;
+
+ if (view == VIEW_STATS)
+ ioctl(fd, NOSY_IOC_FILTER, ~(1 << TCODE_CYCLE_START));
+ else
+ ioctl(fd, NOSY_IOC_FILTER, ~((1 << TCODE_CYCLE_START) |
+ (1 <<TCODE_ISO_DATA)));
+
+ ioctl(fd, NOSY_IOC_START);
+
+ while (1) {
+ length = read(fd, buf, sizeof buf);
+
+ switch (view) {
+ case VIEW_TRANSACTION:
+ handle_packet(buf + 4, length - 4);
+ break;
+ case VIEW_PACKET:
+ print_packet(buf, length);
+ break;
+ case VIEW_STATS:
+ print_stats(buf + 4, length - 4);
+ break;
+ }
+ }
+ }
+ else
+ poptPrintUsage(con, stdout, 0);
+
+ close(fd);
+
+ poptFreeContext(con);
+
+ return 0;
+}
diff --git a/nosy-user.h b/nosy-user.h
new file mode 100644
index 0000000..4cfe49f
--- /dev/null
+++ b/nosy-user.h
@@ -0,0 +1,51 @@
+#ifndef __pcilynx_user_h
+#define __pcilynx_user_h
+
+#include <asm/ioctl.h>
+#include <asm/types.h>
+
+#define NOSY_IOC_GET_STATS _IOR('&', 0, struct nosy_stats)
+#define NOSY_IOC_START _IO('&', 1)
+#define NOSY_IOC_STOP _IO('&', 2)
+#define NOSY_IOC_FILTER _IOW('&', 2, __u32)
+
+#define TCODE_WRITE_QUADLET 0x0
+#define TCODE_WRITE_BLOCK 0x1
+#define TCODE_WRITE_RESPONSE 0x2
+#define TCODE_READ_QUADLET 0x4
+#define TCODE_READ_BLOCK 0x5
+#define TCODE_READ_QUADLET_RESPONSE 0x6
+#define TCODE_READ_BLOCK_RESPONSE 0x7
+#define TCODE_CYCLE_START 0x8
+#define TCODE_LOCK_REQUEST 0x9
+#define TCODE_ISO_DATA 0xa
+#define TCODE_LOCK_RESPONSE 0xb
+#define TCODE_PHY_PACKET 0x10
+
+#define ACK_NO_ACK 0x0
+#define ACK_COMPLETE 0x1
+#define ACK_PENDING 0x2
+#define ACK_BUSY_X 0x4
+#define ACK_BUSY_A 0x5
+#define ACK_BUSY_B 0x6
+#define ACK_DATA_ERROR 0xd
+#define ACK_TYPE_ERROR 0xe
+
+#define ACK_DONE(a) ((a >> 2) == 0)
+#define ACK_BUSY(a) ((a >> 2) == 1)
+#define ACK_ERROR(a) ((a >> 2) == 3)
+
+struct nosy_stats {
+ __u32 total_packet_count;
+ __u32 lost_packet_count;
+};
+
+/*
+ * Format of packets returned from the kernel driver:
+ *
+ * quadlet with timestamp (microseconds)
+ * quadlet padded packet data...
+ * quadlet with ack
+ */
+
+#endif /* __pcilynx_user_h */
diff --git a/nosy.c b/nosy.c
new file mode 100644
index 0000000..e098503
--- /dev/null
+++ b/nosy.c
@@ -0,0 +1,708 @@
+/* -*- c-file-style: "linux" -*-
+ *
+ * nosy.c - Snoop mode driver for TI pcilynx 1394 controllers
+ * Copyright (C) 2002 Kristian Høgsberg
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <linux/errno.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/pci.h>
+#include <linux/fs.h>
+#include <linux/poll.h>
+#include <linux/cdev.h>
+#include <asm/byteorder.h>
+#include <asm/atomic.h>
+#include <asm/io.h>
+#include <asm/uaccess.h>
+#include <asm/timex.h>
+
+#include "nosy.h"
+#include "nosy-user.h"
+
+#define PCI_DEVICE_ID_TI_PCILYNX 0x8000
+#define NOSY_MAJOR 177
+
+#define notify(s, args...) printk(KERN_NOTICE s, ## args)
+#define error(s, args...) printk(KERN_ERR s, ## args)
+#define debug(s, args...) printk(KERN_DEBUG s, ## args)
+
+static const char driver_name[] = "nosy";
+
+struct pcl_status {
+ unsigned int transfer_count : 13;
+ unsigned int reserved0 : 1;
+ unsigned int ack_type : 1;
+ unsigned int ack : 4;
+ unsigned int rcv_speed : 2;
+ unsigned int rcv_dma_channel : 6;
+ unsigned int packet_complete : 1;
+ unsigned int packet_error : 1;
+ unsigned int master_error : 1;
+ unsigned int iso_mode : 1;
+ unsigned int self_id : 1;
+};
+
+/* this is the physical layout of a PCL, its size is 128 bytes */
+struct pcl {
+ u32 next;
+ u32 async_error_next;
+ u32 user_data;
+ struct pcl_status pcl_status;
+ u32 remaining_transfer_count;
+ u32 next_data_buffer;
+ struct {
+ u32 control;
+ u32 pointer;
+ } buffer[13] __attribute__ ((packed));
+} __attribute__ ((packed));
+
+struct packet {
+ unsigned int length : 16;
+ unsigned int code : 16;
+ char data[0];
+};
+
+struct packet_buffer {
+ char *data;
+ size_t capacity;
+ long total_packet_count, lost_packet_count;
+ atomic_t size;
+ struct packet *head, *tail;
+ wait_queue_head_t wait;
+};
+
+struct pcilynx {
+ struct pci_dev *pci_device;
+ unsigned char *registers;
+
+ struct pcl *rcv_start_pcl, *rcv_pcl;
+ u32 *rcv_buffer;
+
+ dma_addr_t rcv_start_pcl_bus, rcv_pcl_bus, rcv_buffer_bus;
+
+ spinlock_t client_list_lock;
+ struct list_head client_list;
+
+ struct class_device *class_device;
+ struct cdev char_device;
+ dev_t devno;
+};
+
+
+struct client {
+ struct pcilynx *lynx;
+ unsigned long tcode_mask;
+ struct packet_buffer buffer;
+ struct list_head link;
+};
+
+static int
+packet_buffer_init(struct packet_buffer *buffer, size_t capacity)
+{
+ buffer->data = kmalloc(capacity, GFP_KERNEL);
+ if (buffer->data == NULL)
+ return -ENOMEM;
+ buffer->head = (struct packet *) buffer->data;
+ buffer->tail = (struct packet *) buffer->data;
+ buffer->capacity = capacity;
+ buffer->lost_packet_count = 0;
+ atomic_set(&buffer->size, 0);
+ init_waitqueue_head(&buffer->wait);
+
+ return 0;
+}
+
+static void
+packet_buffer_destroy(struct packet_buffer *buffer)
+{
+ kfree(buffer->data);
+}
+
+static int
+packet_buffer_get(struct packet_buffer *buffer, void *data, size_t user_length)
+{
+ size_t length;
+ char *end;
+
+ if (wait_event_interruptible(buffer->wait,
+ atomic_read(&buffer->size) > 0))
+ return -ERESTARTSYS;
+
+ /* FIXME: Check length <= user_length. */
+
+ end = buffer->data + buffer->capacity;
+ length = buffer->head->length;
+
+ if (&buffer->head->data[length] < end) {
+ if (copy_to_user(data, buffer->head->data, length))
+ return -EFAULT;
+ buffer->head = (struct packet *) &buffer->head->data[length];
+ }
+ else {
+ size_t split = end - buffer->head->data;
+
+ if (copy_to_user(data, buffer->head->data, split))
+ return -EFAULT;
+ if (copy_to_user(data + split, buffer->data, length - split))
+ return -EFAULT;
+ buffer->head = (struct packet *) &buffer->data[length - split];
+ }
+
+ /* Decrease buffer->size as the last thing, since this is what
+ * keeps the interrupt from overwriting the packet we are
+ * retrieving from the buffer. */
+
+ atomic_sub(sizeof (struct packet) + length, &buffer->size);
+
+ return length;
+}
+
+static void
+packet_buffer_put(struct packet_buffer *buffer, void *data, size_t length)
+{
+ char *end;
+
+ buffer->total_packet_count++;
+
+ if (buffer->capacity <
+ atomic_read(&buffer->size) + sizeof (struct packet) + length) {
+ buffer->lost_packet_count++;
+ return;
+ }
+
+ end = buffer->data + buffer->capacity;
+ buffer->tail->length = length;
+
+ if (&buffer->tail->data[length] < end) {
+ memcpy(buffer->tail->data, data, length);
+ buffer->tail = (struct packet *) &buffer->tail->data[length];
+ }
+ else {
+ size_t split = end - buffer->tail->data;
+
+ memcpy(buffer->tail->data, data, split);
+ memcpy(buffer->data, data + split, length - split);
+ buffer->tail = (struct packet *) &buffer->data[length - split];
+ }
+
+ /* Finally, adjust buffer size and wake up userspace reader. */
+
+ atomic_add(sizeof (struct packet) + length, &buffer->size);
+ wake_up_interruptible(&buffer->wait);
+}
+
+static inline void
+reg_write(struct pcilynx *lynx, int offset, u32 data)
+{
+ writel(data, lynx->registers + offset);
+}
+
+static inline u32
+reg_read(struct pcilynx *lynx, int offset)
+{
+ return readl(lynx->registers + offset);
+}
+
+static inline void
+reg_set_bits(struct pcilynx *lynx, int offset, u32 mask)
+{
+ reg_write(lynx, offset, (reg_read(lynx, offset) | mask));
+}
+
+/* Maybe the pcl programs could be setup to just append data instead
+ * of using a whole packet. */
+
+static inline void
+run_pcl(struct pcilynx *lynx, dma_addr_t pcl_bus, int dmachan)
+{
+ reg_write(lynx, DMA0_CURRENT_PCL + dmachan * 0x20, pcl_bus);
+ reg_write(lynx, DMA0_CHAN_CTRL + dmachan * 0x20,
+ DMA_CHAN_CTRL_ENABLE | DMA_CHAN_CTRL_LINK);
+}
+
+static int
+set_phy_reg(struct pcilynx *lynx, int addr, int val)
+{
+ if (addr > 15) {
+ debug("%s: PHY register address %d out of range",
+ __FUNCTION__, addr);
+ return -1;
+ }
+
+ if (val > 0xff) {
+ debug("%s: PHY register value %d out of range",
+ __FUNCTION__, val);
+ return -1;
+ }
+
+ reg_write(lynx, LINK_PHY, LINK_PHY_WRITE |
+ LINK_PHY_ADDR(addr) | LINK_PHY_WDATA(val));
+
+ return 0;
+}
+
+static void
+nosy_start_snoop(struct client *client)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->lynx->client_list_lock, flags);
+ list_add_tail(&client->link, &client->lynx->client_list);
+ spin_unlock_irqrestore(&client->lynx->client_list_lock, flags);
+}
+
+static void
+nosy_stop_snoop(struct client *client)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&client->lynx->client_list_lock, flags);
+ list_del(&client->link);
+ spin_unlock_irqrestore(&client->lynx->client_list_lock, flags);
+}
+
+static struct client *
+nosy_add_client(struct pcilynx *lynx)
+{
+ struct client *client;
+
+ client = kmalloc(sizeof *client, GFP_KERNEL);
+ client->tcode_mask = ~0;
+ client->lynx = lynx;
+ INIT_LIST_HEAD(&client->link);
+
+ if (packet_buffer_init(&client->buffer, 128 * 1024) < 0) {
+ kfree(client);
+ debug("Failed to allocate packet buffer\n");
+ return NULL;
+ }
+
+ return client;
+}
+
+static void
+nosy_remove_client(struct client *client)
+{
+ nosy_stop_snoop(client);
+ packet_buffer_destroy(&client->buffer);
+ kfree(client);
+}
+
+static int
+nosy_open(struct inode *inode, struct file *file)
+{
+ struct pcilynx *lynx;
+
+ lynx = container_of(inode->i_cdev, struct pcilynx, char_device);
+ file->private_data = nosy_add_client(lynx);
+
+ if (file->private_data == NULL)
+ return -ENOMEM;
+ else
+ return 0;
+}
+
+static int
+nosy_release(struct inode *inode, struct file *file)
+{
+ nosy_remove_client(file->private_data);
+
+ return 0;
+}
+
+static unsigned int
+nosy_poll(struct file *file, poll_table *pt)
+{
+ struct client *client = file->private_data;
+
+ poll_wait(file, &client->buffer.wait, pt);
+
+ if (atomic_read(&client->buffer.size) > 0)
+ return POLLIN | POLLRDNORM;
+ else
+ return 0;
+}
+
+static ssize_t
+nosy_read(struct file *file, char *buffer, size_t count, loff_t *offset)
+{
+ struct client *client = file->private_data;
+
+ return packet_buffer_get(&client->buffer, buffer, count);
+}
+
+static int
+nosy_ioctl(struct inode *inode, struct file *file,
+ unsigned int cmd, unsigned long arg)
+{
+ struct client *client = file->private_data;
+
+ switch (cmd) {
+ case NOSY_IOC_GET_STATS: {
+ struct nosy_stats stats;
+
+ stats.total_packet_count = client->buffer.total_packet_count;
+ stats.lost_packet_count = client->buffer.lost_packet_count;
+ if (copy_to_user((void *) arg, &stats, sizeof stats))
+ return -EFAULT;
+ else
+ return 0;
+ }
+
+ case NOSY_IOC_START:
+ nosy_start_snoop(client);
+ return 0;
+
+ case NOSY_IOC_STOP:
+ nosy_stop_snoop(client);
+ return 0;
+
+ case NOSY_IOC_FILTER:
+ client->tcode_mask = arg;
+ return 0;
+
+ default:
+ return -EINVAL;
+ /* Flush buffer, configure filter. */
+ }
+}
+
+static struct file_operations nosy_ops = {
+ .owner = THIS_MODULE,
+ .read = nosy_read,
+ .ioctl = nosy_ioctl,
+ .poll = nosy_poll,
+ .open = nosy_open,
+ .release = nosy_release,
+};
+
+struct class nosy_class = {
+ .name = "nosy",
+ .release = NULL,
+};
+
+#define PHY_PACKET_SIZE 12 /* 1 payload, 1 inverse, 1 ack = 3 quadlets */
+
+struct link_packet {
+ unsigned int priority : 4;
+ unsigned int tcode : 4;
+ unsigned int rt : 2;
+ unsigned int tlabel : 6;
+ unsigned int destination : 16;
+};
+
+static void
+packet_handler(struct pcilynx *lynx)
+{
+ unsigned long flags;
+ struct list_head *pos;
+ struct client *client;
+ unsigned long tcode_mask;
+ size_t length;
+ struct link_packet *packet;
+ struct timeval tv;
+
+ /* FIXME: Also report rcv_speed. */
+
+ length = lynx->rcv_pcl->pcl_status.transfer_count;
+ packet = (struct link_packet *) &lynx->rcv_buffer[1];
+
+ do_gettimeofday(&tv);
+ lynx->rcv_buffer[0] = tv.tv_usec;
+
+ if (length == PHY_PACKET_SIZE)
+ tcode_mask = 1 << TCODE_PHY_PACKET;
+ else
+ tcode_mask = 1 << packet->tcode;
+
+ spin_lock_irqsave(&lynx->client_list_lock, flags);
+
+ list_for_each(pos, &lynx->client_list) {
+ client = list_entry(pos, struct client, link);
+ if (client->tcode_mask & tcode_mask)
+ packet_buffer_put(&client->buffer,
+ lynx->rcv_buffer, length + 4);
+ }
+
+ spin_unlock_irqrestore(&lynx->client_list_lock, flags);
+}
+
+static void
+bus_reset_handler(struct pcilynx *lynx)
+{
+ unsigned long flags;
+ struct list_head *pos;
+ struct client *client;
+ struct timeval tv;
+
+ do_gettimeofday(&tv);
+
+ spin_lock_irqsave(&lynx->client_list_lock, flags);
+
+ list_for_each(pos, &lynx->client_list) {
+ client = list_entry(pos, struct client, link);
+ packet_buffer_put(&client->buffer, &tv.tv_usec, 4);
+ }
+
+ spin_unlock_irqrestore(&lynx->client_list_lock, flags);
+}
+
+
+
+static irqreturn_t
+irq_handler(int irq, void *device, struct pt_regs *unused)
+{
+ struct pcilynx *lynx = (struct pcilynx *) device;
+ u32 pci_int_status;
+
+ pci_int_status = reg_read(lynx, PCI_INT_STATUS);
+
+ if ((pci_int_status & PCI_INT_INT_PEND) == 0)
+ /* Not our interrupt, bail out quickly. */
+ return IRQ_NONE;
+
+ if ((pci_int_status & PCI_INT_P1394_INT) != 0) {
+ u32 link_int_status;
+
+ link_int_status = reg_read(lynx, LINK_INT_STATUS);
+ reg_write(lynx, LINK_INT_STATUS, link_int_status);
+
+ if ((link_int_status & LINK_INT_PHY_BUSRESET) > 0)
+ bus_reset_handler(lynx);
+ }
+
+ /* Clear the PCI_INT_STATUS register only after clearing the
+ * LINK_INT_STATUS register; otherwise the PCI_INT_P1394 will
+ * be set again immediately. */
+
+ reg_write(lynx, PCI_INT_STATUS, pci_int_status);
+
+ if ((pci_int_status & PCI_INT_DMA0_HLT) > 0) {
+ packet_handler(lynx);
+ run_pcl(lynx, lynx->rcv_start_pcl_bus, 0);
+ }
+
+ return IRQ_HANDLED;
+}
+
+static void
+remove_card(struct pci_dev *dev)
+{
+ struct pcilynx *lynx;
+
+ lynx = pci_get_drvdata(dev);
+ if (!lynx)
+ return;
+ pci_set_drvdata(dev, NULL);
+
+ reg_write(lynx, PCI_INT_ENABLE, 0);
+ free_irq(lynx->pci_device->irq, lynx);
+
+ pci_free_consistent(lynx->pci_device, sizeof (struct pcl),
+ lynx->rcv_start_pcl, lynx->rcv_start_pcl_bus);
+ pci_free_consistent(lynx->pci_device, sizeof (struct pcl),
+ lynx->rcv_pcl, lynx->rcv_pcl_bus);
+ pci_free_consistent(lynx->pci_device, PAGE_SIZE,
+ lynx->rcv_buffer, lynx->rcv_buffer_bus);
+
+ iounmap(lynx->registers);
+
+ class_device_unregister(lynx->class_device);
+ cdev_del(&lynx->char_device);
+
+ kfree(lynx);
+}
+
+
+#define FAIL(s, args...) \
+ do { \
+ error(s, ## args); \
+ return err; \
+ } while (0)
+
+static int __devinit
+add_card(struct pci_dev *dev, const struct pci_device_id *unused)
+{
+ struct pcilynx *lynx;
+ int err;
+
+ err = -ENXIO;
+
+ if (pci_set_dma_mask(dev, 0xffffffff))
+ FAIL("DMA address limits not supported "
+ "for PCILynx hardware.\n");
+ if (pci_enable_device(dev))
+ FAIL("Failed to enable PCILynx hardware.\n");
+ pci_set_master(dev);
+
+ err = -ENOMEM;
+
+ lynx = kmalloc(sizeof *lynx, GFP_KERNEL);
+ if (lynx == NULL)
+ FAIL("Failed to allocate control structure memory.\n");
+
+ lynx->pci_device = dev;
+ pci_set_drvdata(dev, lynx);
+
+ spin_lock_init(&lynx->client_list_lock);
+ INIT_LIST_HEAD(&lynx->client_list);
+
+ lynx->registers = ioremap_nocache(pci_resource_start(dev, 0),
+ PCILYNX_MAX_REGISTER);
+
+ lynx->rcv_start_pcl = pci_alloc_consistent(lynx->pci_device,
+ sizeof(struct pcl),
+ &lynx->rcv_start_pcl_bus);
+ lynx->rcv_pcl = pci_alloc_consistent(lynx->pci_device,
+ sizeof(struct pcl),
+ &lynx->rcv_pcl_bus);
+ lynx->rcv_buffer = pci_alloc_consistent(lynx->pci_device, PAGE_SIZE,
+ &lynx->rcv_buffer_bus);
+ if (lynx->rcv_start_pcl == NULL ||
+ lynx->rcv_pcl == NULL ||
+ lynx->rcv_buffer == NULL)
+ /* FIXME: do proper error handling. */
+ FAIL("Failed to allocate receive buffer.\n");
+
+ lynx->rcv_start_pcl->next = lynx->rcv_pcl_bus;
+ lynx->rcv_pcl->next = PCL_NEXT_INVALID;
+ lynx->rcv_pcl->async_error_next = PCL_NEXT_INVALID;
+ lynx->rcv_pcl->buffer[0].control = PCL_CMD_RCV | PCL_BIGENDIAN | 2048;
+ lynx->rcv_pcl->buffer[0].pointer = lynx->rcv_buffer_bus + 4;
+
+ reg_set_bits(lynx, MISC_CONTROL, MISC_CONTROL_SWRESET);
+ /* Fix buggy cards with autoboot pin not tied low: */
+ reg_write(lynx, DMA0_CHAN_CTRL, 0);
+ reg_write(lynx, DMA_GLOBAL_REGISTER, 0x00 << 24);
+
+#if 0
+ /* now, looking for PHY register set */
+ if ((get_phy_reg(lynx, 2) & 0xe0) == 0xe0) {
+ lynx->phyic.reg_1394a = 1;
+ PRINT(KERN_INFO, lynx->id,
+ "found 1394a conform PHY (using extended register set)");
+ lynx->phyic.vendor = get_phy_vendorid(lynx);
+ lynx->phyic.product = get_phy_productid(lynx);
+ } else {
+ lynx->phyic.reg_1394a = 0;
+ PRINT(KERN_INFO, lynx->id, "found old 1394 PHY");
+ }
+#endif
+
+ /* Setup the general receive FIFO max size. */
+ reg_write(lynx, FIFO_SIZES, 255);
+
+ reg_set_bits(lynx, PCI_INT_ENABLE, PCI_INT_DMA_ALL);
+
+ reg_write(lynx, LINK_INT_ENABLE,
+ LINK_INT_PHY_TIME_OUT | LINK_INT_PHY_REG_RCVD |
+ LINK_INT_PHY_BUSRESET | LINK_INT_IT_STUCK |
+ LINK_INT_AT_STUCK | LINK_INT_SNTRJ |
+ LINK_INT_TC_ERR | LINK_INT_GRF_OVER_FLOW |
+ LINK_INT_ITF_UNDER_FLOW | LINK_INT_ATF_UNDER_FLOW);
+
+ /* Disable the L flag in self ID packets. */
+ set_phy_reg(lynx, 4, 0);
+
+ /* Put this baby into snoop mode */
+ reg_set_bits(lynx, LINK_CONTROL, LINK_CONTROL_SNOOP_ENABLE);
+
+ run_pcl(lynx, lynx->rcv_start_pcl_bus, 0);
+
+ if (request_irq(dev->irq, irq_handler, SA_SHIRQ, driver_name, lynx))
+ FAIL("Failed to allocate shared interrupt %d.", dev->irq);
+
+ err = alloc_chrdev_region(&lynx->devno, 0, 1, "nosy");
+ if (err)
+ FAIL("Failed to register char device region.");
+
+ cdev_init(&lynx->char_device, &nosy_ops);
+ lynx->char_device.owner = THIS_MODULE;
+ kobject_set_name(&lynx->char_device.kobj, "nosy");
+ err = cdev_add(&lynx->char_device, lynx->devno, 1);
+ if (err)
+ FAIL("Failed to register char device.");
+
+ lynx->class_device = class_device_create(&nosy_class, NULL,
+ lynx->devno,
+ &lynx->pci_device->dev,
+ (char *) driver_name);
+
+ notify("Initialized PCILynx IEEE1394 card, irq=%d\n", dev->irq);
+
+ return 0;
+}
+
+static struct pci_device_id pci_table[] __devinitdata = {
+ {
+ .vendor = PCI_VENDOR_ID_TI,
+ .device = PCI_DEVICE_ID_TI_PCILYNX,
+ .subvendor = PCI_ANY_ID,
+ .subdevice = PCI_ANY_ID,
+ },
+ { } /* Terminating entry */
+};
+
+static struct pci_driver lynx_pci_driver = {
+ .name = (char *) driver_name,
+ .id_table = pci_table,
+ .probe = add_card,
+ .remove = __devexit_p(remove_card),
+};
+
+MODULE_AUTHOR("Kristian Høgsberg");
+MODULE_DESCRIPTION("Snoop mode driver for TI pcilynx 1394 controllers");
+MODULE_LICENSE("GPL");
+MODULE_DEVICE_TABLE(pci, pci_table);
+
+static int __init nosy_init(void)
+{
+ int ret;
+
+ ret = class_register(&nosy_class);
+ if (ret) {
+ error("Unable to register nosy class\n");
+ return ret;
+ }
+
+ ret = pci_register_driver(&lynx_pci_driver);
+ if (ret < 0) {
+ error("PCI module init failed\n");
+ class_unregister(&nosy_class);
+ }
+
+ notify("Loaded %s version %s.\n", driver_name, VERSION);
+
+ return ret;
+}
+
+static void __exit nosy_cleanup(void)
+{
+ pci_unregister_driver(&lynx_pci_driver);
+ class_unregister(&nosy_class);
+
+ notify("Unloaded %s.\n", driver_name);
+}
+
+
+module_init(nosy_init);
+module_exit(nosy_cleanup);
diff --git a/nosy.h b/nosy.h
new file mode 100644
index 0000000..3440071
--- /dev/null
+++ b/nosy.h
@@ -0,0 +1,238 @@
+/* Chip register definitions for PCILynx chipset. Based on pcilynx.h
+ * from the Linux 1394 drivers, but modified a bit so the names here
+ * match the specification exactly (even though they have weird names,
+ * like xxx_OVER_FLOW, or arbitrary abbreviations like SNTRJ for "sent
+ * reject" etc.)
+ */
+
+#define PCILYNX_MAX_REGISTER 0xfff
+#define PCILYNX_MAX_MEMORY 0xffff
+
+#define PCI_LATENCY_CACHELINE 0x0c
+
+#define MISC_CONTROL 0x40
+#define MISC_CONTROL_SWRESET (1<<0)
+
+#define SERIAL_EEPROM_CONTROL 0x44
+
+#define PCI_INT_STATUS 0x48
+#define PCI_INT_ENABLE 0x4c
+/* status and enable have identical bit numbers */
+#define PCI_INT_INT_PEND (1<<31)
+#define PCI_INT_FRC_INT (1<<30)
+#define PCI_INT_SLV_ADR_PERR (1<<28)
+#define PCI_INT_SLV_DAT_PERR (1<<27)
+#define PCI_INT_MST_DAT_PERR (1<<26)
+#define PCI_INT_MST_DEV_TO (1<<25)
+#define PCI_INT_INT_SLV_TO (1<<23)
+#define PCI_INT_AUX_TO (1<<18)
+#define PCI_INT_AUX_INT (1<<17)
+#define PCI_INT_P1394_INT (1<<16)
+#define PCI_INT_DMA4_PCL (1<<9)
+#define PCI_INT_DMA4_HLT (1<<8)
+#define PCI_INT_DMA3_PCL (1<<7)
+#define PCI_INT_DMA3_HLT (1<<6)
+#define PCI_INT_DMA2_PCL (1<<5)
+#define PCI_INT_DMA2_HLT (1<<4)
+#define PCI_INT_DMA1_PCL (1<<3)
+#define PCI_INT_DMA1_HLT (1<<2)
+#define PCI_INT_DMA0_PCL (1<<1)
+#define PCI_INT_DMA0_HLT (1<<0)
+/* all DMA interrupts combined: */
+#define PCI_INT_DMA_ALL 0x3ff
+
+#define PCI_INT_DMA_HLT(chan) (1 << (chan * 2))
+#define PCI_INT_DMA_PCL(chan) (1 << (chan * 2 + 1))
+
+#define LBUS_ADDR 0xb4
+#define LBUS_ADDR_SEL_RAM (0x0<<16)
+#define LBUS_ADDR_SEL_ROM (0x1<<16)
+#define LBUS_ADDR_SEL_AUX (0x2<<16)
+#define LBUS_ADDR_SEL_ZV (0x3<<16)
+
+#define GPIO_CTRL_A 0xb8
+#define GPIO_CTRL_B 0xbc
+#define GPIO_DATA_BASE 0xc0
+
+#define DMA_BREG(base, chan) (base + chan * 0x20)
+#define DMA_SREG(base, chan) (base + chan * 0x10)
+
+#define PCL_NEXT_INVALID (1<<0)
+
+/* transfer commands */
+#define PCL_CMD_RCV (0x1<<24)
+#define PCL_CMD_RCV_AND_UPDATE (0xa<<24)
+#define PCL_CMD_XMT (0x2<<24)
+#define PCL_CMD_UNFXMT (0xc<<24)
+#define PCL_CMD_PCI_TO_LBUS (0x8<<24)
+#define PCL_CMD_LBUS_TO_PCI (0x9<<24)
+
+/* aux commands */
+#define PCL_CMD_NOP (0x0<<24)
+#define PCL_CMD_LOAD (0x3<<24)
+#define PCL_CMD_STOREQ (0x4<<24)
+#define PCL_CMD_STORED (0xb<<24)
+#define PCL_CMD_STORE0 (0x5<<24)
+#define PCL_CMD_STORE1 (0x6<<24)
+#define PCL_CMD_COMPARE (0xe<<24)
+#define PCL_CMD_SWAP_COMPARE (0xf<<24)
+#define PCL_CMD_ADD (0xd<<24)
+#define PCL_CMD_BRANCH (0x7<<24)
+
+/* BRANCH condition codes */
+#define PCL_COND_DMARDY_SET (0x1<<20)
+#define PCL_COND_DMARDY_CLEAR (0x2<<20)
+
+#define PCL_GEN_INTR (1<<19)
+#define PCL_LAST_BUFF (1<<18)
+#define PCL_LAST_CMD (PCL_LAST_BUFF)
+#define PCL_WAITSTAT (1<<17)
+#define PCL_BIGENDIAN (1<<16)
+#define PCL_ISOMODE (1<<12)
+
+#define DMA0_PREV_PCL 0x100
+#define DMA1_PREV_PCL 0x120
+#define DMA2_PREV_PCL 0x140
+#define DMA3_PREV_PCL 0x160
+#define DMA4_PREV_PCL 0x180
+#define DMA_PREV_PCL(chan) (DMA_BREG(DMA0_PREV_PCL, chan))
+
+#define DMA0_CURRENT_PCL 0x104
+#define DMA1_CURRENT_PCL 0x124
+#define DMA2_CURRENT_PCL 0x144
+#define DMA3_CURRENT_PCL 0x164
+#define DMA4_CURRENT_PCL 0x184
+#define DMA_CURRENT_PCL(chan) (DMA_BREG(DMA0_CURRENT_PCL, chan))
+
+#define DMA0_CHAN_STAT 0x10c
+#define DMA1_CHAN_STAT 0x12c
+#define DMA2_CHAN_STAT 0x14c
+#define DMA3_CHAN_STAT 0x16c
+#define DMA4_CHAN_STAT 0x18c
+#define DMA_CHAN_STAT(chan) (DMA_BREG(DMA0_CHAN_STAT, chan))
+/* CHAN_STATUS registers share bits */
+#define DMA_CHAN_STAT_SELFID (1<<31)
+#define DMA_CHAN_STAT_ISOPKT (1<<30)
+#define DMA_CHAN_STAT_PCIERR (1<<29)
+#define DMA_CHAN_STAT_PKTERR (1<<28)
+#define DMA_CHAN_STAT_PKTCMPL (1<<27)
+#define DMA_CHAN_STAT_SPECIALACK (1<<14)
+
+
+#define DMA0_CHAN_CTRL 0x110
+#define DMA1_CHAN_CTRL 0x130
+#define DMA2_CHAN_CTRL 0x150
+#define DMA3_CHAN_CTRL 0x170
+#define DMA4_CHAN_CTRL 0x190
+#define DMA_CHAN_CTRL(chan) (DMA_BREG(DMA0_CHAN_CTRL, chan))
+/* CHAN_CTRL registers share bits */
+#define DMA_CHAN_CTRL_ENABLE (1<<31)
+#define DMA_CHAN_CTRL_BUSY (1<<30)
+#define DMA_CHAN_CTRL_LINK (1<<29)
+
+#define DMA0_READY 0x114
+#define DMA1_READY 0x134
+#define DMA2_READY 0x154
+#define DMA3_READY 0x174
+#define DMA4_READY 0x194
+#define DMA_READY(chan) (DMA_BREG(DMA0_READY, chan))
+
+#define DMA_GLOBAL_REGISTER 0x908
+
+#define FIFO_SIZES 0xa00
+
+#define FIFO_CONTROL 0xa10
+#define FIFO_CONTROL_GRF_FLUSH (1<<4)
+#define FIFO_CONTROL_ITF_FLUSH (1<<3)
+#define FIFO_CONTROL_ATF_FLUSH (1<<2)
+
+#define FIFO_XMIT_THRESHOLD 0xa14
+
+#define DMA0_WORD0_CMP_VALUE 0xb00
+#define DMA1_WORD0_CMP_VALUE 0xb10
+#define DMA2_WORD0_CMP_VALUE 0xb20
+#define DMA3_WORD0_CMP_VALUE 0xb30
+#define DMA4_WORD0_CMP_VALUE 0xb40
+#define DMA_WORD0_CMP_VALUE(chan) (DMA_SREG(DMA0_WORD0_CMP_VALUE, chan))
+
+#define DMA0_WORD0_CMP_ENABLE 0xb04
+#define DMA1_WORD0_CMP_ENABLE 0xb14
+#define DMA2_WORD0_CMP_ENABLE 0xb24
+#define DMA3_WORD0_CMP_ENABLE 0xb34
+#define DMA4_WORD0_CMP_ENABLE 0xb44
+#define DMA_WORD0_CMP_ENABLE(chan) (DMA_SREG(DMA0_WORD0_CMP_ENABLE,chan))
+
+#define DMA0_WORD1_CMP_VALUE 0xb08
+#define DMA1_WORD1_CMP_VALUE 0xb18
+#define DMA2_WORD1_CMP_VALUE 0xb28
+#define DMA3_WORD1_CMP_VALUE 0xb38
+#define DMA4_WORD1_CMP_VALUE 0xb48
+#define DMA_WORD1_CMP_VALUE(chan) (DMA_SREG(DMA0_WORD1_CMP_VALUE, chan))
+
+#define DMA0_WORD1_CMP_ENABLE 0xb0c
+#define DMA1_WORD1_CMP_ENABLE 0xb1c
+#define DMA2_WORD1_CMP_ENABLE 0xb2c
+#define DMA3_WORD1_CMP_ENABLE 0xb3c
+#define DMA4_WORD1_CMP_ENABLE 0xb4c
+#define DMA_WORD1_CMP_ENABLE(chan) (DMA_SREG(DMA0_WORD1_CMP_ENABLE,chan))
+/* word 1 compare enable flags */
+#define DMA_WORD1_CMP_MATCH_OTHERBUS (1<<15)
+#define DMA_WORD1_CMP_MATCH_BROADCAST (1<<14)
+#define DMA_WORD1_CMP_MATCH_BUS_BCAST (1<<13)
+#define DMA_WORD1_CMP_MATCH_LOCAL_NODE (1<<12)
+#define DMA_WORD1_CMP_MATCH_EXACT (1<<11)
+#define DMA_WORD1_CMP_ENABLE_SELF_ID (1<<10)
+#define DMA_WORD1_CMP_ENABLE_MASTER (1<<8)
+
+#define LINK_ID 0xf00
+#define LINK_ID_BUS(id) (id<<22)
+#define LINK_ID_NODE(id) (id<<16)
+
+#define LINK_CONTROL 0xf04
+#define LINK_CONTROL_BUSY (1<<29)
+#define LINK_CONTROL_TX_ISO_EN (1<<26)
+#define LINK_CONTROL_RX_ISO_EN (1<<25)
+#define LINK_CONTROL_TX_ASYNC_EN (1<<24)
+#define LINK_CONTROL_RX_ASYNC_EN (1<<23)
+#define LINK_CONTROL_RESET_TX (1<<21)
+#define LINK_CONTROL_RESET_RX (1<<20)
+#define LINK_CONTROL_CYCMASTER (1<<11)
+#define LINK_CONTROL_CYCSOURCE (1<<10)
+#define LINK_CONTROL_CYCTIMEREN (1<<9)
+#define LINK_CONTROL_RCV_CMP_VALID (1<<7)
+#define LINK_CONTROL_SNOOP_ENABLE (1<<6)
+
+#define CYCLE_TIMER 0xf08
+
+#define LINK_PHY 0xf0c
+#define LINK_PHY_READ (1<<31)
+#define LINK_PHY_WRITE (1<<30)
+#define LINK_PHY_ADDR(addr) (addr<<24)
+#define LINK_PHY_WDATA(data) (data<<16)
+#define LINK_PHY_RADDR(addr) (addr<<8)
+
+
+#define LINK_INT_STATUS 0xf14
+#define LINK_INT_ENABLE 0xf18
+/* status and enable have identical bit numbers */
+#define LINK_INT_LINK_INT (1<<31)
+#define LINK_INT_PHY_TIME_OUT (1<<30)
+#define LINK_INT_PHY_REG_RCVD (1<<29)
+#define LINK_INT_PHY_BUSRESET (1<<28)
+#define LINK_INT_TX_RDY (1<<26)
+#define LINK_INT_RX_DATA_RDY (1<<25)
+#define LINK_INT_IT_STUCK (1<<20)
+#define LINK_INT_AT_STUCK (1<<19)
+#define LINK_INT_SNTRJ (1<<17)
+#define LINK_INT_HDR_ERR (1<<16)
+#define LINK_INT_TC_ERR (1<<15)
+#define LINK_INT_CYC_SEC (1<<11)
+#define LINK_INT_CYC_STRT (1<<10)
+#define LINK_INT_CYC_DONE (1<<9)
+#define LINK_INT_CYC_PEND (1<<8)
+#define LINK_INT_CYC_LOST (1<<7)
+#define LINK_INT_CYC_ARB_FAILED (1<<6)
+#define LINK_INT_GRF_OVER_FLOW (1<<5)
+#define LINK_INT_ITF_UNDER_FLOW (1<<4)
+#define LINK_INT_ATF_UNDER_FLOW (1<<3)
+#define LINK_INT_IARB_FAILED (1<<0)