summaryrefslogtreecommitdiffstats
path: root/drivers/ieee1394/ieee1394_transactions.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/ieee1394/ieee1394_transactions.c')
-rw-r--r--drivers/ieee1394/ieee1394_transactions.c514
1 files changed, 514 insertions, 0 deletions
diff --git a/drivers/ieee1394/ieee1394_transactions.c b/drivers/ieee1394/ieee1394_transactions.c
new file mode 100644
index 000000000..c50bce62a
--- /dev/null
+++ b/drivers/ieee1394/ieee1394_transactions.c
@@ -0,0 +1,514 @@
+/*
+ * IEEE 1394 for Linux
+ *
+ * Transaction support.
+ *
+ * Copyright (C) 1999 Andreas E. Bombe
+ */
+
+#include <linux/sched.h>
+#include <asm/errno.h>
+#include <asm/bitops.h>
+
+#include "ieee1394.h"
+#include "ieee1394_types.h"
+#include "hosts.h"
+#include "ieee1394_core.h"
+#include "highlevel.h"
+
+
+#define PREP_ASYNC_HEAD_ADDRESS(tc) \
+ packet->tcode = tc; \
+ packet->header[0] = (packet->node_id << 16) | (packet->tlabel << 10) \
+ | (1 << 8) | (tc << 4); \
+ packet->header[1] = (packet->host->node_id << 16) | (addr >> 32); \
+ packet->header[2] = addr & 0xffffffff
+
+#define PREP_ASYNC_HEAD_RCODE(tc) \
+ packet->tcode = tc; \
+ packet->header[0] = (packet->node_id << 16) | (packet->tlabel << 10) \
+ | (1 << 8) | (tc << 4); \
+ packet->header[1] = (packet->host->node_id << 16) | (rcode << 12); \
+ packet->header[2] = 0
+
+
+void fill_async_readquad(struct hpsb_packet *packet, u64 addr)
+{
+ PREP_ASYNC_HEAD_ADDRESS(TCODE_READQ);
+ packet->header_size = 12;
+ packet->data_size = 0;
+ packet->expect_response = 1;
+}
+
+void fill_async_readquad_resp(struct hpsb_packet *packet, int rcode,
+ quadlet_t data)
+{
+ PREP_ASYNC_HEAD_RCODE(TCODE_READQ_RESPONSE);
+ packet->header[3] = data;
+ packet->header_size = 16;
+ packet->data_size = 0;
+}
+
+void fill_async_readblock(struct hpsb_packet *packet, u64 addr, int length)
+{
+ PREP_ASYNC_HEAD_ADDRESS(TCODE_READB);
+ packet->header[3] = length << 16;
+ packet->header_size = 16;
+ packet->data_size = 0;
+ packet->expect_response = 1;
+}
+
+void fill_async_readblock_resp(struct hpsb_packet *packet, int rcode,
+ int length)
+{
+ if (rcode != RCODE_COMPLETE) {
+ length = 0;
+ }
+
+ PREP_ASYNC_HEAD_RCODE(TCODE_READB_RESPONSE);
+ packet->header[3] = length << 16;
+ packet->header_size = 16;
+ packet->data_size = length;
+}
+
+void fill_async_writequad(struct hpsb_packet *packet, u64 addr, quadlet_t data)
+{
+ PREP_ASYNC_HEAD_ADDRESS(TCODE_WRITEQ);
+ packet->header[3] = data;
+ packet->header_size = 16;
+ packet->data_size = 0;
+ packet->expect_response = 1;
+}
+
+void fill_async_writeblock(struct hpsb_packet *packet, u64 addr, int length)
+{
+ PREP_ASYNC_HEAD_ADDRESS(TCODE_WRITEB);
+ packet->header[3] = length << 16;
+ packet->header_size = 16;
+ packet->data_size = length;
+ packet->expect_response = 1;
+}
+
+void fill_async_write_resp(struct hpsb_packet *packet, int rcode)
+{
+ PREP_ASYNC_HEAD_RCODE(TCODE_WRITE_RESPONSE);
+ packet->header[2] = 0;
+ packet->header_size = 12;
+ packet->data_size = 0;
+}
+
+void fill_async_lock(struct hpsb_packet *packet, u64 addr, int extcode,
+ int length)
+{
+ PREP_ASYNC_HEAD_ADDRESS(TCODE_LOCK_REQUEST);
+ packet->header[3] = (length << 16) | extcode;
+ packet->header_size = 16;
+ packet->data_size = length;
+ packet->expect_response = 1;
+}
+
+void fill_async_lock_resp(struct hpsb_packet *packet, int rcode, int extcode,
+ int length)
+{
+ if (rcode != RCODE_COMPLETE) {
+ length = 0;
+ }
+
+ PREP_ASYNC_HEAD_RCODE(TCODE_LOCK_RESPONSE);
+ packet->header[3] = (length << 16) | extcode;
+ packet->header_size = 16;
+ packet->data_size = length;
+}
+
+void fill_iso_packet(struct hpsb_packet *packet, int length, int channel,
+ int tag, int sync)
+{
+ packet->header[0] = (length << 16) | (tag << 14) | (channel << 8)
+ | (TCODE_ISO_DATA << 4) | sync;
+
+ packet->header_size = 4;
+ packet->data_size = length;
+ packet->tcode = TCODE_ISO_DATA;
+}
+
+
+int get_tlabel(struct hpsb_host *host, nodeid_t nodeid, int wait)
+{
+ unsigned long flags;
+ int tlabel;
+
+ while (1) {
+ spin_lock_irqsave(&host->tlabel_lock, flags);
+
+ if (host->tlabel_count) {
+ host->tlabel_count--;
+
+ if (host->tlabel_pool[0] != ~0) {
+ tlabel = ffz(host->tlabel_pool[0]);
+ host->tlabel_pool[0] |= 1 << tlabel;
+ } else {
+ tlabel = ffz(host->tlabel_pool[1]);
+ host->tlabel_pool[1] |= 1 << tlabel;
+ tlabel += 32;
+ }
+
+ spin_unlock_irqrestore(&host->tlabel_lock, flags);
+ return tlabel;
+ }
+
+ spin_unlock_irqrestore(&host->tlabel_lock, flags);
+
+ if (wait) {
+ sleep_on(&host->tlabel_wait);
+ } else {
+ return -1;
+ }
+ }
+}
+
+void free_tlabel(struct hpsb_host *host, nodeid_t nodeid, int tlabel)
+{
+ unsigned long flags;
+
+ spin_lock_irqsave(&host->tlabel_lock, flags);
+
+ if (tlabel < 32) {
+ host->tlabel_pool[0] &= ~(1 << tlabel);
+ } else {
+ host->tlabel_pool[1] &= ~(1 << (tlabel-32));
+ }
+
+ host->tlabel_count++;
+
+ spin_unlock_irqrestore(&host->tlabel_lock, flags);
+
+ wake_up(&host->tlabel_wait);
+}
+
+
+
+int hpsb_packet_success(struct hpsb_packet *packet)
+{
+ switch (packet->ack_code) {
+ case ACK_PENDING:
+ switch ((packet->header[1] >> 12) & 0xf) {
+ case RCODE_COMPLETE:
+ return 0;
+ case RCODE_CONFLICT_ERROR:
+ return -EAGAIN;
+ case RCODE_DATA_ERROR:
+ return -EREMOTEIO;
+ case RCODE_TYPE_ERROR:
+ return -EACCES;
+ case RCODE_ADDRESS_ERROR:
+ return -EINVAL;
+ default:
+ HPSB_ERR("received reserved rcode %d from node %d",
+ (packet->header[1] >> 12) & 0xf,
+ packet->node_id);
+ return -EAGAIN;
+ }
+ HPSB_PANIC("reached unreachable code 1 in " __FUNCTION__);
+
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ return -EBUSY;
+
+ case ACK_TYPE_ERROR:
+ return -EACCES;
+
+ case ACK_COMPLETE:
+ if (packet->tcode == TCODE_WRITEQ
+ || packet->tcode == TCODE_WRITEB) {
+ return 0;
+ } else {
+ HPSB_ERR("impossible ack_complete from node %d "
+ "(tcode %d)", packet->node_id, packet->tcode);
+ return -EAGAIN;
+ }
+
+
+ case ACK_DATA_ERROR:
+ if (packet->tcode == TCODE_WRITEB
+ || packet->tcode == TCODE_LOCK_REQUEST) {
+ return -EAGAIN;
+ } else {
+ HPSB_ERR("impossible ack_data_error from node %d "
+ "(tcode %d)", packet->node_id, packet->tcode);
+ return -EAGAIN;
+ }
+
+ case ACKX_NONE:
+ case ACKX_SEND_ERROR:
+ case ACKX_ABORTED:
+ case ACKX_TIMEOUT:
+ /* error while sending */
+ return -EAGAIN;
+
+ default:
+ HPSB_ERR("got invalid ack %d from node %d (tcode %d)",
+ packet->ack_code, packet->node_id, packet->tcode);
+ return -EAGAIN;
+ }
+
+ HPSB_PANIC("reached unreachable code 2 in " __FUNCTION__);
+}
+
+
+int hpsb_read_trylocal(struct hpsb_host *host, nodeid_t node, u64 addr,
+ quadlet_t *buffer, size_t length)
+{
+ if (host->node_id != node) return -1;
+ return highlevel_read(host, buffer, addr, length);
+}
+
+struct hpsb_packet *hpsb_make_readqpacket(struct hpsb_host *host, nodeid_t node,
+ u64 addr)
+{
+ struct hpsb_packet *p;
+
+ p = alloc_hpsb_packet(0);
+ if (!p) return NULL;
+
+ p->host = host;
+ p->tlabel = get_tlabel(host, node, 1);
+ p->node_id = node;
+ fill_async_readquad(p, addr);
+
+ return p;
+}
+
+struct hpsb_packet *hpsb_make_readbpacket(struct hpsb_host *host, nodeid_t node,
+ u64 addr, size_t length)
+{
+ struct hpsb_packet *p;
+
+ p = alloc_hpsb_packet(length);
+ if (!p) return NULL;
+
+ p->host = host;
+ p->tlabel = get_tlabel(host, node, 1);
+ p->node_id = node;
+ fill_async_readblock(p, addr, length);
+
+ return p;
+}
+
+struct hpsb_packet *hpsb_make_writeqpacket(struct hpsb_host *host,
+ nodeid_t node, u64 addr,
+ quadlet_t data)
+{
+ struct hpsb_packet *p;
+
+ p = alloc_hpsb_packet(0);
+ if (!p) return NULL;
+
+ p->host = host;
+ p->tlabel = get_tlabel(host, node, 1);
+ p->node_id = node;
+ fill_async_writequad(p, addr, data);
+
+ return p;
+}
+
+struct hpsb_packet *hpsb_make_writebpacket(struct hpsb_host *host,
+ nodeid_t node, u64 addr,
+ size_t length)
+{
+ struct hpsb_packet *p;
+
+ p = alloc_hpsb_packet(length);
+ if (!p) return NULL;
+
+ p->host = host;
+ p->tlabel = get_tlabel(host, node, 1);
+ p->node_id = node;
+ fill_async_writeblock(p, addr, length);
+
+ return p;
+}
+
+
+/*
+ * FIXME - these functions should probably read from / write to user space to
+ * avoid in kernel buffers for user space callers
+ */
+
+int hpsb_read(struct hpsb_host *host, nodeid_t node, u64 addr,
+ quadlet_t *buffer, size_t length)
+{
+ struct hpsb_packet *packet;
+ int retval = 0;
+
+ if (length == 0) {
+ return -EINVAL;
+ }
+
+ if (host->node_id == node) {
+ switch(highlevel_read(host, buffer, addr, length)) {
+ case RCODE_COMPLETE:
+ return 0;
+ case RCODE_TYPE_ERROR:
+ return -EACCES;
+ case RCODE_ADDRESS_ERROR:
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (length & 0x3) {
+ /* FIXME: Lengths not multiple of 4 are not implemented. Mainly
+ * there is the problem with little endian machines because we
+ * always swap to little endian on receive. If we read 5 bytes
+ * 12345 we receive them as 12345000 and swap them to 43210005.
+ * How should we copy that to the caller? Require *buffer to be
+ * a full quadlet multiple in length? */
+ return -EACCES;
+ }
+
+ if (length == 4) {
+ packet = hpsb_make_readqpacket(host, node, addr);
+ } else {
+ packet = hpsb_make_readbpacket(host, node, addr, length);
+ }
+
+ if (!packet) {
+ return -ENOMEM;
+ }
+
+ hpsb_send_packet(packet);
+ down(&packet->state_change);
+ down(&packet->state_change);
+ retval = hpsb_packet_success(packet);
+
+ if (retval == 0) {
+ if (length == 4) {
+ *buffer = packet->header[3];
+ } else {
+ memcpy(buffer, packet->data, length);
+ }
+ }
+
+ free_tlabel(host, node, packet->tlabel);
+ free_hpsb_packet(packet);
+
+ return retval;
+}
+
+
+int hpsb_write(struct hpsb_host *host, nodeid_t node, u64 addr,
+ quadlet_t *buffer, size_t length)
+{
+ struct hpsb_packet *packet;
+ int retval = 0;
+
+ if (length == 0) {
+ return -EINVAL;
+ }
+
+ if (host->node_id == node) {
+ switch(highlevel_write(host, buffer, addr, length)) {
+ case RCODE_COMPLETE:
+ return 0;
+ case RCODE_TYPE_ERROR:
+ return -EACCES;
+ case RCODE_ADDRESS_ERROR:
+ default:
+ return -EINVAL;
+ }
+ }
+
+ if (length & 0x3) {
+ /* FIXME: Lengths not multiple of 4 are not implemented. See function
+ * hpsb_read for explanation, same reason, different direction. */
+ return -EACCES;
+ }
+
+ if (length == 4) {
+ packet = hpsb_make_writeqpacket(host, node, addr, *buffer);
+ } else {
+ packet = hpsb_make_writebpacket(host, node, addr, length);
+ }
+
+ if (!packet) {
+ return -ENOMEM;
+ }
+
+ if (length != 4) {
+ memcpy(packet->data, buffer, length);
+ }
+
+ hpsb_send_packet(packet);
+ down(&packet->state_change);
+ down(&packet->state_change);
+ retval = hpsb_packet_success(packet);
+
+ free_tlabel(host, node, packet->tlabel);
+ free_hpsb_packet(packet);
+
+ return retval;
+}
+
+
+/* We need a hpsb_lock64 function for the 64 bit equivalent. Probably. */
+int hpsb_lock(struct hpsb_host *host, nodeid_t node, u64 addr, int extcode,
+ quadlet_t *data, quadlet_t arg)
+{
+ struct hpsb_packet *packet;
+ int retval = 0, length;
+
+ if (host->node_id == node) {
+ switch(highlevel_lock(host, data, addr, *data, arg, extcode)) {
+ case RCODE_COMPLETE:
+ return 0;
+ case RCODE_TYPE_ERROR:
+ return -EACCES;
+ case RCODE_ADDRESS_ERROR:
+ default:
+ return -EINVAL;
+ }
+ }
+
+ packet = alloc_hpsb_packet(8);
+ if (!packet) {
+ return -ENOMEM;
+ }
+
+ packet->host = host;
+ packet->tlabel = get_tlabel(host, node, 1);
+ packet->node_id = node;
+
+ switch (extcode) {
+ case EXTCODE_MASK_SWAP:
+ case EXTCODE_COMPARE_SWAP:
+ case EXTCODE_BOUNDED_ADD:
+ case EXTCODE_WRAP_ADD:
+ length = 8;
+ packet->data[0] = arg;
+ packet->data[1] = *data;
+ break;
+ case EXTCODE_FETCH_ADD:
+ case EXTCODE_LITTLE_ADD:
+ length = 4;
+ packet->data[0] = *data;
+ break;
+ default:
+ return -EINVAL;
+ }
+ fill_async_lock(packet, addr, extcode, length);
+
+ hpsb_send_packet(packet);
+ down(&packet->state_change);
+ down(&packet->state_change);
+ retval = hpsb_packet_success(packet);
+
+ if (retval == 0) {
+ *data = packet->data[0];
+ }
+
+ free_tlabel(host, node, packet->tlabel);
+ free_hpsb_packet(packet);
+
+ return retval;
+}