992 lines
30 KiB
C
992 lines
30 KiB
C
/*-
|
|
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
|
|
*
|
|
* Copyright (c) 2004-07 Applied Micro Circuits Corporation.
|
|
* Copyright (c) 2004-05 Vinod Kashyap
|
|
* All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
|
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
|
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
|
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
|
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
|
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
|
* SUCH DAMAGE.
|
|
*
|
|
* $FreeBSD$
|
|
*/
|
|
|
|
/*
|
|
* AMCC'S 3ware driver for 9000 series storage controllers.
|
|
*
|
|
* Author: Vinod Kashyap
|
|
* Modifications by: Adam Radford
|
|
* Modifications by: Manjunath Ranganathaiah
|
|
*/
|
|
|
|
/*
|
|
* Common Layer miscellaneous functions.
|
|
*/
|
|
|
|
#include "tw_osl_share.h"
|
|
#include "tw_cl_share.h"
|
|
#include "tw_cl_fwif.h"
|
|
#include "tw_cl_ioctl.h"
|
|
#include "tw_cl.h"
|
|
#include "tw_cl_externs.h"
|
|
#include "tw_osl_ioctl.h"
|
|
|
|
/* AEN severity table. */
|
|
TW_INT8 *tw_cli_severity_string_table[] = {
|
|
"None",
|
|
TW_CL_SEVERITY_ERROR_STRING,
|
|
TW_CL_SEVERITY_WARNING_STRING,
|
|
TW_CL_SEVERITY_INFO_STRING,
|
|
TW_CL_SEVERITY_DEBUG_STRING,
|
|
""
|
|
};
|
|
|
|
/*
|
|
* Function name: tw_cli_drain_complete_queue
|
|
* Description: This function gets called during a controller reset.
|
|
* It errors back to the OS Layer, all those requests that
|
|
* are in the complete queue, at the time of the reset.
|
|
* Any CL internal requests will be simply freed.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cli_drain_complete_queue(struct tw_cli_ctlr_context *ctlr)
|
|
{
|
|
struct tw_cli_req_context *req;
|
|
struct tw_cl_req_packet *req_pkt;
|
|
|
|
tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
/* Walk the busy queue. */
|
|
while ((req = tw_cli_req_q_remove_head(ctlr, TW_CLI_COMPLETE_Q)) !=
|
|
TW_CL_NULL) {
|
|
if (req->flags & TW_CLI_REQ_FLAGS_INTERNAL) {
|
|
/*
|
|
* It's an internal request. Set the appropriate
|
|
* error and call the CL internal callback if there's
|
|
* one. If the request originator is polling for
|
|
* completion, he should be checking req->error to
|
|
* determine that the request did not go through.
|
|
* The request originators are responsible for the
|
|
* clean-up.
|
|
*/
|
|
req->error_code = TW_CL_ERR_REQ_BUS_RESET;
|
|
if (req->tw_cli_callback)
|
|
req->tw_cli_callback(req);
|
|
} else if (req->flags & TW_CLI_REQ_FLAGS_PASSTHRU) {
|
|
/* It's a passthru request. Complete it. */
|
|
if ((req_pkt = req->orig_req) != TW_CL_NULL) {
|
|
req_pkt->status = TW_CL_ERR_REQ_BUS_RESET;
|
|
|
|
if (req_pkt->tw_osl_callback)
|
|
req_pkt->tw_osl_callback(req->req_handle);
|
|
}
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
|
|
} else {
|
|
/* It's an external (SCSI) request. Add it to the reset queue. */
|
|
tw_osl_untimeout(req->req_handle);
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_RESET_Q);
|
|
}
|
|
} /* End of while loop */
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_drain_busy_queue
|
|
* Description: This function gets called during a controller reset.
|
|
* It errors back to the OS Layer, all those requests that
|
|
* were pending with the firmware, at the time of the
|
|
* reset.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cli_drain_busy_queue(struct tw_cli_ctlr_context *ctlr)
|
|
{
|
|
struct tw_cli_req_context *req;
|
|
struct tw_cl_req_packet *req_pkt;
|
|
|
|
tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
/* Walk the busy queue. */
|
|
while ((req = tw_cli_req_q_remove_head(ctlr, TW_CLI_BUSY_Q)) !=
|
|
TW_CL_NULL) {
|
|
if (req->flags & TW_CLI_REQ_FLAGS_INTERNAL) {
|
|
/*
|
|
* It's an internal request. Set the appropriate
|
|
* error and call the CL internal callback if there's
|
|
* one. If the request originator is polling for
|
|
* completion, he should be checking req->error to
|
|
* determine that the request did not go through.
|
|
* The request originators are responsible for the
|
|
* clean-up.
|
|
*/
|
|
req->error_code = TW_CL_ERR_REQ_BUS_RESET;
|
|
if (req->tw_cli_callback)
|
|
req->tw_cli_callback(req);
|
|
} else if (req->flags & TW_CLI_REQ_FLAGS_PASSTHRU) {
|
|
/* It's a passthru request. Complete it. */
|
|
if ((req_pkt = req->orig_req) != TW_CL_NULL) {
|
|
req_pkt->status = TW_CL_ERR_REQ_BUS_RESET;
|
|
|
|
if (req_pkt->tw_osl_callback)
|
|
req_pkt->tw_osl_callback(req->req_handle);
|
|
}
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
|
|
} else {
|
|
/* It's an external (SCSI) request. Add it to the reset queue. */
|
|
tw_osl_untimeout(req->req_handle);
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_RESET_Q);
|
|
}
|
|
} /* End of while loop */
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_drain_pending_queue
|
|
* Description: This function gets called during a controller reset.
|
|
* It errors back to the OS Layer, all those requests that
|
|
* were in the pending queue, at the time of the reset.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
|
|
TW_VOID
|
|
tw_cli_drain_pending_queue(struct tw_cli_ctlr_context *ctlr)
|
|
{
|
|
struct tw_cli_req_context *req;
|
|
struct tw_cl_req_packet *req_pkt;
|
|
|
|
tw_cli_dbg_printf(3, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
/*
|
|
* Pull requests off the pending queue, and complete them.
|
|
*/
|
|
while ((req = tw_cli_req_q_remove_head(ctlr, TW_CLI_PENDING_Q)) !=
|
|
TW_CL_NULL) {
|
|
if (req->flags & TW_CLI_REQ_FLAGS_INTERNAL) {
|
|
/*
|
|
* It's an internal request. Set the appropriate
|
|
* error and call the CL internal callback if there's
|
|
* one. If the request originator is polling for
|
|
* completion, he should be checking req->error to
|
|
* determine that the request did not go through.
|
|
* The request originators are responsible for the
|
|
* clean-up.
|
|
*/
|
|
req->error_code = TW_CL_ERR_REQ_BUS_RESET;
|
|
if (req->tw_cli_callback)
|
|
req->tw_cli_callback(req);
|
|
} else if (req->flags & TW_CLI_REQ_FLAGS_PASSTHRU) {
|
|
/* It's a passthru request. Complete it. */
|
|
if ((req_pkt = req->orig_req) != TW_CL_NULL) {
|
|
req_pkt->status = TW_CL_ERR_REQ_BUS_RESET;
|
|
|
|
if (req_pkt->tw_osl_callback)
|
|
req_pkt->tw_osl_callback(req->req_handle);
|
|
}
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
|
|
} else {
|
|
/* It's an external (SCSI) request. Add it to the reset queue. */
|
|
tw_osl_untimeout(req->req_handle);
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_RESET_Q);
|
|
}
|
|
} /* End of while loop */
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_drain_response_queue
|
|
* Description: Drain the controller response queue.
|
|
*
|
|
* Input: ctlr -- ptr to per ctlr structure
|
|
* Output: None
|
|
* Return value: 0 -- success
|
|
* non-zero-- failure
|
|
*/
|
|
TW_INT32
|
|
tw_cli_drain_response_queue(struct tw_cli_ctlr_context *ctlr)
|
|
{
|
|
TW_UINT32 resp;
|
|
TW_UINT32 status_reg;
|
|
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
for (;;) {
|
|
status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr->ctlr_handle);
|
|
|
|
if (status_reg & TWA_STATUS_RESPONSE_QUEUE_EMPTY)
|
|
return(TW_OSL_ESUCCESS); /* no more response queue entries */
|
|
|
|
resp = TW_CLI_READ_RESPONSE_QUEUE(ctlr->ctlr_handle);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_find_response
|
|
* Description: Find a particular response in the ctlr response queue.
|
|
*
|
|
* Input: ctlr -- ptr to per ctlr structure
|
|
* req_id -- request id of the response to look for
|
|
* Output: None
|
|
* Return value: 0 -- success
|
|
* non-zero-- failure
|
|
*/
|
|
TW_INT32
|
|
tw_cli_find_response(struct tw_cli_ctlr_context *ctlr, TW_INT32 req_id)
|
|
{
|
|
TW_UINT32 resp;
|
|
TW_INT32 resp_id;
|
|
TW_UINT32 status_reg;
|
|
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
for (;;) {
|
|
status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr->ctlr_handle);
|
|
|
|
if (status_reg & TWA_STATUS_RESPONSE_QUEUE_EMPTY)
|
|
return(TW_OSL_ENOTTY); /* no more response queue entries */
|
|
|
|
if (ctlr->device_id == TW_CL_DEVICE_ID_9K) {
|
|
resp = TW_CLI_READ_RESPONSE_QUEUE(ctlr->ctlr_handle);
|
|
resp_id = GET_RESP_ID(resp);
|
|
} else {
|
|
resp = TW_CLI_READ_LARGE_RESPONSE_QUEUE(
|
|
ctlr->ctlr_handle);
|
|
resp_id = GET_LARGE_RESP_ID(resp);
|
|
}
|
|
if (resp_id == req_id)
|
|
return(TW_OSL_ESUCCESS); /* found the req_id */
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_drain_aen_queue
|
|
* Description: Fetches all un-retrieved AEN's posted by fw.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* Output: None
|
|
* Return value: 0 -- success
|
|
* non-zero-- failure
|
|
*/
|
|
TW_INT32
|
|
tw_cli_drain_aen_queue(struct tw_cli_ctlr_context *ctlr)
|
|
{
|
|
struct tw_cli_req_context *req;
|
|
struct tw_cl_command_header *cmd_hdr;
|
|
TW_TIME end_time;
|
|
TW_UINT16 aen_code;
|
|
TW_INT32 error;
|
|
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
for (;;) {
|
|
if ((req = tw_cli_get_request(ctlr
|
|
)) == TW_CL_NULL) {
|
|
error = TW_OSL_EBUSY;
|
|
break;
|
|
}
|
|
|
|
req->flags |= TW_CLI_REQ_FLAGS_INTERNAL;
|
|
req->tw_cli_callback = TW_CL_NULL;
|
|
if ((error = tw_cli_send_scsi_cmd(req,
|
|
0x03 /* REQUEST_SENSE */))) {
|
|
tw_cli_dbg_printf(1, ctlr->ctlr_handle,
|
|
tw_osl_cur_func(),
|
|
"Cannot send command to fetch aen");
|
|
break;
|
|
}
|
|
|
|
end_time = tw_osl_get_local_time() +
|
|
TW_CLI_REQUEST_TIMEOUT_PERIOD;
|
|
do {
|
|
if ((error = req->error_code))
|
|
/*
|
|
* This will take care of completion due to
|
|
* a reset, or a failure in
|
|
* tw_cli_submit_pending_queue.
|
|
*/
|
|
goto out;
|
|
|
|
tw_cli_process_resp_intr(req->ctlr);
|
|
|
|
if ((req->state != TW_CLI_REQ_STATE_BUSY) &&
|
|
(req->state != TW_CLI_REQ_STATE_PENDING))
|
|
break;
|
|
} while (tw_osl_get_local_time() <= end_time);
|
|
|
|
if (req->state != TW_CLI_REQ_STATE_COMPLETE) {
|
|
error = TW_OSL_ETIMEDOUT;
|
|
break;
|
|
}
|
|
|
|
if ((error = req->cmd_pkt->command.cmd_pkt_9k.status)) {
|
|
cmd_hdr = &req->cmd_pkt->cmd_hdr;
|
|
#if 0
|
|
tw_cli_create_ctlr_event(ctlr,
|
|
TW_CL_MESSAGE_SOURCE_CONTROLLER_ERROR,
|
|
cmd_hdr);
|
|
#endif // 0
|
|
break;
|
|
}
|
|
|
|
aen_code = tw_cli_manage_aen(ctlr, req);
|
|
if (aen_code == TWA_AEN_QUEUE_EMPTY)
|
|
break;
|
|
if (aen_code == TWA_AEN_SYNC_TIME_WITH_HOST)
|
|
continue;
|
|
|
|
ctlr->internal_req_busy = TW_CL_FALSE;
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
|
|
}
|
|
|
|
out:
|
|
if (req) {
|
|
if (req->data)
|
|
ctlr->internal_req_busy = TW_CL_FALSE;
|
|
tw_cli_req_q_insert_tail(req, TW_CLI_FREE_Q);
|
|
}
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_find_aen
|
|
* Description: Reports whether a given AEN ever occurred.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* aen_code-- AEN to look for
|
|
* Output: None
|
|
* Return value: 0 -- success
|
|
* non-zero-- failure
|
|
*/
|
|
TW_INT32
|
|
tw_cli_find_aen(struct tw_cli_ctlr_context *ctlr, TW_UINT16 aen_code)
|
|
{
|
|
TW_UINT32 last_index;
|
|
TW_INT32 i;
|
|
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
if (ctlr->aen_q_wrapped)
|
|
last_index = ctlr->aen_head;
|
|
else
|
|
last_index = 0;
|
|
|
|
i = ctlr->aen_head;
|
|
do {
|
|
i = (i + ctlr->max_aens_supported - 1) %
|
|
ctlr->max_aens_supported;
|
|
if (ctlr->aen_queue[i].aen_code == aen_code)
|
|
return(TW_OSL_ESUCCESS);
|
|
} while (i != last_index);
|
|
|
|
return(TW_OSL_EGENFAILURE);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_poll_status
|
|
* Description: Poll for a given status to show up in the firmware
|
|
* status register.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* status -- status to look for
|
|
* timeout -- max # of seconds to wait before giving up
|
|
* Output: None
|
|
* Return value: 0 -- success
|
|
* non-zero-- failure
|
|
*/
|
|
TW_INT32
|
|
tw_cli_poll_status(struct tw_cli_ctlr_context *ctlr, TW_UINT32 status,
|
|
TW_UINT32 timeout)
|
|
{
|
|
TW_TIME end_time;
|
|
TW_UINT32 status_reg;
|
|
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
end_time = tw_osl_get_local_time() + timeout;
|
|
do {
|
|
status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr->ctlr_handle);
|
|
if ((status_reg & status) == status)
|
|
/* got the required bit(s) */
|
|
return(TW_OSL_ESUCCESS);
|
|
|
|
tw_osl_delay(1000);
|
|
} while (tw_osl_get_local_time() <= end_time);
|
|
|
|
return(TW_OSL_ETIMEDOUT);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cl_create_event
|
|
* Description: Creates and queues ctlr/CL/OSL AEN's to be
|
|
* supplied to user-space tools on request.
|
|
* Also notifies OS Layer.
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* queue_event-- TW_CL_TRUE --> queue event;
|
|
* TW_CL_FALSE--> don't queue event
|
|
* (simply notify OSL)
|
|
* event_src -- source of event
|
|
* event_code -- AEN/error code
|
|
* severity -- severity of event
|
|
* severity_str--Text description of severity
|
|
* event_desc -- standard string related to the event/error
|
|
* event_specific_desc -- format string for additional
|
|
* info about the event
|
|
* ... -- additional arguments conforming to the format
|
|
* specified by event_specific_desc
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cl_create_event(struct tw_cl_ctlr_handle *ctlr_handle,
|
|
TW_UINT8 queue_event, TW_UINT8 event_src, TW_UINT16 event_code,
|
|
TW_UINT8 severity, TW_UINT8 *severity_str, TW_UINT8 *event_desc,
|
|
TW_UINT8 *event_specific_desc, ...)
|
|
{
|
|
struct tw_cli_ctlr_context *ctlr = ctlr_handle->cl_ctlr_ctxt;
|
|
struct tw_cl_event_packet event_pkt;
|
|
struct tw_cl_event_packet *event;
|
|
TW_UINT32 aen_head;
|
|
va_list ap;
|
|
|
|
tw_cli_dbg_printf(8, ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
if ((ctlr) && (queue_event)) {
|
|
/* Protect access to ctlr->aen_head. */
|
|
tw_osl_get_lock(ctlr_handle, ctlr->gen_lock);
|
|
|
|
aen_head = ctlr->aen_head;
|
|
ctlr->aen_head = (aen_head + 1) % ctlr->max_aens_supported;
|
|
|
|
/* Queue the event. */
|
|
event = &(ctlr->aen_queue[aen_head]);
|
|
tw_osl_memzero(event->parameter_data,
|
|
sizeof(event->parameter_data));
|
|
|
|
if (event->retrieved == TW_CL_AEN_NOT_RETRIEVED)
|
|
ctlr->aen_q_overflow = TW_CL_TRUE;
|
|
event->sequence_id = ++(ctlr->aen_cur_seq_id);
|
|
if ((aen_head + 1) == ctlr->max_aens_supported) {
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle,
|
|
tw_osl_cur_func(), "AEN queue wrapped");
|
|
ctlr->aen_q_wrapped = TW_CL_TRUE;
|
|
}
|
|
|
|
/* Free access to ctlr->aen_head. */
|
|
tw_osl_free_lock(ctlr_handle, ctlr->gen_lock);
|
|
} else {
|
|
event = &event_pkt;
|
|
tw_osl_memzero(event, sizeof(struct tw_cl_event_packet));
|
|
}
|
|
|
|
event->event_src = event_src;
|
|
event->time_stamp_sec = (TW_UINT32)tw_osl_get_local_time();
|
|
event->aen_code = event_code;
|
|
event->severity = severity;
|
|
tw_osl_strcpy(event->severity_str, severity_str);
|
|
event->retrieved = TW_CL_AEN_NOT_RETRIEVED;
|
|
|
|
va_start(ap, event_specific_desc);
|
|
tw_osl_vsprintf(event->parameter_data, event_specific_desc, ap);
|
|
va_end(ap);
|
|
|
|
event->parameter_len =
|
|
(TW_UINT8)(tw_osl_strlen(event->parameter_data));
|
|
tw_osl_strcpy(event->parameter_data + event->parameter_len + 1,
|
|
event_desc);
|
|
event->parameter_len += (1 + tw_osl_strlen(event_desc));
|
|
|
|
tw_cli_dbg_printf(4, ctlr_handle, tw_osl_cur_func(),
|
|
"event = %x %x %x %x %x %x %x\n %s",
|
|
event->sequence_id,
|
|
event->time_stamp_sec,
|
|
event->aen_code,
|
|
event->severity,
|
|
event->retrieved,
|
|
event->repeat_count,
|
|
event->parameter_len,
|
|
event->parameter_data);
|
|
|
|
tw_osl_notify_event(ctlr_handle, event);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_get_request
|
|
* Description: Gets a request pkt from the free queue.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* req_pkt -- ptr to OSL built req_pkt, if there's one
|
|
* Output: None
|
|
* Return value: ptr to request pkt -- success
|
|
* TW_CL_NULL -- failure
|
|
*/
|
|
struct tw_cli_req_context *
|
|
tw_cli_get_request(struct tw_cli_ctlr_context *ctlr
|
|
)
|
|
{
|
|
struct tw_cli_req_context *req;
|
|
|
|
tw_cli_dbg_printf(4, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
{
|
|
/* Get a free request packet. */
|
|
req = tw_cli_req_q_remove_head(ctlr, TW_CLI_FREE_Q);
|
|
}
|
|
|
|
/* Initialize some fields to their defaults. */
|
|
if (req) {
|
|
req->req_handle = TW_CL_NULL;
|
|
req->data = TW_CL_NULL;
|
|
req->length = 0;
|
|
req->data_phys = 0;
|
|
req->state = TW_CLI_REQ_STATE_INIT; /* req being initialized */
|
|
req->flags = 0;
|
|
req->error_code = 0;
|
|
req->orig_req = TW_CL_NULL;
|
|
req->tw_cli_callback = TW_CL_NULL;
|
|
|
|
/*
|
|
* Look at the status field in the command packet to see how
|
|
* it completed the last time it was used, and zero out only
|
|
* the portions that might have changed. Note that we don't
|
|
* care to zero out the sglist.
|
|
*/
|
|
if (req->cmd_pkt->command.cmd_pkt_9k.status)
|
|
tw_osl_memzero(req->cmd_pkt,
|
|
sizeof(struct tw_cl_command_header) +
|
|
28 /* max bytes before sglist */);
|
|
else
|
|
tw_osl_memzero(&(req->cmd_pkt->command),
|
|
28 /* max bytes before sglist */);
|
|
}
|
|
return(req);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_dbg_printf
|
|
* Description: Calls OSL print function if dbg_level is appropriate
|
|
*
|
|
* Input: dbg_level -- Determines whether or not to print
|
|
* ctlr_handle -- controller handle
|
|
* cur_func -- text name of calling function
|
|
* fmt -- format string for the arguments to follow
|
|
* ... -- variable number of arguments, to be printed
|
|
* based on the fmt string
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cli_dbg_printf(TW_UINT8 dbg_level,
|
|
struct tw_cl_ctlr_handle *ctlr_handle, const TW_INT8 *cur_func,
|
|
TW_INT8 *fmt, ...)
|
|
{
|
|
#ifdef TW_OSL_DEBUG
|
|
TW_INT8 print_str[256];
|
|
va_list ap;
|
|
|
|
tw_osl_memzero(print_str, 256);
|
|
if (dbg_level <= TW_OSL_DEBUG_LEVEL_FOR_CL) {
|
|
tw_osl_sprintf(print_str, "%s: ", cur_func);
|
|
|
|
va_start(ap, fmt);
|
|
tw_osl_vsprintf(print_str + tw_osl_strlen(print_str), fmt, ap);
|
|
va_end(ap);
|
|
|
|
tw_osl_strcpy(print_str + tw_osl_strlen(print_str), "\n");
|
|
tw_osl_dbg_printf(ctlr_handle, "%s", print_str);
|
|
}
|
|
#endif /* TW_OSL_DEBUG */
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_notify_ctlr_info
|
|
* Description: Notify OSL of controller info (fw/BIOS versions, etc.).
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cli_notify_ctlr_info(struct tw_cli_ctlr_context *ctlr)
|
|
{
|
|
TW_INT8 fw_ver[16];
|
|
TW_INT8 bios_ver[16];
|
|
TW_INT8 ctlr_model[16];
|
|
TW_INT32 error[3];
|
|
TW_UINT8 num_ports = 0;
|
|
|
|
tw_cli_dbg_printf(5, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
/* Get the port count. */
|
|
error[0] = tw_cli_get_param(ctlr, TWA_PARAM_CONTROLLER_TABLE,
|
|
TWA_PARAM_CONTROLLER_PORT_COUNT, &num_ports,
|
|
1, TW_CL_NULL);
|
|
|
|
/* Get the firmware and BIOS versions. */
|
|
error[0] = tw_cli_get_param(ctlr, TWA_PARAM_VERSION_TABLE,
|
|
TWA_PARAM_VERSION_FW, fw_ver, 16, TW_CL_NULL);
|
|
error[1] = tw_cli_get_param(ctlr, TWA_PARAM_VERSION_TABLE,
|
|
TWA_PARAM_VERSION_BIOS, bios_ver, 16, TW_CL_NULL);
|
|
error[2] = tw_cli_get_param(ctlr, TWA_PARAM_VERSION_TABLE,
|
|
TWA_PARAM_CTLR_MODEL, ctlr_model, 16, TW_CL_NULL);
|
|
|
|
tw_cl_create_event(ctlr->ctlr_handle, TW_CL_FALSE,
|
|
TW_CL_MESSAGE_SOURCE_COMMON_LAYER_ERROR,
|
|
0x1300, 0x3, TW_CL_SEVERITY_INFO_STRING,
|
|
"Controller details:",
|
|
"Model %.16s, %d ports, Firmware %.16s, BIOS %.16s",
|
|
error[2]?(TW_INT8 *)TW_CL_NULL:ctlr_model,
|
|
num_ports,
|
|
error[0]?(TW_INT8 *)TW_CL_NULL:fw_ver,
|
|
error[1]?(TW_INT8 *)TW_CL_NULL:bios_ver);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_check_ctlr_state
|
|
* Description: Makes sure that the fw status register reports a
|
|
* proper status.
|
|
*
|
|
* Input: ctlr -- ptr to CL internal ctlr context
|
|
* status_reg-- value in the status register
|
|
* Output: None
|
|
* Return value: 0 -- no errors
|
|
* non-zero-- errors
|
|
*/
|
|
TW_INT32
|
|
tw_cli_check_ctlr_state(struct tw_cli_ctlr_context *ctlr, TW_UINT32 status_reg)
|
|
{
|
|
struct tw_cl_ctlr_handle *ctlr_handle = ctlr->ctlr_handle;
|
|
TW_INT32 error = TW_OSL_ESUCCESS;
|
|
|
|
tw_cli_dbg_printf(8, ctlr->ctlr_handle, tw_osl_cur_func(), "entered");
|
|
|
|
/* Check if the 'micro-controller ready' bit is not set. */
|
|
if (!(status_reg & TWA_STATUS_MICROCONTROLLER_READY)) {
|
|
TW_INT8 desc[200];
|
|
|
|
tw_osl_memzero(desc, 200);
|
|
if (!(ctlr->reset_phase1_in_progress)) {
|
|
tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
|
|
TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
|
|
0x1301, 0x1, TW_CL_SEVERITY_ERROR_STRING,
|
|
"Missing expected status bit(s)",
|
|
"status reg = 0x%x; Missing bits: %s",
|
|
status_reg,
|
|
tw_cli_describe_bits(
|
|
TWA_STATUS_MICROCONTROLLER_READY,
|
|
desc));
|
|
error = TW_OSL_EGENFAILURE;
|
|
}
|
|
}
|
|
|
|
/* Check if any error bits are set. */
|
|
if ((status_reg & TWA_STATUS_UNEXPECTED_BITS) != 0) {
|
|
TW_INT8 desc[200];
|
|
|
|
tw_osl_memzero(desc, 200);
|
|
|
|
/* Skip queue error msgs during 9650SE/9690SA reset */
|
|
if (((ctlr->device_id != TW_CL_DEVICE_ID_9K_E) &&
|
|
(ctlr->device_id != TW_CL_DEVICE_ID_9K_SA)) ||
|
|
(!(ctlr->reset_in_progress)) ||
|
|
((status_reg & TWA_STATUS_QUEUE_ERROR_INTERRUPT) == 0))
|
|
tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
|
|
TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
|
|
0x1302, 0x1, TW_CL_SEVERITY_ERROR_STRING,
|
|
"Unexpected status bit(s)",
|
|
"status reg = 0x%x Unexpected bits: %s",
|
|
status_reg & TWA_STATUS_UNEXPECTED_BITS,
|
|
tw_cli_describe_bits(status_reg &
|
|
TWA_STATUS_UNEXPECTED_BITS, desc));
|
|
|
|
if (status_reg & TWA_STATUS_PCI_PARITY_ERROR_INTERRUPT) {
|
|
tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
|
|
TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
|
|
0x1303, 0x1, TW_CL_SEVERITY_ERROR_STRING,
|
|
"PCI parity error: clearing... "
|
|
"Re-seat/move/replace card",
|
|
"status reg = 0x%x %s",
|
|
status_reg,
|
|
tw_cli_describe_bits(status_reg, desc));
|
|
TW_CLI_WRITE_CONTROL_REGISTER(ctlr->ctlr_handle,
|
|
TWA_CONTROL_CLEAR_PARITY_ERROR);
|
|
|
|
#ifdef TW_OSL_PCI_CONFIG_ACCESSIBLE
|
|
tw_osl_write_pci_config(ctlr->ctlr_handle,
|
|
TW_CLI_PCI_CONFIG_STATUS_OFFSET,
|
|
TWA_PCI_CONFIG_CLEAR_PARITY_ERROR, 2);
|
|
#endif /* TW_OSL_PCI_CONFIG_ACCESSIBLE */
|
|
|
|
}
|
|
|
|
if (status_reg & TWA_STATUS_PCI_ABORT_INTERRUPT) {
|
|
tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
|
|
TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
|
|
0x1304, 0x1, TW_CL_SEVERITY_ERROR_STRING,
|
|
"PCI abort: clearing... ",
|
|
"status reg = 0x%x %s",
|
|
status_reg,
|
|
tw_cli_describe_bits(status_reg, desc));
|
|
TW_CLI_WRITE_CONTROL_REGISTER(ctlr->ctlr_handle,
|
|
TWA_CONTROL_CLEAR_PCI_ABORT);
|
|
|
|
#ifdef TW_OSL_PCI_CONFIG_ACCESSIBLE
|
|
tw_osl_write_pci_config(ctlr->ctlr_handle,
|
|
TW_CLI_PCI_CONFIG_STATUS_OFFSET,
|
|
TWA_PCI_CONFIG_CLEAR_PCI_ABORT, 2);
|
|
#endif /* TW_OSL_PCI_CONFIG_ACCESSIBLE */
|
|
}
|
|
|
|
if (status_reg & TWA_STATUS_QUEUE_ERROR_INTERRUPT) {
|
|
/* Skip queue error msgs during 9650SE/9690SA reset */
|
|
if (((ctlr->device_id != TW_CL_DEVICE_ID_9K_E) &&
|
|
(ctlr->device_id != TW_CL_DEVICE_ID_9K_SA)) ||
|
|
(!(ctlr->reset_in_progress)))
|
|
tw_cl_create_event(ctlr_handle, TW_CL_FALSE,
|
|
TW_CL_MESSAGE_SOURCE_COMMON_LAYER_EVENT,
|
|
0x1305, 0x1, TW_CL_SEVERITY_ERROR_STRING,
|
|
"Controller queue error: clearing... ",
|
|
"status reg = 0x%x %s",
|
|
status_reg,
|
|
tw_cli_describe_bits(status_reg, desc));
|
|
TW_CLI_WRITE_CONTROL_REGISTER(ctlr->ctlr_handle,
|
|
TWA_CONTROL_CLEAR_QUEUE_ERROR);
|
|
}
|
|
}
|
|
return(error);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_describe_bits
|
|
* Description: Given the value of the status register, returns a
|
|
* string describing the meaning of each set bit.
|
|
*
|
|
* Input: reg -- status register value
|
|
* Output: Pointer to a string describing each set bit
|
|
* Return value: Pointer to the string describing each set bit
|
|
*/
|
|
TW_INT8 *
|
|
tw_cli_describe_bits(TW_UINT32 reg, TW_INT8 *str)
|
|
{
|
|
tw_osl_strcpy(str, "[");
|
|
|
|
if (reg & TWA_STATUS_COMMAND_QUEUE_EMPTY)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "CMD_Q_EMPTY,");
|
|
if (reg & TWA_STATUS_MICROCONTROLLER_READY)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "MC_RDY,");
|
|
if (reg & TWA_STATUS_RESPONSE_QUEUE_EMPTY)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "RESP_Q_EMPTY,");
|
|
if (reg & TWA_STATUS_COMMAND_QUEUE_FULL)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "CMD_Q_FULL,");
|
|
if (reg & TWA_STATUS_RESPONSE_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "RESP_INTR,");
|
|
if (reg & TWA_STATUS_COMMAND_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "CMD_INTR,");
|
|
if (reg & TWA_STATUS_ATTENTION_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "ATTN_INTR,");
|
|
if (reg & TWA_STATUS_HOST_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "HOST_INTR,");
|
|
if (reg & TWA_STATUS_PCI_ABORT_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "PCI_ABRT,");
|
|
if (reg & TWA_STATUS_QUEUE_ERROR_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "Q_ERR,");
|
|
if (reg & TWA_STATUS_PCI_PARITY_ERROR_INTERRUPT)
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "PCI_PERR");
|
|
|
|
tw_osl_strcpy(&str[tw_osl_strlen(str)], "]");
|
|
return(str);
|
|
}
|
|
|
|
#ifdef TW_OSL_DEBUG
|
|
|
|
/*
|
|
* Function name: tw_cl_print_ctlr_stats
|
|
* Description: Prints the current status of the controller.
|
|
*
|
|
* Input: ctlr_handle-- controller handle
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cl_print_ctlr_stats(struct tw_cl_ctlr_handle *ctlr_handle)
|
|
{
|
|
struct tw_cli_ctlr_context *ctlr =
|
|
(struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);
|
|
TW_UINT32 status_reg;
|
|
TW_INT8 desc[200];
|
|
|
|
tw_cli_dbg_printf(7, ctlr->ctlr_handle, "", "entered");
|
|
|
|
/* Print current controller details. */
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "cl_ctlr_ctxt = %p", ctlr);
|
|
|
|
tw_osl_memzero(desc, 200);
|
|
status_reg = TW_CLI_READ_STATUS_REGISTER(ctlr_handle);
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "status reg = 0x%x %s",
|
|
status_reg, tw_cli_describe_bits(status_reg, desc));
|
|
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "CLq type current max");
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "free %04d %04d",
|
|
ctlr->q_stats[TW_CLI_FREE_Q].cur_len,
|
|
ctlr->q_stats[TW_CLI_FREE_Q].max_len);
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "busy %04d %04d",
|
|
ctlr->q_stats[TW_CLI_BUSY_Q].cur_len,
|
|
ctlr->q_stats[TW_CLI_BUSY_Q].max_len);
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "pending %04d %04d",
|
|
ctlr->q_stats[TW_CLI_PENDING_Q].cur_len,
|
|
ctlr->q_stats[TW_CLI_PENDING_Q].max_len);
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "complete %04d %04d",
|
|
ctlr->q_stats[TW_CLI_COMPLETE_Q].cur_len,
|
|
ctlr->q_stats[TW_CLI_COMPLETE_Q].max_len);
|
|
tw_cli_dbg_printf(0, ctlr_handle, "", "AEN queue head %d tail %d",
|
|
ctlr->aen_head, ctlr->aen_tail);
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cl_reset_stats
|
|
* Description: Resets CL maintained statistics for the controller.
|
|
*
|
|
* Input: ctlr_handle-- controller handle
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cl_reset_stats(struct tw_cl_ctlr_handle *ctlr_handle)
|
|
{
|
|
struct tw_cli_ctlr_context *ctlr =
|
|
(struct tw_cli_ctlr_context *)(ctlr_handle->cl_ctlr_ctxt);
|
|
|
|
tw_cli_dbg_printf(7, ctlr_handle, tw_osl_cur_func(), "entered");
|
|
ctlr->q_stats[TW_CLI_FREE_Q].max_len = 0;
|
|
ctlr->q_stats[TW_CLI_BUSY_Q].max_len = 0;
|
|
ctlr->q_stats[TW_CLI_PENDING_Q].max_len = 0;
|
|
ctlr->q_stats[TW_CLI_COMPLETE_Q].max_len = 0;
|
|
}
|
|
|
|
/*
|
|
* Function name: tw_cli_print_req_info
|
|
* Description: Prints CL internal details of a given request.
|
|
*
|
|
* Input: req -- ptr to CL internal request context
|
|
* Output: None
|
|
* Return value: None
|
|
*/
|
|
TW_VOID
|
|
tw_cl_print_req_info(struct tw_cl_req_handle *req_handle)
|
|
{
|
|
struct tw_cli_req_context *req = req_handle->cl_req_ctxt;
|
|
struct tw_cli_ctlr_context *ctlr = req->ctlr;
|
|
struct tw_cl_ctlr_handle *ctlr_handle = ctlr->ctlr_handle;
|
|
struct tw_cl_command_packet *cmd_pkt = req->cmd_pkt;
|
|
struct tw_cl_command_9k *cmd9k;
|
|
union tw_cl_command_7k *cmd7k;
|
|
TW_UINT8 *cdb;
|
|
TW_VOID *sgl;
|
|
TW_UINT32 sgl_entries;
|
|
TW_UINT32 i;
|
|
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"CL details for request:");
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"req_handle = %p, ctlr = %p,\n"
|
|
"cmd_pkt = %p, cmd_pkt_phys = 0x%llx,\n"
|
|
"data = %p, length = 0x%x, data_phys = 0x%llx,\n"
|
|
"state = 0x%x, flags = 0x%x, error = 0x%x,\n"
|
|
"orig_req = %p, callback = %p, req_id = 0x%x,\n"
|
|
"next_req = %p, prev_req = %p",
|
|
req_handle, ctlr,
|
|
cmd_pkt, req->cmd_pkt_phys,
|
|
req->data, req->length, req->data_phys,
|
|
req->state, req->flags, req->error_code,
|
|
req->orig_req, req->tw_cli_callback, req->request_id,
|
|
req->link.next, req->link.prev);
|
|
|
|
if (req->flags & TW_CLI_REQ_FLAGS_9K) {
|
|
cmd9k = &(cmd_pkt->command.cmd_pkt_9k);
|
|
sgl = cmd9k->sg_list;
|
|
sgl_entries = TW_CL_SWAP16(
|
|
GET_SGL_ENTRIES(cmd9k->lun_h4__sgl_entries));
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"9K cmd: opcode = 0x%x, unit = 0x%x, req_id = 0x%x,\n"
|
|
"status = 0x%x, sgl_offset = 0x%x, sgl_entries = 0x%x",
|
|
GET_OPCODE(cmd9k->res__opcode),
|
|
cmd9k->unit,
|
|
TW_CL_SWAP16(GET_REQ_ID(cmd9k->lun_l4__req_id)),
|
|
cmd9k->status,
|
|
cmd9k->sgl_offset,
|
|
sgl_entries);
|
|
|
|
cdb = (TW_UINT8 *)(cmd9k->cdb);
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"CDB: %x %x %x %x %x %x %x %x"
|
|
"%x %x %x %x %x %x %x %x",
|
|
cdb[0], cdb[1], cdb[2], cdb[3],
|
|
cdb[4], cdb[5], cdb[6], cdb[7],
|
|
cdb[8], cdb[9], cdb[10], cdb[11],
|
|
cdb[12], cdb[13], cdb[14], cdb[15]);
|
|
} else {
|
|
cmd7k = &(cmd_pkt->command.cmd_pkt_7k);
|
|
sgl = cmd7k->param.sgl;
|
|
sgl_entries = (cmd7k->generic.size -
|
|
GET_SGL_OFF(cmd7k->generic.sgl_off__opcode)) /
|
|
((ctlr->flags & TW_CL_64BIT_ADDRESSES) ? 3 : 2);
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"7K cmd: opcode = 0x%x, sgl_offset = 0x%x,\n"
|
|
"size = 0x%x, req_id = 0x%x, unit = 0x%x,\n"
|
|
"status = 0x%x, flags = 0x%x, count = 0x%x",
|
|
GET_OPCODE(cmd7k->generic.sgl_off__opcode),
|
|
GET_SGL_OFF(cmd7k->generic.sgl_off__opcode),
|
|
cmd7k->generic.size,
|
|
TW_CL_SWAP16(cmd7k->generic.request_id),
|
|
GET_UNIT(cmd7k->generic.host_id__unit),
|
|
cmd7k->generic.status,
|
|
cmd7k->generic.flags,
|
|
TW_CL_SWAP16(cmd7k->generic.count));
|
|
}
|
|
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(), "SG entries:");
|
|
|
|
if (ctlr->flags & TW_CL_64BIT_ADDRESSES) {
|
|
struct tw_cl_sg_desc64 *sgl64 = (struct tw_cl_sg_desc64 *)sgl;
|
|
|
|
for (i = 0; i < sgl_entries; i++) {
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"0x%llx 0x%x",
|
|
sgl64[i].address, sgl64[i].length);
|
|
}
|
|
} else {
|
|
struct tw_cl_sg_desc32 *sgl32 = (struct tw_cl_sg_desc32 *)sgl;
|
|
|
|
for (i = 0; i < sgl_entries; i++) {
|
|
tw_cli_dbg_printf(0, ctlr_handle, tw_osl_cur_func(),
|
|
"0x%x 0x%x",
|
|
sgl32[i].address, sgl32[i].length);
|
|
}
|
|
}
|
|
}
|
|
|
|
#endif /* TW_OSL_DEBUG */
|