/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2002 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* s1394_cmp.c
* 1394 Services Layer Connection Management Procedures Support Routines
*/
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/cmn_err.h>
#include <sys/types.h>
#include <sys/kmem.h>
#include <sys/tnf_probe.h>
#include <sys/1394/t1394.h>
#include <sys/1394/s1394.h>
#include <sys/1394/h1394.h>
static void s1394_cmp_init(s1394_hal_t *hal);
static void s1394_cmp_fini(s1394_hal_t *hal);
static void s1394_cmp_ompr_recv_read_request(cmd1394_cmd_t *req);
static void s1394_cmp_impr_recv_read_request(cmd1394_cmd_t *req);
static void s1394_cmp_ompr_recv_lock_request(cmd1394_cmd_t *req);
static void s1394_cmp_impr_recv_lock_request(cmd1394_cmd_t *req);
static void s1394_cmp_notify_reg_change(s1394_hal_t *hal, t1394_cmp_reg_t reg,
s1394_target_t *self);
/*
* number of retries to notify registered targets in case target list
* changes while the list rwlock is dropped for the time of callback
*/
uint_t s1394_cmp_notify_retry_cnt = 3;
s1394_fa_descr_t s1394_cmp_ompr_descr = {
IEC61883_CMP_OMPR_ADDR,
4,
T1394_ADDR_RDENBL | T1394_ADDR_LKENBL,
{
s1394_cmp_ompr_recv_read_request,
NULL,
s1394_cmp_ompr_recv_lock_request
},
0
};
s1394_fa_descr_t s1394_cmp_impr_descr = {
IEC61883_CMP_IMPR_ADDR,
4,
T1394_ADDR_RDENBL | T1394_ADDR_LKENBL,
{
s1394_cmp_impr_recv_read_request,
NULL,
s1394_cmp_impr_recv_lock_request
},
0
};
int
s1394_cmp_register(s1394_target_t *target, t1394_cmp_evts_t *evts)
{
s1394_hal_t *hal = target->on_hal;
static t1394_cmp_evts_t default_evts = { NULL, NULL };
TNF_PROBE_0_DEBUG(s1394_cmp_register_enter, S1394_TNF_SL_CMP_STACK, "");
rw_enter(&hal->target_list_rwlock, RW_WRITER);
/*
* if registering the first target, claim and initialize addresses
*/
if (s1394_fa_list_is_empty(hal, S1394_FA_TYPE_CMP)) {
if (s1394_fa_claim_addr(hal, S1394_FA_TYPE_CMP_OMPR,
&s1394_cmp_ompr_descr) != DDI_SUCCESS) {
rw_exit(&hal->target_list_rwlock);
return (DDI_FAILURE);
}
if (s1394_fa_claim_addr(hal, S1394_FA_TYPE_CMP_IMPR,
&s1394_cmp_impr_descr) != DDI_SUCCESS) {
s1394_fa_free_addr(hal, S1394_FA_TYPE_CMP_OMPR);
rw_exit(&hal->target_list_rwlock);
return (DDI_FAILURE);
}
s1394_cmp_init(hal);
}
/* Add on the target list (we only use one list) */
s1394_fa_list_add(hal, target, S1394_FA_TYPE_CMP);
if (evts == NULL) {
evts = &default_evts;
}
target->target_fa[S1394_FA_TYPE_CMP].fat_u.cmp.cm_evts = *evts;
rw_exit(&hal->target_list_rwlock);
TNF_PROBE_0_DEBUG(s1394_cmp_register_exit, S1394_TNF_SL_CMP_STACK, "");
return (DDI_SUCCESS);
}
int
s1394_cmp_unregister(s1394_target_t *target)
{
s1394_hal_t *hal = target->on_hal;
TNF_PROBE_0_DEBUG(s1394_cmp_unregister_enter, S1394_TNF_SL_CMP_STACK,
"");
rw_enter(&hal->target_list_rwlock, RW_WRITER);
if (s1394_fa_list_remove(hal, target,
S1394_FA_TYPE_CMP) == DDI_SUCCESS) {
if (s1394_fa_list_is_empty(hal, S1394_FA_TYPE_CMP)) {
s1394_fa_free_addr(hal, S1394_FA_TYPE_CMP_OMPR);
s1394_fa_free_addr(hal, S1394_FA_TYPE_CMP_IMPR);
s1394_cmp_fini(hal);
}
} else {
TNF_PROBE_0(s1394_cmp_unregister_common_error_list,
S1394_TNF_SL_CMP_ERROR, "");
}
rw_exit(&hal->target_list_rwlock);
TNF_PROBE_0_DEBUG(s1394_cmp_unregister_exit, S1394_TNF_SL_CMP_STACK,
"");
return (DDI_SUCCESS);
}
int
s1394_cmp_read(s1394_target_t *target, t1394_cmp_reg_t reg, uint32_t *valp)
{
s1394_hal_t *hal = target->on_hal;
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
int ret = DDI_FAILURE;
TNF_PROBE_0_DEBUG(s1394_cmp_read_enter, S1394_TNF_SL_CMP_STACK, "");
if (reg == T1394_CMP_OMPR) {
rw_enter(&cmp->cmp_ompr_rwlock, RW_READER);
*valp = cmp->cmp_ompr_val;
rw_exit(&cmp->cmp_ompr_rwlock);
ret = DDI_SUCCESS;
} else if (reg == T1394_CMP_IMPR) {
rw_enter(&cmp->cmp_impr_rwlock, RW_READER);
*valp = cmp->cmp_impr_val;
rw_exit(&cmp->cmp_impr_rwlock);
ret = DDI_SUCCESS;
}
TNF_PROBE_0_DEBUG(s1394_cmp_read_exit, S1394_TNF_SL_CMP_STACK, "");
return (ret);
}
int
s1394_cmp_cas(s1394_target_t *target, t1394_cmp_reg_t reg, uint32_t arg_val,
uint32_t new_val, uint32_t *old_valp)
{
s1394_hal_t *hal = target->on_hal;
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
int ret = DDI_SUCCESS;
TNF_PROBE_0_DEBUG(s1394_cmp_cas_enter, S1394_TNF_SL_CMP_STACK, "");
if (reg == T1394_CMP_OMPR) {
rw_enter(&cmp->cmp_ompr_rwlock, RW_WRITER);
*old_valp = cmp->cmp_ompr_val;
if (cmp->cmp_ompr_val == arg_val) {
cmp->cmp_ompr_val = new_val;
}
rw_exit(&cmp->cmp_ompr_rwlock);
} else if (reg == T1394_CMP_IMPR) {
rw_enter(&cmp->cmp_impr_rwlock, RW_WRITER);
*old_valp = cmp->cmp_impr_val;
if (cmp->cmp_impr_val == arg_val) {
cmp->cmp_impr_val = new_val;
}
rw_exit(&cmp->cmp_impr_rwlock);
} else {
ret = DDI_FAILURE;
}
/* notify other targets */
if (ret == DDI_SUCCESS) {
s1394_cmp_notify_reg_change(hal, reg, target);
}
TNF_PROBE_0_DEBUG(s1394_cmp_cas_exit, S1394_TNF_SL_CMP_STACK, "");
return (ret);
}
static void
s1394_cmp_init(s1394_hal_t *hal)
{
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
rw_init(&cmp->cmp_ompr_rwlock, NULL, RW_DRIVER, NULL);
rw_init(&cmp->cmp_impr_rwlock, NULL, RW_DRIVER, NULL);
cmp->cmp_ompr_val = IEC61883_CMP_OMPR_INIT_VAL;
cmp->cmp_impr_val = IEC61883_CMP_IMPR_INIT_VAL;
}
static void
s1394_cmp_fini(s1394_hal_t *hal)
{
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
rw_destroy(&cmp->cmp_ompr_rwlock);
rw_destroy(&cmp->cmp_impr_rwlock);
}
/*
* iMPR/oMPR read/lock requests
*/
static void
s1394_cmp_ompr_recv_read_request(cmd1394_cmd_t *req)
{
s1394_hal_t *hal = req->cmd_callback_arg;
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
TNF_PROBE_0_DEBUG(s1394_cmp_ompr_recv_read_request_enter,
S1394_TNF_SL_CMP_STACK, "");
if (req->cmd_type != CMD1394_ASYNCH_RD_QUAD) {
req->cmd_result = IEEE1394_RESP_TYPE_ERROR;
} else {
rw_enter(&cmp->cmp_ompr_rwlock, RW_READER);
req->cmd_u.q.quadlet_data = cmp->cmp_ompr_val;
rw_exit(&cmp->cmp_ompr_rwlock);
req->cmd_result = IEEE1394_RESP_COMPLETE;
}
(void) s1394_send_response(hal, req);
TNF_PROBE_0_DEBUG(s1394_cmp_ompr_recv_read_request_exit,
S1394_TNF_SL_CMP_STACK, "");
}
static void
s1394_cmp_impr_recv_read_request(cmd1394_cmd_t *req)
{
s1394_hal_t *hal = req->cmd_callback_arg;
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
TNF_PROBE_0_DEBUG(s1394_cmp_impr_recv_read_request_enter,
S1394_TNF_SL_CMP_STACK, "");
if (req->cmd_type != CMD1394_ASYNCH_RD_QUAD) {
req->cmd_result = IEEE1394_RESP_TYPE_ERROR;
} else {
rw_enter(&cmp->cmp_impr_rwlock, RW_READER);
req->cmd_u.q.quadlet_data = cmp->cmp_impr_val;
rw_exit(&cmp->cmp_impr_rwlock);
req->cmd_result = IEEE1394_RESP_COMPLETE;
}
(void) s1394_send_response(hal, req);
TNF_PROBE_0_DEBUG(s1394_cmp_impr_recv_read_request_exit,
S1394_TNF_SL_CMP_STACK, "");
}
static void
s1394_cmp_ompr_recv_lock_request(cmd1394_cmd_t *req)
{
s1394_hal_t *hal = req->cmd_callback_arg;
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
boolean_t notify = B_TRUE;
TNF_PROBE_0_DEBUG(s1394_cmp_ompr_recv_lock_request_enter,
S1394_TNF_SL_CMP_STACK, "");
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;
notify = B_FALSE;
} else {
rw_enter(&cmp->cmp_ompr_rwlock, RW_WRITER);
req->cmd_u.l32.old_value = cmp->cmp_ompr_val;
if (cmp->cmp_ompr_val == req->cmd_u.l32.arg_value) {
/* write only allowed bits */
cmp->cmp_ompr_val = (req->cmd_u.l32.data_value &
IEC61883_CMP_OMPR_LOCK_MASK) |
(cmp->cmp_ompr_val & ~IEC61883_CMP_OMPR_LOCK_MASK);
}
rw_exit(&cmp->cmp_ompr_rwlock);
req->cmd_result = IEEE1394_RESP_COMPLETE;
}
(void) s1394_send_response(hal, req);
/* notify all targets */
if (notify) {
s1394_cmp_notify_reg_change(hal, T1394_CMP_OMPR, NULL);
}
TNF_PROBE_0_DEBUG(s1394_cmp_ompr_recv_lock_request_exit,
S1394_TNF_SL_CMP_STACK, "");
}
static void
s1394_cmp_impr_recv_lock_request(cmd1394_cmd_t *req)
{
s1394_hal_t *hal = req->cmd_callback_arg;
s1394_cmp_hal_t *cmp = &hal->hal_cmp;
boolean_t notify = B_TRUE;
TNF_PROBE_0_DEBUG(s1394_cmp_impr_recv_lock_request_enter,
S1394_TNF_SL_CMP_STACK, "");
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;
notify = B_FALSE;
} else {
rw_enter(&cmp->cmp_impr_rwlock, RW_WRITER);
req->cmd_u.l32.old_value = cmp->cmp_impr_val;
if (cmp->cmp_impr_val == req->cmd_u.l32.arg_value) {
/* write only allowed bits */
cmp->cmp_impr_val = (req->cmd_u.l32.data_value &
IEC61883_CMP_IMPR_LOCK_MASK) |
(cmp->cmp_impr_val & ~IEC61883_CMP_IMPR_LOCK_MASK);
}
rw_exit(&cmp->cmp_impr_rwlock);
req->cmd_result = IEEE1394_RESP_COMPLETE;
}
(void) s1394_send_response(hal, req);
/* notify all targets */
if (notify) {
s1394_cmp_notify_reg_change(hal, T1394_CMP_IMPR, NULL);
}
TNF_PROBE_0_DEBUG(s1394_cmp_impr_recv_lock_request_exit,
S1394_TNF_SL_CMP_STACK, "");
}
/*
* Notify registered targets except 'self' about register value change
*/
static void
s1394_cmp_notify_reg_change(s1394_hal_t *hal, t1394_cmp_reg_t reg,
s1394_target_t *self)
{
s1394_target_t *target;
s1394_fa_target_t *fat;
uint_t saved_gen;
int num_retries = 0;
void (*cb)(opaque_t, t1394_cmp_reg_t);
opaque_t arg;
TNF_PROBE_0_DEBUG(s1394_cmp_notify_reg_change_enter,
S1394_TNF_SL_CMP_STACK, "");
rw_enter(&hal->target_list_rwlock, RW_READER);
start:
target = hal->hal_fa[S1394_FA_TYPE_CMP].fal_head;
for (; target; target = fat->fat_next) {
fat = &target->target_fa[S1394_FA_TYPE_CMP];
/*
* even if the target list changes when the lock is dropped,
* comparing with self is safe because the target should
* not unregister until all CMP operations are completed
*/
if (target == self) {
continue;
}
cb = fat->fat_u.cmp.cm_evts.cmp_reg_change;
if (cb == NULL) {
continue;
}
arg = fat->fat_u.cmp.cm_evts.cmp_arg;
saved_gen = s1394_fa_list_gen(hal, S1394_FA_TYPE_CMP);
rw_exit(&hal->target_list_rwlock);
cb(arg, reg);
rw_enter(&hal->target_list_rwlock, RW_READER);
/*
* List could change while we dropped the lock. In such
* case, start all over again, because missing a register
* change can have more serious consequences for a
* target than receiving same notification more than once
*/
if (saved_gen != s1394_fa_list_gen(hal, S1394_FA_TYPE_CMP)) {
TNF_PROBE_2(s1394_cmp_notify_reg_change_error,
S1394_TNF_SL_CMP_ERROR, "",
tnf_string, msg, "list gen changed",
tnf_opaque, num_retries, num_retries);
if (++num_retries <= s1394_cmp_notify_retry_cnt) {
goto start;
} else {
break;
}
}
}
rw_exit(&hal->target_list_rwlock);
TNF_PROBE_0_DEBUG(s1394_cmp_notify_reg_change_exit,
S1394_TNF_SL_CMP_STACK, "");
}