diff options
Diffstat (limited to 'drivers/ieee1394/ieee1394_core.c')
-rw-r--r-- | drivers/ieee1394/ieee1394_core.c | 698 |
1 files changed, 698 insertions, 0 deletions
diff --git a/drivers/ieee1394/ieee1394_core.c b/drivers/ieee1394/ieee1394_core.c new file mode 100644 index 000000000..eccbe8933 --- /dev/null +++ b/drivers/ieee1394/ieee1394_core.c @@ -0,0 +1,698 @@ +/* + * IEEE 1394 for Linux + * + * Core support: hpsb_packet management, packet handling and forwarding to + * csr or lowlevel code + * + * Copyright (C) 1999 Andreas E. Bombe + */ + +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/string.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/interrupt.h> +#include <asm/bitops.h> +#include <asm/byteorder.h> +#include <asm/semaphore.h> + +#include "ieee1394_types.h" +#include "ieee1394.h" +#include "hosts.h" +#include "ieee1394_core.h" +#include "highlevel.h" +#include "ieee1394_transactions.h" +#include "csr.h" + + +atomic_t hpsb_generation = ATOMIC_INIT(0); + + +static void dump_packet(const char *text, quadlet_t *data, int size) +{ + int i; + + size /= 4; + size = (size > 4 ? 4 : size); + + printk(KERN_DEBUG "ieee1394: %s", text); + for (i = 0; i < size; i++) { + printk(" %8.8x", data[i]); + } + printk("\n"); +} + + +struct hpsb_packet *alloc_hpsb_packet(size_t data_size) +{ + struct hpsb_packet *packet = NULL; + void *header = NULL, *data = NULL; + int kmflags = in_interrupt() ? GFP_ATOMIC : GFP_KERNEL; + + packet = kmalloc(sizeof(struct hpsb_packet), kmflags); + header = kmalloc(5 * 4, kmflags); + if (header == NULL || packet == NULL) { + kfree(header); + kfree(packet); + return NULL; + } + + memset(packet, 0, sizeof(struct hpsb_packet)); + packet->header = header; + + if (data_size) { + data = kmalloc(data_size + 4, kmflags); + if (data == NULL) { + kfree(header); + kfree(packet); + return NULL; + } + + packet->data = data; + packet->data_size = data_size - 4; + } + + INIT_LIST_HEAD(&packet->list); + sema_init(&packet->state_change, 0); + packet->state = unused; + packet->generation = get_hpsb_generation(); + +#ifdef __BIG_ENDIAN + /* set default */ + packet->data_be = 1; +#endif + + return packet; +} + +void free_hpsb_packet(struct hpsb_packet *packet) +{ + if (packet == NULL) { + return; + } + + kfree(packet->data); + kfree(packet->header); + kfree(packet); +} + + +void reset_host_bus(struct hpsb_host *host) +{ + if (!host->initialized) { + return; + } + + hpsb_bus_reset(host); + host->template->devctl(host, RESET_BUS, 0); +} + + +void hpsb_bus_reset(struct hpsb_host *host) +{ + if (!host->in_bus_reset) { + abort_requests(host); + host->in_bus_reset = 1; + host->irm_id = -1; + host->busmgr_id = -1; + host->node_count = 0; + host->selfid_count = 0; + } else { + HPSB_NOTICE(__FUNCTION__ + " called while bus reset already in progress"); + } +} + + +/* + * Verify num_of_selfids SelfIDs and return number of nodes. Return zero in + * case verification failed. + */ +static int check_selfids(struct hpsb_host *host, unsigned int num_of_selfids) +{ + int nodeid = -1; + int rest_of_selfids = num_of_selfids; + quadlet_t *sidp = host->topology_map; + quadlet_t sid = *sidp; + int esid_seq = 23; + int i; + + while (rest_of_selfids--) { + sid = *(sidp++); + + if (!(sid & 0x00800000) /* !extended */) { + nodeid++; + esid_seq = 0; + + if (((sid >> 24) & NODE_MASK) != nodeid) { + HPSB_INFO("SelfIDs failed monotony check with " + "%d", (sid >> 24) & NODE_MASK); + return 0; + } + + /* "if is contender and link active" */ + if ((sid & (1<<11)) && (sid & (1<<22))) { + host->irm_id = LOCAL_BUS | ((sid >> 24) + & NODE_MASK); + } + } else { + if ((((sid >> 24) & NODE_MASK) != nodeid) + || (((sid >> 20) & 0x7) != esid_seq)) { + HPSB_INFO("SelfIDs failed monotony check with " + "%d/%d", (sid >> 24) & NODE_MASK, + (sid >> 20) & 0x7); + return 0; + } + esid_seq++; + } + } + + sidp--; + while (sid & 0x00800000 /* extended */) { + /* check that no ports go to a parent */ + for (i = 2; i < 18; i += 2) { + if ((sid & (0x3 << i)) == (0x2 << i)) { + HPSB_INFO("SelfIDs failed root check on " + "extended SelfID"); + return 0; + } + } + sid = *(sidp--); + } + + for (i = 2; i < 8; i += 2) { + if ((sid & (0x3 << i)) == (0x2 << i)) { + HPSB_INFO("SelfIDs failed root check"); + return 0; + } + } + + return nodeid + 1; +} + +void hpsb_selfid_received(struct hpsb_host *host, quadlet_t sid) +{ + if (host->in_bus_reset) { + printk("including selfid 0x%x\n", sid); + host->topology_map[host->selfid_count++] = sid; + } else { + /* FIXME - info on which host */ + HPSB_NOTICE("spurious selfid packet (0x%8.8x) received", sid); + } +} + +void hpsb_selfid_complete(struct hpsb_host *host, int phyid, int isroot) +{ + + + host->node_id = 0xffc0 | phyid; + host->in_bus_reset = 0; + host->is_root = isroot; + + host->node_count = check_selfids(host, host->selfid_count); + if (!host->node_count) { + if (host->reset_retries++ < 20) { + /* selfid stage did not complete without error */ + HPSB_NOTICE("error in SelfID stage - resetting"); + reset_host_bus(host); + return; + } else { + HPSB_NOTICE("stopping out-of-control reset loop"); + HPSB_NOTICE("warning - topology map will therefore not " + "be valid"); + } + } + + /* irm_id is kept up to date by check_selfids() */ + host->is_irm = (host->irm_id == host->node_id); + + host->reset_retries = 0; + inc_hpsb_generation(); + highlevel_host_reset(host); +} + + +void hpsb_packet_sent(struct hpsb_host *host, struct hpsb_packet *packet, + int ackcode) +{ + unsigned long flags; + + packet->ack_code = ackcode; + + if (packet->no_waiter) { + /* must not have a tlabel allocated */ + free_hpsb_packet(packet); + return; + } + + if (ackcode != ACK_PENDING || !packet->expect_response) { + packet->state = complete; + up(&packet->state_change); + up(&packet->state_change); + run_task_queue(&packet->complete_tq); + return; + } + + packet->state = pending; + packet->sendtime = jiffies; + + spin_lock_irqsave(&host->pending_pkt_lock, flags); + list_add_tail(&packet->list, &host->pending_packets); + spin_unlock_irqrestore(&host->pending_pkt_lock, flags); + + up(&packet->state_change); + queue_task(&host->timeout_tq, &tq_timer); +} + +int hpsb_send_packet(struct hpsb_packet *packet) +{ + struct hpsb_host *host = packet->host; + + if (!host->initialized || host->in_bus_reset + || (packet->generation != get_hpsb_generation())) { + return 0; + } + + packet->state = queued; + + dump_packet("send packet:", packet->header, packet->header_size); + + return host->template->transmit_packet(host, packet); +} + +static void send_packet_nocare(struct hpsb_packet *packet) +{ + if (!hpsb_send_packet(packet)) { + free_hpsb_packet(packet); + } +} + + +void handle_packet_response(struct hpsb_host *host, int tcode, quadlet_t *data, + size_t size) +{ + struct hpsb_packet *packet = NULL; + struct list_head *lh; + int tcode_match = 0; + int tlabel; + unsigned long flags; + + tlabel = (data[0] >> 10) & 0x3f; + + spin_lock_irqsave(&host->pending_pkt_lock, flags); + + lh = host->pending_packets.next; + while (lh != &host->pending_packets) { + packet = list_entry(lh, struct hpsb_packet, list); + if ((packet->tlabel == tlabel) + && (packet->node_id == (data[1] >> 16))){ + break; + } + lh = lh->next; + } + + if (lh == &host->pending_packets) { + HPSB_INFO("unsolicited response packet received - np"); + dump_packet("contents:", data, 16); + spin_unlock_irqrestore(&host->pending_pkt_lock, flags); + return; + } + + switch (packet->tcode) { + case TCODE_WRITEQ: + case TCODE_WRITEB: + if (tcode == TCODE_WRITE_RESPONSE) tcode_match = 1; + break; + case TCODE_READQ: + if (tcode == TCODE_READQ_RESPONSE) tcode_match = 1; + break; + case TCODE_READB: + if (tcode == TCODE_READB_RESPONSE) tcode_match = 1; + break; + case TCODE_LOCK_REQUEST: + if (tcode == TCODE_LOCK_RESPONSE) tcode_match = 1; + break; + } + + if (!tcode_match || (packet->tlabel != tlabel) + || (packet->node_id != (data[1] >> 16))) { + HPSB_INFO("unsolicited response packet received"); + dump_packet("contents:", data, 16); + + spin_unlock_irqrestore(&host->pending_pkt_lock, flags); + return; + } + + list_del(&packet->list); + + spin_unlock_irqrestore(&host->pending_pkt_lock, flags); + + /* FIXME - update size fields? */ + switch (tcode) { + case TCODE_WRITE_RESPONSE: + memcpy(packet->header, data, 12); + break; + case TCODE_READQ_RESPONSE: + memcpy(packet->header, data, 16); + break; + case TCODE_READB_RESPONSE: + memcpy(packet->header, data, 16); + memcpy(packet->data, data + 4, size - 16); + break; + case TCODE_LOCK_RESPONSE: + memcpy(packet->header, data, 16); + memcpy(packet->data, data + 4, (size - 16) > 8 ? 8 : size - 16); + break; + } + + packet->state = complete; + up(&packet->state_change); + run_task_queue(&packet->complete_tq); +} + + +struct hpsb_packet *create_reply_packet(struct hpsb_host *host, quadlet_t *data, + size_t dsize) +{ + struct hpsb_packet *p; + + p = alloc_hpsb_packet(dsize); + if (p == NULL) { + /* FIXME - send data_error response */ + return NULL; + } + + p->type = async; + p->state = unused; + p->host = host; + p->node_id = data[1] >> 16; + p->tlabel = (data[0] >> 10) & 0x3f; + p->no_waiter = 1; + + return p; +} + +#define PREP_REPLY_PACKET(length) \ + packet = create_reply_packet(host, data, length); \ + if (packet == NULL) break + +inline void swap_quadlets_on_le(quadlet_t *q) +{ +#ifdef __LITTLE_ENDIAN + quadlet_t saved = q[0]; + q[0] = q[1]; + q[1] = saved; +#endif +} + + +void handle_incoming_packet(struct hpsb_host *host, int tcode, quadlet_t *data, + size_t size) +{ + struct hpsb_packet *packet; + int length, rcode, extcode; + u64 addr; + + /* big FIXME - no error checking is done for an out of bounds length */ + + switch (tcode) { + case TCODE_WRITEQ: + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_write(host, data+3, addr, 4); + + if (((data[0] >> 16) & NODE_MASK) != NODE_MASK) { + /* not a broadcast write, reply */ + PREP_REPLY_PACKET(0); + fill_async_write_resp(packet, rcode); + send_packet_nocare(packet); + } + break; + + case TCODE_WRITEB: + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_write(host, data+4, addr, data[3]>>16); + + if (((data[0] >> 16) & NODE_MASK) != NODE_MASK) { + /* not a broadcast write, reply */ + PREP_REPLY_PACKET(0); + fill_async_write_resp(packet, rcode); + send_packet_nocare(packet); + } + break; + + case TCODE_READQ: + PREP_REPLY_PACKET(0); + + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_read(host, data, addr, 4); + fill_async_readquad_resp(packet, rcode, *data); + send_packet_nocare(packet); + break; + + case TCODE_READB: + length = data[3] >> 16; + PREP_REPLY_PACKET(length); + + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + rcode = highlevel_read(host, packet->data, addr, length); + fill_async_readblock_resp(packet, rcode, length); + send_packet_nocare(packet); + break; + + case TCODE_LOCK_REQUEST: + length = data[3] >> 16; + extcode = data[3] & 0xffff; + addr = (((u64)(data[1] & 0xffff)) << 32) | data[2]; + + PREP_REPLY_PACKET(8); + + if ((extcode == 0) || (extcode >= 7)) { + /* let switch default handle error */ + length = 0; + } + + switch (length) { + case 4: + rcode = highlevel_lock(host, packet->data, addr, + data[4], 0, extcode); + fill_async_lock_resp(packet, rcode, extcode, 4); + break; + case 8: + if ((extcode != EXTCODE_FETCH_ADD) + && (extcode != EXTCODE_LITTLE_ADD)) { + rcode = highlevel_lock(host, packet->data, addr, + data[5], data[4], + extcode); + fill_async_lock_resp(packet, rcode, extcode, 4); + } else { + swap_quadlets_on_le(data + 4); + rcode = highlevel_lock64(host, + (octlet_t *)packet->data, addr, + *(octlet_t *)(data + 4), 0ULL, + extcode); + swap_quadlets_on_le(packet->data); + fill_async_lock_resp(packet, rcode, extcode, 8); + } + break; + case 16: + swap_quadlets_on_le(data + 4); + swap_quadlets_on_le(data + 6); + rcode = highlevel_lock64(host, (octlet_t *)packet->data, + addr, *(octlet_t *)(data + 6), + *(octlet_t *)(data + 4), + extcode); + swap_quadlets_on_le(packet->data); + fill_async_lock_resp(packet, rcode, extcode, 8); + break; + default: + fill_async_lock_resp(packet, RCODE_TYPE_ERROR, + extcode, 0); + } + + send_packet_nocare(packet); + break; + } + +} +#undef PREP_REPLY_PACKET + + +void hpsb_packet_received(struct hpsb_host *host, quadlet_t *data, size_t size) +{ + int tcode; + + if (host->in_bus_reset) { + HPSB_INFO("received packet during reset; ignoring"); + return; + } + + dump_packet("received packet:", data, size); + + tcode = (data[0] >> 4) & 0xf; + + switch (tcode) { + case TCODE_WRITE_RESPONSE: + case TCODE_READQ_RESPONSE: + case TCODE_READB_RESPONSE: + case TCODE_LOCK_RESPONSE: + handle_packet_response(host, tcode, data, size); + break; + + case TCODE_WRITEQ: + case TCODE_WRITEB: + case TCODE_READQ: + case TCODE_READB: + case TCODE_LOCK_REQUEST: + handle_incoming_packet(host, tcode, data, size); + break; + + + case TCODE_ISO_DATA: + highlevel_iso_receive(host, data, size); + break; + + case TCODE_CYCLE_START: + /* simply ignore this packet if it is passed on */ + break; + + default: + HPSB_NOTICE("received packet with bogus transaction code %d", + tcode); + break; + } +} + + +void abort_requests(struct hpsb_host *host) +{ + unsigned long flags; + struct hpsb_packet *packet; + struct list_head *lh; + LIST_HEAD(llist); + + host->template->devctl(host, CANCEL_REQUESTS, 0); + + spin_lock_irqsave(&host->pending_pkt_lock, flags); + list_splice(&host->pending_packets, &llist); + INIT_LIST_HEAD(&host->pending_packets); + spin_unlock_irqrestore(&host->pending_pkt_lock, flags); + + lh = llist.next; + + while (lh != &llist) { + packet = list_entry(lh, struct hpsb_packet, list); + lh = lh->next; + packet->state = complete; + packet->ack_code = ACKX_ABORTED; + up(&packet->state_change); + run_task_queue(&packet->complete_tq); + } +} + +void abort_timedouts(struct hpsb_host *host) +{ + unsigned long flags; + struct hpsb_packet *packet; + unsigned long expire; + struct list_head *lh; + LIST_HEAD(expiredlist); + + spin_lock_irqsave(&host->csr.lock, flags); + expire = (host->csr.split_timeout_hi * 8000 + + (host->csr.split_timeout_lo >> 19)) + * HZ / 8000; + /* Avoid shortening of timeout due to rounding errors: */ + expire++; + spin_unlock_irqrestore(&host->csr.lock, flags); + + + spin_lock_irqsave(&host->pending_pkt_lock, flags); + lh = host->pending_packets.next; + + while (lh != &host->pending_packets) { + packet = list_entry(lh, struct hpsb_packet, list); + lh = lh->next; + if (time_before(packet->sendtime + expire, jiffies)) { + list_del(&packet->list); + list_add(&packet->list, &expiredlist); + } + } + + if (!list_empty(&host->pending_packets)) { + queue_task(&host->timeout_tq, &tq_timer); + } + spin_unlock_irqrestore(&host->pending_pkt_lock, flags); + + lh = expiredlist.next; + while (lh != &expiredlist) { + packet = list_entry(lh, struct hpsb_packet, list); + lh = lh->next; + packet->state = complete; + packet->ack_code = ACKX_TIMEOUT; + up(&packet->state_change); + run_task_queue(&packet->complete_tq); + } +} + + +#if 0 +int hpsb_host_thread(void *hostPointer) +{ + struct hpsb_host *host = (struct hpsb_host *)hostPointer; + + /* I don't understand why, but I just want to be on the safe side. */ + lock_kernel(); + + HPSB_INFO(__FUNCTION__ " starting for one %s adapter", + host->template->name); + + exit_mm(current); + exit_files(current); + exit_fs(current); + + strcpy(current->comm, "ieee1394 thread"); + + /* ... but then again, I think the following is safe. */ + unlock_kernel(); + + for (;;) { + siginfo_t info; + unsigned long signr; + + if (signal_pending(current)) { + spin_lock_irq(¤t->sigmask_lock); + signr = dequeue_signal(¤t->blocked, &info); + spin_unlock_irq(¤t->sigmask_lock); + + break; + } + + abort_timedouts(host); + } + + HPSB_INFO(__FUNCTION__ " exiting"); + return 0; +} +#endif + + +#ifndef MODULE + +void __init ieee1394_init(void) +{ + register_builtin_lowlevels(); + init_hpsb_highlevel(); + init_csr(); +} + +#else + +int init_module(void) +{ + init_hpsb_highlevel(); + init_csr(); + return 0; +} + +#endif |