diff options
author | Ralf Baechle <ralf@linux-mips.org> | 2000-02-16 01:07:24 +0000 |
---|---|---|
committer | Ralf Baechle <ralf@linux-mips.org> | 2000-02-16 01:07:24 +0000 |
commit | 95db6b748fc86297827fbd9c9ef174d491c9ad89 (patch) | |
tree | 27a92a942821cde1edda9a1b088718d436b3efe4 /drivers/ieee1394/ieee1394_transactions.c | |
parent | 45b27b0a0652331d104c953a5b192d843fff88f8 (diff) |
Merge with Linux 2.3.40.
Diffstat (limited to 'drivers/ieee1394/ieee1394_transactions.c')
-rw-r--r-- | drivers/ieee1394/ieee1394_transactions.c | 514 |
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; +} |