/*
* 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 (c) 1999-2000 by Sun Microsystems, Inc.
* All rights reserved.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* s1394_isoch.c
* 1394 Services Layer Isochronous Communication Routines
* This file contains routines for managing isochronous bandwidth
* and channel needs for registered targets (through the target
* isoch interfaces).
*/
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/types.h>
#include <sys/tnf_probe.h>
#include <sys/1394/t1394.h>
#include <sys/1394/s1394.h>
#include <sys/1394/h1394.h>
#include <sys/1394/ieee1394.h>
/*
* s1394_isoch_rsrc_realloc()
* is called during bus reset processing to reallocate any isochronous
* resources that were previously allocated.
*/
void
s1394_isoch_rsrc_realloc(s1394_hal_t *hal)
{
s1394_isoch_cec_t *cec_curr;
uint32_t chnl_mask;
uint32_t old_chnl_mask;
uint_t bw_alloc_units;
uint_t generation;
uint_t chnl_num;
int err;
int ret;
TNF_PROBE_0_DEBUG(s1394_isoch_rsrc_realloc_enter,
S1394_TNF_SL_ISOCH_STACK, "");
/*
* Get the current generation number - don't need the
* topology tree mutex here because it is read-only, and
* there is a race condition with or without it.
*/
generation = hal->generation_count;
/* Lock the Isoch CEC list */
mutex_enter(&hal->isoch_cec_list_mutex);
cec_curr = hal->isoch_cec_list_head;
while (cec_curr != NULL) {
/* Lock the Isoch CEC member list */
mutex_enter(&cec_curr->isoch_cec_mutex);
/* Are we supposed to reallocate resources? */
if (!(cec_curr->cec_options & T1394_NO_IRM_ALLOC) &&
(cec_curr->realloc_valid == B_TRUE) &&
(cec_curr->realloc_failed == B_FALSE)) {
/* Reallocate some bandwidth */
bw_alloc_units = s1394_compute_bw_alloc_units(hal,
cec_curr->bandwidth, cec_curr->realloc_speed);
/* Check that the generation has not changed */
if (generation != hal->generation_count) {
/* Try the next Isoch CEC */
goto next_isoch_cec;
}
/* Unlock the Isoch CEC member list */
mutex_exit(&cec_curr->isoch_cec_mutex);
/*
* We can unlock the Isoch CEC list here
* because we know this Isoch CEC can not
* go away (we are trying to realloc its
* resources so it can't be in a state that
* will allow a free).
*/
mutex_exit(&hal->isoch_cec_list_mutex);
/* Try to reallocate bandwidth */
ret = s1394_bandwidth_alloc(hal, bw_alloc_units,
generation, &err);
/* Lock the Isoch CEC list */
mutex_enter(&hal->isoch_cec_list_mutex);
/* Lock the Isoch CEC member list */
mutex_enter(&cec_curr->isoch_cec_mutex);
/* If we failed because we couldn't get bandwidth */
if (ret == DDI_FAILURE) {
cec_curr->realloc_failed = B_TRUE;
cec_curr->realloc_fail_reason =
T1394_RSRC_BANDWIDTH;
}
}
/* Are we supposed to reallocate resources? */
if (!(cec_curr->cec_options & T1394_NO_IRM_ALLOC) &&
(cec_curr->realloc_valid == B_TRUE) &&
(cec_curr->realloc_failed == B_FALSE)) {
/* Reallocate the channel */
chnl_num = cec_curr->realloc_chnl_num;
chnl_mask = (1 << ((63 - chnl_num) % 32));
/* Unlock the Isoch CEC member list */
mutex_exit(&cec_curr->isoch_cec_mutex);
/*
* We can unlock the Isoch CEC list here
* because we know this Isoch CEC can not
* go away (we are trying to realloc its
* resources so it can't be in a state that
* will allow a free).
*/
mutex_exit(&hal->isoch_cec_list_mutex);
if (chnl_num < 32) {
ret = s1394_channel_alloc(hal, chnl_mask,
generation, S1394_CHANNEL_ALLOC_HI,
&old_chnl_mask, &err);
} else {
ret = s1394_channel_alloc(hal, chnl_mask,
generation, S1394_CHANNEL_ALLOC_LO,
&old_chnl_mask, &err);
}
/* Lock the Isoch CEC list */
mutex_enter(&hal->isoch_cec_list_mutex);
/* Lock the Isoch CEC member list */
mutex_enter(&cec_curr->isoch_cec_mutex);
if (ret == DDI_FAILURE) {
if (err != CMD1394_EBUSRESET) {
/*
* If we successfully reallocate
* bandwidth, and then fail getting
* the channel, we need to free up
* the bandwidth
*/
/* Try to free up the bandwidth */
ret = s1394_bandwidth_free(hal,
bw_alloc_units, generation, &err);
if ((ret == DDI_FAILURE) &&
(err != CMD1394_EBUSRESET)) {
TNF_PROBE_1(
s1394_isoch_rsrc_realloc_error,
S1394_TNF_SL_ISOCH_ERROR,
"", tnf_string, msg,
"Unable to free bandwidth");
}
/* Try the next Isoch CEC */
goto next_isoch_cec;
}
cec_curr->realloc_failed = B_TRUE;
cec_curr->realloc_fail_reason =
T1394_RSRC_CHANNEL;
}
}
next_isoch_cec:
/* Unlock the Isoch CEC member list */
mutex_exit(&cec_curr->isoch_cec_mutex);
cec_curr = cec_curr->cec_next;
}
/* Unlock the Isoch CEC list */
mutex_exit(&hal->isoch_cec_list_mutex);
TNF_PROBE_0_DEBUG(s1394_isoch_rsrc_realloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
}
/*
* s1394_isoch_rsrc_realloc_notify()
* is called during bus reset processing to notify all targets for
* which isochronous resources were not able to be reallocated.
*/
void
s1394_isoch_rsrc_realloc_notify(s1394_hal_t *hal)
{
s1394_isoch_cec_t *cec_curr;
s1394_isoch_cec_member_t *member_curr;
t1394_isoch_rsrc_error_t fail_arg;
opaque_t evts_arg;
s1394_isoch_cec_type_t type;
void (*rsrc_fail_callback)(t1394_isoch_cec_handle_t, opaque_t,
t1394_isoch_rsrc_error_t);
TNF_PROBE_0_DEBUG(s1394_isoch_rsrc_realloc_notify_enter,
S1394_TNF_SL_ISOCH_STACK, "");
/* Lock the Isoch CEC list */
mutex_enter(&hal->isoch_cec_list_mutex);
/* Notify all targets that failed realloc */
cec_curr = hal->isoch_cec_list_head;
while (cec_curr != NULL) {
/* Lock the Isoch CEC member list */
mutex_enter(&cec_curr->isoch_cec_mutex);
/* Do we notify of realloc failure? */
if (!(cec_curr->cec_options & T1394_NO_IRM_ALLOC) &&
(cec_curr->realloc_valid == B_TRUE) &&
(cec_curr->realloc_failed == B_TRUE)) {
/* Reason for realloc failure */
fail_arg = cec_curr->realloc_fail_reason;
/* Now we are going into the callbacks */
cec_curr->in_fail_callbacks = B_TRUE;
type = cec_curr->cec_type;
/* Unlock the Isoch CEC member list */
mutex_exit(&cec_curr->isoch_cec_mutex);
/*
* We can unlock the Isoch CEC list here
* because we have the in_fail_callbacks
* field set to B_TRUE. And free will fail
* if we are in fail callbacks.
*/
mutex_exit(&hal->isoch_cec_list_mutex);
/* Call all of the rsrc_fail_target() callbacks */
/* Start at the head (talker first) and */
/* go toward the tail (listeners last) */
member_curr = cec_curr->cec_member_list_head;
while (member_curr != NULL) {
rsrc_fail_callback = member_curr->
isoch_cec_evts.rsrc_fail_target;
evts_arg = member_curr->isoch_cec_evts_arg;
if (rsrc_fail_callback != NULL) {
if (type == S1394_PEER_TO_PEER) {
rsrc_fail_callback(
(t1394_isoch_cec_handle_t)
cec_curr, evts_arg,
fail_arg);
} else {
rsrc_fail_callback(
(t1394_isoch_cec_handle_t)
cec_curr, evts_arg,
fail_arg);
}
}
member_curr = member_curr->cec_mem_next;
}
/* Lock the Isoch CEC list */
mutex_enter(&hal->isoch_cec_list_mutex);
/* Lock the Isoch CEC member list */
mutex_enter(&cec_curr->isoch_cec_mutex);
/* We are finished with the callbacks */
cec_curr->in_fail_callbacks = B_FALSE;
if (cec_curr->cec_want_wakeup == B_TRUE) {
cec_curr->cec_want_wakeup = B_FALSE;
cv_broadcast(&cec_curr->in_callbacks_cv);
}
/* Set flags back to original state */
cec_curr->realloc_valid = B_FALSE;
cec_curr->realloc_failed = B_FALSE;
}
/* Unlock the Isoch CEC member list */
mutex_exit(&cec_curr->isoch_cec_mutex);
cec_curr = cec_curr->cec_next;
}
/* Unlock the Isoch CEC list */
mutex_exit(&hal->isoch_cec_list_mutex);
TNF_PROBE_0_DEBUG(s1394_isoch_rsrc_realloc_notify_exit,
S1394_TNF_SL_ISOCH_STACK, "");
}
/*
* s1394_channel_alloc()
* is used to allocate an isochronous channel. A channel mask and
* generation are passed. A request is sent to whichever node is the
* IRM for the appropriate channels. If it fails because of a bus
* reset it can be retried. If it fails for another reason the
* channel(s) may not be availble or there may be no IRM.
*/
int
s1394_channel_alloc(s1394_hal_t *hal, uint32_t channel_mask, uint_t generation,
uint_t flags, uint32_t *old_channels, int *result)
{
cmd1394_cmd_t *cmd;
uint64_t IRM_ID_addr;
uint32_t compare;
uint32_t swap;
uint32_t old_value;
uint_t hal_node_num;
uint_t IRM_node;
uint_t offset;
int ret;
int i;
int num_retries = S1394_ISOCH_ALLOC_RETRIES;
TNF_PROBE_0_DEBUG(s1394_channel_alloc_enter,
S1394_TNF_SL_ISOCH_STACK, "");
/* Lock the topology tree */
mutex_enter(&hal->topology_tree_mutex);
hal_node_num = IEEE1394_NODE_NUM(hal->node_id);
IRM_node = hal->IRM_node;
/* Unlock the topology tree */
mutex_exit(&hal->topology_tree_mutex);
/* Make sure there is a valid IRM on the bus */
if (IRM_node == -1) {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"No IRM on the 1394 bus");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
if (flags & S1394_CHANNEL_ALLOC_HI) {
offset =
(IEEE1394_SCSR_CHANS_AVAIL_HI & IEEE1394_CSR_OFFSET_MASK);
} else {
offset =
(IEEE1394_SCSR_CHANS_AVAIL_LO & IEEE1394_CSR_OFFSET_MASK);
}
/* Send compare-swap to CHANNELS_AVAILABLE */
/* register on the Isoch Rsrc Mgr */
if (IRM_node == hal_node_num) {
/* Local */
i = num_retries;
do {
(void) HAL_CALL(hal).csr_read(hal->halinfo.hal_private,
offset, &old_value);
/* Check that the generation has not changed */
if (generation != hal->generation_count) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
compare = old_value;
swap = old_value & (~channel_mask);
ret = HAL_CALL(hal).csr_cswap32(
hal->halinfo.hal_private, generation,
offset, compare, swap, &old_value);
if (ret != DDI_SUCCESS) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error in cswap32");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
"stacktrace 1394 s1394", "");
return (DDI_FAILURE);
}
if ((~old_value & channel_mask) != 0) {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Channels already taken");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
if (old_value == compare) {
*result = CMD1394_CMDSUCCESS;
*old_channels = old_value;
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
}
} while (i--);
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Retries exceeded");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
} else {
/* Remote */
if (s1394_alloc_cmd(hal, 0, &cmd) != DDI_SUCCESS) {
*result = CMD1394_EUNKNOWN_ERROR;
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Unable to allocate command");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
cmd->cmd_options = (CMD1394_CANCEL_ON_BUS_RESET |
CMD1394_OVERRIDE_ADDR | CMD1394_BLOCKING);
cmd->cmd_type = CMD1394_ASYNCH_LOCK_32;
if (flags & S1394_CHANNEL_ALLOC_HI) {
IRM_ID_addr = (IEEE1394_ADDR_BUS_ID_MASK |
IEEE1394_SCSR_CHANS_AVAIL_HI) |
(((uint64_t)IRM_node) <<
IEEE1394_ADDR_PHY_ID_SHIFT);
} else {
IRM_ID_addr = (IEEE1394_ADDR_BUS_ID_MASK |
IEEE1394_SCSR_CHANS_AVAIL_LO) |
(((uint64_t)IRM_node) <<
IEEE1394_ADDR_PHY_ID_SHIFT);
}
cmd->cmd_addr = IRM_ID_addr;
cmd->bus_generation = generation;
cmd->cmd_u.l32.data_value = T1394_DATA32(~channel_mask);
cmd->cmd_u.l32.num_retries = num_retries;
cmd->cmd_u.l32.lock_type = CMD1394_LOCK_BIT_AND;
ret = s1394_split_lock_req(hal, NULL, cmd);
if (ret == DDI_SUCCESS) {
if (cmd->cmd_result == CMD1394_CMDSUCCESS) {
*old_channels = T1394_DATA32(
cmd->cmd_u.l32.old_value);
if ((~(*old_channels) & channel_mask) != 0) {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "",
tnf_string, msg,
"Channels already taken");
TNF_PROBE_0_DEBUG(
s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
ret = DDI_FAILURE;
} else {
*result = cmd->cmd_result;
}
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (ret);
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error allocating isoch channel");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_channel_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Error allocating isoch channel");
TNF_PROBE_0_DEBUG(s1394_channel_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
}
}
/*
* s1394_channel_free()
* is used to free up an isochronous channel. A channel mask and
* generation are passed. A request is sent to whichever node is the
* IRM for the appropriate channels. If it fails because of a bus
* reset it can be retried. If it fails for another reason the
* channel(s) may already be free or there may be no IRM.
*/
int
s1394_channel_free(s1394_hal_t *hal, uint32_t channel_mask, uint_t generation,
uint_t flags, uint32_t *old_channels, int *result)
{
cmd1394_cmd_t *cmd;
uint64_t IRM_ID_addr;
uint32_t compare;
uint32_t swap;
uint32_t old_value;
uint_t hal_node_num;
uint_t IRM_node;
uint_t offset;
int ret;
int i;
int num_retries = S1394_ISOCH_ALLOC_RETRIES;
TNF_PROBE_0_DEBUG(s1394_channel_free_enter,
S1394_TNF_SL_ISOCH_STACK, "");
/* Lock the topology tree */
mutex_enter(&hal->topology_tree_mutex);
hal_node_num = IEEE1394_NODE_NUM(hal->node_id);
IRM_node = hal->IRM_node;
/* Unlock the topology tree */
mutex_exit(&hal->topology_tree_mutex);
/* Make sure there is a valid IRM on the bus */
if (IRM_node == -1) {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_channel_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"No IRM on the 1394 bus");
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
if (flags & S1394_CHANNEL_ALLOC_HI) {
offset =
(IEEE1394_SCSR_CHANS_AVAIL_HI & IEEE1394_CSR_OFFSET_MASK);
} else {
offset =
(IEEE1394_SCSR_CHANS_AVAIL_LO & IEEE1394_CSR_OFFSET_MASK);
}
/* Send compare-swap to CHANNELS_AVAILABLE */
/* register on the Isoch Rsrc Mgr */
if (hal->IRM_node == hal_node_num) {
/* Local */
i = num_retries;
do {
(void) HAL_CALL(hal).csr_read(hal->halinfo.hal_private,
offset, &old_value);
/* Check that the generation has not changed */
if (generation != hal->generation_count) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
compare = old_value;
swap = old_value | channel_mask;
ret = HAL_CALL(hal).csr_cswap32(
hal->halinfo.hal_private, hal->generation_count,
offset, compare, swap, &old_value);
if (ret != DDI_SUCCESS) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_1(s1394_channel_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error in cswap32");
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
"stacktrace 1394 s1394", "");
return (DDI_FAILURE);
}
if (old_value == compare) {
*result = CMD1394_CMDSUCCESS;
*old_channels = old_value;
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
}
} while (i--);
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_channel_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Retries exceeded");
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
} else {
/* Remote */
if (s1394_alloc_cmd(hal, 0, &cmd) != DDI_SUCCESS) {
*result = CMD1394_EUNKNOWN_ERROR;
TNF_PROBE_1(s1394_channel_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Unable to allocate command");
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
cmd->cmd_options = (CMD1394_CANCEL_ON_BUS_RESET |
CMD1394_OVERRIDE_ADDR | CMD1394_BLOCKING);
cmd->cmd_type = CMD1394_ASYNCH_LOCK_32;
if (flags & S1394_CHANNEL_ALLOC_HI) {
IRM_ID_addr = (IEEE1394_ADDR_BUS_ID_MASK |
IEEE1394_SCSR_CHANS_AVAIL_HI) |
(((uint64_t)IRM_node) <<
IEEE1394_ADDR_PHY_ID_SHIFT);
} else {
IRM_ID_addr = (IEEE1394_ADDR_BUS_ID_MASK |
IEEE1394_SCSR_CHANS_AVAIL_LO) |
(((uint64_t)IRM_node) <<
IEEE1394_ADDR_PHY_ID_SHIFT);
}
cmd->cmd_addr = IRM_ID_addr;
cmd->bus_generation = generation;
cmd->cmd_u.l32.data_value = T1394_DATA32(channel_mask);
cmd->cmd_u.l32.num_retries = num_retries;
cmd->cmd_u.l32.lock_type = CMD1394_LOCK_BIT_OR;
ret = s1394_split_lock_req(hal, NULL, cmd);
if (ret == DDI_SUCCESS) {
if (cmd->cmd_result == CMD1394_CMDSUCCESS) {
*old_channels = T1394_DATA32(
cmd->cmd_u.l32.old_value);
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_channel_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error freeing isoch channel");
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_channel_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Error freeing isoch channel");
TNF_PROBE_0_DEBUG(s1394_channel_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
}
}
/*
* s1394_bandwidth_alloc()
* is used to allocate isochronous bandwidth. A number of bandwidth
* allocation units and a generation are passed. The request is sent
* to whichever node is the IRM for this amount of bandwidth. If it
* fails because of a bus reset it can be retried. If it fails for
* another reason the bandwidth may not be available or there may be
* no IRM.
*/
int
s1394_bandwidth_alloc(s1394_hal_t *hal, uint32_t bw_alloc_units,
uint_t generation, int *result)
{
cmd1394_cmd_t *cmd;
uint64_t IRM_ID_addr;
uint32_t compare;
uint32_t swap;
uint32_t old_value;
uint_t hal_node_num;
uint_t IRM_node;
int temp_value;
int ret;
int i;
int num_retries = S1394_ISOCH_ALLOC_RETRIES;
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_enter,
S1394_TNF_SL_ISOCH_STACK, "");
/* Lock the topology tree */
mutex_enter(&hal->topology_tree_mutex);
hal_node_num = IEEE1394_NODE_NUM(hal->node_id);
IRM_node = hal->IRM_node;
/* Unlock the topology tree */
mutex_exit(&hal->topology_tree_mutex);
/* Make sure there is a valid IRM on the bus */
if (IRM_node == -1) {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"No IRM on the 1394 bus");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
/* Send compare-swap to BANDWIDTH_AVAILABLE */
/* register on the Isoch Rsrc Mgr */
if (IRM_node == hal_node_num) {
/* Local */
i = num_retries;
do {
(void) HAL_CALL(hal).csr_read(hal->halinfo.hal_private,
(IEEE1394_SCSR_BANDWIDTH_AVAIL &
IEEE1394_CSR_OFFSET_MASK), &old_value);
/*
* Check that the generation has not changed -
* don't need the lock (read-only)
*/
if (generation != hal->generation_count) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
temp_value = (old_value - bw_alloc_units);
if ((old_value >= bw_alloc_units) &&
(temp_value >= IEEE1394_BANDWIDTH_MIN)) {
compare = old_value;
swap = (uint32_t)temp_value;
} else {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Retries exceeded");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
ret = HAL_CALL(hal).csr_cswap32(
hal->halinfo.hal_private, generation,
(IEEE1394_SCSR_BANDWIDTH_AVAIL &
IEEE1394_CSR_OFFSET_MASK), compare, swap,
&old_value);
if (ret != DDI_SUCCESS) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error in cswap32");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
if (old_value == compare) {
*result = CMD1394_CMDSUCCESS;
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
}
} while (i--);
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Too many retries");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
} else {
/* Remote */
if (s1394_alloc_cmd(hal, 0, &cmd) != DDI_SUCCESS) {
*result = CMD1394_EUNKNOWN_ERROR;
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Unable to allocate command");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
cmd->cmd_options = (CMD1394_CANCEL_ON_BUS_RESET |
CMD1394_OVERRIDE_ADDR | CMD1394_BLOCKING);
cmd->cmd_type = CMD1394_ASYNCH_LOCK_32;
IRM_ID_addr = (IEEE1394_ADDR_BUS_ID_MASK |
IEEE1394_SCSR_BANDWIDTH_AVAIL) | (((uint64_t)IRM_node) <<
IEEE1394_ADDR_PHY_ID_SHIFT);
cmd->cmd_addr = IRM_ID_addr;
cmd->bus_generation = generation;
cmd->cmd_u.l32.arg_value = 0;
cmd->cmd_u.l32.data_value = bw_alloc_units;
cmd->cmd_u.l32.num_retries = num_retries;
cmd->cmd_u.l32.lock_type = CMD1394_LOCK_THRESH_SUBTRACT;
ret = s1394_split_lock_req(hal, NULL, cmd);
if (ret == DDI_SUCCESS) {
if (cmd->cmd_result == CMD1394_CMDSUCCESS) {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error allocating isoch bandwidth");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_bandwidth_alloc_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Error allocating isoch bandwidth");
TNF_PROBE_0_DEBUG(s1394_bandwidth_alloc_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
}
}
/*
* s1394_compute_bw_alloc_units()
* is used to compute the number of "bandwidth allocation units" that
* are necessary for a given bit rate. It calculates the overhead
* necessary for isoch packet headers, bus arbitration, etc. (See
* IEEE 1394-1995 Section 8.3.2.3.7 for an explanation of what a
* "bandwidth allocation unit" is.
*/
uint_t
s1394_compute_bw_alloc_units(s1394_hal_t *hal, uint_t bandwidth, uint_t speed)
{
uint_t total_quads;
uint_t speed_factor;
uint_t bau;
int max_hops;
/* Lock the topology tree */
mutex_enter(&hal->topology_tree_mutex);
/* Calculate the 1394 bus diameter */
max_hops = s1394_topology_tree_calculate_diameter(hal);
/* Unlock the topology tree */
mutex_exit(&hal->topology_tree_mutex);
/* Calculate the total bandwidth (including overhead) */
total_quads = (bandwidth >> 2) + IEEE1394_ISOCH_HDR_QUAD_SZ;
switch (speed) {
case IEEE1394_S400:
speed_factor = ISOCH_SPEED_FACTOR_S400;
break;
case IEEE1394_S200:
speed_factor = ISOCH_SPEED_FACTOR_S200;
break;
case IEEE1394_S100:
speed_factor = ISOCH_SPEED_FACTOR_S100;
break;
}
/* See IEC 61883-1 pp. 26-29 for this formula */
bau = (32 * max_hops) + (total_quads * speed_factor);
return (bau);
}
/*
* s1394_bandwidth_free()
* is used to free up isochronous bandwidth. A number of bandwidth
* allocation units and a generation are passed. The request is sent
* to whichever node is the IRM for this amount of bandwidth. If it
* fails because of a bus reset it can be retried. If it fails for
* another reason the bandwidth may already be freed or there may
* be no IRM.
*/
int
s1394_bandwidth_free(s1394_hal_t *hal, uint32_t bw_alloc_units,
uint_t generation, int *result)
{
cmd1394_cmd_t *cmd;
uint64_t IRM_ID_addr;
uint32_t compare;
uint32_t swap;
uint32_t old_value;
uint32_t temp_value;
uint_t hal_node_num;
uint_t IRM_node;
int ret;
int i;
int num_retries = S1394_ISOCH_ALLOC_RETRIES;
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_enter,
S1394_TNF_SL_ISOCH_STACK, "");
/* Lock the topology tree */
mutex_enter(&hal->topology_tree_mutex);
hal_node_num = IEEE1394_NODE_NUM(hal->node_id);
IRM_node = hal->IRM_node;
/* Unlock the topology tree */
mutex_exit(&hal->topology_tree_mutex);
/* Make sure there is a valid IRM on the bus */
if (IRM_node == -1) {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"No IRM on the 1394 bus");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
/* Send compare-swap to BANDWIDTH_AVAILABLE */
/* register on the Isoch Rsrc Mgr */
if (IRM_node == hal_node_num) {
i = num_retries;
do {
(void) HAL_CALL(hal).csr_read(hal->halinfo.hal_private,
(IEEE1394_SCSR_BANDWIDTH_AVAIL &
IEEE1394_CSR_OFFSET_MASK), &old_value);
/* Check that the generation has not changed */
if (generation != hal->generation_count) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
temp_value = (old_value + bw_alloc_units);
if ((temp_value >= old_value) &&
(temp_value <= IEEE1394_BANDWIDTH_MAX)) {
compare = old_value;
swap = temp_value;
} else {
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Too many retries");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
ret = HAL_CALL(hal).csr_cswap32(
hal->halinfo.hal_private, generation,
(IEEE1394_SCSR_BANDWIDTH_AVAIL &
IEEE1394_CSR_OFFSET_MASK), compare, swap,
&old_value);
if (ret != DDI_SUCCESS) {
*result = CMD1394_EBUSRESET;
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error in cswap32");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
if (old_value == compare) {
*result = CMD1394_CMDSUCCESS;
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
}
} while (i--);
*result = CMD1394_ERETRIES_EXCEEDED;
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Retries exceeded");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
} else {
/* Remote */
if (s1394_alloc_cmd(hal, 0, &cmd) != DDI_SUCCESS) {
*result = CMD1394_EUNKNOWN_ERROR;
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Unable to allocate command");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
cmd->cmd_options = (CMD1394_CANCEL_ON_BUS_RESET |
CMD1394_OVERRIDE_ADDR | CMD1394_BLOCKING);
cmd->cmd_type = CMD1394_ASYNCH_LOCK_32;
IRM_ID_addr = (IEEE1394_ADDR_BUS_ID_MASK |
IEEE1394_SCSR_BANDWIDTH_AVAIL) |
(((uint64_t)hal->IRM_node) << IEEE1394_ADDR_PHY_ID_SHIFT);
cmd->cmd_addr = IRM_ID_addr;
cmd->bus_generation = generation;
cmd->cmd_u.l32.arg_value = IEEE1394_BANDWIDTH_MAX;
cmd->cmd_u.l32.data_value = bw_alloc_units;
cmd->cmd_u.l32.num_retries = num_retries;
cmd->cmd_u.l32.lock_type = CMD1394_LOCK_THRESH_ADD;
ret = s1394_split_lock_req(hal, NULL, cmd);
if (ret == DDI_SUCCESS) {
if (cmd->cmd_result == CMD1394_CMDSUCCESS) {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_SUCCESS);
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string,
msg, "Error freeing isoch bandwidth");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
} else {
*result = cmd->cmd_result;
/* Need to free the command */
(void) s1394_free_cmd(hal, &cmd);
TNF_PROBE_1(s1394_bandwidth_free_error,
S1394_TNF_SL_ISOCH_ERROR, "", tnf_string, msg,
"Error freeing isoch bandwidth");
TNF_PROBE_0_DEBUG(s1394_bandwidth_free_exit,
S1394_TNF_SL_ISOCH_STACK, "");
return (DDI_FAILURE);
}
}
}
/*
* s1394_isoch_cec_list_insert()
* is used to insert an Isoch CEC into a given HAL's list of Isoch CECs.
*/
void
s1394_isoch_cec_list_insert(s1394_hal_t *hal, s1394_isoch_cec_t *cec)
{
s1394_isoch_cec_t *cec_temp;
TNF_PROBE_0_DEBUG(s1394_isoch_cec_list_insert_enter,
S1394_TNF_SL_ISOCH_STACK, "");
ASSERT(MUTEX_HELD(&hal->isoch_cec_list_mutex));
/* Is the Isoch CEC list empty? */
if ((hal->isoch_cec_list_head == NULL) &&
(hal->isoch_cec_list_tail == NULL)) {
hal->isoch_cec_list_head = cec;
hal->isoch_cec_list_tail = cec;
cec->cec_next = NULL;
cec->cec_prev = NULL;
} else {
cec->cec_next = hal->isoch_cec_list_head;
cec->cec_prev = NULL;
cec_temp = hal->isoch_cec_list_head;
cec_temp->cec_prev = cec;
hal->isoch_cec_list_head = cec;
}
TNF_PROBE_0_DEBUG(s1394_isoch_cec_list_insert_exit,
S1394_TNF_SL_ISOCH_STACK, "");
}
/*
* s1394_isoch_cec_list_remove()
* is used to remove an Isoch CEC from a given HAL's list of Isoch CECs.
*/
void
s1394_isoch_cec_list_remove(s1394_hal_t *hal, s1394_isoch_cec_t *cec)
{
s1394_isoch_cec_t *prev_cec;
s1394_isoch_cec_t *next_cec;
TNF_PROBE_0_DEBUG(s1394_isoch_cec_list_remove_enter,
S1394_TNF_SL_ISOCH_STACK, "");
ASSERT(MUTEX_HELD(&hal->isoch_cec_list_mutex));
prev_cec = cec->cec_prev;
next_cec = cec->cec_next;
cec->cec_prev = NULL;
cec->cec_next = NULL;
if (prev_cec != NULL) {
prev_cec->cec_next = next_cec;
} else {
if (hal->isoch_cec_list_head == cec)
hal->isoch_cec_list_head = next_cec;
}
if (next_cec != NULL) {
next_cec->cec_prev = prev_cec;
} else {
if (hal->isoch_cec_list_tail == cec)
hal->isoch_cec_list_tail = prev_cec;
}
TNF_PROBE_0_DEBUG(s1394_isoch_cec_list_remove_exit,
S1394_TNF_SL_ISOCH_STACK, "");
}
/*
* s1394_isoch_cec_member_list_insert()
* is used to insert a new member (target) into the list of members for
* a given Isoch CEC.
*/
/* ARGSUSED */
void
s1394_isoch_cec_member_list_insert(s1394_hal_t *hal, s1394_isoch_cec_t *cec,
s1394_isoch_cec_member_t *member)
{
s1394_isoch_cec_member_t *member_temp;
TNF_PROBE_0_DEBUG(s1394_isoch_cec_member_list_insert_enter,
S1394_TNF_SL_ISOCH_STACK, "");
ASSERT(MUTEX_HELD(&cec->isoch_cec_mutex));
/* Is the Isoch CEC member list empty? */
if ((cec->cec_member_list_head == NULL) &&
(cec->cec_member_list_tail == NULL)) {
cec->cec_member_list_head = member;
cec->cec_member_list_tail = member;
member->cec_mem_next = NULL;
member->cec_mem_prev = NULL;
} else if (member->cec_mem_options & T1394_TALKER) {
/* Put talker at the head of the list */
member->cec_mem_next = cec->cec_member_list_head;
member->cec_mem_prev = NULL;
member_temp = cec->cec_member_list_head;
member_temp->cec_mem_prev = member;
cec->cec_member_list_head = member;
} else {
/* Put listeners at the tail of the list */
member->cec_mem_prev = cec->cec_member_list_tail;
member->cec_mem_next = NULL;
member_temp = cec->cec_member_list_tail;
member_temp->cec_mem_next = member;
cec->cec_member_list_tail = member;
}
TNF_PROBE_0_DEBUG(s1394_isoch_cec_member_list_insert_exit,
S1394_TNF_SL_ISOCH_STACK, "");
}
/*
* s1394_isoch_cec_member_list_remove()
* is used to remove a member (target) from the list of members for
* a given Isoch CEC.
*/
/* ARGSUSED */
void
s1394_isoch_cec_member_list_remove(s1394_hal_t *hal, s1394_isoch_cec_t *cec,
s1394_isoch_cec_member_t *member)
{
s1394_isoch_cec_member_t *prev_member;
s1394_isoch_cec_member_t *next_member;
TNF_PROBE_0_DEBUG(s1394_isoch_cec_member_list_remove_enter,
S1394_TNF_SL_ISOCH_STACK, "");
ASSERT(MUTEX_HELD(&cec->isoch_cec_mutex));
prev_member = member->cec_mem_prev;
next_member = member->cec_mem_next;
member->cec_mem_prev = NULL;
member->cec_mem_next = NULL;
if (prev_member != NULL) {
prev_member->cec_mem_next = next_member;
} else {
if (cec->cec_member_list_head == member)
cec->cec_member_list_head = next_member;
}
if (next_member != NULL) {
next_member->cec_mem_prev = prev_member;
} else {
if (cec->cec_member_list_tail == member)
cec->cec_member_list_tail = prev_member;
}
TNF_PROBE_0_DEBUG(s1394_isoch_cec_member_list_remove_exit,
S1394_TNF_SL_ISOCH_STACK, "");
}