diff options
| author | 2008-04-30 14:37:28 -0700 | |
|---|---|---|
| committer | 2008-04-30 14:37:28 -0700 | |
| commit | f9681ff59da0acca543ad5d15213c6253114f0ce (patch) | |
| tree | ea1da14c7a5fba1c6c6f1fceff1fb76c25ef8551 /src/fw-iso.c | |
| parent | libraw1394.sgml, raw1394.h: remove information about deprecated isochronous (diff) | |
Move the source code files in the juju directory into the src directory and
give them 'fw' names instead of 'juju.'
Diffstat (limited to 'src/fw-iso.c')
| -rw-r--r-- | src/fw-iso.c | 529 |
1 files changed, 529 insertions, 0 deletions
diff --git a/src/fw-iso.c b/src/fw-iso.c new file mode 100644 index 0000000..471d981 --- /dev/null +++ b/src/fw-iso.c @@ -0,0 +1,529 @@ +/* -*- c-basic-offset: 8 -*- + * + * raw1394-iso.c -- Emulation of the raw1394 rawiso API on the firewire stack + * + * Copyright (C) 2007 Kristian Hoegsberg <krh@bitplanet.net> + * + * 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 <string.h> +#include <sys/mman.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> +#include <stdio.h> +#include <sys/epoll.h> +#include <sys/ioctl.h> + +#include "fw.h" +#include "raw1394_private.h" + +static int +queue_packet(fw_handle_t handle, + unsigned int length, unsigned int header_length, + unsigned char tag, unsigned char sy) +{ + struct fw_cdev_queue_iso queue_iso; + struct fw_cdev_iso_packet *p; + int err; + + p = &handle->iso.packets[handle->iso.packet_index]; + p->control = + FW_CDEV_ISO_PAYLOAD_LENGTH(length) | + FW_CDEV_ISO_TAG(tag) | + FW_CDEV_ISO_SY(sy) | + FW_CDEV_ISO_HEADER_LENGTH(header_length); + + if (handle->iso.packet_phase == handle->iso.irq_interval - 1) + p->control |= FW_CDEV_ISO_INTERRUPT; + + handle->iso.head += length; + handle->iso.packet_count++; + handle->iso.packet_phase++; + handle->iso.packet_index++; + + if (handle->iso.packet_phase == handle->iso.irq_interval) + handle->iso.packet_phase = 0; + + if (handle->iso.head + handle->iso.max_packet_size > handle->iso.buffer_end) + handle->iso.head = handle->iso.buffer; + + /* Queue the packets in the kernel if we filled up the packets + * array or wrapped the payload buffer. */ + if (handle->iso.packet_index == handle->iso.irq_interval || + handle->iso.head == handle->iso.buffer) { + queue_iso.packets = ptr_to_u64(handle->iso.packets); + queue_iso.size = handle->iso.packet_index * sizeof handle->iso.packets[0]; + queue_iso.data = ptr_to_u64(handle->iso.first_payload); + queue_iso.handle = 0; + handle->iso.packet_index = 0; + handle->iso.first_payload = handle->iso.head; + + err = ioctl(handle->iso.fd, FW_CDEV_IOC_QUEUE_ISO, &queue_iso); + if (err < 0) + return -1; + } +} + +static int +queue_xmit_packets(raw1394handle_t handle, int limit) +{ + fw_handle_t fwhandle = handle->mode.fw; + enum raw1394_iso_disposition d; + unsigned char tag, sy; + int len, cycle, dropped; + + if (fwhandle->iso.xmit_handler == NULL) + return 0; + + while (fwhandle->iso.packet_count < limit) { + + d = fwhandle->iso.xmit_handler(handle, fwhandle->iso.head, + &len, &tag, &sy, cycle, dropped); + + switch (d) { + case RAW1394_ISO_OK: + queue_packet(fwhandle, len, 0, tag, sy); + break; + case RAW1394_ISO_DEFER: + case RAW1394_ISO_AGAIN: + default: + return 0; + case RAW1394_ISO_ERROR: + return -1; + case RAW1394_ISO_STOP: + fw_iso_stop(fwhandle); + return 0; + } + } + + return 0; +} + +int fw_iso_xmit_start(raw1394handle_t handle, int start_on_cycle, + int prebuffer_packets) +{ + fw_handle_t fwhandle = handle->mode.fw; + struct fw_cdev_start_iso start_iso; + int retval; + + if (prebuffer_packets == -1) + prebuffer_packets = fwhandle->iso.irq_interval; + + fwhandle->iso.prebuffer = prebuffer_packets; + fwhandle->iso.start_on_cycle = start_on_cycle; + + queue_xmit_packets(handle, prebuffer_packets); + + if (fwhandle->iso.prebuffer <= fwhandle->iso.packet_count) { + start_iso.cycle = start_on_cycle; + start_iso.handle = 0; + + retval = ioctl(fwhandle->iso.fd, + FW_CDEV_IOC_START_ISO, &start_iso); + if (retval < 0) + return retval; + } + + return queue_xmit_packets(handle, fwhandle->iso.buf_packets); +} + +static int +queue_recv_packets(fw_handle_t handle) +{ + while (handle->iso.packet_count <= handle->iso.buf_packets) + queue_packet(handle, handle->iso.max_packet_size, 4, 0, 0); + + return 0; +} + +static enum raw1394_iso_disposition +flush_recv_packets(raw1394handle_t handle, + struct fw_cdev_event_iso_interrupt *interrupt) +{ + fw_handle_t fwhandle = handle->mode.fw; + enum raw1394_iso_disposition d; + quadlet_t header, *p, *end; + unsigned int len, cycle, dropped; + unsigned char channel, tag, sy; + + p = interrupt->header; + end = (void *) interrupt->header + interrupt->header_length; + cycle = interrupt->cycle; + dropped = 0; + d = RAW1394_ISO_OK; + + while (p < end) { + header = be32_to_cpu(*p++); + len = header >> 16; + tag = (header >> 14) & 0x3; + channel = (header >> 8) & 0x3f; + sy = header & 0x0f; + + d = fwhandle->iso.recv_handler(handle, fwhandle->iso.tail, len, + channel, tag, sy, cycle, dropped); + if (d != RAW1394_ISO_OK) + /* FIXME: we need to save the headers so we + * can restart this loop. */ + break; + cycle++; + + fwhandle->iso.tail += fwhandle->iso.max_packet_size; + fwhandle->iso.packet_count--; + + if (fwhandle->iso.tail + fwhandle->iso.max_packet_size > fwhandle->iso.buffer_end) + fwhandle->iso.tail = fwhandle->iso.buffer; + } + + switch (d) { + case RAW1394_ISO_OK: + case RAW1394_ISO_DEFER: + default: + break; + + case RAW1394_ISO_ERROR: + return -1; + + case RAW1394_ISO_STOP: + fw_iso_stop(fwhandle); + return 0; + } + + queue_recv_packets(fwhandle); + + return 0; +} + +int fw_iso_recv_start(fw_handle_t handle, int start_on_cycle, + int tag_mask, int sync) +{ + struct fw_cdev_start_iso start_iso; + + queue_recv_packets(handle); + + start_iso.cycle = start_on_cycle; + start_iso.tags = + tag_mask == -1 ? FW_CDEV_ISO_CONTEXT_MATCH_ALL_TAGS : tag_mask; + /* sync is documented as 'not used' */ + start_iso.sync = 0; + start_iso.handle = 0; + + return ioctl(handle->iso.fd, FW_CDEV_IOC_START_ISO, &start_iso); +} + +static int handle_iso_event(raw1394handle_t handle, + struct epoll_closure *closure, __uint32_t events) +{ + fw_handle_t fwhandle = handle->mode.fw; + struct fw_cdev_event_iso_interrupt *interrupt; + int len; + + len = read(fwhandle->iso.fd, fwhandle->buffer, sizeof fwhandle->buffer); + if (len < 0) + return -1; + + interrupt = (struct fw_cdev_event_iso_interrupt *) fwhandle->buffer; + if (interrupt->type != FW_CDEV_EVENT_ISO_INTERRUPT) + return 0; + + switch (fwhandle->iso.type) { + case FW_CDEV_ISO_CONTEXT_TRANSMIT: + fwhandle->iso.packet_count -= fwhandle->iso.irq_interval; + return queue_xmit_packets(handle, fwhandle->iso.buf_packets); + case FW_CDEV_ISO_CONTEXT_RECEIVE: + return flush_recv_packets(handle, interrupt); + default: + /* Doesn't happen. */ + return -1; + } +} + +int fw_iso_xmit_write(raw1394handle_t handle, unsigned char *data, + unsigned int len, unsigned char tag, + unsigned char sy) +{ + fw_handle_t fwhandle = handle->mode.fw; + struct fw_cdev_queue_iso queue_iso; + struct fw_cdev_start_iso start_iso; + struct fw_cdev_iso_packet *p; + + if (len > fwhandle->iso.max_packet_size) { + errno = EINVAL; + return -1; + } + + /* Block until we have space for another packet. */ + while (fwhandle->iso.packet_count + fwhandle->iso.irq_interval > + fwhandle->iso.buf_packets) + fw_loop_iterate(handle); + + memcpy(fwhandle->iso.head, data, len); + if (queue_packet(fwhandle, len, 0, tag, sy) < 0) + return -1; + + /* Start the streaming if it's not already running and if + * we've buffered up enough packets. */ + if (fwhandle->iso.prebuffer > 0 && + fwhandle->iso.packet_count >= fwhandle->iso.prebuffer) { + /* Set this to 0 to indicate that we're running. */ + fwhandle->iso.prebuffer = 0; + start_iso.cycle = fwhandle->iso.start_on_cycle; + start_iso.handle = 0; + + len = ioctl(fwhandle->iso.fd, + FW_CDEV_IOC_START_ISO, &start_iso); + if (len < 0) + return len; + } + + return 0; +} + +int fw_iso_xmit_sync(raw1394handle_t handle) +{ + fw_handle_t fwhandle = handle->mode.fw; + struct fw_cdev_iso_packet skip; + struct fw_cdev_queue_iso queue_iso; + int len; + + skip.control = FW_CDEV_ISO_INTERRUPT | FW_CDEV_ISO_SKIP; + queue_iso.packets = ptr_to_u64(&skip); + queue_iso.size = sizeof skip; + queue_iso.data = 0; + queue_iso.handle = 0; + + len = ioctl(fwhandle->iso.fd, FW_CDEV_IOC_QUEUE_ISO, &queue_iso); + if (len < 0) + return -1; + + /* Now that we've queued the skip packet, we'll get an + * interrupt when the transmit buffer is flushed, so all we do + * here is wait. */ + while (fwhandle->iso.packet_count > 0) + fw_loop_iterate(handle); + + /* The iso mainloop thinks that interrutps indicate another + * irq_interval number of packets was sent, so the skip + * interrupt makes it go out of whack. We just reset it. */ + fwhandle->iso.head = fwhandle->iso.buffer; + fwhandle->iso.tail = fwhandle->iso.buffer; + fwhandle->iso.first_payload = fwhandle->iso.buffer; + fwhandle->iso.packet_phase = 0; + fwhandle->iso.packet_count = 0; + + return 0; +} + +int fw_iso_recv_flush(fw_handle_t handle) +{ + /* FIXME: huh, we'll need kernel support here... */ + + return 0; +} + +static unsigned int +round_to_power_of_two(unsigned int value) +{ + unsigned int pot; + + pot = 1; + while (pot < value) + pot <<= 1; + + return pot; +} + +static int +iso_init(fw_handle_t handle, int type, + raw1394_iso_xmit_handler_t xmit_handler, + raw1394_iso_recv_handler_t recv_handler, + unsigned int buf_packets, + unsigned int max_packet_size, + unsigned char channel, + enum raw1394_iso_speed speed, + int irq_interval) +{ + struct fw_cdev_create_iso_context create; + struct epoll_event ep; + int retval, prot; + + if (handle->iso.fd != -1) { + errno = EBUSY; + return -1; + } + + switch (type) { + case FW_CDEV_ISO_CONTEXT_TRANSMIT: + prot = PROT_READ | PROT_WRITE; + break; + case FW_CDEV_ISO_CONTEXT_RECEIVE: + prot = PROT_READ; + break; + default: + errno = EINVAL; + return -1; + } + + handle->iso.type = type; + if (irq_interval < 0) + handle->iso.irq_interval = 256; + else + handle->iso.irq_interval = irq_interval; + handle->iso.xmit_handler = xmit_handler; + handle->iso.recv_handler = recv_handler; + handle->iso.buf_packets = buf_packets; + handle->iso.max_packet_size = round_to_power_of_two(max_packet_size); + handle->iso.packet_phase = 0; + handle->iso.packet_count = 0; + handle->iso.packets = + malloc(handle->iso.irq_interval * sizeof handle->iso.packets[0]); + if (handle->iso.packets == NULL) + return -1; + + handle->iso.fd = open(handle->local_filename, O_RDWR); + if (handle->iso.fd < 0) { + free(handle->iso.packets); + handle->iso.packets = NULL; + return -1; + } + + handle->iso.closure.func = handle_iso_event; + ep.events = EPOLLIN; + ep.data.ptr = &handle->iso.closure; + if (epoll_ctl(handle->epoll_fd, EPOLL_CTL_ADD, + handle->iso.fd, &ep) < 0) { + close(handle->iso.fd); + free(handle->iso.packets); + handle->iso.packets = NULL; + return -1; + } + + create.type = type; + create.channel = channel; + create.speed = speed; + create.header_size = 4; + + retval = ioctl(handle->iso.fd, + FW_CDEV_IOC_CREATE_ISO_CONTEXT, &create); + if (retval < 0) { + close(handle->iso.fd); + free(handle->iso.packets); + handle->iso.packets = NULL; + return retval; + } + + handle->iso.buffer = + mmap(NULL, buf_packets * handle->iso.max_packet_size, + prot, MAP_SHARED, handle->iso.fd, 0); + + if (handle->iso.buffer == MAP_FAILED) { + close(handle->iso.fd); + free(handle->iso.packets); + handle->iso.packets = NULL; + return -1; + } + + handle->iso.buffer_end = handle->iso.buffer + + buf_packets * handle->iso.max_packet_size; + handle->iso.head = handle->iso.buffer; + handle->iso.tail = handle->iso.buffer; + handle->iso.first_payload = handle->iso.buffer; + + return 0; +} + +int fw_iso_xmit_init(fw_handle_t handle, + raw1394_iso_xmit_handler_t handler, + unsigned int buf_packets, + unsigned int max_packet_size, + unsigned char channel, + enum raw1394_iso_speed speed, + int irq_interval) +{ + return iso_init(handle, FW_CDEV_ISO_CONTEXT_TRANSMIT, + handler, NULL, buf_packets, max_packet_size, + channel, speed, irq_interval); +} + +int fw_iso_recv_init(fw_handle_t handle, + raw1394_iso_recv_handler_t handler, + unsigned int buf_packets, + unsigned int max_packet_size, + unsigned char channel, + enum raw1394_iso_dma_recv_mode mode, + int irq_interval) +{ + return iso_init(handle, FW_CDEV_ISO_CONTEXT_RECEIVE, + NULL, handler, buf_packets, max_packet_size, + channel, 0, irq_interval); +} + +int fw_iso_multichannel_recv_init(fw_handle_t handle, + raw1394_iso_recv_handler_t handler, + unsigned int buf_packets, + unsigned int max_packet_size, + int irq_interval) +{ + /* FIXME: gah */ + errno = ENOSYS; + return -1; +} + +int fw_iso_recv_listen_channel(fw_handle_t handle, + unsigned char channel) +{ + /* FIXME: multichannel */ + errno = ENOSYS; + return -1; +} + +int fw_iso_recv_unlisten_channel(fw_handle_t handle, + unsigned char channel) +{ + /* FIXME: multichannel */ + errno = ENOSYS; + return -1; +} + +int fw_iso_recv_set_channel_mask(fw_handle_t handle, u_int64_t mask) +{ + /* FIXME: multichannel */ + errno = ENOSYS; + return -1; +} + +void fw_iso_stop(fw_handle_t handle) +{ + struct fw_cdev_stop_iso stop_iso; + + stop_iso.handle = 0; + ioctl(handle->iso.fd, FW_CDEV_IOC_STOP_ISO); + + handle->iso.head = handle->iso.buffer; + handle->iso.tail = handle->iso.buffer; + handle->iso.first_payload = handle->iso.buffer; + handle->iso.packet_phase = 0; + handle->iso.packet_count = 0; +} + +void fw_iso_shutdown(fw_handle_t handle) +{ + munmap(handle->iso.buffer, + handle->iso.buf_packets * handle->iso.max_packet_size); + close(handle->iso.fd); + free(handle->iso.packets); + handle->iso.packets = NULL; +} |
