diff options
author | Kristian Høgsberg <krh@redhat.com> | 2006-10-23 22:41:42 -0400 |
---|---|---|
committer | Kristian Høgsberg <krh@redhat.com> | 2006-10-23 22:41:42 -0400 |
commit | 3bf76d9d76687b2bbbfa5c5d1a5621ae7faa3b4e (patch) | |
tree | d071b78b2dbb1bd40617f4bc71c4f1f02319b69a |
Pull over nosy from mercurial repo.
-rw-r--r-- | Makefile | 46 | ||||
-rw-r--r-- | list.h | 62 | ||||
-rw-r--r-- | nosy-dump.c | 1032 | ||||
-rw-r--r-- | nosy-user.h | 51 | ||||
-rw-r--r-- | nosy.c | 708 | ||||
-rw-r--r-- | nosy.h | 238 |
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 @@ -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 */ @@ -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); @@ -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) |