/* * 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 transmit module */ #include static int av1394_it_start_common(av1394_ic_t *); /* configuration routines */ static void av1394_it_cleanup(av1394_ic_t *, int); static int av1394_it_bld_ixl(av1394_ic_t *); static void av1394_it_destroy_ixl(av1394_ic_t *); static int av1394_it_ixl_bld_data(av1394_ic_t *); static void av1394_it_ixl_destroy_data(av1394_ic_t *); static av1394_it_ixl_buf_t *av1394_it_ixl_bld_buf(av1394_ic_t *, int, int, off_t, int, int); static void av1394_it_ixl_complete_buf(av1394_it_ixl_buf_t *, av1394_it_ixl_empty_cip_t *); static void av1394_it_ixl_complete_buf2(av1394_it_ixl_buf_t *, av1394_it_ixl_buf_t *); static av1394_it_ixl_empty_cip_t *av1394_it_ixl_bld_empty_cip(av1394_ic_t *, int); static void av1394_it_ixl_complete_empty_cip(av1394_it_ixl_empty_cip_t *, av1394_it_ixl_buf_t *); static void av1394_it_ixl_bld_begin(av1394_ic_t *); static void av1394_it_ixl_begin_update_pkts(av1394_ic_t *, av1394_it_ixl_buf_t *); static int av1394_it_alloc_isoch_dma(av1394_ic_t *); static void av1394_it_free_isoch_dma(av1394_ic_t *); static void av1394_it_dma_sync_frames(av1394_ic_t *, int, int); /* callbacks */ static void av1394_it_ixl_begin_cb(opaque_t, struct ixl1394_callback *); static void av1394_it_ixl_buf_cb(opaque_t, struct ixl1394_callback *); static void av1394_it_ixl_eof_cb(av1394_it_ixl_buf_t *bp); static int av1394_it_underrun_resume(av1394_ic_t *); static void av1394_it_dma_stopped_cb(t1394_isoch_dma_handle_t, opaque_t, id1394_isoch_dma_stopped_t); /* data transfer routines */ static int av1394_it_add_frames(av1394_ic_t *, int, int); static int av1394_it_wait_frames(av1394_ic_t *, int *, int *, int *); static void av1394_it_update_frame_syt(av1394_ic_t *, int, int, uint16_t); static uint16_t av1394_it_ts_cyc2syt(uint16_t); static uint16_t av1394_it_ts_syt_inc(uint16_t, uint16_t); static void av1394_it_kcopyin(av1394_ic_t *, void *, size_t); static int av1394_it_copyin(av1394_ic_t *, struct uio *, int *, int); static boolean_t av1394_it_is_dv_frame_start(caddr_t); static void av1394_it_reset_frame_syt(av1394_ic_t *, int); /* tunables */ int av1394_it_hiwat_sub = 2; int av1394_it_lowat = 3; int av1394_it_start_thre = 3; /* xmit start threshold */ int av1394_it_syt_off = 3; /* SYT offset in cycles */ int av1394_it_dump_ixl = 0; int av1394_it_init(av1394_ic_t *icp, int *error) { av1394_it_t *itp = &icp->ic_it; av1394_isoch_pool_t *pool = &itp->it_data_pool; int nframes; nframes = av1394_ic_alloc_pool(pool, icp->ic_framesz, icp->ic_nframes, AV1394_IT_NFRAMES_MIN); if (nframes == 0) { *error = IEC61883_ERR_NOMEM; return (EINVAL); } mutex_enter(&icp->ic_mutex); icp->ic_nframes = nframes; itp->it_hiwat = nframes - av1394_it_hiwat_sub; itp->it_lowat = av1394_it_lowat; itp->it_start_thre = av1394_it_start_thre; itp->it_nempty = icp->ic_nframes; itp->it_last_full = icp->ic_nframes - 1; if (av1394_ic_dma_setup(icp, pool) != DDI_SUCCESS) { mutex_exit(&icp->ic_mutex); *error = IEC61883_ERR_NOMEM; av1394_it_cleanup(icp, 1); return (EINVAL); } if (av1394_it_bld_ixl(icp) != DDI_SUCCESS) { mutex_exit(&icp->ic_mutex); *error = IEC61883_ERR_NOMEM; av1394_it_cleanup(icp, 2); return (EINVAL); } mutex_exit(&icp->ic_mutex); if (av1394_it_alloc_isoch_dma(icp) != DDI_SUCCESS) { *error = IEC61883_ERR_NOMEM; av1394_it_cleanup(icp, 3); return (EINVAL); } return (0); } void av1394_it_fini(av1394_ic_t *icp) { av1394_it_cleanup(icp, AV1394_CLEANUP_LEVEL_MAX); } int av1394_it_start(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; int ret = 0; mutex_enter(&icp->ic_mutex); ASSERT(icp->ic_state == AV1394_IC_IDLE); /* should be enough full frames to be able to start */ if (itp->it_nfull >= itp->it_start_thre) { ret = av1394_it_start_common(icp); } mutex_exit(&icp->ic_mutex); return (ret); } static int av1394_it_start_common(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; id1394_isoch_dma_ctrlinfo_t idma_ctrlinfo = { 0 }; int result; int err; int ret = 0; ASSERT(icp->ic_state == AV1394_IC_IDLE); err = t1394_start_isoch_dma(avp->av_t1394_hdl, icp->ic_isoch_hdl, &idma_ctrlinfo, 0, &result); if (err == DDI_SUCCESS) { icp->ic_state = AV1394_IC_DMA; } else { ret = EIO; } return (ret); } int av1394_it_stop(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; av1394_it_t *itp = &icp->ic_it; 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; itp->it_nempty = icp->ic_nframes; itp->it_first_empty = 0; itp->it_last_full = icp->ic_nframes - 1; itp->it_nfull = 0; } mutex_exit(&icp->ic_mutex); return (0); } int av1394_it_xmit(av1394_ic_t *icp, iec61883_xmit_t *xmit) { av1394_it_t *itp = &icp->ic_it; int ret = 0; int idx, cnt; idx = xmit->tx_xfer.xf_full_idx; cnt = xmit->tx_xfer.xf_full_cnt; mutex_enter(&icp->ic_mutex); /* check arguments */ if ((idx < 0) || (cnt < 0) || (cnt > itp->it_nempty)) { mutex_exit(&icp->ic_mutex); return (EINVAL); } /* add full frames to the pool */ if (cnt > 0) { if ((ret = av1394_it_add_frames(icp, idx, cnt)) != 0) { mutex_exit(&icp->ic_mutex); return (ret); } } if ((icp->ic_state == AV1394_IC_IDLE) && (itp->it_nfull >= itp->it_start_thre)) { if ((ret = av1394_it_start_common(icp)) != 0) { mutex_exit(&icp->ic_mutex); return (ret); } } /* wait for new empty frames */ ret = av1394_it_wait_frames(icp, &xmit->tx_xfer.xf_empty_idx, &xmit->tx_xfer.xf_empty_cnt, &xmit->tx_miss_cnt); mutex_exit(&icp->ic_mutex); return (ret); } int av1394_it_write(av1394_ic_t *icp, struct uio *uiop) { av1394_inst_t *avp = icp->ic_avp; av1394_it_t *itp = &icp->ic_it; av1394_isoch_autoxmit_t *axp = &avp->av_i.i_autoxmit; int dv; int ret = 0; int full_cnt; int miss_cnt; mutex_enter(&icp->ic_mutex); dv = (axp->ax_fmt & AV1394_ISOCH_AUTOXMIT_DV); while (uiop->uio_resid > 0) { /* must have at least one empty frame */ if (itp->it_write_cnt == 0) { ret = av1394_it_wait_frames(icp, &itp->it_write_idx, &itp->it_write_cnt, &miss_cnt); if (ret != 0) { break; } } /* copyin as much data as we can */ if (axp->ax_copy_ciph) { ASSERT(itp->it_write_off == 0); av1394_it_kcopyin(icp, axp->ax_ciph, AV1394_CIPSZ); axp->ax_copy_ciph = B_FALSE; } if ((ret = av1394_it_copyin(icp, uiop, &full_cnt, dv)) != 0) { break; } /* add full frames to the pool */ if (full_cnt > 0) { ret = av1394_it_add_frames(icp, itp->it_write_idx, full_cnt); if (ret != 0) { break; } itp->it_write_idx += full_cnt; itp->it_write_idx %= icp->ic_nframes; } /* start xfer if not already */ if ((icp->ic_state == AV1394_IC_IDLE) && (itp->it_nfull >= itp->it_start_thre)) { if ((ret = av1394_it_start_common(icp)) != 0) { mutex_exit(&icp->ic_mutex); return (ret); } } } mutex_exit(&icp->ic_mutex); return (ret); } /* * * --- configuration routines * */ static void av1394_it_cleanup(av1394_ic_t *icp, int level) { av1394_isoch_pool_t *pool = &icp->ic_it.it_data_pool; ASSERT((level > 0) && (level <= AV1394_CLEANUP_LEVEL_MAX)); switch (level) { default: av1394_it_free_isoch_dma(icp); /* FALLTHRU */ case 3: av1394_it_destroy_ixl(icp); /* FALLTHRU */ case 2: av1394_ic_dma_cleanup(icp, pool); /* FALLTHRU */ case 1: av1394_ic_free_pool(pool); /* FALLTHRU */ } } /* * av1394_it_bld_ixl() * Build an IXL chain out of several blocks. */ static int av1394_it_bld_ixl(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; int ret; /* data block */ if ((ret = av1394_it_ixl_bld_data(icp)) != DDI_SUCCESS) { return (ret); } /* begin block */ if (icp->ic_param.cp_ts_mode != IEC61883_TS_NONE) { av1394_it_ixl_bld_begin(icp); itp->it_ixlp = (ixl1394_command_t *)&itp->it_ixl_begin; } else { itp->it_ixlp = (ixl1394_command_t *) &((av1394_it_ixl_buf_t *)itp->it_ixl_data)->tb_label; } if (av1394_it_dump_ixl) { av1394_ic_ixl_dump(itp->it_ixlp); } return (ret); } static void av1394_it_destroy_ixl(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; av1394_it_ixl_destroy_data(icp); itp->it_ixlp = NULL; } /* * build data transmit part of the IXL chain */ static int av1394_it_ixl_bld_data(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; av1394_isoch_pool_t *pool = &itp->it_data_pool; int total = 0; /* # of pkts in the chain */ int nfull = 0; /* # of full CIPs in a series */ int framenum = -1; /* frame number */ int bufsz_max; /* max buffer size in pkts */ int segnum = 0; /* current segment number */ int segsz; /* segment size in pkts */ off_t segoff = 0; /* segment offset in pkts */ av1394_it_ixl_empty_cip_t *ep = NULL; /* last empty CIP */ av1394_it_ixl_buf_t *bp = NULL; /* last data buffer */ av1394_it_ixl_buf_t *prevbp = NULL; int a, n, d; /* N/D algorithm variables */ int type, ptype; /* current and prev CIP type */ int tb_flags; itp->it_frame_info = kmem_zalloc(icp->ic_nframes * sizeof (av1394_it_frame_info_t), KM_SLEEP); bufsz_max = AV1394_IXL_BUFSZ_MAX / icp->ic_pktsz; n = icp->ic_param.cp_n; d = icp->ic_param.cp_d; /* * following assert guarantees no more than one empty CIP in a row, * i.e. empty CIPs account for <=50% of all packets. * this should be ensured by ioctl argument validation. */ ASSERT((n == 0) || (d / n > 1)); /* * build the chain. it is hard to precalculate amount of memory * needed for the entire chain, so we simply allocate as we go. */ ptype = AV1394_CIP_EMPTY; segsz = pool->ip_seg[0].is_size / icp->ic_pktsz; a = n; while (total < icp->ic_nframes * icp->ic_npkts) { /* insert empty CIPs using N/D algorithm */ a += n; if (a > d) { a -= d; type = AV1394_CIP_EMPTY; } else { type = AV1394_CIP_FULL; nfull++; } /* * merge series of full packets into single SEND_BUF commands. * a series can be terminated by: * - an empty CIP; * - series buffer size reached maximum; * - end of isoch segment; * - end of frame (which is always at the end of segment); */ if (((type == AV1394_CIP_EMPTY) || (segoff + nfull == segsz) || (nfull == bufsz_max)) && (nfull > 0)) { /* build buffer block */ prevbp = bp; tb_flags = 0; if (type == AV1394_CIP_EMPTY) { tb_flags |= AV1394_IT_IXL_BUF_NEXT_EMPTY; } if (total % icp->ic_npkts == 0) { tb_flags |= AV1394_IT_IXL_BUF_SOF; framenum++; } if ((total + nfull) % icp->ic_npkts == 0) { tb_flags |= AV1394_IT_IXL_BUF_EOF; } bp = av1394_it_ixl_bld_buf(icp, nfull, segnum, segoff, tb_flags, framenum); if (itp->it_ixl_data == NULL) { itp->it_ixl_data = &bp->tb_common; } /* complete previous empty CIP or a buffer */ if (ep) { av1394_it_ixl_complete_empty_cip(ep, bp); ep = NULL; } else if (prevbp) { av1394_it_ixl_complete_buf2(prevbp, bp); } /* if current segment is used up, pick next one */ segoff += nfull; if (segoff == segsz) { if (++segnum < pool->ip_nsegs) { segsz = pool->ip_seg[segnum].is_size / icp->ic_pktsz; } segoff = 0; } total += nfull; nfull = 0; } /* insert an empty packet if needed */ if (type == AV1394_CIP_EMPTY) { ep = av1394_it_ixl_bld_empty_cip(icp, framenum); av1394_it_ixl_complete_buf(bp, ep); } ptype = type; } ASSERT(nfull == 0); /* last packet must be an empty CIP, except when n == 0 */ if (n != 0) { if (ptype != AV1394_CIP_EMPTY) { ep = av1394_it_ixl_bld_empty_cip(icp, framenum); av1394_it_ixl_complete_buf(bp, ep); } av1394_it_ixl_complete_empty_cip(ep, (av1394_it_ixl_buf_t *)itp->it_ixl_data); ep->te_jump.next_ixlp = NULL; ep->te_common.tc_next = NULL; } else { bp->tb_jump.label = (ixl1394_command_t *) &(((av1394_it_ixl_buf_t *)itp->it_ixl_data)->tb_label); } return (DDI_SUCCESS); } static void av1394_it_ixl_destroy_data(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; av1394_it_ixl_common_t *cmd, *cmd_next; for (cmd = itp->it_ixl_data; cmd != NULL; cmd = cmd_next) { cmd_next = cmd->tc_next; kmem_free(cmd, cmd->tc_size); } itp->it_ixl_data = NULL; kmem_free(itp->it_frame_info, icp->ic_nframes * sizeof (av1394_it_frame_info_t)); } static av1394_it_ixl_buf_t * av1394_it_ixl_bld_buf(av1394_ic_t *icp, int cnt, int segnum, off_t off, int flags, int framenum) { av1394_it_t *itp = &icp->ic_it; av1394_isoch_seg_t *isp = &itp->it_data_pool.ip_seg[segnum]; av1394_it_ixl_buf_t *bp; int pktsz = icp->ic_pktsz; bp = kmem_zalloc(sizeof (av1394_it_ixl_buf_t), KM_SLEEP); bp->tb_common.tc_size = sizeof (av1394_it_ixl_buf_t); /* tc_next later */ bp->tb_flags = flags; bp->tb_framenum = framenum; bp->tb_icp = icp; bp->tb_label.ixl_opcode = IXL1394_OP_LABEL; bp->tb_label.next_ixlp = (ixl1394_command_t *)&bp->tb_buf; bp->tb_buf.ixl_opcode = IXL1394_OP_SEND_BUF; bp->tb_buf.pkt_size = pktsz; bp->tb_buf.size = cnt * pktsz; bp->tb_buf.ixl_buf._dmac_ll = isp->is_dma_cookie[0].dmac_laddress + off * pktsz; bp->tb_buf.mem_bufp = isp->is_kaddr + off * pktsz; if (flags & AV1394_IT_IXL_BUF_EOF) { bp->tb_buf.next_ixlp = (ixl1394_command_t *)&bp->tb_store_ts; bp->tb_store_ts.ixl_opcode = IXL1394_OP_STORE_TIMESTAMP; bp->tb_store_ts.next_ixlp = (ixl1394_command_t *)&bp->tb_cb; bp->tb_cb.ixl_opcode = IXL1394_OP_CALLBACK; bp->tb_cb.callback = av1394_it_ixl_buf_cb; bp->tb_cb.callback_arg = bp; bp->tb_cb.next_ixlp = (ixl1394_command_t *)&bp->tb_jump; bp->tb_jump.ixl_opcode = IXL1394_OP_JUMP_U; } else { bp->tb_buf.next_ixlp = (ixl1394_command_t *)&bp->tb_jump; bp->tb_jump.ixl_opcode = IXL1394_OP_JUMP; } /* * jump label and next_ixlp later. * unset fields will be set in av1394_it_ixl_complete_buf() * * save additional frame info */ if (flags & AV1394_IT_IXL_BUF_SOF) { itp->it_frame_info[framenum].fi_first_buf = bp; itp->it_frame_info[framenum].fi_ts_off = bp->tb_buf.mem_bufp + AV1394_TS_MODE_GET_OFF(icp->ic_param.cp_ts_mode); } else if (flags & AV1394_IT_IXL_BUF_EOF) { itp->it_frame_info[framenum].fi_last_buf = bp; } itp->it_frame_info[framenum].fi_ncycs += cnt; return (bp); } static void av1394_it_ixl_complete_buf(av1394_it_ixl_buf_t *bp, av1394_it_ixl_empty_cip_t *ep) { bp->tb_common.tc_next = &ep->te_common; bp->tb_jump.label = bp->tb_jump.next_ixlp = (ixl1394_command_t *)&ep->te_label; } static void av1394_it_ixl_complete_buf2(av1394_it_ixl_buf_t *bp, av1394_it_ixl_buf_t *nextbp) { bp->tb_common.tc_next = &nextbp->tb_common; bp->tb_jump.label = bp->tb_jump.next_ixlp = (ixl1394_command_t *)&nextbp->tb_label; } static av1394_it_ixl_empty_cip_t * av1394_it_ixl_bld_empty_cip(av1394_ic_t *icp, int framenum) { av1394_it_t *itp = &icp->ic_it; av1394_it_ixl_empty_cip_t *ep; ep = kmem_zalloc(sizeof (av1394_it_ixl_empty_cip_t), KM_SLEEP); ep->te_common.tc_size = sizeof (av1394_it_ixl_empty_cip_t); /* tc_next later */ ep->te_label.ixl_opcode = IXL1394_OP_LABEL; ep->te_label.next_ixlp = (ixl1394_command_t *)&ep->te_pkt; ep->te_pkt.ixl_opcode = IXL1394_OP_SEND_PKT_ST; ep->te_pkt.size = AV1394_CIPSZ; /* ixl_buf and mem_bufp later */ ep->te_pkt.next_ixlp = (ixl1394_command_t *)&ep->te_jump; ep->te_jump.ixl_opcode = IXL1394_OP_JUMP; /* * label and next_ixlp later. * unset fields will be set in av1394_it_ixl_complete_empty_cip() */ itp->it_frame_info[framenum].fi_ncycs++; return (ep); } /* * empty CIP packet contains CIP header of the next packet, * so we just point to the same address as the next packet's header */ static void av1394_it_ixl_complete_empty_cip(av1394_it_ixl_empty_cip_t *ep, av1394_it_ixl_buf_t *bp) { ep->te_common.tc_next = &bp->tb_common; ep->te_pkt.ixl_buf._dmac_ll = bp->tb_buf.ixl_buf._dmac_ll; ep->te_pkt.mem_bufp = bp->tb_buf.mem_bufp; ep->te_jump.label = ep->te_jump.next_ixlp = (ixl1394_command_t *)&bp->tb_label; } static void av1394_it_ixl_bld_begin(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; av1394_it_ixl_buf_t *bp = (av1394_it_ixl_buf_t *)itp->it_ixl_data; av1394_it_ixl_begin_t *bep = &itp->it_ixl_begin; int i; bep->be_label.ixl_opcode = IXL1394_OP_LABEL; bep->be_label.next_ixlp = (ixl1394_command_t *)&bep->be_empty_pre; bep->be_empty_pre.ixl_opcode = IXL1394_OP_SEND_PKT_ST; bep->be_empty_pre.size = AV1394_CIPSZ; bep->be_empty_pre.ixl_buf._dmac_ll = bp->tb_buf.ixl_buf._dmac_ll; bep->be_empty_pre.mem_bufp = bp->tb_buf.mem_bufp; bep->be_empty_pre.next_ixlp = (ixl1394_command_t *)&bep->be_store_ts; bep->be_store_ts.ixl_opcode = IXL1394_OP_STORE_TIMESTAMP; bep->be_store_ts.next_ixlp = (ixl1394_command_t *)&bep->be_cb; bep->be_cb.ixl_opcode = IXL1394_OP_CALLBACK; bep->be_cb.callback = av1394_it_ixl_begin_cb; bep->be_cb.callback_arg = &bep->be_store_ts.timestamp; bep->be_cb.next_ixlp = (ixl1394_command_t *)&bep->be_empty_post[0]; for (i = 0; i < AV1394_IT_IXL_BEGIN_NPOST; i++) { bep->be_empty_post[i].ixl_opcode = IXL1394_OP_SEND_PKT_ST; bep->be_empty_post[i].size = AV1394_CIPSZ; bep->be_empty_post[i].ixl_buf._dmac_ll = bp->tb_buf.ixl_buf._dmac_ll; bep->be_empty_post[i].mem_bufp = bp->tb_buf.mem_bufp; bep->be_empty_post[i].next_ixlp = (ixl1394_command_t *)&bep->be_empty_post[i + 1]; } bep->be_empty_post[AV1394_IT_IXL_BEGIN_NPOST - 1].next_ixlp = (ixl1394_command_t *)&bep->be_jump; bep->be_jump.ixl_opcode = IXL1394_OP_JUMP_U; bep->be_jump.label = (ixl1394_command_t *)&bp->tb_label; bep->be_jump.next_ixlp = (ixl1394_command_t *)&bp->tb_label; } static void av1394_it_ixl_begin_update_pkts(av1394_ic_t *icp, av1394_it_ixl_buf_t *bp) { av1394_it_t *itp = &icp->ic_it; av1394_it_ixl_begin_t *bep = &itp->it_ixl_begin; int i; for (i = 0; i < AV1394_IT_IXL_BEGIN_NPOST; i++) { bep->be_empty_post[i].ixl_buf._dmac_ll = bp->tb_buf.ixl_buf._dmac_ll; bep->be_empty_post[i].mem_bufp = bp->tb_buf.mem_bufp; } } static int av1394_it_alloc_isoch_dma(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; av1394_it_t *itp = &icp->ic_it; id1394_isoch_dmainfo_t di; int result; int ret; di.ixlp = itp->it_ixlp; di.channel_num = icp->ic_num; di.idma_options = ID1394_TALK; di.it_speed = icp->ic_param.cp_bus_speed; /* * XXX this should really be IXL1394_SKIP_TO_NEXT, * but it can't be used yet due to the Framework bug */ di.it_default_skip = IXL1394_SKIP_TO_SELF; di.default_tag = 1; di.default_sync = 0; di.global_callback_arg = icp; di.isoch_dma_stopped = av1394_it_dma_stopped_cb; di.idma_evt_arg = icp; ret = t1394_alloc_isoch_dma(avp->av_t1394_hdl, &di, 0, &icp->ic_isoch_hdl, &result); return (ret); } static void av1394_it_free_isoch_dma(av1394_ic_t *icp) { av1394_inst_t *avp = icp->ic_avp; t1394_free_isoch_dma(avp->av_t1394_hdl, 0, &icp->ic_isoch_hdl); } static void av1394_it_dma_sync_frames(av1394_ic_t *icp, int idx, int cnt) { av1394_ic_dma_sync_frames(icp, idx, cnt, &icp->ic_it.it_data_pool, DDI_DMA_SYNC_FORDEV); } /* * * --- callbacks * */ static void av1394_it_ixl_begin_cb(opaque_t arg, struct ixl1394_callback *cb) { av1394_ic_t *icp = arg; av1394_it_t *itp = &icp->ic_it; uint16_t *cycp = cb->callback_arg; /* cycle timestamp pointer */ uint16_t syt; int first; mutex_enter(&icp->ic_mutex); /* save initial timestamp value */ itp->it_ts_init.ts_syt = av1394_it_ts_cyc2syt(*cycp); /* * update frame timestamps if needed */ if ((itp->it_nfull <= 0) || (AV1394_TS_MODE_GET_SIZE(icp->ic_param.cp_ts_mode) == 0)) { mutex_exit(&icp->ic_mutex); return; } ASSERT(itp->it_nfull <= icp->ic_nframes); syt = av1394_it_ts_syt_inc(itp->it_ts_init.ts_syt, AV1394_IT_IXL_BEGIN_NPOST + av1394_it_syt_off); first = (itp->it_last_full + icp->ic_nframes - itp->it_nfull + 1) % icp->ic_nframes; av1394_it_update_frame_syt(icp, first, itp->it_nfull, syt); mutex_exit(&icp->ic_mutex); } /*ARGSUSED*/ static void av1394_it_ixl_buf_cb(opaque_t arg, struct ixl1394_callback *cb) { av1394_it_ixl_buf_t *bp = cb->callback_arg; if (bp->tb_flags & AV1394_IT_IXL_BUF_EOF) { av1394_it_ixl_eof_cb(bp); } } static void av1394_it_ixl_eof_cb(av1394_it_ixl_buf_t *bp) { av1394_ic_t *icp = bp->tb_icp; av1394_isoch_t *ip = &icp->ic_avp->av_i; av1394_it_t *itp = &icp->ic_it; mutex_enter(&ip->i_mutex); mutex_enter(&icp->ic_mutex); if (itp->it_nempty < icp->ic_nframes) { itp->it_nempty++; itp->it_nfull--; cv_signal(&icp->ic_xfer_cv); } if ((itp->it_nempty >= itp->it_hiwat) && (icp->ic_state == AV1394_IC_DMA)) { av1394_ic_trigger_softintr(icp, icp->ic_num, AV1394_PREQ_IT_UNDERRUN); } mutex_exit(&icp->ic_mutex); mutex_exit(&ip->i_mutex); } void av1394_it_underrun(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; av1394_inst_t *avp = icp->ic_avp; int idx; ixl1394_jump_t *old_jmp; ixl1394_jump_t new_jmp; id1394_isoch_dma_updateinfo_t update_info; int err; int result; /* * update the last full frame's jump to NULL */ idx = (itp->it_first_empty + icp->ic_nframes - 1) % icp->ic_nframes; old_jmp = &itp->it_frame_info[idx].fi_last_buf->tb_jump; itp->it_saved_label = old_jmp->label; 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) { itp->it_underrun_idx = idx; icp->ic_state = AV1394_IC_SUSPENDED; cv_signal(&icp->ic_xfer_cv); } } /* * resume from the underrun condition */ static int av1394_it_underrun_resume(av1394_ic_t *icp) { av1394_it_t *itp = &icp->ic_it; av1394_inst_t *avp = icp->ic_avp; av1394_it_ixl_buf_t *bp; int idx; ixl1394_jump_t *old_jmp; ixl1394_jump_t new_jmp; id1394_isoch_dma_updateinfo_t update_info; int err; int result; /* * resuming the transfer it a lot like starting the transfer: * first the IXL begin block needs to be executed, then the rest * of the IXL chain. The following dynamic updates are needed: * * 1. update the begin block to jump to the first empty frame; * 2. restore the original jump label which we previously * changed to jump to the underrun block; * * update #1 * start by updating the begin block with a new buffer address */ idx = (itp->it_underrun_idx + 1) % icp->ic_nframes; bp = itp->it_frame_info[idx].fi_first_buf; av1394_it_ixl_begin_update_pkts(icp, bp); old_jmp = &itp->it_ixl_begin.be_jump; new_jmp.ixl_opcode = IXL1394_OP_JUMP_U; new_jmp.label = (ixl1394_command_t *)&bp->tb_label; 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) { return (EIO); } /* * update #2 */ bp = itp->it_frame_info[itp->it_underrun_idx].fi_last_buf; old_jmp = &bp->tb_jump; new_jmp.ixl_opcode = IXL1394_OP_JUMP_U; new_jmp.label = itp->it_saved_label; 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) { return (EIO); } icp->ic_state = AV1394_IC_DMA; return (0); } /*ARGSUSED*/ static void av1394_it_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; mutex_enter(&icp->ic_mutex); icp->ic_state = AV1394_IC_IDLE; mutex_exit(&icp->ic_mutex); } /* * * --- data transfer routines * * av1394_it_add_frames() * Add full frames to the pool. */ static int av1394_it_add_frames(av1394_ic_t *icp, int idx, int cnt) { av1394_it_t *itp = &icp->ic_it; av1394_it_frame_info_t *fip; int prev_full = itp->it_last_full; uint16_t syt; int ret = 0; /* can only add to tail */ if (idx != ((itp->it_last_full + 1) % icp->ic_nframes)) { return (EINVAL); } /* turn empty frames into full ones */ itp->it_nempty -= cnt; itp->it_first_empty = (itp->it_first_empty + cnt) % icp->ic_nframes; itp->it_nfull += cnt; itp->it_last_full = (itp->it_last_full + cnt) % icp->ic_nframes; ASSERT((itp->it_nempty >= 0) && (itp->it_nfull <= icp->ic_nframes)); /* * update frame timestamps if needed */ if (AV1394_TS_MODE_GET_SIZE(icp->ic_param.cp_ts_mode) > 0) { ASSERT(prev_full >= 0); fip = &itp->it_frame_info[prev_full]; syt = *(uint16_t *)fip->fi_ts_off; syt = av1394_it_ts_syt_inc(syt, fip->fi_ncycs); av1394_it_update_frame_syt(icp, idx, cnt, syt); } av1394_it_dma_sync_frames(icp, idx, cnt); /* if suspended due to overrun, check if we can resume */ if ((icp->ic_state == AV1394_IC_SUSPENDED) && (itp->it_nempty >= itp->it_lowat)) { ret = av1394_it_underrun_resume(icp); } return (ret); } /* * wait for empty frames */ static int av1394_it_wait_frames(av1394_ic_t *icp, int *idx, int *cnt, int *nlost) { av1394_it_t *itp = &icp->ic_it; int ret = 0; while ((itp->it_nempty == 0) && (icp->ic_state == AV1394_IC_DMA)) { if (cv_wait_sig(&icp->ic_xfer_cv, &icp->ic_mutex) <= 0) { ret = EINTR; break; } } if (itp->it_nempty > 0) { *idx = itp->it_first_empty; *cnt = itp->it_nempty; *nlost = 0; ret = 0; } return (ret); } /* * update frame timestamps for a range of frames */ static void av1394_it_update_frame_syt(av1394_ic_t *icp, int first, int cnt, uint16_t syt) { av1394_it_t *itp = &icp->ic_it; int i; int j = first; /* frame number */ for (i = cnt; i > 0; i--) { *(uint16_t *)itp->it_frame_info[j].fi_ts_off = syt; syt = av1394_it_ts_syt_inc(syt, itp->it_frame_info[j].fi_ncycs); j = (j + 1) % icp->ic_nframes; } } /* * convert cycle timestamp into SYT timestamp: * * Cycle timer: cycleSeconds cycleCount cycleOffset * 31_30_29_28_27_26_25 24___15_14_13_12 11________0 * Cycle timestamp: |------------------------| * SYT timestamp: |----------------------| */ static uint16_t av1394_it_ts_cyc2syt(uint16_t cyc) { return (((cyc & 0xF) << 12) + 0x800); } /* * increment SYT by a number of cycles */ static uint16_t av1394_it_ts_syt_inc(uint16_t syt, uint16_t ncycs) { return (syt + (ncycs << 12)); } /* * copyin from the kernel buffer */ static void av1394_it_kcopyin(av1394_ic_t *icp, void *buf, size_t len) { av1394_it_t *itp = &icp->ic_it; av1394_isoch_seg_t *seg = itp->it_data_pool.ip_seg; ASSERT(itp->it_write_off + len < icp->ic_framesz); bcopy(buf, seg[itp->it_write_idx].is_kaddr + itp->it_write_off, len); itp->it_write_off += len; } /* * copyin from the user buffer */ static int av1394_it_copyin(av1394_ic_t *icp, struct uio *uiop, int *full_cnt, int dv) { av1394_it_t *itp = &icp->ic_it; av1394_isoch_seg_t *seg = itp->it_data_pool.ip_seg; int idx = itp->it_write_idx; int framesz = icp->ic_framesz; size_t len, frame_resid, start_resid; caddr_t kaddr, kaddr_end; int ret = 0; *full_cnt = 0; while ((uiop->uio_resid > 0) && (itp->it_write_cnt > 0)) { kaddr = seg[idx].is_kaddr + itp->it_write_off; frame_resid = framesz - itp->it_write_off; len = min(uiop->uio_resid, frame_resid); mutex_exit(&icp->ic_mutex); ret = uiomove(kaddr, len, UIO_WRITE, uiop); mutex_enter(&icp->ic_mutex); if (ret != 0) { break; } itp->it_write_off += len; if ((itp->it_write_off == framesz) && dv) { /* * for DV formats, make sure we got a frame start. * this is to ensure correct timestamping */ kaddr = seg[idx].is_kaddr; kaddr_end = kaddr + framesz; while (!av1394_it_is_dv_frame_start(kaddr)) { kaddr += icp->ic_pktsz; if (kaddr == kaddr_end) { break; } } start_resid = kaddr_end - kaddr; if (start_resid != framesz) { bcopy(kaddr, seg[idx].is_kaddr, start_resid); itp->it_write_off = start_resid; } } if (itp->it_write_off == framesz) { /* for DV formats, reset frame's SYT fields */ if (dv) { av1394_it_reset_frame_syt(icp, idx); } itp->it_write_off = 0; itp->it_write_cnt--; idx = (idx + 1) % icp->ic_nframes; (*full_cnt)++; } } return (ret); } /* * check if a packet starts a new DV frame */ static boolean_t av1394_it_is_dv_frame_start(caddr_t kaddr) { uint8_t *p = (uint8_t *)kaddr + 8; /* * in the DIF block ID data, which immediately follows CIP header, * SCT, Dseq and DBN fields should be zero (Ref: IEC 61834-2, Fig. 66) */ return (((p[0] & 0xE0) == 0) && ((p[1] & 0xF0) == 0) && (p[2] == 0)); } /* * reset all frame's SYT fields */ static void av1394_it_reset_frame_syt(av1394_ic_t *icp, int idx) { caddr_t kaddr = icp->ic_it.it_data_pool.ip_seg[idx].is_kaddr; caddr_t kaddr_end = kaddr + icp->ic_framesz; kaddr += 6; while (kaddr < kaddr_end) { *(uint16_t *)kaddr = 0xFFFF; kaddr += icp->ic_pktsz; } }