/* * 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 CMP (Connection Management Procedures) */ #include /* configuration routines */ static void av1394_cmp_cleanup(av1394_inst_t *icp); /* ioctl routines */ static int av1394_ioctl_plug_init_local(av1394_inst_t *, iec61883_plug_init_t *); static int av1394_ioctl_plug_init_remote(av1394_inst_t *, iec61883_plug_init_t *); /* local PCR routines */ static int av1394_pcr_init(av1394_inst_t *, int, uint32_t); static void av1394_pcr_fini(av1394_inst_t *, int); static int av1394_pcr_alloc_addr(av1394_inst_t *, uint64_t, t1394_addr_handle_t *); static void av1394_pcr_free_addr(av1394_inst_t *, t1394_addr_handle_t *); static int av1394_pcr_make_ph(int, int, int); static int av1394_pcr_ph2idx(int); static av1394_pcr_t *av1394_pcr_ph2pcr(av1394_cmp_t *, int); static uint64_t av1394_pcr_idx2addr(int); static int av1394_pcr_idx2num(int); static boolean_t av1394_pcr_idx_is_mpr(int); static boolean_t av1394_pcr_ph_is_mpr(int); static boolean_t av1394_pcr_ph_is_remote(int); /* callbacks */ static void av1394_pcr_recv_read_request(cmd1394_cmd_t *); static void av1394_pcr_recv_lock_request(cmd1394_cmd_t *); /* remote PCR routines */ static int av1394_pcr_remote_read(av1394_inst_t *, int, uint32_t *); static int av1394_pcr_remote_cas(av1394_inst_t *, int, uint32_t *, uint32_t, uint32_t); #define AV1394_TNF_ENTER(func) \ TNF_PROBE_0_DEBUG(func##_enter, AV1394_TNF_CMP_STACK, ""); #define AV1394_TNF_EXIT(func) \ TNF_PROBE_0_DEBUG(func##_exit, AV1394_TNF_CMP_STACK, ""); int av1394_cmp_init(av1394_inst_t *avp) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; ddi_iblock_cookie_t ibc = avp->av_attachinfo.iblock_cookie; int ret; AV1394_TNF_ENTER(av1394_cmp_init); ret = t1394_cmp_register(avp->av_t1394_hdl, NULL, 0); if (ret == DDI_SUCCESS) { rw_init(&cmp->cmp_pcr_rwlock, NULL, RW_DRIVER, ibc); } AV1394_TNF_EXIT(av1394_cmp_init); return (ret); } void av1394_cmp_fini(av1394_inst_t *avp) { AV1394_TNF_ENTER(av1394_cmp_fini); av1394_cmp_cleanup(avp); AV1394_TNF_EXIT(av1394_cmp_fini); } void av1394_cmp_bus_reset(av1394_inst_t *avp) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int i; AV1394_TNF_ENTER(av1394_cmp_bus_reset); /* reset PCR values */ rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); for (i = 0; i < NELEM(cmp->cmp_pcr); i++) { if ((i == AV1394_OMPR_IDX) || (i == AV1394_IMPR_IDX)) { continue; } if (cmp->cmp_pcr[i]) { if (i < AV1394_IMPR_IDX) { cmp->cmp_pcr[i]->pcr_val &= ~AV1394_OPCR_BR_CLEAR_MASK; } else { cmp->cmp_pcr[i]->pcr_val &= ~AV1394_IPCR_BR_CLEAR_MASK; } } } rw_exit(&cmp->cmp_pcr_rwlock); AV1394_TNF_EXIT(av1394_cmp_bus_reset); } /* * on close, free iPCRs and oPCRs not finalized by application */ void av1394_cmp_close(av1394_inst_t *avp) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int i; rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); for (i = 0; i < NELEM(cmp->cmp_pcr); i++) { if ((i == AV1394_OMPR_IDX) || (i == AV1394_IMPR_IDX)) { continue; } if (cmp->cmp_pcr[i]) { av1394_pcr_fini(avp, i); } } rw_exit(&cmp->cmp_pcr_rwlock); } /* * * --- ioctls * * IEC61883_PLUG_INIT */ int av1394_ioctl_plug_init(av1394_inst_t *avp, void *arg, int mode) { int ret = 0; iec61883_plug_init_t pi; if (ddi_copyin(arg, &pi, sizeof (pi), mode) != 0) { return (EFAULT); } /* check arguments */ if (((pi.pi_type != IEC61883_PLUG_IN) && (pi.pi_type != IEC61883_PLUG_OUT) && (pi.pi_type != IEC61883_PLUG_MASTER_IN) && (pi.pi_type != IEC61883_PLUG_MASTER_OUT)) || (((pi.pi_num < 0) || (pi.pi_num >= AV1394_NPCR)) && (pi.pi_num != IEC61883_PLUG_ANY))) { return (EINVAL); } if (pi.pi_loc == IEC61883_LOC_LOCAL) { ret = av1394_ioctl_plug_init_local(avp, &pi); } else if (pi.pi_loc == IEC61883_LOC_REMOTE) { ret = av1394_ioctl_plug_init_remote(avp, &pi); } else { ret = EINVAL; } if (ret == 0) { if (ddi_copyout(&pi, arg, sizeof (pi), mode) != 0) { ret = EFAULT; } } return (ret); } /* * IEC61883_PLUG_FINI */ /*ARGSUSED*/ int av1394_ioctl_plug_fini(av1394_inst_t *avp, void *arg, int mode) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int ret; int ph; ph = (int)(intptr_t)arg; if (av1394_pcr_ph_is_remote(ph) || av1394_pcr_ph_is_mpr(ph)) { return (0); } rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); if (av1394_pcr_ph2pcr(cmp, ph) != NULL) { av1394_pcr_fini(avp, av1394_pcr_ph2idx(ph)); ret = 0; } else { ret = EINVAL; } rw_exit(&cmp->cmp_pcr_rwlock); return (ret); } /* * IEC61883_PLUG_REG_READ */ int av1394_ioctl_plug_reg_read(av1394_inst_t *avp, void *arg, int mode) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int ret = 0; iec61883_plug_reg_val_t pr; int ph; av1394_pcr_t *pcr; if (ddi_copyin(arg, &pr, sizeof (pr), mode) != 0) { return (EFAULT); } ph = pr.pr_handle; if (av1394_pcr_ph_is_remote(ph)) { ret = av1394_pcr_remote_read(avp, ph, &pr.pr_val); } else { switch (av1394_pcr_ph2idx(ph)) { case AV1394_OMPR_IDX: ret = t1394_cmp_read(avp->av_t1394_hdl, T1394_CMP_OMPR, &pr.pr_val); break; case AV1394_IMPR_IDX: ret = t1394_cmp_read(avp->av_t1394_hdl, T1394_CMP_IMPR, &pr.pr_val); break; default: rw_enter(&cmp->cmp_pcr_rwlock, RW_READER); if ((pcr = av1394_pcr_ph2pcr(cmp, ph)) != NULL) { pr.pr_val = pcr->pcr_val; } else { ret = EINVAL; } rw_exit(&cmp->cmp_pcr_rwlock); } } if (ret == 0) { if (ddi_copyout(&pr, arg, sizeof (pr), mode) != 0) { ret = EFAULT; } } return (ret); } /* * IEC61883_PLUG_REG_CAS */ int av1394_ioctl_plug_reg_cas(av1394_inst_t *avp, void *arg, int mode) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int ret = 0; iec61883_plug_reg_lock_t pl; int ph; av1394_pcr_t *pcr; if (ddi_copyin(arg, &pl, sizeof (pl), mode) != 0) { return (EFAULT); } ph = pl.pl_handle; if (av1394_pcr_ph_is_remote(ph)) { ret = av1394_pcr_remote_cas(avp, ph, &pl.pl_old, pl.pl_data, pl.pl_arg); } else { switch (av1394_pcr_ph2idx(ph)) { case AV1394_OMPR_IDX: ret = t1394_cmp_cas(avp->av_t1394_hdl, T1394_CMP_OMPR, pl.pl_arg, pl.pl_data, &pl.pl_old); break; case AV1394_IMPR_IDX: ret = t1394_cmp_cas(avp->av_t1394_hdl, T1394_CMP_IMPR, pl.pl_arg, pl.pl_data, &pl.pl_old); break; default: rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); if ((pcr = av1394_pcr_ph2pcr(cmp, ph)) != NULL) { /* compare_swap */ pl.pl_old = pcr->pcr_val; if (pcr->pcr_val == pl.pl_arg) { pcr->pcr_val = pl.pl_data; } } else { ret = EINVAL; } rw_exit(&cmp->cmp_pcr_rwlock); } } if (ret == 0) { if (ddi_copyout(&pl, arg, sizeof (pl), mode) != 0) { ret = EFAULT; } } return (ret); } /* * * --- configuration routines * */ static void av1394_cmp_cleanup(av1394_inst_t *avp) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int i; rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); for (i = 0; i < NELEM(cmp->cmp_pcr); i++) { if (cmp->cmp_pcr[i]) { av1394_pcr_fini(avp, i); } } rw_exit(&cmp->cmp_pcr_rwlock); rw_destroy(&cmp->cmp_pcr_rwlock); (void) t1394_cmp_unregister(avp->av_t1394_hdl); } /* * * --- ioctl routines * * IEC61883_PLUG_INIT for local plugs */ static int av1394_ioctl_plug_init_local(av1394_inst_t *avp, iec61883_plug_init_t *pip) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; int err; int ph; /* plug handle */ int idx, max_idx; /* plug index */ /* MPR's are a special case */ if ((pip->pi_type == IEC61883_PLUG_MASTER_IN) || (pip->pi_type == IEC61883_PLUG_MASTER_OUT)) { pip->pi_handle = av1394_pcr_make_ph(pip->pi_loc, pip->pi_type, 0); return (0); } /* PCR */ rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); if (pip->pi_num == IEC61883_PLUG_ANY) { if (pip->pi_type == IEC61883_PLUG_OUT) { idx = AV1394_OPCR0_IDX; max_idx = idx + AV1394_PCR_ADDR_NOPCR - 1; } else { ASSERT(pip->pi_type == IEC61883_PLUG_IN); idx = AV1394_IPCR0_IDX; max_idx = idx + AV1394_PCR_ADDR_NIPCR - 1; } /* find unused PCR */ for (; idx <= max_idx; idx++) { if (cmp->cmp_pcr[idx] != NULL) { continue; } err = av1394_pcr_init(avp, idx, AV1394_PCR_INIT_VAL); if (err == DDI_SUCCESS) { break; } } } else { ph = av1394_pcr_make_ph(pip->pi_loc, pip->pi_type, pip->pi_num); idx = max_idx = av1394_pcr_ph2idx(ph); /* create PCR if not already */ if (cmp->cmp_pcr[idx] == NULL) { err = av1394_pcr_init(avp, idx, AV1394_PCR_INIT_VAL); } } rw_exit(&cmp->cmp_pcr_rwlock); if ((err != DDI_SUCCESS) || (idx > max_idx)) { return (EBUSY); } pip->pi_rnum = av1394_pcr_idx2num(idx); pip->pi_handle = av1394_pcr_make_ph(pip->pi_loc, pip->pi_type, pip->pi_rnum); return (0); } /* * IEC61883_PLUG_INIT for remote plugs */ static int av1394_ioctl_plug_init_remote(av1394_inst_t *avp, iec61883_plug_init_t *pip) { int ph; uint32_t val; int ret; if (pip->pi_num == IEC61883_PLUG_ANY) { return (EINVAL); } ph = av1394_pcr_make_ph(pip->pi_loc, pip->pi_type, pip->pi_num); /* check PCR existance by attempting to read it */ if ((ret = av1394_pcr_remote_read(avp, ph, &val)) == 0) { pip->pi_handle = ph; pip->pi_rnum = pip->pi_num; } return (ret); } /* * * --- plug routines * * initialize a PCR */ static int av1394_pcr_init(av1394_inst_t *avp, int idx, uint32_t val) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; av1394_pcr_t *pcr; uint64_t addr; int ret; pcr = kmem_zalloc(sizeof (av1394_pcr_t), KM_SLEEP); pcr->pcr_val = val; cmp->cmp_pcr[idx] = pcr; addr = av1394_pcr_idx2addr(idx); ret = av1394_pcr_alloc_addr(avp, addr, &pcr->pcr_addr_hdl); if (ret != DDI_SUCCESS) { kmem_free(pcr, sizeof (av1394_pcr_t)); cmp->cmp_pcr[idx] = NULL; } return (ret); } /* * finalize a PCR */ static void av1394_pcr_fini(av1394_inst_t *avp, int idx) { av1394_cmp_t *cmp = &avp->av_i.i_cmp; av1394_pcr_free_addr(avp, &cmp->cmp_pcr[idx]->pcr_addr_hdl); kmem_free(cmp->cmp_pcr[idx], sizeof (av1394_pcr_t)); cmp->cmp_pcr[idx] = NULL; } /* * allocate CSR address for a PCR */ static int av1394_pcr_alloc_addr(av1394_inst_t *avp, uint64_t addr, t1394_addr_handle_t *hdlp) { t1394_alloc_addr_t aa; int ret; int result; AV1394_TNF_ENTER(av1394_pcr_addr_alloc); bzero(&aa, sizeof (aa)); aa.aa_address = addr; aa.aa_length = 4; aa.aa_type = T1394_ADDR_FIXED; aa.aa_enable = T1394_ADDR_RDENBL | T1394_ADDR_LKENBL; aa.aa_evts.recv_read_request = av1394_pcr_recv_read_request; aa.aa_evts.recv_lock_request = av1394_pcr_recv_lock_request; aa.aa_arg = avp; ret = t1394_alloc_addr(avp->av_t1394_hdl, &aa, 0, &result); if (ret != DDI_SUCCESS) { TNF_PROBE_2(av1394_pcr_alloc_addr_error, AV1394_TNF_CMP_ERROR, "", tnf_int, ret, ret, tnf_int, result, result); } else { *hdlp = aa.aa_hdl; } AV1394_TNF_EXIT(av1394_pcr_addr_alloc); return (ret); } /* * free CSR address occupied by a PCR */ static void av1394_pcr_free_addr(av1394_inst_t *avp, t1394_addr_handle_t *hdlp) { int ret; ret = t1394_free_addr(avp->av_t1394_hdl, hdlp, 0); if (ret != DDI_SUCCESS) { TNF_PROBE_1(av1394_pcr_free_addr_error, AV1394_TNF_CMP_ERROR, "", tnf_int, ret, ret); } } /* * make plug handle. range checking should be performed by caller */ static int av1394_pcr_make_ph(int loc, int type, int num) { int ph; switch (type) { case IEC61883_PLUG_IN: ph = num + AV1394_IPCR0_IDX; break; case IEC61883_PLUG_OUT: ph = num + AV1394_OPCR0_IDX; break; case IEC61883_PLUG_MASTER_IN: ph = AV1394_IMPR_IDX; break; case IEC61883_PLUG_MASTER_OUT: ph = AV1394_OMPR_IDX; break; default: ASSERT(0); } if (loc == IEC61883_LOC_REMOTE) { ph |= AV1394_PCR_REMOTE; } return (ph); } /* * convert plug handle to PCR index */ static int av1394_pcr_ph2idx(int ph) { return (ph & ~AV1394_PCR_REMOTE); } /* * convert plug handle to PCR pointer */ static av1394_pcr_t * av1394_pcr_ph2pcr(av1394_cmp_t *cmp, int ph) { int idx = av1394_pcr_ph2idx(ph); if ((idx >= 0) && (idx < NELEM(cmp->cmp_pcr))) { return (cmp->cmp_pcr[idx]); } else { return (NULL); } } /* * convert PCR index to CSR address */ static uint64_t av1394_pcr_idx2addr(int idx) { return (AV1394_PCR_ADDR_START + idx * 4); } /* * convert PCR index to number */ static int av1394_pcr_idx2num(int idx) { ASSERT(!av1394_pcr_idx_is_mpr(idx)); return ((idx - 1) % 32); } /* * returns B_TRUE if a master plug */ static boolean_t av1394_pcr_idx_is_mpr(int idx) { return (idx % 32 == 0); } static boolean_t av1394_pcr_ph_is_mpr(int ph) { return (av1394_pcr_ph2idx(ph) % 32 == 0); } /* * returns B_TRUE if a remote plug */ static boolean_t av1394_pcr_ph_is_remote(int ph) { return ((ph & AV1394_PCR_REMOTE) != 0); } /* * * --- callbacks * */ static void av1394_pcr_recv_read_request(cmd1394_cmd_t *req) { av1394_inst_t *avp = req->cmd_callback_arg; av1394_cmp_t *cmp = &avp->av_i.i_cmp; int idx; /* PCR index */ av1394_pcr_t *pcr; int err; AV1394_TNF_ENTER(av1394_pcr_recv_read_request); idx = (req->cmd_addr - AV1394_PCR_ADDR_START) / 4; if (req->cmd_type != CMD1394_ASYNCH_RD_QUAD) { req->cmd_result = IEEE1394_RESP_TYPE_ERROR; } else if ((idx >= NELEM(cmp->cmp_pcr)) || ((pcr = cmp->cmp_pcr[idx]) == NULL)) { req->cmd_result = IEEE1394_RESP_ADDRESS_ERROR; } else { /* read */ rw_enter(&cmp->cmp_pcr_rwlock, RW_READER); req->cmd_u.q.quadlet_data = pcr->pcr_val; rw_exit(&cmp->cmp_pcr_rwlock); req->cmd_result = IEEE1394_RESP_COMPLETE; } err = t1394_recv_request_done(avp->av_t1394_hdl, req, 0); if (err != DDI_SUCCESS) { TNF_PROBE_1(av1394_pcr_recv_read_request_done_error, AV1394_TNF_CMP_ERROR, "", tnf_int, err, err); } AV1394_TNF_EXIT(av1394_pcr_recv_read_request); } static void av1394_pcr_recv_lock_request(cmd1394_cmd_t *req) { av1394_inst_t *avp = req->cmd_callback_arg; av1394_cmp_t *cmp = &avp->av_i.i_cmp; int idx; /* PCR index */ av1394_pcr_t *pcr; int err; AV1394_TNF_ENTER(av1394_pcr_recv_lock_request); idx = (req->cmd_addr - AV1394_PCR_ADDR_START) / 4; if ((req->cmd_type != CMD1394_ASYNCH_LOCK_32) || (req->cmd_u.l32.lock_type != CMD1394_LOCK_COMPARE_SWAP)) { req->cmd_result = IEEE1394_RESP_TYPE_ERROR; } else if ((idx >= NELEM(cmp->cmp_pcr)) || ((pcr = cmp->cmp_pcr[idx]) == NULL)) { req->cmd_result = IEEE1394_RESP_ADDRESS_ERROR; } else { /* compare_swap */ rw_enter(&cmp->cmp_pcr_rwlock, RW_WRITER); if (pcr->pcr_val == req->cmd_u.l32.arg_value) { pcr->pcr_val = req->cmd_u.l32.data_value; } req->cmd_u.l32.old_value = pcr->pcr_val; rw_exit(&cmp->cmp_pcr_rwlock); req->cmd_result = IEEE1394_RESP_COMPLETE; } err = t1394_recv_request_done(avp->av_t1394_hdl, req, 0); if (err != DDI_SUCCESS) { TNF_PROBE_2(av1394_pcr_recv_lock_request_done_error, AV1394_TNF_CMP_ERROR, "", tnf_int, err, err, tnf_int, result, req->cmd_result); } AV1394_TNF_EXIT(av1394_pcr_recv_lock_request); } /* * * --- remote PCR routines * * read specified PCR on the remote node */ static int av1394_pcr_remote_read(av1394_inst_t *avp, int ph, uint32_t *valp) { cmd1394_cmd_t *cmd; int ret = 0; int err; ret = t1394_alloc_cmd(avp->av_t1394_hdl, 0, &cmd); if (ret != DDI_SUCCESS) { return (ENOMEM); } cmd->cmd_addr = av1394_pcr_idx2addr(av1394_pcr_ph2idx(ph)); cmd->cmd_type = CMD1394_ASYNCH_RD_QUAD; cmd->cmd_options = CMD1394_BLOCKING; if (((err = t1394_read(avp->av_t1394_hdl, cmd)) == DDI_SUCCESS) && (cmd->cmd_result == CMD1394_CMDSUCCESS)) { *valp = cmd->cmd_u.q.quadlet_data; } else { TNF_PROBE_2(av1394_pcr_remote_read_error, AV1394_TNF_CMP_ERROR, "", tnf_int, err, err, tnf_int, result, cmd->cmd_result); ret = EIO; } err = t1394_free_cmd(avp->av_t1394_hdl, 0, &cmd); ASSERT(err == DDI_SUCCESS); return (ret); } /* * compare_swap specified PCR on the remote node */ static int av1394_pcr_remote_cas(av1394_inst_t *avp, int ph, uint32_t *old_valuep, uint32_t data_value, uint32_t arg_value) { cmd1394_cmd_t *cmd; int ret = 0; int err; ret = t1394_alloc_cmd(avp->av_t1394_hdl, 0, &cmd); if (ret != DDI_SUCCESS) { return (ENOMEM); } cmd->cmd_addr = av1394_pcr_idx2addr(av1394_pcr_ph2idx(ph)); cmd->cmd_type = CMD1394_ASYNCH_LOCK_32; cmd->cmd_u.l32.lock_type = CMD1394_LOCK_COMPARE_SWAP; cmd->cmd_u.l32.data_value = data_value; cmd->cmd_u.l32.arg_value = arg_value; cmd->cmd_u.l32.num_retries = 0; cmd->cmd_options = CMD1394_BLOCKING; if (((err = t1394_lock(avp->av_t1394_hdl, cmd)) == DDI_SUCCESS) && (cmd->cmd_result == CMD1394_CMDSUCCESS)) { *old_valuep = cmd->cmd_u.l32.old_value; } else { TNF_PROBE_2(av1394_pcr_remote_cas_error, AV1394_TNF_CMP_ERROR, "", tnf_int, err, err, tnf_int, result, cmd->cmd_result); ret = EIO; } err = t1394_free_cmd(avp->av_t1394_hdl, 0, &cmd); ASSERT(err == DDI_SUCCESS); return (ret); }