/* * libraw1394 - library for raw access to the 1394 bus with the Linux subsystem. * * Copyright (C) 1999,2000,2001,2002 Andreas Bombe * new ISO API by Dan Maas * * This library is licensed under the GNU Lesser General Public License (LGPL), * version 2.1 or later. See the file COPYING.LIB in the distribution for * details. */ #include #include #include #include #include #include #include #include #include "raw1394.h" #include "kernel-raw1394.h" #include "raw1394_private.h" /* This implements x = (x+1) % n using a branch based implementation */ static inline int increment_and_wrap(int x, int n) { ++x; if (x >= n) x = 0; return x; } /* reset the dropped counter each time it is seen */ static unsigned int _iso_dropped(ieee1394handle_t handle) { unsigned int retval = handle->iso_packets_dropped; handle->iso_packets_dropped = 0; return retval; } /* common code for iso_xmit_init and iso_recv_init */ static int do_iso_init(ieee1394handle_t handle, unsigned int buf_packets, unsigned int max_packet_size, int channel, enum raw1394_iso_speed speed, enum raw1394_iso_dma_recv_mode mode, int irq_interval, int cmd) { unsigned int stride; int result; /* already initialized? */ if(handle->iso_mode != ISO_INACTIVE) return -1; /* choose a power-of-two stride for the packet data buffer, so that an even number of packets fits on one page */ for(stride = 4; stride < max_packet_size; stride *= 2); if(stride > getpagesize()) { errno = ENOMEM; return -1; } handle->iso_buf_stride = stride; handle->iso_status.config.data_buf_size = stride * buf_packets; handle->iso_status.config.buf_packets = buf_packets; handle->iso_status.config.channel = channel; handle->iso_status.config.speed = speed; handle->iso_status.config.irq_interval = irq_interval; handle->iso_status.config.dma_mode = mode; if(ioctl(handle->fd, cmd, &handle->iso_status)) return -1; /* mmap the DMA buffer */ /* (we assume the kernel sets buf_size to an even number of pages) */ handle->iso_buffer = mmap(NULL, handle->iso_status.config.data_buf_size, PROT_READ | PROT_WRITE, MAP_SHARED, handle->fd, 0); if(handle->iso_buffer == (unsigned char*) MAP_FAILED) { handle->iso_buffer = NULL; ioctl(handle->fd, RAW1394_IOC_ISO_SHUTDOWN, 0); return -1; } handle->iso_status.overflows = 0; handle->iso_packets_dropped = 0; handle->iso_xmit_handler = NULL; handle->iso_recv_handler = NULL; handle->iso_state = ISO_STOP; handle->iso_packet_infos = malloc(buf_packets * sizeof(struct raw1394_iso_packet_info)); if(handle->iso_packet_infos == NULL) { munmap(handle->iso_buffer, handle->iso_status.config.data_buf_size); handle->iso_buffer = NULL; ioctl(handle->fd, RAW1394_IOC_ISO_SHUTDOWN, 0); return -1; } #if _POSIX_MEMLOCK > 0 result = mlock(handle->iso_packet_infos, buf_packets * sizeof(struct raw1394_iso_packet_info)); /* Ignore a permission error - app is responsible for that, */ if (result < 0 && result != -EPERM) { munmap(handle->iso_buffer, handle->iso_status.config.data_buf_size); handle->iso_buffer = NULL; ioctl(handle->fd, RAW1394_IOC_ISO_SHUTDOWN, 0); return -1; } #endif return 0; } int ieee1394_iso_xmit_init(ieee1394handle_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) { if (do_iso_init(handle, buf_packets, max_packet_size, channel, speed, RAW1394_DMA_DEFAULT, irq_interval, RAW1394_IOC_ISO_XMIT_INIT)) return -1; handle->iso_mode = ISO_XMIT; handle->iso_xmit_handler = handler; handle->next_packet = 0; return 0; } int ieee1394_iso_recv_init(ieee1394handle_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) { /* any speed will work */ if (do_iso_init(handle, buf_packets, max_packet_size, channel, RAW1394_ISO_SPEED_100, mode, irq_interval, RAW1394_IOC_ISO_RECV_INIT)) return -1; handle->iso_mode = ISO_RECV; handle->iso_recv_handler = handler; return 0; } int ieee1394_iso_multichannel_recv_init(ieee1394handle_t handle, raw1394_iso_recv_handler_t handler, unsigned int buf_packets, unsigned int max_packet_size, int irq_interval) { /* any speed will work */ if (do_iso_init(handle, buf_packets, max_packet_size, -1, RAW1394_ISO_SPEED_100, RAW1394_DMA_BUFFERFILL, irq_interval, RAW1394_IOC_ISO_RECV_INIT)) return -1; handle->iso_mode = ISO_RECV; handle->iso_recv_handler = handler; return 0; } int ieee1394_iso_recv_listen_channel(ieee1394handle_t handle, unsigned char channel) { if (handle->iso_mode != ISO_RECV) { errno = EINVAL; return -1; } return ioctl(handle->fd, RAW1394_IOC_ISO_RECV_LISTEN_CHANNEL, channel); } int ieee1394_iso_recv_unlisten_channel(ieee1394handle_t handle, unsigned char channel) { if (handle->iso_mode != ISO_RECV) { errno = EINVAL; return -1; } return ioctl(handle->fd, RAW1394_IOC_ISO_RECV_UNLISTEN_CHANNEL, channel); } int ieee1394_iso_recv_flush(ieee1394handle_t handle) { if (handle->iso_mode != ISO_RECV) { errno = EINVAL; return -1; } return ioctl(handle->fd, RAW1394_IOC_ISO_RECV_FLUSH, 0); } int ieee1394_iso_recv_set_channel_mask(ieee1394handle_t handle, u_int64_t mask) { if (handle->iso_mode != ISO_RECV) { errno = EINVAL; return -1; } return ioctl(handle->fd, RAW1394_IOC_ISO_RECV_SET_CHANNEL_MASK, (void*) &mask); } int ieee1394_iso_recv_start(ieee1394handle_t handle, int start_on_cycle, int tag_mask, int sync) { int args[3]; if(handle->iso_mode != ISO_RECV) { errno = EINVAL; return -1; } args[0] = start_on_cycle; args[1] = tag_mask; args[2] = sync; if(ioctl(handle->fd, RAW1394_IOC_ISO_RECV_START, &args[0])) return -1; handle->iso_state = ISO_GO; return 0; } static int _iso_xmit_queue_packets(raw1394handle_t handle) { ieee1394handle_t ihandle = handle->mode.ieee1394; struct raw1394_iso_status *stat = &ihandle->iso_status; struct raw1394_iso_packets packets; int retval = -1; int stop_sync = 0; if(ihandle->iso_mode != ISO_XMIT) { errno = EINVAL; goto out; } /* ensure stat->n_packets is sane */ if (stat->n_packets > stat->config.buf_packets) stat->n_packets = stat->config.buf_packets; /* we could potentially send up to stat->n_packets packets */ packets.n_packets = 0; packets.infos = ihandle->iso_packet_infos; if(packets.infos == NULL) goto out; while(stat->n_packets > 1) { enum raw1394_iso_disposition disp; unsigned int len; struct raw1394_iso_packet_info *info = &packets.infos[packets.n_packets]; info->offset = ihandle->iso_buf_stride * ihandle->next_packet; /* call handler */ disp = ihandle->iso_xmit_handler(handle, ihandle->iso_buffer + info->offset, &len, &info->tag, &info->sy, stat->xmit_cycle, _iso_dropped(ihandle)); info->len = len; /* advance packet cursors and cycle counter */ stat->n_packets--; ihandle->next_packet = increment_and_wrap(ihandle->next_packet, stat->config.buf_packets); if(stat->xmit_cycle != -1) stat->xmit_cycle = increment_and_wrap(stat->xmit_cycle, 8000); packets.n_packets++; if(disp == RAW1394_ISO_DEFER) { /* queue an event so that we don't hang in the next read() */ if(ioctl(ihandle->fd, RAW1394_IOC_ISO_QUEUE_ACTIVITY, 0)) goto out_produce; break; } else if(disp == RAW1394_ISO_AGAIN) { /* the last packet was not ready, decrement counter */ packets.n_packets--; /* queue an event so that we don't hang in the next read() */ if(ioctl(ihandle->fd, RAW1394_IOC_ISO_QUEUE_ACTIVITY, 0)) goto out_produce; break; } else if(disp == RAW1394_ISO_STOP) { stop_sync = 1; break; } else if(disp == RAW1394_ISO_STOP_NOSYNC) { ieee1394_iso_stop(ihandle); break; } else if(disp == RAW1394_ISO_ERROR) { goto out_produce; } } /* success */ retval = 0; out_produce: if(packets.n_packets > 0) { if(ioctl(ihandle->fd, RAW1394_IOC_ISO_XMIT_PACKETS, &packets)) retval = -1; } out: if(stop_sync) { if(ieee1394_iso_xmit_sync(ihandle)) return -1; ieee1394_iso_stop(ihandle); } return retval; } int ieee1394_iso_xmit_write(raw1394handle_t handle, unsigned char *data, unsigned int len, unsigned char tag, unsigned char sy) { ieee1394handle_t ihandle = handle->mode.ieee1394; struct raw1394_iso_status *stat = &ihandle->iso_status; struct raw1394_iso_packets packets; struct raw1394_iso_packet_info info; if(ihandle->iso_mode != ISO_XMIT || ihandle->iso_xmit_handler != NULL) { errno = EINVAL; return -1; } /* wait until buffer space is available */ while(ihandle->iso_status.n_packets <= 1) { /* if the file descriptor has been set non-blocking, return immediately */ if(fcntl(ihandle->fd, F_GETFL) & O_NONBLOCK) { errno = EAGAIN; return -1; } if(ieee1394_loop_iterate(handle)) { return -1; } } /* copy the data to the packet buffer */ info.offset = ihandle->next_packet * ihandle->iso_buf_stride; info.len = len; info.tag = tag; info.sy = sy; memcpy(ihandle->iso_buffer + info.offset, data, len); packets.n_packets = 1; packets.infos = &info; if(ioctl(ihandle->fd, RAW1394_IOC_ISO_XMIT_PACKETS, &packets)) return -1; stat->n_packets--; ihandle->next_packet = increment_and_wrap(ihandle->next_packet, stat->config.buf_packets); if(stat->xmit_cycle != -1) stat->xmit_cycle = increment_and_wrap(stat->xmit_cycle, 8000); return 0; } int ieee1394_iso_xmit_start(ieee1394handle_t handle, int start_on_cycle, int prebuffer_packets) { int args[2]; if(handle->iso_mode != ISO_XMIT) { errno = EINVAL; return -1; } args[0] = start_on_cycle; args[1] = prebuffer_packets; if(ioctl(handle->fd, RAW1394_IOC_ISO_XMIT_START, &args[0])) return -1; handle->iso_state = ISO_GO; return 0; } int ieee1394_iso_xmit_sync(ieee1394handle_t handle) { if(handle->iso_mode != ISO_XMIT) { errno = EINVAL; return -1; } return ioctl(handle->fd, RAW1394_IOC_ISO_XMIT_SYNC, 0); } void ieee1394_iso_stop(ieee1394handle_t handle) { if(handle->iso_mode == ISO_INACTIVE) { return; } ioctl(handle->fd, RAW1394_IOC_ISO_XMIT_RECV_STOP, 0); handle->iso_state = ISO_STOP; } void ieee1394_iso_shutdown(ieee1394handle_t handle) { if(handle->iso_buffer) { munmap(handle->iso_buffer, handle->iso_status.config.data_buf_size); handle->iso_buffer = NULL; } if(handle->iso_mode != ISO_INACTIVE) { ieee1394_iso_stop(handle); ioctl(handle->fd, RAW1394_IOC_ISO_SHUTDOWN, 0); } if(handle->iso_packet_infos) { #if _POSIX_MEMLOCK > 0 munlock(handle->iso_packet_infos, handle->iso_status.config.buf_packets * sizeof(struct raw1394_iso_packet_info)); #endif free(handle->iso_packet_infos); handle->iso_packet_infos = NULL; } handle->iso_mode = ISO_INACTIVE; } int ieee1394_read_cycle_timer(ieee1394handle_t handle, u_int32_t *cycle_timer, u_int64_t *local_time) { int err; struct raw1394_cycle_timer ctr = { 0 }; err = ioctl(handle->fd, RAW1394_IOC_GET_CYCLE_TIMER, &ctr); if (!err) { *cycle_timer = ctr.cycle_timer; *local_time = ctr.local_time; } return err; } static int _iso_recv_packets(raw1394handle_t handle) { ieee1394handle_t ihandle = handle->mode.ieee1394; struct raw1394_iso_status *stat = &ihandle->iso_status; struct raw1394_iso_packets packets; int retval = -1, packets_done = 0; if(ihandle->iso_mode != ISO_RECV) { errno = EINVAL; return -1; } /* ask the kernel to fill an array with packet info structs */ packets.n_packets = stat->n_packets; packets.infos = ihandle->iso_packet_infos; if(packets.infos == NULL) goto out; if(ioctl(ihandle->fd, RAW1394_IOC_ISO_RECV_PACKETS, &packets) < 0) goto out; while(stat->n_packets > 0) { struct raw1394_iso_packet_info *info; enum raw1394_iso_disposition disp; info = &packets.infos[packets_done]; /* call handler */ disp = ihandle->iso_recv_handler(handle, ihandle->iso_buffer + info->offset, info->len, info->channel, info->tag, info->sy, info->cycle, _iso_dropped(ihandle)); /* advance packet cursors */ stat->n_packets--; packets_done++; if(disp == RAW1394_ISO_DEFER) { /* queue an event so that we don't hang in the next read() */ if(ioctl(ihandle->fd, RAW1394_IOC_ISO_QUEUE_ACTIVITY, 0)) goto out_consume; break; } else if(disp == RAW1394_ISO_STOP || disp == RAW1394_ISO_STOP_NOSYNC) { ieee1394_iso_stop(ihandle); break; } else if(disp == RAW1394_ISO_ERROR) { goto out_consume; } } /* success */ retval = 0; out_consume: if(packets_done > 0) { if(ioctl(ihandle->fd, RAW1394_IOC_ISO_RECV_RELEASE_PACKETS, packets_done)) retval = -1; } out: return retval; } /* run the ISO state machine; called from raw1394_loop_iterate() */ int _ieee1394_iso_iterate(raw1394handle_t handle) { ieee1394handle_t ihandle = handle->mode.ieee1394; int err; if(ihandle->iso_mode == ISO_INACTIVE) return 0; err = ioctl(ihandle->fd, RAW1394_IOC_ISO_GET_STATUS, &ihandle->iso_status); if(err != 0) return err; ihandle->iso_packets_dropped += ihandle->iso_status.overflows; if(ihandle->iso_state == ISO_GO) { if(ihandle->iso_mode == ISO_XMIT) { if(ihandle->iso_xmit_handler) { return _iso_xmit_queue_packets(handle); } } if(ihandle->iso_mode == ISO_RECV) { if(ihandle->iso_recv_handler) { return _iso_recv_packets(handle); } } } return 0; }