* Copyright 2004-2007, Haiku, Inc. All RightsReserved.
* Copyright 2002-2004, Thomas Kurschel. All rights reserved.
*
* Distributed under the terms of the MIT License.
*/
#include "scsi_internal.h"
#include "queuing.h"
#include <string.h>
#include <algorithm>
void
scsi_requeue_request(scsi_ccb *request, bool bus_overflow)
{
scsi_bus_info *bus = request->bus;
scsi_device_info *device = request->device;
bool was_servicable, start_retry;
SHOW_FLOW0(3, "");
if (request->state != SCSI_STATE_SENT) {
panic("Unsent ccb was request to requeue\n");
return;
}
request->state = SCSI_STATE_QUEUED;
mutex_lock(&bus->mutex);
was_servicable = scsi_can_service_bus(bus);
if (bus->left_slots++ == 0)
scsi_unblock_bus_noresume(bus, false);
if (device->left_slots++ == 0 || request->ordered)
scsi_unblock_device_noresume(device, false);
scsi_add_req_queue_first(request);
if (bus_overflow) {
scsi_set_bus_overflow(bus);
scsi_add_device_queue_last(device);
} else {
scsi_set_device_overflow(device);
scsi_remove_device_queue(device);
scsi_clear_bus_overflow(bus);
}
start_retry = !was_servicable && scsi_can_service_bus(bus);
mutex_unlock(&bus->mutex);
if (start_retry)
release_sem_etc(bus->start_service, 1, 0);
}
void
scsi_resubmit_request(scsi_ccb *request)
{
scsi_bus_info *bus = request->bus;
scsi_device_info *device = request->device;
bool was_servicable, start_retry;
SHOW_FLOW0(3, "");
if (request->state != SCSI_STATE_SENT) {
panic("Unsent ccb was asked to get resubmitted\n");
return;
}
request->state = SCSI_STATE_QUEUED;
mutex_lock(&bus->mutex);
was_servicable = scsi_can_service_bus(bus);
if (bus->left_slots++ == 0)
scsi_unblock_bus_noresume(bus, false);
if (device->left_slots++ == 0 || request->ordered)
scsi_unblock_device_noresume(device, false);
scsi_clear_device_overflow(device);
scsi_clear_bus_overflow(bus);
request->ordered = true;
scsi_add_req_queue_first(request);
if (device->lock_count == 0) {
scsi_add_device_queue_first(device);
bus->waiting_devices = device;
}
start_retry = !was_servicable && scsi_can_service_bus(bus);
mutex_unlock(&bus->mutex);
if (start_retry)
release_sem_etc(bus->start_service, 1, 0);
}
static void
submit_autosense(scsi_ccb *request)
{
scsi_device_info *device = request->device;
SHOW_FLOW0(3, "sending autosense");
device->auto_sense_request->buffered = false;
device->auto_sense_request->ordered = true;
device->auto_sense_request->emulated = false;
device->auto_sense_originator = request;
scsi_add_queued_request_first(device->auto_sense_request);
}
static void
finish_autosense(scsi_device_info *device)
{
scsi_ccb *orig_request = device->auto_sense_originator;
scsi_ccb *request = device->auto_sense_request;
SHOW_FLOW0(3, "");
if (request->subsys_status == SCSI_REQ_CMP) {
int sense_len;
sense_len = std::min((uint32)SCSI_MAX_SENSE_SIZE,
request->data_length - request->data_resid);
SHOW_FLOW(3, "Got sense: %d bytes", sense_len);
memcpy(orig_request->sense, request->data, sense_len);
orig_request->sense_resid = SCSI_MAX_SENSE_SIZE - sense_len;
orig_request->subsys_status |= SCSI_AUTOSNS_VALID;
} else {
orig_request->subsys_status = SCSI_AUTOSENSE_FAIL;
}
orig_request->completion_cond.NotifyOne();
}
static void
scsi_device_queue_overflow(scsi_ccb *request, uint num_requests)
{
scsi_bus_info *bus = request->bus;
scsi_device_info *device = request->device;
int diff_max_slots;
--num_requests;
if (num_requests < 1)
num_requests = 1;
SHOW_INFO(2, "Restricting device queue to %d requests", num_requests);
mutex_lock(&bus->mutex);
diff_max_slots = device->total_slots - num_requests;
device->total_slots = num_requests;
device->left_slots -= diff_max_slots;
mutex_unlock(&bus->mutex);
scsi_requeue_request(request, false);
}
void
scsi_request_finished(scsi_ccb *request, uint num_requests)
{
scsi_device_info *device = request->device;
scsi_bus_info *bus = request->bus;
bool was_servicable, start_service, do_autosense;
SHOW_FLOW(3, "%p", request);
if (request->state != SCSI_STATE_SENT) {
panic("Unsent ccb %p was reported as done\n", request);
return;
}
if (request->subsys_status == SCSI_REQ_INPROG) {
panic("ccb %p with status \"Request in Progress\" was reported as done\n",
request);
return;
}
if (request->subsys_status == SCSI_REQ_CMP_ERR
&& request->device_status == SCSI_STATUS_QUEUE_FULL) {
scsi_device_queue_overflow(request, num_requests);
return;
}
request->state = SCSI_STATE_FINISHED;
mutex_lock(&bus->mutex);
was_servicable = scsi_can_service_bus(bus);
do_autosense = device->manual_autosense
&& (request->flags & SCSI_DIS_AUTOSENSE) == 0
&& request->subsys_status == SCSI_REQ_CMP_ERR
&& request->device_status == SCSI_STATUS_CHECK_CONDITION;
if (request->subsys_status != SCSI_REQ_CMP) {
SHOW_FLOW(3, "subsys=%x, device=%x, flags=%" B_PRIx32
", manual_auto_sense=%d", request->subsys_status,
request->device_status, request->flags, device->manual_autosense);
}
if (do_autosense) {
submit_autosense(request);
}
if (bus->left_slots++ == 0)
scsi_unblock_bus_noresume(bus, false);
if (device->left_slots++ == 0 || request->ordered)
scsi_unblock_device_noresume(device, false);
scsi_clear_device_overflow(device);
scsi_clear_bus_overflow(bus);
if (device->lock_count == 0 && device->queued_reqs != NULL)
scsi_add_device_queue_last(device);
start_service = !was_servicable && scsi_can_service_bus(bus);
mutex_unlock(&bus->mutex);
if (start_service)
release_sem_etc(bus->start_service, 1, 0);
if (request->emulated)
scsi_finish_emulation(request);
if (request->buffered)
scsi_release_dma_buffer(request);
if (request == device->auto_sense_request)
finish_autosense(device);
else {
if (!do_autosense)
request->completion_cond.NotifyOne();
}
}
* return: true if request can be executed
* side effect: updates device->last_sort
*/
static inline bool
scsi_check_enqueue_request(scsi_ccb *request)
{
scsi_bus_info *bus = request->bus;
scsi_device_info *device = request->device;
bool execute;
mutex_lock(&bus->mutex);
if (device->lock_count > 0 || device->queued_reqs != NULL
|| bus->lock_count > 0 || bus->waiting_devices != NULL) {
SHOW_FLOW0(3, "bus/device is currently locked");
scsi_add_queued_request(request);
execute = false;
} else {
if (--bus->left_slots == 0) {
SHOW_FLOW0(3, "bus is saturated, blocking further requests");
scsi_block_bus_nolock(bus, false);
}
if (--device->left_slots == 0 || request->ordered) {
SHOW_FLOW0( 3, "device is saturated/blocked by requests, blocking further requests" );
scsi_block_device_nolock(device, false);
}
if (request->sort >= 0) {
device->last_sort = request->sort;
SHOW_FLOW(1, "%" B_PRId64, device->last_sort);
}
execute = true;
}
mutex_unlock(&bus->mutex);
return execute;
}
int func_group_len[8] = {
6, 10, 10, 0, 16, 12, 0, 0
};
void
scsi_async_io(scsi_ccb *request)
{
scsi_bus_info *bus = request->bus;
if (request->state != SCSI_STATE_FINISHED) {
panic("Passed ccb to scsi_action that isn't ready (state = %d)\n",
request->state);
}
if (request->cdb_length < func_group_len[request->cdb[0] >> 5]) {
SHOW_ERROR(3, "invalid command len (%d instead of %d)",
request->cdb_length, func_group_len[request->cdb[0] >> 5]);
request->subsys_status = SCSI_REQ_INVALID;
goto err;
}
if (!request->device->valid) {
SHOW_ERROR0( 3, "device got removed" );
request->subsys_status = SCSI_DEV_NOT_THERE;
goto err;
}
if ((request->flags & SCSI_DIR_MASK) != SCSI_DIR_NONE
&& request->sg_list == NULL && request->data_length > 0) {
SHOW_ERROR( 3, "Asynchronous SCSI I/O requires S/G list (data is %"
B_PRIu32 " bytes)", request->data_length );
request->subsys_status = SCSI_DATA_RUN_ERR;
goto err;
}
request->buffered = request->emulated = 0;
if ((request->flags & SCSI_DMA_SAFE) == 0 && request->data_length > 0) {
request->buffered = true;
if (!scsi_get_dma_buffer(request)) {
SHOW_ERROR0( 3, "cannot create DMA buffer for request - reduce data volume" );
request->subsys_status = SCSI_DATA_RUN_ERR;
goto err;
}
}
if ((request->device->emulation_map[request->cdb[0] >> 3]
& (1 << (request->cdb[0] & 7))) != 0) {
request->emulated = true;
if (!scsi_start_emulation(request)) {
SHOW_ERROR(3, "cannot emulate SCSI command 0x%02x", request->cdb[0]);
goto err2;
}
}
{
scsi_cmd_tur *cmd = (scsi_cmd_tur *)request->cdb;
cmd->lun = request->device->target_lun;
}
request->ordered = (request->flags & SCSI_ORDERED_QTAG) != 0;
SHOW_FLOW(3, "ordered=%d", request->ordered);
if (!scsi_check_enqueue_request(request))
return;
bus = request->bus;
request->state = SCSI_STATE_SENT;
bus->interface->scsi_io(bus->sim_cookie, request);
return;
err2:
if (request->buffered)
scsi_release_dma_buffer(request);
err:
request->completion_cond.NotifyOne(B_ERROR);
return;
}
void
scsi_sync_io(scsi_ccb *request)
{
bool tmp_sg = false;
if ((request->flags & SCSI_DIR_MASK) != SCSI_DIR_NONE
&& request->sg_list == NULL && request->data_length > 0) {
tmp_sg = true;
if (!create_temp_sg(request)) {
SHOW_ERROR0( 3, "data is too much fragmented - you should use s/g list" );
request->subsys_status = SCSI_DATA_RUN_ERR;
return;
}
}
ConditionVariableEntry entry;
request->completion_cond.Add(&entry);
scsi_async_io(request);
entry.Wait();
if (tmp_sg)
cleanup_temp_sg(request);
}
uchar
scsi_term_io(scsi_ccb *ccb_to_terminate)
{
scsi_bus_info *bus = ccb_to_terminate->bus;
return bus->interface->term_io(bus->sim_cookie, ccb_to_terminate);
}
uchar
scsi_abort(scsi_ccb *req_to_abort)
{
scsi_bus_info *bus = req_to_abort->bus;
if (bus == NULL) {
return SCSI_REQ_INVALID;
}
mutex_lock(&bus->mutex);
switch (req_to_abort->state) {
case SCSI_STATE_FINISHED:
case SCSI_STATE_SENT:
mutex_unlock(&bus->mutex);
break;
case SCSI_STATE_QUEUED: {
bool was_servicable, start_retry;
was_servicable = scsi_can_service_bus(bus);
scsi_remove_queued_request(req_to_abort);
start_retry = scsi_can_service_bus(bus) && !was_servicable;
mutex_unlock(&bus->mutex);
req_to_abort->subsys_status = SCSI_REQ_ABORTED;
if (req_to_abort->emulated)
scsi_finish_emulation(req_to_abort);
if (req_to_abort->buffered)
scsi_release_dma_buffer(req_to_abort);
req_to_abort->completion_cond.NotifyOne(B_CANCELED);
if (start_retry)
release_sem(bus->start_service);
break;
}
}
return SCSI_REQ_CMP;
}
bool
scsi_check_exec_service(scsi_bus_info *bus)
{
SHOW_FLOW0(3, "");
mutex_lock(&bus->mutex);
if (scsi_can_service_bus(bus)) {
scsi_ccb *request;
scsi_device_info *device;
SHOW_FLOW0(3, "servicing bus");
device = bus->waiting_devices;
bus->waiting_devices = bus->waiting_devices->waiting_next;
request = device->queued_reqs;
scsi_remove_queued_request(request);
if (--bus->left_slots == 0) {
SHOW_FLOW0(3, "bus is saturated, blocking further requests");
scsi_block_bus_nolock(bus, false);
}
if (--device->left_slots == 0 || request->ordered) {
SHOW_FLOW0(3, "device is saturated/blocked by requests, blocking further requests");
scsi_block_device_nolock(device, false);
}
if (request->sort >= 0) {
device->last_sort = request->sort;
SHOW_FLOW(1, "%" B_PRId64, device->last_sort);
}
mutex_unlock(&bus->mutex);
request->state = SCSI_STATE_SENT;
bus->interface->scsi_io(bus->sim_cookie, request);
return true;
}
mutex_unlock(&bus->mutex);
return false;
}