/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * av1394 isochronous receive module */ #include /* configuration routines */ static void av1394_ir_cleanup(av1394_ic_t *, int); static int av1394_ir_build_ixl(av1394_ic_t *); static void av1394_ir_ixl_label_init(av1394_ir_ixl_data_t *, ixl1394_command_t *); static void av1394_ir_ixl_buf_init(av1394_ic_t *, ixl1394_xfer_buf_t *, av1394_isoch_seg_t *, off_t, uint64_t, uint16_t, ixl1394_command_t *); static void av1394_ir_ixl_cb_init(av1394_ic_t *, av1394_ir_ixl_data_t *, int); static void av1394_ir_ixl_jump_init(av1394_ic_t *, av1394_ir_ixl_data_t *, int); static void av1394_ir_destroy_ixl(av1394_ic_t *); static int av1394_ir_alloc_isoch_dma(av1394_ic_t *); static void av1394_ir_free_isoch_dma(av1394_ic_t *); static void av1394_ir_dma_sync_frames(av1394_ic_t *, int, int); /* callbacks */ static void av1394_ir_ixl_frame_cb(opaque_t, struct ixl1394_callback *); static void av1394_ir_overflow_resume(av1394_ic_t *icp); static void av1394_ir_dma_stopped_cb(t1394_isoch_dma_handle_t, opaque_t, id1394_isoch_dma_stopped_t); /* data transfer routines */ static int av1394_ir_add_frames(av1394_ic_t *, int, int); static int av1394_ir_wait_frames(av1394_ic_t *, int *, int *); static int av1394_ir_copyout(av1394_ic_t *, struct uio *, int *); static void av1394_ir_zero_pkts(av1394_ic_t *, int, int); /* value complementary to hi & lo watermarks (modulo number of frames) */ int av1394_ir_hiwat_sub = 2; int av1394_ir_lowat_sub = 3; int av1394_ir_dump_ixl = 0; #define AV1394_TNF_ENTER(func) \ TNF_PROBE_0_DEBUG(func##_enter, AV1394_TNF_ISOCH_STACK, ""); #define AV1394_TNF_EXIT(func) \ TNF_PROBE_0_DEBUG(func##_exit, AV1394_TNF_ISOCH_STACK, ""); int av1394_ir_init(av1394_ic_t *icp, int *error) { av1394_ir_t *irp = &icp->ic_ir; av1394_isoch_pool_t *pool = &irp->ir_data_pool; int nframes; AV1394_TNF_ENTER(av1394_ir_init); nframes = av1394_ic_alloc_pool(pool, icp->ic_framesz, icp->ic_nframes, AV1394_IR_NFRAMES_MIN); if (nframes == 0) { *error = IEC61883_ERR_NOMEM; AV1394_TNF_EXIT(av1394_ir_init); return (EINVAL); } mutex_enter(&icp->ic_mutex); icp->ic_nframes = nframes; irp->ir_hiwat = nframes - av1394_ir_hiwat_sub; irp->ir_lowat = nframes - av1394_ir_lowat_sub; if (av1394_ic_dma_setup(icp, pool) != DDI_SUCCESS) { mutex_exit(&icp->ic_mutex); *error = IEC61883_ERR_NOMEM; av1394_ir_cleanup(icp, 1); AV1394_TNF_EXIT(av1394_ir_init); return (EINVAL); } if (av1394_ir_build_ixl(icp) != DDI_SUCCESS) { mutex_exit(&icp->ic_mutex); *error = IEC61883_ERR_NOMEM; av1394_ir_cleanup(icp, 2); AV1394_TNF_EXIT(av1394_ir_init); return (EINVAL); } mutex_exit(&icp->ic_mutex); if (av1394_ir_alloc_isoch_dma(icp) != DDI_SUCCESS) { *error = IEC61883_ERR_NOMEM; av1394_ir_cleanup(icp, 3); AV1394_TNF_EXIT(av1394_ir_init); return (EINVAL); } AV1394_TNF_EXIT(av1394_ir_init); return (0); } void av1394_ir_fini(av1394_ic_t *icp) { AV1394_TNF_ENTER(av1394_ir_fini); av1394_ir_cleanup(icp, AV1394_CLEANUP_LEVEL_MAX); AV1394_TNF_ENTER(av1394_ir_fini); } int av1394_ir_start(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; av1394_ir_t *irp = &icp->ic_ir; id1394_isoch_dma_ctrlinfo_t idma_ctrlinfo = { 0 }; int result; int err; int ret = 0; AV1394_TNF_ENTER(av1394_ir_start); mutex_enter(&icp->ic_mutex); if (icp->ic_state != AV1394_IC_IDLE) { mutex_exit(&icp->ic_mutex); return (0); } irp->ir_first_full = 0; irp->ir_last_empty = icp->ic_nframes - 1; irp->ir_nfull = 0; irp->ir_nempty = icp->ic_nframes; irp->ir_read_cnt = 0; mutex_exit(&icp->ic_mutex); err = t1394_start_isoch_dma(avp->av_t1394_hdl, icp->ic_isoch_hdl, &idma_ctrlinfo, 0, &result); if (err == DDI_SUCCESS) { mutex_enter(&icp->ic_mutex); icp->ic_state = AV1394_IC_DMA; mutex_exit(&icp->ic_mutex); } else { TNF_PROBE_1(av1394_ir_start_error, AV1394_TNF_ISOCH_ERROR, "", tnf_int, result, result); ret = EIO; } AV1394_TNF_EXIT(av1394_ir_start); return (ret); } int av1394_ir_stop(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; AV1394_TNF_ENTER(av1394_ir_stop); mutex_enter(&icp->ic_mutex); if (icp->ic_state != AV1394_IC_IDLE) { mutex_exit(&icp->ic_mutex); t1394_stop_isoch_dma(avp->av_t1394_hdl, icp->ic_isoch_hdl, 0); mutex_enter(&icp->ic_mutex); icp->ic_state = AV1394_IC_IDLE; } mutex_exit(&icp->ic_mutex); AV1394_TNF_EXIT(av1394_ir_stop); return (0); } int av1394_ir_recv(av1394_ic_t *icp, iec61883_recv_t *recv) { int ret = 0; int idx, cnt; idx = recv->rx_xfer.xf_empty_idx; cnt = recv->rx_xfer.xf_empty_cnt; /* check arguments */ if ((idx < 0) || (idx >= icp->ic_nframes) || (cnt < 0) || (cnt > icp->ic_nframes)) { TNF_PROBE_2(av1394_ir_recv_error_args, AV1394_TNF_ISOCH_ERROR, "", tnf_int, idx, idx, tnf_int, cnt, cnt); return (EINVAL); } mutex_enter(&icp->ic_mutex); if (cnt > 0) { /* add empty frames to the pool */ if ((ret = av1394_ir_add_frames(icp, idx, cnt)) != 0) { mutex_exit(&icp->ic_mutex); return (ret); } } /* wait for new frames to arrive */ ret = av1394_ir_wait_frames(icp, &recv->rx_xfer.xf_full_idx, &recv->rx_xfer.xf_full_cnt); mutex_exit(&icp->ic_mutex); return (ret); } int av1394_ir_read(av1394_ic_t *icp, struct uio *uiop) { av1394_ir_t *irp = &icp->ic_ir; int ret = 0; int empty_cnt; AV1394_TNF_ENTER(av1394_ir_read); mutex_enter(&icp->ic_mutex); while (uiop->uio_resid) { /* wait for full frames, if necessary */ if (irp->ir_read_cnt == 0) { irp->ir_read_off = 0; ret = av1394_ir_wait_frames(icp, &irp->ir_read_idx, &irp->ir_read_cnt); if (ret != 0) { mutex_exit(&icp->ic_mutex); AV1394_TNF_EXIT(av1394_ir_read); return (ret); } } /* copyout the data */ ret = av1394_ir_copyout(icp, uiop, &empty_cnt); /* return freed frames to the pool */ if (empty_cnt > 0) { av1394_ir_zero_pkts(icp, irp->ir_read_idx, empty_cnt); ret = av1394_ir_add_frames(icp, irp->ir_read_idx, empty_cnt); irp->ir_read_idx += empty_cnt; irp->ir_read_idx %= icp->ic_nframes; irp->ir_read_cnt -= empty_cnt; } } mutex_exit(&icp->ic_mutex); AV1394_TNF_EXIT(av1394_ir_read); return (ret); } /* * * --- configuration routines * */ static void av1394_ir_cleanup(av1394_ic_t *icp, int level) { av1394_isoch_pool_t *pool = &icp->ic_ir.ir_data_pool; ASSERT((level > 0) && (level <= AV1394_CLEANUP_LEVEL_MAX)); switch (level) { default: av1394_ir_free_isoch_dma(icp); /* FALLTHRU */ case 3: av1394_ir_destroy_ixl(icp); /* FALLTHRU */ case 2: av1394_ic_dma_cleanup(icp, pool); /* FALLTHRU */ case 1: av1394_ic_free_pool(pool); /* FALLTHRU */ } } /* * av1394_ir_build_ixl() * Build an IXL chain to receive CIP data. The smallest instance of data * that can be received is a packet, typically 512 bytes. Frames consist * of a number of packets, typically 250-300. Packet size, frame size and * number of frames allocated are set by a user process. The received data * made available to the user process in full frames, hence there an IXL * callback at the end of each frame. A sequence of IXL commands that * receives one frame is further referred to as an IXL data block. * * During normal operation, frames are in a circular list and IXL chain * does not change. When the user process does not keep up with the * data flow and there are too few empty frames left, the jump following * last empty frame is dynamically updated to point to NULL -- otherwise * the first full frame would be overwritten. When IXL execution reaches * the nulled jump, it just waits until the driver updates it again or * stops the transfer. Once a user process frees up enough frames, the * jump is restored and transfer continues. User process will be able to * detect dropped packets using continuity conters embedded in the data. * * Because RECV_BUF buffer size is limited to AV1394_IXL_BUFSZ_MAX, and due * to isoch pool segmentaion, the number of RECV_BUF commands per IXL data * block depends on frame size. Also, to simplify calculations, we consider * a sequence of RECV_BUF commands to consist of two parts: zero or more * equal-sized RECV_BUF commands followed by one "tail" REC_BUF command, * whose size may not be equal to others. * * Schematically the IXL chain looks like this: * * ... * LABEL N; * RECV_BUF(buf) * ... * RECV_BUF(tail) * CALLBACK(frame done); * JUMP_U(LABEL (N+1)%nframes or NULL); * ... */ static int av1394_ir_build_ixl(av1394_ic_t *icp) { av1394_ir_t *irp = &icp->ic_ir; av1394_isoch_pool_t *pool = &irp->ir_data_pool; int i; /* segment index */ int j; int fi; /* frame index */ int bi; /* buffer index */ AV1394_TNF_ENTER(av1394_ir_build_ixl); /* allocate space for IXL data blocks */ irp->ir_ixl_data = kmem_zalloc(icp->ic_nframes * sizeof (av1394_ir_ixl_data_t), KM_SLEEP); /* * We have a bunch of segments, and each is divided into cookies. We * need to cover the segments with RECV_BUFs such that they * - don't span cookies * - don't span frames * - are at most AV1394_IXL_BUFSZ_MAX * * The straightforward algorithm is to start from the beginning, find * the next lowest frame or cookie boundary, and either make a buf for * it if it is smaller than AV1394_IXL_BUFSZ_MAX, or make multiple * bufs for it as with av1394_ic_ixl_seg_decomp(). And repeat. */ irp->ir_ixl_nbufs = 0; for (i = 0; i < pool->ip_nsegs; ++i) { av1394_isoch_seg_t *isp = &pool->ip_seg[i]; size_t dummy1, dummy2; uint_t off = 0; uint_t end; uint_t frame_end = icp->ic_framesz; int ci = 0; uint_t cookie_end = isp->is_dma_cookie[ci].dmac_size; for (;;) { end = min(frame_end, cookie_end); if (end - off <= AV1394_IXL_BUFSZ_MAX) { ++irp->ir_ixl_nbufs; } else { irp->ir_ixl_nbufs += av1394_ic_ixl_seg_decomp( end - off, icp->ic_pktsz, &dummy1, &dummy2); /* count the tail buffer */ ++irp->ir_ixl_nbufs; } off = end; if (off >= isp->is_size) break; if (off == frame_end) frame_end += icp->ic_framesz; if (off == cookie_end) { ++ci; cookie_end += isp->is_dma_cookie[ci].dmac_size; } } } irp->ir_ixl_buf = kmem_zalloc(irp->ir_ixl_nbufs * sizeof (ixl1394_xfer_buf_t), KM_SLEEP); fi = 0; bi = 0; for (i = 0; i < pool->ip_nsegs; ++i) { av1394_isoch_seg_t *isp = &pool->ip_seg[i]; uint_t off = 0; /* offset into segment */ uint_t end; uint_t coff = 0; /* offset into cookie */ uint_t frame_end = icp->ic_framesz; int ci = 0; uint_t cookie_end = isp->is_dma_cookie[ci].dmac_size; ixl1394_command_t *nextp; av1394_ir_ixl_label_init(&irp->ir_ixl_data[fi], (ixl1394_command_t *)&irp->ir_ixl_buf[bi]); for (;;) { end = min(frame_end, cookie_end); if (end == frame_end) nextp = (ixl1394_command_t *) &irp->ir_ixl_data[fi].rd_cb; else nextp = (ixl1394_command_t *) &irp->ir_ixl_buf[bi + 1]; if (end - off <= AV1394_IXL_BUFSZ_MAX) { av1394_ir_ixl_buf_init(icp, &irp->ir_ixl_buf[bi], isp, off, isp->is_dma_cookie[ci].dmac_laddress + coff, end - off, nextp); coff += end - off; off = end; ++bi; } else { size_t reg, tail; uint_t nbufs; nbufs = av1394_ic_ixl_seg_decomp(end - off, icp->ic_pktsz, ®, &tail); for (j = 0; j < nbufs; ++j) { av1394_ir_ixl_buf_init(icp, &irp->ir_ixl_buf[bi], isp, off, isp->is_dma_cookie[ci]. dmac_laddress + coff, reg, (ixl1394_command_t *) &irp->ir_ixl_buf[bi + 1]); ++bi; off += reg; coff += reg; } av1394_ir_ixl_buf_init(icp, &irp->ir_ixl_buf[bi], isp, off, isp->is_dma_cookie[ci].dmac_laddress + coff, tail, nextp); ++bi; off += tail; coff += tail; } ASSERT((off == frame_end) || (off == cookie_end)); if (off >= isp->is_size) break; if (off == frame_end) { av1394_ir_ixl_cb_init(icp, &irp->ir_ixl_data[fi], fi); av1394_ir_ixl_jump_init(icp, &irp->ir_ixl_data[fi], fi); ++fi; frame_end += icp->ic_framesz; av1394_ir_ixl_label_init(&irp->ir_ixl_data[fi], (ixl1394_command_t *)&irp->ir_ixl_buf[bi]); } if (off == cookie_end) { ++ci; cookie_end += isp->is_dma_cookie[ci].dmac_size; coff = 0; } } av1394_ir_ixl_cb_init(icp, &irp->ir_ixl_data[fi], fi); av1394_ir_ixl_jump_init(icp, &irp->ir_ixl_data[fi], fi); ++fi; } ASSERT(fi == icp->ic_nframes); ASSERT(bi == irp->ir_ixl_nbufs); irp->ir_ixlp = (ixl1394_command_t *)irp->ir_ixl_data; if (av1394_ir_dump_ixl) { av1394_ic_ixl_dump(irp->ir_ixlp); } AV1394_TNF_EXIT(av1394_ir_build_ixl); return (DDI_SUCCESS); } static void av1394_ir_ixl_label_init(av1394_ir_ixl_data_t *dp, ixl1394_command_t *nextp) { dp->rd_label.ixl_opcode = IXL1394_OP_LABEL; dp->rd_label.next_ixlp = nextp; } static void av1394_ir_ixl_buf_init(av1394_ic_t *icp, ixl1394_xfer_buf_t *buf, av1394_isoch_seg_t *isp, off_t offset, uint64_t addr, uint16_t size, ixl1394_command_t *nextp) { buf->ixl_opcode = IXL1394_OP_RECV_BUF; buf->size = size; buf->pkt_size = icp->ic_pktsz; buf->ixl_buf._dmac_ll = addr; buf->mem_bufp = isp->is_kaddr + offset; buf->next_ixlp = nextp; } /*ARGSUSED*/ static void av1394_ir_ixl_cb_init(av1394_ic_t *icp, av1394_ir_ixl_data_t *dp, int i) { dp->rd_cb.ixl_opcode = IXL1394_OP_CALLBACK; dp->rd_cb.callback = av1394_ir_ixl_frame_cb; dp->rd_cb.callback_arg = (void *)(intptr_t)i; dp->rd_cb.next_ixlp = (ixl1394_command_t *)&dp->rd_jump; } static void av1394_ir_ixl_jump_init(av1394_ic_t *icp, av1394_ir_ixl_data_t *dp, int i) { av1394_ir_t *irp = &icp->ic_ir; int next_idx; ixl1394_command_t *jump_cmd; next_idx = (i + 1) % icp->ic_nframes; jump_cmd = (ixl1394_command_t *)&irp->ir_ixl_data[next_idx]; dp->rd_jump.ixl_opcode = IXL1394_OP_JUMP_U; dp->rd_jump.label = jump_cmd; dp->rd_jump.next_ixlp = (next_idx != 0) ? jump_cmd : NULL; } static void av1394_ir_destroy_ixl(av1394_ic_t *icp) { av1394_ir_t *irp = &icp->ic_ir; AV1394_TNF_ENTER(av1394_ir_destroy_ixl); mutex_enter(&icp->ic_mutex); kmem_free(irp->ir_ixl_buf, irp->ir_ixl_nbufs * sizeof (ixl1394_xfer_buf_t)); kmem_free(irp->ir_ixl_data, icp->ic_nframes * sizeof (av1394_ir_ixl_data_t)); irp->ir_ixlp = NULL; irp->ir_ixl_buf = NULL; irp->ir_ixl_data = NULL; mutex_exit(&icp->ic_mutex); AV1394_TNF_EXIT(av1394_ir_destroy_ixl); } static int av1394_ir_alloc_isoch_dma(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; av1394_ir_t *irp = &icp->ic_ir; id1394_isoch_dmainfo_t di; int result; int ret; AV1394_TNF_ENTER(av1394_ir_alloc_isoch_dma); di.ixlp = irp->ir_ixlp; di.channel_num = icp->ic_num; di.global_callback_arg = icp; di.idma_options = ID1394_LISTEN_PKT_MODE; di.isoch_dma_stopped = av1394_ir_dma_stopped_cb; di.idma_evt_arg = icp; if ((ret = t1394_alloc_isoch_dma(avp->av_t1394_hdl, &di, 0, &icp->ic_isoch_hdl, &result)) != DDI_SUCCESS) { TNF_PROBE_1(av1394_ir_alloc_isoch_dma_error, AV1394_TNF_ISOCH_ERROR, "", tnf_int, result, result); } AV1394_TNF_EXIT(av1394_ir_alloc_isoch_dma); return (ret); } static void av1394_ir_free_isoch_dma(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; AV1394_TNF_ENTER(av1394_ir_free_isoch_rsrc); t1394_free_isoch_dma(avp->av_t1394_hdl, 0, &icp->ic_isoch_hdl); AV1394_TNF_EXIT(av1394_ir_free_isoch_rsrc); } static void av1394_ir_dma_sync_frames(av1394_ic_t *icp, int idx, int cnt) { av1394_ic_dma_sync_frames(icp, idx, cnt, &icp->ic_ir.ir_data_pool, DDI_DMA_SYNC_FORCPU); } /* * * --- callbacks * */ /*ARGSUSED*/ static void av1394_ir_ixl_frame_cb(opaque_t arg, struct ixl1394_callback *cb) { av1394_ic_t *icp = arg; av1394_isoch_t *ip = &icp->ic_avp->av_i; av1394_ir_t *irp = &icp->ic_ir; AV1394_TNF_ENTER(av1394_ir_ixl_frame_cb); mutex_enter(&ip->i_mutex); mutex_enter(&icp->ic_mutex); if (irp->ir_nfull < icp->ic_nframes) { irp->ir_nfull++; irp->ir_nempty--; cv_broadcast(&icp->ic_xfer_cv); /* * signal the overflow condition early, so we get enough * time to handle it before old data is overwritten */ if (irp->ir_nfull >= irp->ir_hiwat) { av1394_ic_trigger_softintr(icp, icp->ic_num, AV1394_PREQ_IR_OVERFLOW); } } mutex_exit(&icp->ic_mutex); mutex_exit(&ip->i_mutex); AV1394_TNF_EXIT(av1394_ir_ixl_frame_cb); } /* * received data overflow */ void av1394_ir_overflow(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; av1394_ir_t *irp = &icp->ic_ir; int idx; ixl1394_jump_t *old_jmp; ixl1394_jump_t new_jmp; id1394_isoch_dma_updateinfo_t update_info; int err; int result; AV1394_TNF_ENTER(av1394_ir_overflow); /* * in the circular IXL chain overflow means overwriting the least * recent data. to avoid that, we suspend the transfer by NULL'ing * the last IXL block until the user process frees up some frames. */ idx = irp->ir_last_empty; old_jmp = &irp->ir_ixl_data[idx].rd_jump; new_jmp.ixl_opcode = IXL1394_OP_JUMP_U; new_jmp.label = NULL; new_jmp.next_ixlp = NULL; update_info.orig_ixlp = (ixl1394_command_t *)old_jmp; update_info.temp_ixlp = (ixl1394_command_t *)&new_jmp; update_info.ixl_count = 1; mutex_exit(&icp->ic_mutex); err = t1394_update_isoch_dma(avp->av_t1394_hdl, icp->ic_isoch_hdl, &update_info, 0, &result); mutex_enter(&icp->ic_mutex); if (err == DDI_SUCCESS) { irp->ir_overflow_idx = idx; icp->ic_state = AV1394_IC_SUSPENDED; } else { TNF_PROBE_2(av1394_ir_overflow_error_update, AV1394_TNF_ISOCH_ERROR, "", tnf_int, err, err, tnf_int, result, result); } AV1394_TNF_EXIT(av1394_ir_overflow); } /* * restore from overflow condition */ static void av1394_ir_overflow_resume(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; av1394_ir_t *irp = &icp->ic_ir; int idx, next_idx; ixl1394_jump_t *old_jmp; ixl1394_jump_t new_jmp; id1394_isoch_dma_updateinfo_t update_info; int err; int result; AV1394_TNF_ENTER(av1394_ir_overflow_resume); /* * restore the jump command we NULL'ed in av1394_ir_overflow() */ idx = irp->ir_overflow_idx; next_idx = (idx + 1) % icp->ic_nframes; old_jmp = &irp->ir_ixl_data[idx].rd_jump; new_jmp.ixl_opcode = IXL1394_OP_JUMP_U; new_jmp.label = (ixl1394_command_t *)&irp->ir_ixl_data[next_idx]; new_jmp.next_ixlp = NULL; update_info.orig_ixlp = (ixl1394_command_t *)old_jmp; update_info.temp_ixlp = (ixl1394_command_t *)&new_jmp; update_info.ixl_count = 1; mutex_exit(&icp->ic_mutex); err = t1394_update_isoch_dma(avp->av_t1394_hdl, icp->ic_isoch_hdl, &update_info, 0, &result); mutex_enter(&icp->ic_mutex); if (err == DDI_SUCCESS) { icp->ic_state = AV1394_IC_DMA; } else { TNF_PROBE_2(av1394_ir_overflow_resume_error_update, AV1394_TNF_ISOCH_ERROR, "", tnf_int, err, err, tnf_int, result, result); } AV1394_TNF_EXIT(av1394_ir_overflow_resume); } /*ARGSUSED*/ static void av1394_ir_dma_stopped_cb(t1394_isoch_dma_handle_t t1394_idma_hdl, opaque_t idma_evt_arg, id1394_isoch_dma_stopped_t status) { av1394_ic_t *icp = idma_evt_arg; AV1394_TNF_ENTER(av1394_ir_dma_stopped_cb); mutex_enter(&icp->ic_mutex); icp->ic_state = AV1394_IC_IDLE; mutex_exit(&icp->ic_mutex); AV1394_TNF_EXIT(av1394_ir_dma_stopped_cb); } /* * * --- data transfer routines * * av1394_ir_add_frames() * Add empty frames to the pool. */ static int av1394_ir_add_frames(av1394_ic_t *icp, int idx, int cnt) { av1394_ir_t *irp = &icp->ic_ir; /* can only add to the tail */ if (idx != ((irp->ir_last_empty + 1) % icp->ic_nframes)) { TNF_PROBE_1(av1394_ir_add_frames_error, AV1394_TNF_ISOCH_ERROR, "", tnf_int, idx, idx); return (EINVAL); } /* turn full frames into empty ones */ irp->ir_nfull -= cnt; irp->ir_first_full = (irp->ir_first_full + cnt) % icp->ic_nframes; irp->ir_nempty += cnt; irp->ir_last_empty = (irp->ir_last_empty + cnt) % icp->ic_nframes; ASSERT((irp->ir_nfull >= 0) && (irp->ir_nempty <= icp->ic_nframes)); /* if suspended due to overflow, check if iwe can resume */ if ((icp->ic_state == AV1394_IC_SUSPENDED) && (irp->ir_nempty >= irp->ir_lowat)) { av1394_ir_overflow_resume(icp); } return (0); } static int av1394_ir_wait_frames(av1394_ic_t *icp, int *idx, int *cnt) { av1394_ir_t *irp = &icp->ic_ir; int ret = 0; while (irp->ir_nfull == 0) { if (cv_wait_sig(&icp->ic_xfer_cv, &icp->ic_mutex) <= 0) { ret = EINTR; break; } } if (irp->ir_nfull > 0) { *idx = irp->ir_first_full; *cnt = irp->ir_nfull; av1394_ir_dma_sync_frames(icp, *idx, *cnt); ret = 0; } return (ret); } /* * copyout the data, adjust to data format and remove empty CIPs if possible */ static int av1394_ir_copyout(av1394_ic_t *icp, struct uio *uiop, int *empty_cnt) { av1394_ir_t *irp = &icp->ic_ir; av1394_isoch_seg_t *seg = irp->ir_data_pool.ip_seg; int idx = irp->ir_read_idx; int cnt = irp->ir_read_cnt; int pktsz = icp->ic_pktsz; int bs; /* data block size */ caddr_t kaddr_begin, kaddr; int pkt_off; /* offset into current packet */ int len; int frame_resid; /* bytes left in the current frame */ int ret = 0; *empty_cnt = 0; /* DBS -> block size */ bs = *(uchar_t *)(seg[idx].is_kaddr + 1) * 4 + AV1394_CIPSZ; if ((bs > pktsz) || (bs < AV1394_CIPSZ + 8)) { bs = pktsz; } while ((cnt > 0) && (uiop->uio_resid > 0) && (ret == 0)) { kaddr = kaddr_begin = seg[idx].is_kaddr + irp->ir_read_off; frame_resid = icp->ic_framesz - irp->ir_read_off; mutex_exit(&icp->ic_mutex); /* copyout data blocks, skipping empty CIPs */ while ((uiop->uio_resid > 0) && (frame_resid > 0)) { pkt_off = (uintptr_t)kaddr % pktsz; /* * a quadlet following CIP header can't be zero * unless in an empty packet */ if ((pkt_off == 0) && (*(uint32_t *)(kaddr + AV1394_CIPSZ) == 0)) { kaddr += pktsz; frame_resid -= pktsz; continue; } len = bs - pkt_off; if (len > uiop->uio_resid) { len = uiop->uio_resid; } if (len > frame_resid) { len = frame_resid; } if ((ret = uiomove(kaddr, len, UIO_READ, uiop)) != 0) { break; } if (pkt_off + len == bs) { kaddr += pktsz - pkt_off; frame_resid -= pktsz - pkt_off; } else { kaddr += len; frame_resid -= len; } } mutex_enter(&icp->ic_mutex); if (frame_resid > 0) { irp->ir_read_off = kaddr - kaddr_begin; } else { irp->ir_read_off = 0; idx = (idx + 1) % icp->ic_nframes; cnt--; (*empty_cnt)++; } } return (ret); } /* * zero a quadlet in each packet so we can recognize empty CIPs */ static void av1394_ir_zero_pkts(av1394_ic_t *icp, int idx, int cnt) { av1394_ir_t *irp = &icp->ic_ir; av1394_isoch_seg_t *seg = irp->ir_data_pool.ip_seg; caddr_t kaddr, kaddr_end; int pktsz = icp->ic_pktsz; int i; for (i = cnt; i > 0; i--) { kaddr = seg[idx].is_kaddr + AV1394_CIPSZ; kaddr_end = seg[idx].is_kaddr + icp->ic_framesz; do { *(uint32_t *)kaddr = 0; kaddr += pktsz; } while (kaddr < kaddr_end); idx = (idx + 1) % icp->ic_nframes; } }