Added SCSI bus manager written by Thomas Kurschel.

git-svn-id: file:///srv/svn/repos/haiku/trunk/current@7776 a95241bf-73f2-0310-859d-f6bbb57e9c96
This commit is contained in:
Axel Dörfler
2004-06-07 00:55:49 +00:00
parent debb0e18cc
commit 1450572a5d
21 changed files with 5046 additions and 0 deletions
@@ -0,0 +1,23 @@
SubDir OBOS_TOP src add-ons kernel bus_managers scsi ;
UsePrivateHeaders kernel ;
UsePrivateHeaders [ FDirName kernel arch $(OBOS_ARCH) ] ;
UsePrivateHeaders [ FDirName kernel boot platform $(OBOS_BOOT_PLATFORM) ] ;
KernelAddon scsi : kernel bus_managers :
bus_raw.c
busses.c
ccb.c
device_scan.c
devices.c
dma_buffer.c
dpc.c
emulation.c
queuing.c
scsi.c
scsi_io.c
scatter_gather.c
sim_interface.c
virtual_memory.c
;
@@ -0,0 +1,45 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Functions that are missing in kernel.
*/
#ifndef _KERNEL_EXPORT_EXT_H
#define _KERNEL_EXPORT_EXT_H
#include <KernelExport.h>
#include <iovec.h>
// get memory map of iovec
status_t get_iovec_memory_map(
iovec *vec, // iovec to analyze
size_t vec_count, // number of entries in vec
size_t vec_offset, // number of bytes to skip at beginning of vec
size_t len, // number of bytes to analyze
physical_entry *map, // resulting memory map
size_t max_entries, // max number of entries in map
size_t *num_entries, // actual number of map entries used
size_t *mapped_len // actual number of bytes described by map
);
// map main memory into virtual address space
status_t map_mainmemory(
addr_t physical_addr, // physical address to map
void **virtual_addr // receives corresponding virtual address
);
// unmap main memory from virtual address space
status_t unmap_mainmemory(
void *virtual_addr // virtual address to release
);
// this should be moved to SupportDefs.h
int32 atomic_exchange(vint32 *value, int32 newValue);
#endif
@@ -0,0 +1,195 @@
/*
** Copyright 2002-04, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Devfs entry for raw bus access.
This interface will go away. It's used by scsi_probe as
long as we have no proper pnpfs where all the info can
be retrieved from.
*/
#include "scsi_internal.h"
#include <device/scsi_bus_raw_driver.h>
#include <pnp_devfs.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
// info about bus
// (used both as bus cookie and file handle cookie)
typedef struct bus_raw_info {
scsi_bus_interface *interface;
scsi_bus cookie;
pnp_node_handle node;
} bus_raw_info;
static status_t scsi_bus_raw_init_device(
pnp_node_handle node, void *user_cookie, void **cookie )
{
bus_raw_info *bus;
scsi_bus_interface *interface;
scsi_bus bus_cookie;
status_t res;
res = pnp->load_driver( pnp->get_parent( node ), NULL,
(pnp_driver_info **)&interface, (void **)&bus_cookie );
if( res != B_OK )
return res;
bus = malloc( sizeof( *bus ));
bus->interface = interface;
bus->cookie = bus_cookie;
bus->node = node;
*cookie = bus;
return B_OK;
}
static status_t scsi_bus_raw_uninit_device( bus_raw_info *bus )
{
status_t res;
res = pnp->unload_driver( pnp->get_parent( bus->node ));
if( res != B_OK )
return res;
free( bus );
return B_OK;
}
static status_t scsi_bus_raw_probe( pnp_node_handle parent )
{
uint8 path_id;
char *name;
if( pnp->get_attr_uint8( parent, SCSI_BUS_PATH_ID_ITEM, &path_id, false ) != B_OK )
return B_ERROR;
// put that on heap to not overflow the limited kernel stack
name = malloc( PATH_MAX + 1 );
if( name == NULL )
return B_NO_MEMORY;
sprintf( name, "bus/scsi/%d/bus_raw", path_id );
{
pnp_node_attr attributes[] = {
{ PNP_DRIVER_DRIVER, B_STRING_TYPE, { string: SCSI_BUS_RAW_MODULE_NAME }},
{ PNP_DRIVER_TYPE, B_STRING_TYPE, { string: PNP_DEVFS_TYPE_NAME }},
{ PNP_DRIVER_FIXED_CONSUMER, B_STRING_TYPE, { string: PNP_DEVFS_MODULE_NAME }},
{ PNP_DRIVER_CONNECTION, B_STRING_TYPE, { string: "bus_raw" }},
{ PNP_DRIVER_DEVICE_IDENTIFIER, B_STRING_TYPE, { string: "bus_raw" }},
{ PNP_DEVFS_FILENAME, B_STRING_TYPE, { string: name }},
{}
};
pnp_node_handle node;
status_t res;
res = pnp->register_device( parent, attributes, NULL, &node );
free( name );
return res;
}
}
static status_t scsi_bus_raw_open( bus_raw_info *bus, uint32 flags,
bus_raw_info **handle_cookie )
{
*handle_cookie = bus;
return B_ERROR;
}
static status_t scsi_bus_raw_close( void *cookie )
{
return B_OK;
}
static status_t scsi_bus_raw_free( void *cookie )
{
return B_ERROR;
}
static status_t scsi_bus_raw_control( bus_raw_info *bus, uint32 op, void *data,
size_t len )
{
switch( op ) {
case B_SCSI_BUS_RAW_RESET:
return bus->interface->reset_bus( bus->cookie );
case B_SCSI_BUS_RAW_PATH_INQUIRY:
return bus->interface->path_inquiry( bus->cookie, data );
}
return B_ERROR;
}
static status_t scsi_bus_raw_read( void *cookie, off_t position, void *data,
size_t *numBytes )
{
*numBytes = 0;
return B_ERROR;
}
static status_t scsi_bus_raw_write( void *cookie, off_t position,
const void *data, size_t *numBytes )
{
*numBytes = 0;
return B_ERROR;
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
pnp_devfs_driver_info scsi_bus_raw_module = {
{
{
SCSI_BUS_RAW_MODULE_NAME,
0,
std_ops
},
scsi_bus_raw_init_device,
(status_t (*) (void *))scsi_bus_raw_uninit_device,
scsi_bus_raw_probe,
NULL,
NULL
},
(status_t (*) (void *, uint32, void **))scsi_bus_raw_open,
scsi_bus_raw_close,
scsi_bus_raw_free,
(status_t (*) (void *, uint32, void *, size_t))scsi_bus_raw_control,
scsi_bus_raw_read,
scsi_bus_raw_write
};
@@ -0,0 +1,346 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Bus node layer.
Whenever a controller driver publishes a new controller, a new SCSI bus
for public and internal use is registered in turn. After that, this
bus is told to rescan for devices. For each device, there is a
device registered for peripheral drivers. (see devices.c)
*/
#include "scsi_internal.h"
#include <string.h>
#include <malloc.h>
#include <blkman.h>
// bus service should hurry up a bit - good controllers don't take much time
// but are very happy to be busy; don't make it realtime though as we
// don't really need that but would risk to steel processing power of
// realtime-demanding threads
#define BUS_SERVICE_PRIORITY B_URGENT_DISPLAY_PRIORITY
/** implementation of service thread:
* it handles DPC and pending requests
*/
static void
scsi_do_service(scsi_bus_info *bus)
{
while (true) {
SHOW_FLOW0( 3, "" );
// handle DPCs first as they are more urgent
if (scsi_check_exec_dpc(bus))
continue;
if (scsi_check_exec_service(bus))
continue;
break;
}
}
/** main loop of service thread */
static int32
scsi_service_threadproc(void *arg)
{
scsi_bus_info *bus = (scsi_bus_info *)arg;
int32 processed_notifications = 0;
SHOW_FLOW(3, "bus = %p", bus);
while (true) {
// we handle multiple requests in scsi_do_service at once;
// to save time, we will acquire all notifications that are sent
// up to now at once.
// (Sadly, there is no "set semaphore to zero" function, so this
// is a poor-man emulation)
acquire_sem_etc(bus->start_service, processed_notifications + 1, 0, 0);
SHOW_FLOW0( 3, "1" );
if (bus->shutting_down)
break;
// get number of notifications _before_ servicing to make sure no new
// notifications are sent after do_service()
get_sem_count(bus->start_service, &processed_notifications);
scsi_do_service(bus);
}
return 0;
}
static scsi_bus_info *
scsi_create_bus(pnp_node_handle node, uint8 path_id)
{
scsi_bus_info *bus;
int res;
SHOW_FLOW0(3, "");
bus = (scsi_bus_info *)malloc(sizeof(*bus));
if (bus == NULL)
return NULL;
memset(bus, 0, sizeof(*bus));
bus->path_id = path_id;
bus->node = node;
bus->lock_count = bus->blocked[0] = bus->blocked[1] = 0;
bus->sim_overflow = 0;
bus->shutting_down = false;
bus->waiting_devices = NULL;
//bus->resubmitted_req = NULL;
bus->dpc_list = NULL;
if ((bus->scan_lun_lock = create_sem(1, "scsi_scan_lun_lock")) < 0) {
res = bus->scan_lun_lock;
goto err6;
}
bus->start_service = create_sem(0, "scsi_start_service");
if (bus->start_service < 0) {
res = bus->start_service;
goto err4;
}
res = INIT_BEN(&bus->mutex, "scsi_bus_mutex");
if (res < B_OK)
goto err3;
spinlock_irq_init(&bus->dpc_lock);
res = scsi_init_ccb_alloc(bus);
if (res < B_OK)
goto err2;
bus->service_thread = spawn_kernel_thread(scsi_service_threadproc,
"scsi_bus_service", BUS_SERVICE_PRIORITY, bus);
if (bus->service_thread < 0) {
res = bus->service_thread;
goto err1;
}
resume_thread(bus->service_thread);
return bus;
err1:
scsi_uninit_ccb_alloc(bus);
err2:
DELETE_BEN(&bus->mutex);
err3:
delete_sem(bus->start_service);
err4:
//scsi_destroy_device(bus->global_device);
//err5:
delete_sem(bus->scan_lun_lock);
err6:
free(bus);
return NULL;
}
static status_t
scsi_destroy_bus(scsi_bus_info *bus)
{
int32 retcode;
// noone is using this bus now, time to clean it up
bus->shutting_down = true;
release_sem(bus->start_service);
wait_for_thread(bus->service_thread, &retcode);
delete_sem(bus->start_service);
DELETE_BEN(&bus->mutex);
delete_sem(bus->scan_lun_lock);
scsi_uninit_ccb_alloc(bus);
return B_OK;
}
static status_t
scsi_init_bus(pnp_node_handle node, void *user_cookie, void **cookie)
{
uint8 path_id;
scsi_bus_info *bus;
status_t res;
SHOW_FLOW0( 3, "" );
if (pnp->get_attr_uint8(node, SCSI_BUS_PATH_ID_ITEM, &path_id, false) != B_OK)
return B_ERROR;
bus = scsi_create_bus(node, path_id);
if (bus == NULL)
return B_NO_MEMORY;
// extract controller/protocoll restrictions from node
if (pnp->get_attr_uint32(node, BLKDEV_DMA_ALIGNMENT, &bus->dma_params.alignment, true) != B_OK)
bus->dma_params.alignment = 0;
if (pnp->get_attr_uint32(node, BLKDEV_MAX_BLOCKS_ITEM, &bus->dma_params.max_blocks, true) != B_OK)
bus->dma_params.max_blocks = 0xffffffff;
if (pnp->get_attr_uint32(node, BLKDEV_DMA_BOUNDARY, &bus->dma_params.dma_boundary, true) != B_OK)
bus->dma_params.dma_boundary = ~0;
if (pnp->get_attr_uint32(node, BLKDEV_MAX_SG_BLOCK_SIZE, &bus->dma_params.max_sg_block_size, true) != B_OK)
bus->dma_params.max_sg_block_size = 0xffffffff;
if (pnp->get_attr_uint32(node, BLKDEV_MAX_SG_BLOCKS, &bus->dma_params.max_sg_blocks, true) != B_OK)
bus->dma_params.max_sg_blocks = ~0;
// do some sanity check:
// (see blkman.c)
bus->dma_params.max_sg_block_size &= ~bus->dma_params.alignment;
if (bus->dma_params.alignment > B_PAGE_SIZE) {
SHOW_ERROR(0, "Alignment (0x%x) must be less then B_PAGE_SIZE",
(int)bus->dma_params.alignment);
res = B_ERROR;
goto err;
}
if (bus->dma_params.max_sg_block_size < 1) {
SHOW_ERROR(0, "Max s/g block size (0x%x) is too small",
(int)bus->dma_params.max_sg_block_size);
res = B_ERROR;
goto err;
}
if (bus->dma_params.dma_boundary < B_PAGE_SIZE - 1) {
SHOW_ERROR(0, "DMA boundary (0x%x) must be at least B_PAGE_SIZE",
(int)bus->dma_params.dma_boundary);
res = B_ERROR;
goto err;
}
if (bus->dma_params.max_blocks < 1 || bus->dma_params.max_sg_blocks < 1) {
SHOW_ERROR(0, "Max blocks (%d) and max s/g blocks (%d) must be at least 1",
(int)bus->dma_params.max_blocks, (int)bus->dma_params.max_sg_blocks);
res = B_ERROR;
goto err;
}
res = pnp->load_driver(pnp->get_parent(node), bus,
(pnp_driver_info **)&bus->interface,
(void **)&bus->sim_cookie);
if (res != B_OK)
goto err;
// cache inquiry data
scsi_inquiry_path(bus, &bus->inquiry_data);
// get max. number of commands on bus
bus->left_slots = bus->inquiry_data.hba_queue_size;
SHOW_FLOW( 3, "Bus has %d slots", bus->left_slots );
*cookie = bus;
return B_OK;
err:
scsi_destroy_bus(bus);
return res;
}
static status_t
scsi_uninit_bus(scsi_bus_info *bus)
{
pnp->unload_driver(pnp->get_parent(bus->node));
scsi_destroy_bus(bus);
return B_OK;
}
uchar
scsi_inquiry_path(scsi_bus bus, scsi_path_inquiry *inquiry_data)
{
SHOW_FLOW(4, "path_id=%d", bus->path_id);
return bus->interface->path_inquiry(bus->sim_cookie, inquiry_data);
}
static uchar
scsi_reset_bus(scsi_bus_info *bus)
{
return bus->interface->reset_bus(bus->sim_cookie);
}
static status_t
scsi_bus_module_init(void)
{
SHOW_FLOW0(4, "");
return init_temp_sg();
}
static status_t
scsi_bus_module_uninit(void)
{
SHOW_INFO0(4, "");
uninit_temp_sg();
return B_OK;
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
return scsi_bus_module_init();
case B_MODULE_UNINIT:
return scsi_bus_module_uninit();
default:
return B_ERROR;
}
}
scsi_bus_interface scsi_bus_module = {
{
{
{
SCSI_BUS_MODULE_NAME,
0,
std_ops
},
scsi_init_bus,
(status_t (*) (void *)) scsi_uninit_bus,
NULL,
NULL
},
(status_t (*) (void *)) scsi_scan_bus
},
scsi_inquiry_path,
scsi_reset_bus,
};
+116
View File
@@ -0,0 +1,116 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
CCB manager
As allocation of ccb can be on the paging path we must use a
locked pool.
*/
#include "scsi_internal.h"
// ccb are relatively large, so don't make it too small to not waste memory
#define CCB_CHUNK_SIZE 16*1024
// maximum number of CCBs - probably, we want to make that editable
// it must be at least 1 for normal use and 1 for stand-by autosense request
#define CCB_NUM_MAX 128
scsi_ccb *
scsi_alloc_ccb(scsi_device_info *device)
{
scsi_ccb *ccb;
SHOW_FLOW0( 3, "" );
ccb = (scsi_ccb *)locked_pool->alloc(device->bus->ccb_pool);
ccb->state = SCSI_STATE_FINISHED;
ccb->device = device;
ccb->target_id = device->target_id;
ccb->target_lun = device->target_lun;
// reset some very important fields
// TODO: should we better omit that to find bugs easier?
ccb->sg_list = NULL;
ccb->sort = -1;
SHOW_FLOW(3, "path=%d", ccb->path_id);
return ccb;
}
void
scsi_free_ccb(scsi_ccb *ccb)
{
SHOW_FLOW0( 3, "" );
if (ccb->state != SCSI_STATE_FINISHED)
panic("Tried to free ccb that's still in use (state %d)\n", ccb->state);
ccb->state = SCSI_STATE_FREE;
locked_pool->free(ccb->bus->ccb_pool, ccb);
}
static status_t
ccb_low_alloc_hook(void *block, void *arg)
{
scsi_ccb *ccb = (scsi_ccb *)block;
scsi_bus_info *bus = (scsi_bus_info *)arg;
status_t res;
physical_entry map[2];
get_memory_map(ccb, sizeof(*ccb), map, 2);
ccb->bus = bus;
ccb->path_id = bus->path_id;
ccb->state = SCSI_STATE_FREE;
if ((res = ccb->completion_sem = create_sem(0, "ccb_sem")) < 0)
return res;
return B_OK;
}
static void
ccb_low_free_hook(void *block, void *arg)
{
scsi_ccb *ccb = (scsi_ccb *)block;
delete_sem(ccb->completion_sem);
}
status_t
scsi_init_ccb_alloc(scsi_bus_info *bus)
{
// initially, we want no CCB allocated as the path_id of
// the bus is not ready yet so the CCB cannot be initialized
// correctly
bus->ccb_pool = locked_pool->create(sizeof(scsi_ccb), sizeof(uint32) - 1, 0,
CCB_CHUNK_SIZE, CCB_NUM_MAX, 0, "scsi_ccb_pool", B_FULL_LOCK | B_CONTIGUOUS,
ccb_low_alloc_hook, ccb_low_free_hook, bus);
if (bus->ccb_pool == NULL)
return B_NO_MEMORY;
return B_OK;
}
void
scsi_uninit_ccb_alloc(scsi_bus_info *bus)
{
locked_pool->destroy(bus->ccb_pool);
}
@@ -0,0 +1,288 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Device scanner.
Scans SCSI busses for devices. Scanning is initiated by
a SCSI device node probe (see device_mgr.c)
*/
#include "scsi_internal.h"
#include <string.h>
#include <stdlib.h>
// send TUR
// result: true, if device answered
// false, if there is no device
static bool scsi_scan_send_tur( scsi_ccb *worker_req )
{
scsi_cmd_tur *cmd = (scsi_cmd_tur *)worker_req->cdb;
SHOW_FLOW0( 3, "" );
memset( cmd, 0, sizeof( *cmd ));
cmd->opcode = SCSI_OP_TUR;
worker_req->sg_list = NULL;
worker_req->data = NULL;
worker_req->data_len = 0;
worker_req->cdb_len = sizeof( *cmd );
worker_req->timeout = 0;
worker_req->sort = -1;
worker_req->flags = 0;
scsi_sync_io( worker_req );
SHOW_FLOW( 3, "status=%x", worker_req->subsys_status );
// as this command was only for syncing, we ignore almost all errors
switch( worker_req->subsys_status ) {
case SCSI_SEL_TIMEOUT:
// there seems to be no device around
return false;
default:
return true;
}
}
/** get inquiry data
* returns true on success
*/
static bool
scsi_scan_get_inquiry(scsi_ccb *worker_req, scsi_res_inquiry *new_inquiry_data)
{
scsi_cmd_inquiry *cmd = (scsi_cmd_inquiry *)worker_req->cdb;
scsi_device_info *device = worker_req->device;
SHOW_FLOW0( 3, "" );
// in case not whole structure gets transferred, we set remaining data to zero
memset(new_inquiry_data, 0, sizeof(*new_inquiry_data));
cmd->opcode = SCSI_OP_INQUIRY;
cmd->LUN = device->target_lun;
cmd->EVPD = 0;
cmd->page_code = 0;
cmd->allocation_length = sizeof( *new_inquiry_data );
worker_req->sg_list = NULL;
worker_req->data = (uchar *)new_inquiry_data;
worker_req->data_len = sizeof(*new_inquiry_data);
worker_req->cdb_len = 6;
worker_req->timeout = SCSI_STD_TIMEOUT;
worker_req->sort = -1;
worker_req->flags = SCSI_DIR_IN;
scsi_sync_io(worker_req);
switch (worker_req->subsys_status) {
case SCSI_REQ_CMP: {
char vendor[9], product[17], rev[5];
SHOW_FLOW0(3, "send successfully");
// we could check transmission length here, but as we reset
// missing bytes before, we get kind of valid data anyway (hopefully)
strlcpy(vendor, new_inquiry_data->vendor_ident, sizeof(vendor));
strlcpy(product, new_inquiry_data->product_ident, sizeof(product));
strlcpy(rev, new_inquiry_data->product_rev, sizeof(rev));
SHOW_INFO(3, "device type: %d, qualifier: %d, removable: %d, ANSI version: %d, response data format: %d\n"
"vendor: %s, product: %s, rev: %s",
new_inquiry_data->device_type, new_inquiry_data->device_qualifier,
new_inquiry_data->RMB, new_inquiry_data->ANSI_version,
new_inquiry_data->response_data_format,
vendor, product, rev);
SHOW_INFO(3, "additional_length: %d", new_inquiry_data->additional_length + 4);
// time to show standards the device conforms to;
// unfortunately, ATAPI CD-ROM drives tend to tell that they have
// only minimal info (36 bytes), but still they return (valid!) 96 bytes -
// bad luck
if (min(cmd->allocation_length, new_inquiry_data->additional_length + 4)
>= (int)offsetof(scsi_res_inquiry, version_descriptor[9])) {
int i, prev_standard;
prev_standard = -1;
for (i = 0; i < 8; ++i) {
int standard = (new_inquiry_data->version_descriptor[0].high << 8) |
new_inquiry_data->version_descriptor[0].low;
// omit standards reported twice
if( standard != prev_standard && standard != 0 )
SHOW_INFO( 3, "standard: %04x", standard );
prev_standard = standard;
}
}
//snooze( 1000000 );
/* {
unsigned int i;
for( i = 0; i < worker_req->data_len - worker_req->data_resid; ++i ) {
dprintf( "%2x ", *((char *)new_inquiry_data + i) );
}
dprintf( "\n" );
}*/
return true;
}
default:
return false;
}
}
status_t
scsi_scan_lun(scsi_bus_info *bus, uchar target_id, uchar target_lun)
{
scsi_ccb *worker_req;
scsi_res_inquiry new_inquiry_data;
status_t res;
scsi_device_info *device;
bool found;
//snooze( 1000000 );
SHOW_FLOW( 3, "%d:%d:%d", bus->path_id, target_id, target_lun );
res = scsi_force_get_device(bus, target_id, target_lun, &device);
if (res != B_OK)
goto err;
//SHOW_FLOW( 3, "temp_device: %d", (int)temp_device );
worker_req = scsi_alloc_ccb(device);
if (worker_req == NULL) {
// there is no out-of-mem code
res = B_NO_MEMORY;
goto err2;
}
SHOW_FLOW0( 3, "2" );
worker_req->flags = SCSI_DIR_IN;
// to give controller a chance to transfer speed negotiation, we
// send a TUR first; unfortunatily, some devices don't like TURing
// invalid luns apart from lun 0...
if (device->target_lun == 0) {
if (!scsi_scan_send_tur(worker_req)) {
// TBD: need better error code like "device not found"
res = B_NAME_NOT_FOUND;
goto err3;
}
}
// get inquiry data to be used as identification
// and to check whether there is a device at all
found = scsi_scan_get_inquiry(worker_req, &new_inquiry_data)
&& new_inquiry_data.device_qualifier == scsi_periph_qual_connected;
// get rid of temporary device - as soon as the device is
// registered, it can be loaded, and we don't want two data
// structures for one device (the temporary and the official one)
scsi_free_ccb(worker_req);
scsi_put_forced_device(device);
if (!found) {
// TBD: better error code, s.a.
return B_NAME_NOT_FOUND;
}
// !danger!
// if a new device is detected on the same connection, all connections
// to the old device are disabled;
// scenario: you plug in a device, scan the bus, replace the device and then
// open it; in this case, the connection seems to be to the old device, but really
// is to the new one; if you scan the bus now, the opened connection is disabled
// - bad luck -
// solution 1: scan device during each scsi_init_device
// disadvantage: it takes time and we had to submit commands during the load
// sequence, which could lead to deadlocks
// solution 2: device drivers must scan devices before first use
// disadvantage: it takes time and driver must perform a task that
// the bus_manager should really take care of
scsi_register_device(bus, target_id, target_lun, &new_inquiry_data);
return B_OK;
err3:
scsi_free_ccb(worker_req);
err2:
scsi_put_forced_device(device);
err:
return res;
}
status_t
scsi_scan_bus(scsi_bus_info *bus)
{
int initiator_id, target_id;
scsi_path_inquiry inquiry;
uchar res;
SHOW_FLOW0( 3, "" );
// get ID of initiator (i.e. controller)
res = scsi_inquiry_path( bus, &inquiry );
if( res != SCSI_REQ_CMP )
return B_ERROR;
initiator_id = inquiry.initiator_id;
SHOW_FLOW( 3, "initiator_id=%d", initiator_id );
// tell SIM to rescan bus (needed at least by IDE translator)
// as this function is optional for SIM, we ignore its result
bus->interface->scan_bus( bus->sim_cookie );
for( target_id = 0; target_id <= 1/*MAX_TARGET_ID*/; ++target_id ) {
int lun;
SHOW_FLOW( 3, "target: %d", target_id );
if( target_id == initiator_id )
continue;
// TODO: there are a lot of devices out there that go mad if you probe
// anything but LUN 0, so we should probably add a black-list
// or something
for( lun = 0; lun <= MAX_LUN_ID; ++lun ) {
status_t res;
SHOW_FLOW( 3, "lun: %d", lun );
res = scsi_scan_lun( bus, target_id, lun );
// if there is no device at lun 0, there's probably no device at all
if( lun == 0 && res != SCSI_REQ_CMP )
break;
}
}
SHOW_FLOW0( 3, "done" );
return B_OK;
}
@@ -0,0 +1,541 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Device node layer.
When a SCSI bus is registered, this layer scans for SCSI devices
and registers a node for each of them. Peripheral drivers are on
top of these nodes.
*/
#include "scsi_internal.h"
#include <blkman.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
static fast_log_event_type scsi_device_events[] = {
{ ev_scsi_requeue_request, "ev_scsi_requeue_request" },
{ ev_scsi_resubmit_request, "ev_scsi_resubmit_request" },
{ ev_scsi_submit_autosense, "ev_scsi_submit_autosense" },
{ ev_scsi_finish_autosense, "ev_scsi_finish_autosense" },
{ ev_scsi_device_queue_overflow, "ev_scsi_device_queue_overflow" },
{ ev_scsi_request_finished, "ev_scsi_request_finished" },
{ ev_scsi_async_io, "ev_scsi_async_io" },
{ ev_scsi_do_resend_request, "ev_scsi_do_resend_request" },
{ ev_copy_sg_data, "ev_copy_sg_data" },
{}
};
// free autosense request of device
static void scsi_free_autosense_request( scsi_device_info *device )
{
SHOW_FLOW0( 3, "" );
if( device->auto_sense_request != NULL ) {
scsi_free_ccb( device->auto_sense_request );
device->auto_sense_request = NULL;
}
if( device->auto_sense_area > 0 ) {
delete_area( device->auto_sense_area );
device->auto_sense_area = 0;
}
}
// free all data of device
static void scsi_free_device( scsi_device_info *device )
{
SHOW_FLOW0( 3, "" );
scsi_free_emulation_buffer( device );
scsi_free_autosense_request( device );
unregister_kernel_daemon( scsi_dma_buffer_daemon, device );
scsi_dma_buffer_free( &device->dma_buffer );
DELETE_BEN( &device->dma_buffer_lock );
delete_sem( device->dma_buffer_owner );
if( device->log != NULL )
fast_log->stop_log( device->log );
free( device );
}
// copy string src without trailing zero to dst and remove trailing spaces
// size of dst is dst_size, size of src is dst_size-1
static void beautify_string( char *dst, char *src, int dst_size )
{
int i;
memcpy( dst, src, dst_size - 1 );
for( i = dst_size - 2; i >= 0; --i ) {
if( dst[i] != ' ' )
break;
}
dst[i + 1] = 0;
}
/** register new device */
status_t
scsi_register_device(scsi_bus_info *bus, uchar target_id,
uchar target_lun, scsi_res_inquiry *inquiry_data)
{
bool is_atapi, manual_autosense;
uint32 orig_max_blocks, max_blocks;
SHOW_FLOW0( 3, "" );
// ask for restrictions
bus->interface->get_restrictions(bus->sim_cookie,
target_id, &is_atapi, &manual_autosense, &max_blocks);
// find maximum transfer blocks
// set default value to max (need something like ULONG_MAX here)
orig_max_blocks = ~0;
pnp->get_attr_uint32(bus->node, BLKDEV_MAX_BLOCKS_ITEM, &orig_max_blocks, true);
max_blocks = min(max_blocks, orig_max_blocks);
{
char vendor_ident[sizeof( inquiry_data->vendor_ident ) + 1];
char product_ident[sizeof( inquiry_data->product_ident ) + 1];
char product_rev[sizeof( inquiry_data->product_rev ) + 1];
pnp_node_attr attrs[] = {
// info about driver
{ PNP_DRIVER_DRIVER, B_STRING_TYPE, { string: SCSI_DEVICE_MODULE_NAME }},
{ PNP_DRIVER_TYPE, B_STRING_TYPE, { string: SCSI_DEVICE_TYPE_NAME }},
// connection
{ SCSI_DEVICE_TARGET_ID_ITEM, B_UINT8_TYPE, { ui8: target_id }},
{ SCSI_DEVICE_TARGET_LUN_ITEM, B_UINT8_TYPE, { ui8: target_lun }},
{ PNP_DRIVER_CONNECTION, B_STRING_TYPE, { string:
"target: %"SCSI_DEVICE_TARGET_ID_ITEM
"%, lun: %"SCSI_DEVICE_TARGET_LUN_ITEM"%" }},
// inquiry data (used for both identification and information)
{ SCSI_DEVICE_INQUIRY_ITEM, B_RAW_TYPE,
{ raw: { inquiry_data, sizeof( *inquiry_data ) }}},
{ PNP_DRIVER_DEVICE_IDENTIFIER, B_STRING_TYPE, { string:
"inquiry: %" SCSI_DEVICE_INQUIRY_ITEM "%" }},
// some more info for driver loading
{ SCSI_DEVICE_TYPE_ITEM, B_UINT8_TYPE, { ui8: inquiry_data->device_type }},
{ SCSI_DEVICE_VENDOR_ITEM, B_STRING_TYPE, { string: vendor_ident }},
{ SCSI_DEVICE_PRODUCT_ITEM, B_STRING_TYPE, { string: product_ident }},
{ SCSI_DEVICE_REVISION_ITEM, B_STRING_TYPE, { string: product_rev }},
// description of peripheral drivers
// ToDo: temporary hack to get things started!
{ PNP_DRIVER_FIXED_CONSUMER, B_STRING_TYPE, { string: "drivers/disk/scsi/scsi_dsk/scsi/device/v1" }},
{ PNP_DRIVER_DYNAMIC_CONSUMER, B_STRING_TYPE, { string:
SCSI_PERIPHERAL_DRIVERS_DIR "/"
"type: %" SCSI_DEVICE_TYPE_ITEM "%|"
", vendor: %" SCSI_DEVICE_VENDOR_ITEM "%|"
", product: %" SCSI_DEVICE_PRODUCT_ITEM "%|"
", revision: %" SCSI_DEVICE_REVISION_ITEM "%" }},
// extra restriction of maximum number of blocks per transfer
{ BLKDEV_MAX_BLOCKS_ITEM, B_UINT32_TYPE, { ui32: max_blocks }},
// atapi emulation
{ SCSI_DEVICE_IS_ATAPI_ITEM, B_UINT8_TYPE, { ui8: is_atapi }},
// manual autosense
{ SCSI_DEVICE_MANUAL_AUTOSENSE_ITEM, B_UINT8_TYPE, { ui8: manual_autosense }},
{ NULL }
};
pnp_node_handle node;
status_t res;
beautify_string(vendor_ident, inquiry_data->vendor_ident, sizeof(vendor_ident));
beautify_string(product_ident, inquiry_data->product_ident, sizeof(product_ident));
beautify_string(product_rev, inquiry_data->product_rev, sizeof(product_rev));
res = pnp->register_device(bus->node, attrs, NULL, &node);
if (res < 0)
return res;
}
return B_OK;
}
// create data structure for a device
static scsi_device_info *
scsi_create_device( pnp_node_handle node, scsi_bus_info *bus,
int target_id, int target_lun)
{
scsi_device_info *device;
SHOW_FLOW0( 3, "" );
device = (scsi_device_info *)malloc( sizeof( *device ));
if( device == NULL )
return NULL;
memset( device, 0, sizeof( *device ));
device->lock_count = device->blocked[0] = device->blocked[1] = 0;
device->sim_overflow = 0;
device->queued_reqs = NULL;
device->bus = bus;
device->target_id = target_id;
device->target_lun = target_lun;
device->valid = true;
device->node = node;
scsi_dma_buffer_init( &device->dma_buffer );
if( INIT_BEN( &device->dma_buffer_lock, "dma_buffer" ) < 0 )
goto err;
device->dma_buffer_owner = create_sem( 1, "dma_buffer" );
if( device->dma_buffer_owner < 0 )
goto err2;
register_kernel_daemon( scsi_dma_buffer_daemon, device, 5 * 10 );
return device;
err2:
DELETE_BEN( &device->dma_buffer_lock );
err:
free( device );
return NULL;
}
// prepare autosense request.
// this cannot be done on demand but during init as we may
// have run out of ccbs when we need it
static status_t scsi_create_autosense_request( scsi_device_info *device )
{
scsi_ccb *request;
char *buffer;
scsi_cmd_request_sense *cmd;
size_t total_size;
SHOW_FLOW0( 3, "" );
device->auto_sense_request = request = scsi_alloc_ccb( device );
if( device->auto_sense_request == NULL )
return B_NO_MEMORY;
total_size = SCSI_MAX_SENSE_SIZE + sizeof( physical_entry );
total_size = (total_size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
// allocate buffer for space sense data and S/G list
device->auto_sense_area = create_area( "auto_sense",
(void **)&buffer, B_ANY_KERNEL_ADDRESS, B_PAGE_SIZE, B_FULL_LOCK, 0 );
if( device->auto_sense_area < 0 )
goto err;
request->data = buffer;
request->data_len = SCSI_MAX_SENSE_SIZE;
request->sg_list = (physical_entry *)(buffer + SCSI_MAX_SENSE_SIZE);
request->sg_cnt = 1;
get_memory_map( buffer, SCSI_MAX_SENSE_SIZE,
(physical_entry *)request->sg_list, 1 );
// disable auto-autosense, just in case;
// make sure no other request overtakes sense request;
// buffer is/must be DMA safe as we cannot risk trouble with
// dynamically allocated DMA buffer
request->flags = SCSI_DIR_IN | SCSI_DIS_AUTOSENSE |
SCSI_ORDERED_QTAG | SCSI_DMA_SAFE;
cmd = (scsi_cmd_request_sense *)request->cdb;
request->cdb_len = sizeof( *cmd );
memset( cmd, 0, sizeof( *cmd ));
cmd->opcode = SCSI_OP_REQUEST_SENSE;
cmd->LUN = device->target_lun;
cmd->alloc_length = SCSI_MAX_SENSE_SIZE;
return B_OK;
err:
scsi_free_ccb( request );
return B_NO_MEMORY;
}
#define SET_BIT( field, bit ) field[(bit) >> 3] |= 1 << ((bit) & 7)
static status_t
scsi_init_device(pnp_node_handle node, void *user_cookie, void **cookie)
{
scsi_res_inquiry *inquiry_data = NULL;
uint8 target_id, target_lun, path_id;
scsi_bus_info *bus;
scsi_device_info *device;
status_t res;
pnp_driver_info *bus_interface;
size_t inquiry_data_len;
uint8 is_atapi, manual_autosense;
SHOW_FLOW0( 3, "" );
if( pnp->get_attr_uint8( node, SCSI_DEVICE_TARGET_ID_ITEM, &target_id, false ) != B_OK ||
pnp->get_attr_uint8( node, SCSI_DEVICE_TARGET_LUN_ITEM, &target_lun, false ) != B_OK ||
pnp->get_attr_uint8( node, SCSI_DEVICE_IS_ATAPI_ITEM, &is_atapi, false ) != B_OK ||
pnp->get_attr_uint8( node, SCSI_DEVICE_MANUAL_AUTOSENSE_ITEM, &manual_autosense, false ) != B_OK ||
pnp->get_attr_raw( node, SCSI_DEVICE_INQUIRY_ITEM,
(void **)&inquiry_data, &inquiry_data_len, false ) != B_OK ||
inquiry_data_len != sizeof( *inquiry_data ))
{
res = B_ERROR;
goto err3;
}
dprintf("**** b ****\n");
res = pnp->load_driver(pnp->get_parent(node), NULL, &bus_interface,
(void **)&bus);
if (res != B_OK)
goto err3;
dprintf("**** b! ****\n");
device = scsi_create_device(node, bus, target_id, target_lun);
if (device == NULL) {
res = B_NO_MEMORY;
goto err2;
}
// never mind if there is no path - it might be an emulated controller
path_id = -1;
pnp->get_attr_uint8(node, SCSI_BUS_PATH_ID_ITEM, &path_id, true);
sprintf( device->name, "scsi_device %u:%u:%u", path_id, target_id, target_lun );
device->log = fast_log->start_log( device->name, scsi_device_events );
if( device->log == NULL ) {
res = B_NO_MEMORY;
goto err;
}
device->inquiry_data = *inquiry_data;
// save restrictions
device->is_atapi = is_atapi;
device->manual_autosense = manual_autosense;
// size of device queue must be detected by trial and error, so
// we start with a really high number and see when the device chokes
device->total_slots = 4096;
// disable queuing if bus doesn't support it
if( (bus->inquiry_data.hba_inquiry & SCSI_PI_TAG_ABLE) == 0 )
device->total_slots = 1;
// if there is no autosense, disable queuing to make sure autosense is
// not overtaken by other requests
if( device->manual_autosense )
device->total_slots = 1;
device->left_slots = device->total_slots;
// get autosense request if required
if( device->manual_autosense ) {
if( scsi_create_autosense_request( device ) != B_OK ) {
res = B_NO_MEMORY;
goto err;
}
}
// if this is an ATAPI device, we need an emulation buffer
if( scsi_init_emulation_buffer( device, SCSI_ATAPI_BUFFER_SIZE ) != B_OK ) {
res = B_NO_MEMORY;
goto err;
}
memset( device->emulation_map, 0, sizeof( device->emulation_map ));
if( device->is_atapi ) {
SET_BIT( device->emulation_map, SCSI_OP_READ_6 );
SET_BIT( device->emulation_map, SCSI_OP_WRITE_6 );
SET_BIT( device->emulation_map, SCSI_OP_MODE_SENSE_6 );
SET_BIT( device->emulation_map, SCSI_OP_MODE_SELECT_6 );
SET_BIT( device->emulation_map, SCSI_OP_INQUIRY );
}
free( inquiry_data );
*cookie = device;
return B_OK;
err:
scsi_free_device( device );
err2:
pnp->unload_driver( pnp->get_parent( node ));
err3:
if( inquiry_data != NULL )
free( inquiry_data );
return res;
}
static status_t
scsi_uninit_device(scsi_device_info *device)
{
pnp_node_handle node = device->node;
SHOW_FLOW0( 3, "" );
scsi_free_device( device );
// must unload parent at last as scsi_free_device access it
pnp->unload_driver( pnp->get_parent( node ));
return B_OK;
}
static void
scsi_device_removed(pnp_node_handle node, scsi_device_info *device)
{
SHOW_FLOW0( 3, "" );
if( device == NULL )
return;
// this must be atomic as no lock is used
device->valid = false;
}
// get device info; create a temporary one if it's not registered
// (used during detection)
// on success, scan_lun_lock of bus is hold
status_t scsi_force_get_device( scsi_bus_info *bus,
uchar target_id, uchar target_lun, scsi_device_info **res_device )
{
pnp_node_attr attrs[] = {
{ PNP_DRIVER_TYPE, B_STRING_TYPE, { string: SCSI_DEVICE_TYPE_NAME }},
{ SCSI_DEVICE_TARGET_ID_ITEM, B_UINT8_TYPE, { ui8: target_id }},
{ SCSI_DEVICE_TARGET_LUN_ITEM, B_UINT8_TYPE, { ui8: target_lun }},
{ NULL }
};
pnp_node_handle node;
status_t res;
pnp_driver_info *driver_interface;
scsi_device device;
SHOW_FLOW0( 3, "" );
// very important: only one can use a forced device to avoid double detection
acquire_sem( bus->scan_lun_lock );
// check whether device registered already
node = pnp->find_device( bus->node, attrs );
SHOW_FLOW( 3, "%p", node );
if( node != NULL ) {
// there is one - get it
res = pnp->load_driver( node, NULL, &driver_interface,
(void **)&device );
} else {
// device doesn't exist yet - create a temporary one
device = scsi_create_device( NULL, bus, target_id, target_lun );
if( device == NULL )
res = B_NO_MEMORY;
else
res = B_OK;
}
*res_device = device;
if( res != B_OK )
release_sem( bus->scan_lun_lock );
return res;
}
// cleanup device received from scsi_force_get_device
// on return, scan_lun_lock of bus is released
void scsi_put_forced_device( scsi_device_info *device )
{
scsi_bus_info *bus = device->bus;
SHOW_FLOW0( 3, "" );
if( device->node != NULL )
// device is registered
pnp->unload_driver( device->node );
else
// device is temporary
scsi_free_device( device );
release_sem( bus->scan_lun_lock );
}
static uchar scsi_reset_device( scsi_device_info *device )
{
SHOW_FLOW0( 3, "" );
if( device->node == NULL )
return SCSI_DEV_NOT_THERE;
return device->bus->interface->reset_device(
device->bus->sim_cookie, device->target_id, device->target_lun );
}
static status_t std_ops( int32 op, ... )
{
switch( op ) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
scsi_device_interface scsi_device_module =
{
{
{
SCSI_DEVICE_MODULE_NAME,
0,
std_ops
},
scsi_init_device,
(status_t (*)( void * )) scsi_uninit_device,
NULL,
(void (*)( pnp_node_handle, void * )) scsi_device_removed
},
scsi_alloc_ccb,
scsi_free_ccb,
scsi_async_io,
scsi_sync_io,
scsi_abort,
scsi_reset_device,
scsi_term_io
};
@@ -0,0 +1,88 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Macros for double linked lists
*/
#ifndef _DL_LIST_H
#define _DL_LIST_H
#define REMOVE_DL_LIST( item, head, prefix ) \
do { \
if( item->prefix##prev ) \
item->prefix##prev->prefix##next = item->prefix##next; \
else \
head = item->prefix##next; \
\
if( item->prefix##next ) \
item->prefix##next->prefix##prev = item->prefix##prev; \
} while( 0 )
#define ADD_DL_LIST_HEAD( item, head, prefix ) \
do { \
item->prefix##next = head; \
item->prefix##prev = NULL; \
\
if( (head) ) \
(head)->prefix##prev = item; \
\
(head) = item; \
} while( 0 )
#define REMOVE_CDL_LIST( item, head, prefix ) \
do { \
item->prefix##next->prefix##prev = item->prefix##prev; \
item->prefix##prev->prefix##next = item->prefix##next; \
\
if( item == (head) ) { \
if( item->prefix##next != item ) \
(head) = item->prefix##next; \
else \
(head) = NULL; \
} \
} while( 0 )
#define ADD_CDL_LIST_TAIL( item, type, head, prefix ) \
do { \
type *old_head = head; \
\
if( old_head ) { \
type *first, *last; \
\
first = old_head; \
last = first->prefix##prev; \
\
item->prefix##next = first; \
item->prefix##prev = last; \
first->prefix##prev = item; \
last->prefix##next = item; \
} else { \
head = item; \
item->prefix##next = item->prefix##prev = item; \
} \
} while( 0 )
#define ADD_CDL_LIST_HEAD( item, type, head, prefix ) \
do { \
type *old_head = head; \
\
head = item; \
if( old_head ) { \
type *first, *last; \
\
first = old_head; \
last = first->prefix##prev; \
\
item->prefix##next = first; \
item->prefix##prev = last; \
first->prefix##prev = item; \
last->prefix##next = item; \
} else { \
item->prefix##next = item->prefix##prev = item; \
} \
} while( 0 )
#endif
@@ -0,0 +1,519 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
DMA buffer handling.
If the peripheral driver hasn't made sure that the data of a request
is DMA safe, we check that and copy data to a buffer if needed.
The buffer is enlarged on demand and destroyed after a time-out
by a daemon. Obviously, it's a good idea to avoid all this, therefore
blkman takes care of that for read/write requests.
To be able to copy data back after the request was finished, we need a
S/G list to the original data as the copying is done in a different
thread/process context (namely the service thread).
Currently, there is only one buffer per device; in the future,
we may support multiple buffers, especially if we want to support
more then 4 GB memory, which leads to trouble with 32-bit PCI cards.
*/
#include "scsi_internal.h"
#include "KernelExport_ext.h"
#include <string.h>
// check whether S/G list of request is supported DMA controller
static bool
is_sg_list_dma_safe(scsi_ccb *request)
{
scsi_bus_info *bus = request->bus;
const physical_entry *sg_list = request->sg_list;
uint32 sg_count = request->sg_cnt;
uint32 dma_boundary = bus->dma_params.dma_boundary;
uint32 alignment = bus->dma_params.alignment;
uint32 max_sg_block_size = bus->dma_params.max_sg_block_size;
uint32 cur_idx;
// not too many S/G list entries
if( sg_count > bus->dma_params.max_sg_blocks ) {
SHOW_FLOW0( 0, "S/G-list too long" );
return false;
}
// if there are no further restrictions - be happy
if (dma_boundary == ~0UL && alignment == 0 && max_sg_block_size == 0)
return true;
// argh - controller is a bit picky, so make sure he likes us
for( cur_idx = sg_count; cur_idx >= 1; --cur_idx, ++sg_list ) {
addr_t max_len;
// calculate space upto next dma boundary crossing and
// verify that it isn't crossed
max_len = (dma_boundary + 1) -
((addr_t)sg_list->address & dma_boundary);
if( max_len < sg_list->size ) {
SHOW_FLOW( 0, "S/G-entry crosses DMA boundary @0x%x",
(int)sg_list->address + (int)max_len);
return false;
}
// check both begin and end of entry for alignment
if( ((addr_t)sg_list->address & alignment) != 0 ) {
SHOW_FLOW( 0, "S/G-entry has bad alignment @0x%x",
(int)sg_list->address );
return false;
}
if( (((addr_t)sg_list->address + sg_list->size) & alignment) != 0 ) {
SHOW_FLOW( 0, "end of S/G-entry has bad alignment @0x%x",
(int)sg_list->address + (int)sg_list->size );
return false;
}
// verify entry size
if( sg_list->size > max_sg_block_size ) {
SHOW_FLOW( 0, "S/G-entry is too long (%d/%d bytes)",
(int)sg_list->size, (int)max_sg_block_size );
return false;
}
}
return true;
}
/** copy data from/to DMA buffer */
static bool
scsi_copy_dma_buffer(scsi_ccb *request, uint32 size, bool to_buffer)
{
dma_buffer *buffer = request->dma_buffer;
const physical_entry *sg_list = buffer->sg_list_orig;
uint32 num_vecs = buffer->sg_cnt_orig;
char *buffer_data = buffer->address;
SHOW_FLOW(0, "to_buffer=%d, %d bytes", to_buffer, (int)size);
// survive even if controller returned invalid data size
size = min(size, request->data_len);
// we have to use S/G list to original data; the DMA buffer
// was allocated in kernel and is thus visible even if the thread
// was changed
for( ; size > 0 && num_vecs > 0; ++sg_list, --num_vecs ) {
size_t bytes;
void *virt_addr;
bytes = min( size, sg_list->size );
if( map_mainmemory( (addr_t)sg_list->address, &virt_addr ) != B_OK )
return false;
if( to_buffer )
memcpy( buffer_data, virt_addr, bytes );
else
memcpy( virt_addr, buffer_data, bytes );
unmap_mainmemory( virt_addr );
buffer_data += bytes;
}
return true;
}
// get log2
static int log2( uint32 x )
{
int y;
for( y = 31; y >= 0; --y )
if( x == ((uint32)1 << y) )
break;
return y;
}
static void scsi_free_dma_buffer( dma_buffer *buffer )
{
if( buffer->area > 0 ) {
SHOW_FLOW0( 0, "Destroying buffer" );
delete_area( buffer->area );
buffer->area = 0;
buffer->size = 0;
}
if( buffer->sg_list_area > 0 ) {
delete_area( buffer->sg_list_area );
buffer->sg_list_area = 0;
}
}
/** allocate dma buffer for given device, deleting old one
* size - buffer size in bytes
*/
static bool
scsi_alloc_dma_buffer(dma_buffer *buffer, dma_params *dma_params, uint32 size)
{
size_t sg_list_size, sg_list_entries;
// free old buffer first
scsi_free_dma_buffer( buffer );
// just in case alignment is redicuously huge
size = (size + dma_params->alignment) & ~dma_params->alignment;
size = (size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
// calculate worst case number of S/G entries, i.e. if they are non-continuous;
// there is a controller limit and a limit by our own S/G manager to check
if( size / B_PAGE_SIZE > dma_params->max_sg_blocks ||
size / B_PAGE_SIZE > MAX_TEMP_SG_FRAGMENTS )
{
uint32 boundary = dma_params->dma_boundary;
uchar *dma_buffer_address_unaligned;
// alright - a contiguous buffer is required to keep S/G table short
SHOW_INFO( 0, "need to setup contiguous DMA buffer of size %d",
(int)size );
// verify that we don't get problems with dma boundary
if (boundary != ~0UL) {
if( size > boundary + 1 ) {
SHOW_ERROR( 2, "data is longer then maximum DMA transfer len (%d/%d bytes)",
(int)size, (int)boundary + 1 );
return false;
}
// round up to next power of two and allocate a buffer double the
// needed size so we can cut out an area that doesn't cross
// dma boundary
size = (1 << log2( size )) * 2;
}
buffer->area = create_area( "DMA buffer",
(void **)&dma_buffer_address_unaligned,
B_ANY_KERNEL_ADDRESS, size,
B_FULL_LOCK | B_CONTIGUOUS, 0 );
if( buffer->area < 0 ) {
SHOW_ERROR( 2, "Cannot create contignous DMA buffer of %d bytes",
(int)size );
return false;
}
if (boundary != ~0UL) {
uchar *next_boundary;
// boundary case: cut out piece aligned on "size"
buffer->address = (uchar *)(
((addr_t)dma_buffer_address_unaligned + size - 1) & ~(size - 1));
// determine how many bytes are available until next DMA boundary
next_boundary = (uchar *)(((addr_t)buffer->address + boundary - 1) &
~(boundary - 1));
// adjust next boundary if outside allocated area
if( next_boundary > dma_buffer_address_unaligned + size )
next_boundary = dma_buffer_address_unaligned + size;
buffer->size = next_boundary - buffer->address;
} else {
// non-boundary case: use buffer directly
buffer->address = dma_buffer_address_unaligned;
buffer->size = size;
}
} else {
// we can live with a fragmented buffer - very nice
buffer->area = create_area( "DMA buffer",
(void **)&buffer->address,
B_ANY_KERNEL_ADDRESS, size,
B_FULL_LOCK, 0 );
if( buffer->area < 0 ) {
SHOW_ERROR( 2, "Cannot create DMA buffer of %d bytes",
(int)size );
return false;
}
buffer->size = size;
}
// create S/G list
// worst case is one entry per page, and size is page-aligned
sg_list_size = buffer->size / B_PAGE_SIZE * sizeof( physical_entry );
// create_area has page-granularity
sg_list_size = (sg_list_size + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
buffer->sg_list_area = create_area( "DMA buffer S/G table",
(void **)&buffer->sg_list,
B_ANY_KERNEL_ADDRESS, sg_list_size,
B_FULL_LOCK, 0 );
if( buffer->sg_list_area < 0 ) {
SHOW_ERROR( 2, "Cannot craete DMA buffer S/G list of %d bytes",
(int)sg_list_size );
delete_area( buffer->area );
buffer->area = 0;
return false;
}
sg_list_entries = sg_list_size / sizeof( physical_entry );
{
size_t mapped_len;
status_t res;
iovec vec = {
buffer->address,
buffer->size
};
res = get_iovec_memory_map(
&vec, 1, 0, buffer->size,
buffer->sg_list, sg_list_entries, &buffer->sg_cnt,
&mapped_len );
if( res != B_OK || mapped_len != buffer->size ) {
SHOW_ERROR( 0, "Error creating S/G list for DMA buffer (%s; wanted %d, got %d bytes)",
strerror( res ), (int)mapped_len, (int)buffer->size );
}
}
return true;
}
static void scsi_free_dma_buffer_sg_orig( dma_buffer *buffer )
{
if( buffer->sg_orig > 0 ) {
delete_area( buffer->sg_orig );
buffer->sg_orig = 0;
buffer->sg_cnt_max_orig = 0;
}
}
// allocate S/G list to original data
static bool scsi_alloc_dma_buffer_sg_orig( dma_buffer *buffer, int size )
{
// free old list first
scsi_free_dma_buffer_sg_orig( buffer );
size = (size * sizeof( physical_entry ) + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
buffer->sg_orig = create_area( "S/G to original data",
(void **)&buffer->sg_list_orig,
B_ANY_KERNEL_ADDRESS, size,
B_NO_LOCK, 0 );
if( buffer->sg_orig < 0 ) {
SHOW_ERROR( 2, "Cannot S/G list buffer to original data of %d bytes",
(int)size );
return false;
}
buffer->sg_cnt_max_orig = size / sizeof( physical_entry );
SHOW_INFO( 3, "Got up to %d S/G entries to original data",
(int)buffer->sg_cnt_max_orig );
return true;
}
// helper: dump S/G table
static void dump_sg_table( const physical_entry *sg_list,
uint32 sg_list_count )
{
uint32 cur_idx;
SHOW_FLOW( 0, "count=%d", (int)sg_list_count );
for( cur_idx = sg_list_count; cur_idx >= 1; --cur_idx, ++sg_list ) {
SHOW_FLOW( 0, "addr=%x, size=%d", (int)sg_list->address,
(int)sg_list->size );
}
}
// compose S/G list to original data of request
static bool scsi_dma_buffer_compose_sg_orig( dma_buffer *buffer, scsi_ccb *request )
{
// enlarge buffer is required
if( buffer->sg_cnt_max_orig < request->sg_cnt ) {
if( !scsi_alloc_dma_buffer_sg_orig( buffer, request->sg_cnt ))
return false;
}
SHOW_FLOW0( 0, "copy S/G list" );
memcpy( buffer->sg_list_orig, request->sg_list,
request->sg_cnt * sizeof( physical_entry ));
buffer->sg_cnt_orig = request->sg_cnt;
return true;
}
// init DMA buffer and copy data to it if required
// note: S/G list of request must already be setup
bool scsi_get_dma_buffer( scsi_ccb *request )
{
scsi_device_info *device = request->device;
dma_buffer *buffer;
request->buffered = false;
// perhaps we have luck and no buffering is needed
if( is_sg_list_dma_safe( request ))
return true;
SHOW_FLOW0( 0, "Buffer is not DMA safe" );
dump_sg_table( request->sg_list, request->sg_cnt );
// only one buffer at a time
acquire_sem( device->dma_buffer_owner );
// make sure, clean-up daemon doesn't bother us
ACQUIRE_BEN( &device->dma_buffer_lock );
// there is only one buffer, so no further management
buffer = &device->dma_buffer;
buffer->inuse = true;
RELEASE_BEN( &device->dma_buffer_lock );
// memorize buffer for cleanup
request->dma_buffer = buffer;
// enlarge buffer if too small
if( buffer->size < request->data_len ) {
if( !scsi_alloc_dma_buffer( buffer, &device->bus->dma_params,
request->data_len ))
{
goto err;
}
}
// create S/G to original data (necessary for copying from-buffer on end
// of request, but also used during copying to-buffer in a second because
// of lazyness)
scsi_dma_buffer_compose_sg_orig( &device->dma_buffer, request );
// copy data to buffer
if( (request->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT ) {
if( !scsi_copy_dma_buffer( request, request->data_len, true ))
goto err;
}
// replace data address, so noone notices that a buffer is used
buffer->orig_data = request->data;
buffer->orig_sg_list = request->sg_list;
buffer->orig_sg_cnt = request->sg_cnt;
request->data = buffer->address;
request->sg_list = buffer->sg_list;
request->sg_cnt = buffer->sg_cnt;
SHOW_INFO( 0, "bytes: %d", (int)request->data_len );
SHOW_INFO0( 3, "we can start now" );
request->buffered = true;
return true;
err:
SHOW_INFO0( 3, "error setting up DMA buffer" );
ACQUIRE_BEN( &device->dma_buffer_lock );
// some of this is probably not required, but I'm paranoid
buffer->inuse = false;
RELEASE_BEN( &device->dma_buffer_lock );
release_sem( device->dma_buffer_owner );
return false;
}
// copy data back and release DMA buffer;
// you must have called cleanup_tmp_sg before
void scsi_release_dma_buffer( scsi_ccb *request )
{
scsi_device_info *device = request->device;
dma_buffer *buffer = request->dma_buffer;
SHOW_FLOW( 0, "Buffering finished, %x, %x",
request->subsys_status & SCSI_SUBSYS_STATUS_MASK,
(int)(request->flags & SCSI_DIR_MASK) );
// copy data from buffer if required and if operation succeeded
if( (request->subsys_status & SCSI_SUBSYS_STATUS_MASK) == SCSI_REQ_CMP &&
(request->flags & SCSI_DIR_MASK) == SCSI_DIR_IN )
scsi_copy_dma_buffer( request, request->data_len - request->data_resid, false );
// restore request
request->data = buffer->orig_data;
request->sg_list = buffer->orig_sg_list;
request->sg_cnt = buffer->orig_sg_cnt;
// free buffer
ACQUIRE_BEN( &device->dma_buffer_lock );
buffer->last_use = system_time();
buffer->inuse = false;
RELEASE_BEN( &device->dma_buffer_lock );
release_sem( device->dma_buffer_owner );
request->buffered = false;
}
// dameon that deletes DMA buffer if not used for some time
void scsi_dma_buffer_daemon( void *dev, int counter )
{
scsi_device_info *device = dev;
dma_buffer *buffer;
ACQUIRE_BEN( &device->dma_buffer_lock );
buffer = &device->dma_buffer;
if( !buffer->inuse &&
buffer->last_use - system_time() > SCSI_DMA_BUFFER_CLEANUP_DELAY )
{
scsi_free_dma_buffer( buffer );
scsi_free_dma_buffer_sg_orig( buffer );
}
RELEASE_BEN( &device->dma_buffer_lock );
}
void scsi_dma_buffer_free( dma_buffer *buffer )
{
scsi_free_dma_buffer( buffer );
scsi_free_dma_buffer_sg_orig( buffer );
}
void scsi_dma_buffer_init( dma_buffer *buffer )
{
buffer->area = 0;
buffer->size = 0;
buffer->sg_orig = 0;
buffer->sg_cnt_max_orig = 0;
}
+105
View File
@@ -0,0 +1,105 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
DPC handling (deferred procedure calls).
DPC are executed by the service thread of the bus
(see busses.c).
*/
#include "scsi_internal.h"
#include <string.h>
#include <stdlib.h>
status_t
scsi_alloc_dpc(scsi_dpc_info **dpc)
{
SHOW_FLOW0( 3, "" );
*dpc = (scsi_dpc_info *)malloc( sizeof( **dpc ));
if( *dpc == NULL )
return B_NO_MEMORY;
memset( *dpc, 0, sizeof( **dpc ));
return B_OK;
}
status_t
scsi_free_dpc(scsi_dpc_info *dpc)
{
SHOW_FLOW0( 3, "" );
if( dpc != NULL )
free( dpc );
return B_OK;
}
status_t
scsi_schedule_dpc(scsi_bus_info *bus, scsi_dpc_info *dpc, /*int flags,*/
void (*func)( void *arg ), void *arg)
{
SHOW_FLOW( 3, "bus=%p, dpc=%p", bus, dpc );
acquire_spinlock_irq( &bus->dpc_lock );
dpc->func = func;
dpc->arg = arg;
if( !dpc->registered ) {
dpc->registered = true;
dpc->next = bus->dpc_list;
bus->dpc_list = dpc;
} else
SHOW_FLOW0( 3, "already registered - ignored" );
release_spinlock_irq( &bus->dpc_lock );
// this is called in IRQ context, so scheduler is not allowed
release_sem_etc( bus->start_service, 1, B_DO_NOT_RESCHEDULE );
return B_OK;
}
/** execute pending DPCs */
bool
scsi_check_exec_dpc(scsi_bus_info *bus)
{
SHOW_FLOW( 3, "bus=%p, dpc_list=%p", bus, bus->dpc_list );
acquire_spinlock_irq(&bus->dpc_lock);
if (bus->dpc_list) {
scsi_dpc_info *dpc;
void (*dpc_func)( void * );
void *dpc_arg;
dpc = bus->dpc_list;
bus->dpc_list = dpc->next;
dpc_func = dpc->func;
dpc_arg = dpc->arg;
dpc->registered = false;
release_spinlock_irq(&bus->dpc_lock);
dpc_func(dpc_arg);
return true;
}
release_spinlock_irq(&bus->dpc_lock);
return false;
}
@@ -0,0 +1,581 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Emulation of SCSI commands that a device cannot handle.
Some SCSI devices don't support all SCSI commands, especially
those connected via ATAPI, USB or FireWire. These commands are
emulated here.
*/
#include "scsi_internal.h"
#include "KernelExport_ext.h"
#include <string.h>
// move some function to end of file to avoid inlining
static void set_sense(scsi_ccb *request, int sense_key, int sense_asc);
static bool copy_sg_data(scsi_ccb *request, uint offset, uint allocation_length,
void *buffer, int size, bool to_buffer);
static void get_emulation_buffer(scsi_ccb *request);
static void replace_request_data(scsi_ccb *request);
static void release_emulation_buffer(scsi_ccb *request);
static void restore_request_data(scsi_ccb *request);
/** free emulation buffer */
void
scsi_free_emulation_buffer(scsi_device_info *device)
{
if (device->buffer_area)
delete_area(device->buffer_area);
device->buffer_area = 0;
device->buffer = NULL;
device->buffer_sg_list = NULL;
device->buffer_size = 0;
if (device->buffer_sem > 0)
delete_sem(device->buffer_sem);
}
/** setup buffer used to emulate unsupported SCSI commands
* buffer_size must be power of two
*/
status_t
scsi_init_emulation_buffer(scsi_device_info *device, size_t buffer_size)
{
physical_entry map[1];
void *unaligned_phys, *aligned_phys, *aligned_addr, *unaligned_addr;
size_t total_size;
SHOW_FLOW0(3, "");
device->buffer_sem = create_sem(1, "SCSI emulation buffer");
if (device->buffer_sem < 0) {
SHOW_ERROR(1, "cannot create DMA buffer semaphore (%s)", strerror(device->buffer_sem));
return device->buffer_sem;
}
// we append S/G list to buffer as it must be locked as well
total_size = (buffer_size + sizeof(physical_entry) + B_PAGE_SIZE - 1) & ~(B_PAGE_SIZE - 1);
// to satisfy alignment, we must allocate a buffer twice its required size
// and find the properly aligned part of it, ouch!
device->buffer_area = create_area("ATAPI buffer", &unaligned_addr, B_ANY_KERNEL_ADDRESS,
2 * total_size, B_CONTIGUOUS, 0);
if (device->buffer_area < 0) {
SHOW_ERROR( 1, "cannot create DMA buffer (%s)", strerror(device->buffer_area));
delete_sem(device->buffer_sem);
return device->buffer_area;
}
get_memory_map(unaligned_addr, B_PAGE_SIZE, map, 1);
// get aligned part
unaligned_phys = (char *)map[0].address;
aligned_phys = (char *)(((int32)unaligned_phys + buffer_size - 1) & ~(buffer_size - 1));
aligned_addr = unaligned_addr + (aligned_phys - unaligned_phys);
SHOW_FLOW(3, "unaligned_phys=%p, aligned_phys=%p, unaligned_addr=%p, aligned_addr=%p",
unaligned_phys, aligned_phys, unaligned_addr, aligned_addr);
device->buffer = aligned_addr;
device->buffer_size = buffer_size;
// s/g list is directly after buffer
device->buffer_sg_list = aligned_addr + buffer_size;
device->buffer_sg_list[0].address = aligned_phys;
device->buffer_sg_list[0].size = buffer_size;
device->buffer_sg_cnt = 1;
return B_OK;
}
/** some ATAPI devices don't like 6 byte read/write commands, so
* we translate them to their 10 byte partners;
* USB devices usually don't like 10 bytes either
*/
static bool
scsi_read_write_6(scsi_ccb *request)
{
scsi_cmd_rw_6 *cmd = (scsi_cmd_rw_6 *)request->orig_cdb;
scsi_cmd_rw_10 *cdb = (scsi_cmd_rw_10 *)request->cdb;
SHOW_FLOW0(3, "patching READ/WRITE(6) to READ/WRITE(10)");
request->cdb_len = sizeof(*cdb);
memset(cdb, 0, sizeof(*cdb));
cdb->opcode = cmd->opcode + (SCSI_OP_READ_10 - SCSI_OP_READ_6);
cdb->LUN = cmd->LUN;
cdb->low_LBA = cmd->low_LBA;
cdb->mid_LBA = cmd->mid_LBA;
cdb->high_LBA = cmd->high_LBA;
cdb->low_length = cmd->length;
cdb->high_length = cmd->length == 0;
cdb->control = cmd->control;
return true;
}
/** all ATAPI devices don't like 6 byte MODE SENSE, so we translate
* that to 10 byte MODE SENSE
*/
static bool
scsi_start_mode_sense_6(scsi_ccb *request)
{
scsi_cmd_mode_sense_6 *cmd = (scsi_cmd_mode_sense_6 *)request->orig_cdb;
scsi_cmd_mode_sense_10 *cdb = (scsi_cmd_mode_sense_10 *)request->cdb;
size_t allocation_length;
SHOW_FLOW0(3, "patching MODE SENSE(6) to MODE SENSE(10)");
request->cdb_len = sizeof(*cdb);
memset(cdb, 0, sizeof(*cdb));
cdb->opcode = SCSI_OP_MODE_SENSE_10;
cdb->DBD = cmd->DBD;
cdb->LUN = cmd->LUN;
cdb->page_code = cmd->page_code;
cdb->PC = cmd->PC;
allocation_length = cmd->allocation_length
- sizeof(scsi_cmd_mode_sense_6) + sizeof(scsi_cmd_mode_sense_10);
cdb->high_allocation_length = allocation_length >> 8;
cdb->low_allocation_length = allocation_length & 0xff;
SHOW_FLOW(3, "allocation_length=%ld", allocation_length);
cdb->control = cmd->control;
// data header of 10 byte version is longer, so use internal buffer
// and copy it back once the command is finished
get_emulation_buffer(request);
replace_request_data(request);
// restrict data buffer len to length specified in cdb
request->data_len = allocation_length;
return true;
}
/** all ATAPI devices don't like 6 byte MODE SELECT, so we translate
* that to 10 byte MODE SELECT
*/
static bool
scsi_start_mode_select_6(scsi_ccb *request)
{
scsi_device_info *device = request->device;
scsi_cmd_mode_select_6 *cmd = (scsi_cmd_mode_select_6 *)request->orig_cdb;
scsi_cmd_mode_select_10 *cdb = (scsi_cmd_mode_select_10 *)request->cdb;
scsi_mode_param_header_6 header_6;
scsi_mode_param_header_10 *header_10 = (scsi_mode_param_header_10 *)device->buffer;
size_t param_list_length_6, param_list_length_10;
SHOW_FLOW0(3, "patching MODE SELECT(6) to MODE SELECT(10)");
// calculate new data buffer size
param_list_length_6 = cmd->param_list_length;
param_list_length_10 = param_list_length_6
- sizeof(scsi_mode_param_header_6) + sizeof(scsi_mode_param_header_10);
// we need to replace data header, thus use internal buffer
get_emulation_buffer(request);
// make sure our buffer is large enough
if (param_list_length_10 > device->buffer_size)
goto err;
// construct new cdb
request->cdb_len = sizeof(*cdb);
memset(cdb, 0, sizeof(*cdb));
cdb->opcode = SCSI_OP_MODE_SELECT_10;
cdb->SP = cmd->SP;
cdb->PF = cmd->PF;
cdb->LUN = cmd->LUN;
cdb->high_param_list_length = param_list_length_10 >> 8;
cdb->low_param_list_length = param_list_length_10 & 0xff;
SHOW_FLOW(3, "param_list_length=%ld", param_list_length_6);
cdb->control = cmd->control;
// copy and adapt header
if (!copy_sg_data(request, 0, param_list_length_6, &header_6, sizeof(header_6), true))
goto err;
memset(header_10, 0, sizeof(*header_10));
// mode_data_len is reserved for MODE SELECT
header_10->medium_type = header_6.medium_type;
header_10->dev_spec_parameter = header_6.dev_spec_parameter;
header_10->low_block_desc_len = header_6.block_desc_len;
// append actual mode select data
if (!copy_sg_data( request, sizeof(header_6), param_list_length_6, header_10 + 1,
param_list_length_10 - sizeof(*header_10), true))
goto err;
replace_request_data(request);
// restrict buffer size to the one specified in cdb
request->data_len = param_list_length_10;
return true;
err:
release_emulation_buffer(request);
set_sense(request, SCSIS_KEY_ILLEGAL_REQUEST, SCSIS_ASC_INV_PARAM_LIST_FIELD);
return false;
}
/** emulation procedure at start of command
* returns false if command is to be finished without further execution
*/
bool
scsi_start_emulation(scsi_ccb *request)
{
//snooze( 1000000 );
SHOW_FLOW(3, "command=%x", request->cdb[0]);
memcpy(request->orig_cdb, request->cdb, SCSI_MAX_CDB_SIZE);
request->orig_cdb_len = request->cdb_len;
switch (request->orig_cdb[0]) {
case SCSI_OP_READ_6:
case SCSI_OP_WRITE_6:
return scsi_read_write_6(request);
case SCSI_OP_MODE_SENSE_6:
return scsi_start_mode_sense_6(request);
case SCSI_OP_MODE_SELECT_6:
return scsi_start_mode_select_6(request);
default:
}
return true;
}
/** back-translate MODE SENSE 10 to MODE SENSE 6 */
static void
scsi_finish_mode_sense_10_6(scsi_ccb *request)
{
scsi_device_info *device = request->device;
scsi_mode_param_header_6 header_6;
scsi_mode_param_header_10 *header_10 = (scsi_mode_param_header_10 *)device->buffer;
int transfer_size_6, transfer_size_10;
if (request->subsys_status != SCSI_REQ_CMP || request->device_status != SCSI_STATUS_GOOD) {
// on error, do nothing
release_emulation_buffer(request);
return;
}
// check how much data we got from device and thus will copy into
// request data
transfer_size_10 = request->data_len - request->data_resid;
transfer_size_6 = transfer_size_10
- sizeof(scsi_mode_param_header_10 *) + sizeof(scsi_mode_param_header_6 *);
SHOW_FLOW(0, "fixing MODE SENSE(6) (%d bytes)", transfer_size_6);
restore_request_data(request);
// adapt header
// convert total length
// (+1 is there because mode_data_len in 10 byte header ignores first
// two bytes, whereas in the 6 byte header it ignores only one byte)
header_6.mode_data_len = header_10->low_mode_data_len
- sizeof(scsi_mode_param_header_10) + sizeof(scsi_mode_param_header_6)
+ 1;
header_6.medium_type = header_10->medium_type;
header_6.dev_spec_parameter = header_10->dev_spec_parameter;
header_6.block_desc_len = header_10->low_block_desc_len;
// copy adapted header
copy_sg_data(request, 0, transfer_size_6, &header_6, sizeof(header_6), false);
// copy remaining data
copy_sg_data(request, sizeof(header_6), transfer_size_6,
header_10 + 1, transfer_size_10 - sizeof(*header_10), false);
request->data_resid = request->data_len - transfer_size_6;
release_emulation_buffer(request);
}
/** back-translate MODE SELECT 10 to MODE SELECT 6 */
static void
scsi_finish_mode_select_10_6(scsi_ccb *request)
{
SHOW_FLOW0(3, "fixing MODE SELECT(6)");
// adjust transmission length as we've used the longer
// mode select 10 data header
request->data_resid += sizeof(scsi_mode_param_header_6)
- sizeof(scsi_mode_param_header_10);
restore_request_data(request);
release_emulation_buffer(request);
}
/** fix inquiry data; some ATAPI devices return wrong version */
static void
scsi_finish_inquiry(scsi_ccb *request)
{
int transfer_size;
scsi_res_inquiry res;
SHOW_FLOW0(3, "fixing INQUIRY");
if (request->subsys_status != SCSI_REQ_CMP || request->device_status != SCSI_STATUS_GOOD)
return;
transfer_size = request->data_len - request->data_resid;
copy_sg_data(request, 0, transfer_size, &res, sizeof(res), true);
SHOW_FLOW(3, "ANSI version: %d, response data format: %d",
res.ANSI_version, res.response_data_format);
res.ANSI_version = 2;
res.response_data_format = 2;
copy_sg_data(request, 0, transfer_size, &res, sizeof(res), false);
}
/** adjust result of emulated request */
void
scsi_finish_emulation(scsi_ccb *request)
{
SHOW_FLOW0(3, "");
switch ((((int)request->cdb[0]) << 8) | request->orig_cdb[0]) {
case (SCSI_OP_MODE_SENSE_10 << 8) | SCSI_OP_MODE_SENSE_6:
scsi_finish_mode_sense_10_6(request);
break;
case (SCSI_OP_MODE_SELECT_10 << 8) | SCSI_OP_MODE_SELECT_6:
scsi_finish_mode_select_10_6(request);
break;
case (SCSI_OP_INQUIRY << 8) | SCSI_OP_INQUIRY:
scsi_finish_inquiry(request);
break;
}
// restore cdb
memcpy(request->cdb, request->orig_cdb, SCSI_MAX_CDB_SIZE);
request->cdb_len = request->orig_cdb_len;
}
/** set sense of request */
static void
set_sense(scsi_ccb *request, int sense_key, int sense_asc)
{
scsi_sense *sense = (scsi_sense *)request->sense;
SHOW_FLOW( 3, "sense_key=%d, sense_asc=%d", sense_key, sense_asc );
request->subsys_status = SCSI_REQ_CMP;
request->device_status = SCSI_STATUS_CHECK_CONDITION;
// TBD: we can only handle requests with autosense
// without autosense, we had to manage virtual sense data,
// which is probably not worth the hazzle
if ((request->flags & SCSI_DIS_AUTOSENSE) != 0)
return;
memset(sense, 0, sizeof(*sense));
sense->error_code = SCSIS_CURR_ERROR;
sense->sense_key = sense_key;
sense->add_sense_length = sizeof(*sense) - 7;
sense->asc = (sense_asc >> 8) & 0xff;
sense->ascq = sense_asc;
sense->sense_key_spec.raw.SKSV = 0; // no additional info
request->subsys_status |= SCSI_AUTOSNS_VALID;
}
/** copy data between request data and buffer
* request - request to copy data from/to
* offset - offset of data in request
* allocation_length- limit of request's data buffer according to CDB
* buffer - data to copy data from/to
* size - number of bytes to copy
* to_buffer - true: copy from request to buffer
* false: copy from buffer to request
* return: true, if data of request was large enough
*/
static bool
copy_sg_data(scsi_ccb *request, uint offset, uint allocation_length,
void *buffer, int size, bool to_buffer)
{
const physical_entry *sg_list = request->sg_list;
int sg_cnt = request->sg_cnt;
int req_size;
SHOW_FLOW(3, "offset=%u, req_size_limit=%d, size=%d, sg_list=%p, sg_cnt=%d, %s buffer",
offset, allocation_length, size, sg_list, sg_cnt, to_buffer ? "to" : "from");
// skip unused S/G entries
while (sg_cnt > 0 && offset >= sg_list->size) {
offset -= sg_list->size;
++sg_list;
--sg_cnt;
}
if (sg_cnt == 0)
return 0;
// remaining bytes we are allowed to copy from/to request
req_size = min(allocation_length, request->data_len) - offset;
// copy one S/G entry at a time
for (; size > 0 && req_size > 0 && sg_cnt > 0; ++sg_list, --sg_cnt) {
size_t bytes;
void *virt_addr;
bytes = min(size, req_size);
bytes = min(bytes, sg_list->size);
if (map_mainmemory((addr_t)sg_list->address, &virt_addr) != B_OK)
return false;
SHOW_FLOW(0, "buffer=%p, virt_addr=%p, bytes=%d, to_buffer=%d",
buffer, virt_addr + offset, (int)bytes, to_buffer);
if (to_buffer)
memcpy(buffer, virt_addr + offset, bytes);
else
memcpy(virt_addr + offset, buffer, bytes);
#if 0
{
int i;
for (i = 0; i < bytes; ++i) {
char byte;
if (to_buffer)
byte = ((char *)virt_addr)[offset + i];
else
byte = ((char *)buffer)[i];
FAST_LOG1( request->device->log, ev_copy_sg_data, byte );
}
}
#endif
unmap_mainmemory(virt_addr);
(char *)buffer += bytes;
size -= bytes;
offset = 0;
}
return size == 0;
}
/** allocate emulation buffer */
static void
get_emulation_buffer(scsi_ccb *request)
{
scsi_device_info *device = request->device;
SHOW_FLOW0( 3, "" );
acquire_sem(device->buffer_sem);
request->orig_sg_list = request->sg_list;
request->orig_sg_cnt = request->sg_cnt;
request->orig_data_len = request->data_len;
request->sg_list = device->buffer_sg_list;
request->sg_cnt = device->buffer_sg_cnt;
request->data_len = device->buffer_size;
}
/** replace request data with emulation buffer, saving original pointer;
* you must have called get_emulation_buffer() first
*/
static void
replace_request_data(scsi_ccb *request)
{
scsi_device_info *device = request->device;
SHOW_FLOW0( 3, "" );
request->orig_sg_list = request->sg_list;
request->orig_sg_cnt = request->sg_cnt;
request->orig_data_len = request->data_len;
request->sg_list = device->buffer_sg_list;
request->sg_cnt = device->buffer_sg_cnt;
request->data_len = device->buffer_size;
}
/** release emulation buffer */
static void
release_emulation_buffer(scsi_ccb *request)
{
SHOW_FLOW0( 3, "" );
release_sem(request->device->buffer_sem);
}
/** restore original request data pointers */
static void
restore_request_data(scsi_ccb *request)
{
SHOW_FLOW0( 3, "" );
request->sg_list = request->orig_sg_list;
request->sg_cnt = request->orig_sg_cnt;
request->data_len = request->orig_data_len;
}
@@ -0,0 +1,393 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Queuing of SCSI request
Some words about queuing, i.e. making sure that no request
gets stuck in some queue forever
As long as the SIM accepts new reqs, XPT doesn't take care of
stuck requests - thus, peripheral drivers should use synced reqs
once in a while (or even always) to force the HBA to finish
previously submitted reqs. This is also important for non-queuing
HBAs as in this case the queuing is done by the SCSI bus manager
which uses elevator sort.
Requests can be blocked by SIM or by the SCSI bus manager on a per-device
or per-bus basis. There are three possible reasons:
1. the hardware queue is too small
- detected by bus manager automatically, or
- detected by SIM which calls "requeue" for affected request
2. SIM blocks bus/device explictely
3. bus manager blocks bus/device explictly (currently used for
ordered requests)
4. the device has queued requests or the bus has waiting devices resp.
The first condition is automatically cleared when a request has
been completed, but can be reset manually by the SIM. In the second and
third cases, the SIM/bus manager must explictely unblock the bus/device.
For easier testing, the lock_count is the sum of the overflow bit, the
SIM lock count and the bus manager lock count. The blocking test is in
scsi_check_enqueue_request() in scsi_io.c.
If a bus/device is blocked or has waiting requests/devices, new requests
are added to a device-specific request queue in an elevator-sort style,
taking care that no ordered requests are overtaken. Exceptions are
requeued request and autosense-requests, which are added first. Each bus
has a queue of non-blocked devices that have waiting requests. If a
device is to be added to this queue, it is always appended at tail
to ensure fair processing.
*/
#include "scsi_internal.h"
#include "queuing.h"
// add request to device queue, using evelvator sort
static void scsi_insert_new_request( scsi_device_info *device,
scsi_ccb *new_request )
{
scsi_ccb *first, *last, *before, *next;
SHOW_FLOW( 3, "inserting new_request=%p, pos=%Ld", new_request, new_request->sort );
first = device->queued_reqs;
if( first == NULL ) {
SHOW_FLOW0( 1, "no other queued request" );
scsi_add_req_queue_first( new_request );
return;
}
SHOW_FLOW( 3, "first=%p, pos=%Ld, last_pos=%Ld",
first, first->sort, device->last_sort );
// don't let syncs bypass others
if( new_request->ordered ) {
SHOW_FLOW0( 1, "adding synced request to tail" );
scsi_add_req_queue_last( new_request );
return;
}
if( new_request->sort < 0 ) {
SHOW_FLOW0( 1, "adding unsortable request to tail" );
scsi_add_req_queue_last( new_request );
return;
}
// to reduce head seek time, we have three goals:
// - sort request accendingly according to head position
// as as disks use to read ahead and not backwards
// - ordered accesses can neither get overtaken by or overtake other requests
//
// in general, we only have block position, so head, track or
// whatever specific optimizations can only be done by the disks
// firmware;
//
// thus, sorting is done ascendingly with only a few exceptions:
// - if position of request to be inserted is between current
// (i.e. last) position and position of first queued request,
// insert it as first queue entry; i.e. we get descending order
// - if position of first queued request is before current position
// and position of new req is before first queued request, add it
// as first queue entry; i.e. the new and the (previously) first
// request are sorted monotically increasing
//
// the first exception should help if the queue is short (not sure
// wether this actually hurts if we have a long queue), the
// second one maximizes monotonic ranges
last = first->prev;
if( (device->last_sort <= new_request->sort &&
new_request->sort <= first->sort) ||
(first->sort < device->last_sort &&
new_request->sort <= first->sort) )
{
// these are the exceptions described above
SHOW_FLOW0( 3, "trying to insert req at head of device req queue" );
// we should have a new first request, make sure we don't bypass syncs
for( before = last; !before->ordered; ) {
before = before->prev;
if( before == last )
break;
}
if( !before->ordered ) {
SHOW_FLOW0( 1, "scheduled request in front of all other reqs of device" );
scsi_add_req_queue_first( new_request );
return;
} else
SHOW_FLOW0( 1, "req would bypass ordered request" );
}
// the insertion sort loop ignores ordered flag of last request,
// so check that here
if( last->ordered ) {
SHOW_FLOW0( 1, "last entry is ordered, adding new request as last" );
scsi_add_req_queue_last( new_request );
return;
}
SHOW_FLOW0( 3, "performing insertion sort" );
// insertion sort starts with last entry to avoid unnecessary overtaking
for( before = last->prev, next = last;
before != last && !before->ordered;
next = before, before = before->prev )
{
if( before->sort <= new_request->sort && new_request->sort <= next->sort )
break;
}
// if we bumped into ordered request, append new request at tail
if( before->ordered ) {
SHOW_FLOW0( 1, "overtaking ordered request in sorting - adding as last" );
scsi_add_req_queue_last( new_request );
return;
}
SHOW_FLOW( 1, "inserting after %p (pos=%Ld) and before %p (pos=%Ld)",
before, before->sort, next, next->sort );
// if we haven't found a proper position, we automatically insert
// new request as last because request list is circular;
// don't check whether we added request as first as this is impossible
new_request->next = next;
new_request->prev = before;
next->prev = new_request;
before->next = new_request;
}
// add request to end of device queue and device to bus queue
// used for normal requests
void scsi_add_queued_request( scsi_ccb *request )
{
scsi_device_info *device = request->device;
SHOW_FLOW0( 3, "" );
request->state = SCSI_STATE_QUEUED;
scsi_insert_new_request( device, request );
/* {
scsi_ccb *tmp = device->queued_reqs;
dprintf( "pos=%Ld, to_insert=%Ld; ", device->last_sort,
request->sort );
do {
dprintf( "%Ld, %s", tmp->sort, tmp->next == device->queued_reqs ? "\n" : "" );
tmp = tmp->next;
} while( tmp != device->queued_reqs );
}*/
// if device is not deliberately locked, mark it as waiting
if( device->lock_count == 0 ) {
SHOW_FLOW0( 3, "mark device as waiting" );
scsi_add_device_queue_last( device );
}
}
// add request to begin of device queue and device to bus queue
// used only for auto-sense request
void scsi_add_queued_request_first( scsi_ccb *request )
{
scsi_device_info *device = request->device;
SHOW_FLOW0( 3, "" );
request->state = SCSI_STATE_QUEUED;
scsi_add_req_queue_first( request );
// if device is not deliberately locked, mark it as waiting
if( device->lock_count == 0 ) {
SHOW_FLOW0( 3, "mark device as waiting" );
// make device first in bus queue to execute sense ASAP
scsi_add_device_queue_first( device );
}
}
// remove requests from queue, removing device from queue if idle
void scsi_remove_queued_request( scsi_ccb *request )
{
scsi_remove_req_queue( request );
if( request->device->queued_reqs == NULL )
scsi_remove_device_queue( request->device );
}
// explictely unblock bus
static void scsi_unblock_bus_int( scsi_bus_info *bus, bool by_SIM )
{
bool was_servicable, start_retry;
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
was_servicable = scsi_can_service_bus( bus );
scsi_unblock_bus_noresume( bus, by_SIM );
start_retry = !was_servicable && scsi_can_service_bus( bus );
RELEASE_BEN( &bus->mutex );
if( start_retry )
release_sem( bus->start_service );
}
// explicitely unblock bus as requested by SIM
void scsi_unblock_bus( scsi_bus_info *bus )
{
scsi_unblock_bus_int( bus, true );
}
// explicitly unblock device
static void scsi_unblock_device_int( scsi_device_info *device, bool by_SIM )
{
scsi_bus_info *bus = device->bus;
bool was_servicable, start_retry;
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
was_servicable = scsi_can_service_bus( bus );
scsi_unblock_device_noresume( device, by_SIM );
// add to bus queue if not locked explicitly anymore and requests are waiting
if( device->lock_count == 0 && device->queued_reqs != NULL )
scsi_add_device_queue_last( device );
start_retry = !was_servicable && scsi_can_service_bus( bus );
RELEASE_BEN( &bus->mutex );
if( start_retry )
release_sem( bus->start_service );
}
// explicitely unblock device as requested by SIM
void scsi_unblock_device( scsi_device_info *device )
{
return scsi_unblock_device_int( device, true );
}
// SIM signals that it can handle further requests for this bus
void scsi_cont_send_bus( scsi_bus_info *bus )
{
bool was_servicable, start_retry;
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
was_servicable = scsi_can_service_bus( bus );
scsi_clear_bus_overflow( bus );
start_retry = !was_servicable && scsi_can_service_bus( bus );
RELEASE_BEN( &bus->mutex );
if( start_retry )
release_sem_etc( bus->start_service, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
}
// SIM signals that it can handle further requests for this device
void scsi_cont_send_device( scsi_device_info *device )
{
scsi_bus_info *bus = device->bus;
bool was_servicable, start_retry;
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
was_servicable = scsi_can_service_bus( bus );
if( device->sim_overflow ) {
device->sim_overflow = false;
--device->lock_count;
// add to bus queue if not locked explicitly anymore and requests are waiting
if( device->lock_count == 0 && device->queued_reqs != NULL )
scsi_add_device_queue_last( device );
}
// no device overflow implicits no bus overflow
// (and if not, we'll detect that on next submit)
scsi_clear_bus_overflow( bus );
start_retry = !was_servicable && scsi_can_service_bus( bus );
RELEASE_BEN( &bus->mutex );
// tell service thread if there are pending requests which
// weren't pending before
if( start_retry )
release_sem_etc( bus->start_service, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
}
// explicitly block bus
static void scsi_block_bus_int( scsi_bus_info *bus, bool by_SIM )
{
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
scsi_block_bus_nolock( bus, by_SIM );
RELEASE_BEN( &bus->mutex );
}
// explicitly block bus as requested by SIM
void scsi_block_bus( scsi_bus_info *bus )
{
return scsi_block_bus_int( bus, true );
}
// explicitly block device
static void scsi_block_device_int( scsi_device_info *device, bool by_SIM )
{
scsi_bus_info *bus = device->bus;
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
scsi_block_device_nolock( device, by_SIM );
// remove device from bus queue as it cannot be processed anymore
scsi_remove_device_queue( device );
RELEASE_BEN( &bus->mutex );
}
// explicitly block device as requested by SIM
void scsi_block_device( scsi_device_info *device )
{
return scsi_block_device_int( device, true );
}
@@ -0,0 +1,189 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Handling of bus/device blocking. Inline functions defined
here don't wake service thread if required and don't lock bus, so
everyone using them must take care of that himself.
*/
#ifndef __BLOCKING_H__
#define __BLOCKING_H__
#include <dl_list.h>
void scsi_add_queued_request( scsi_ccb *request );
void scsi_add_queued_request_first( scsi_ccb *request );
void scsi_remove_queued_request( scsi_ccb *request );
static inline void scsi_add_req_queue_first( scsi_ccb *request )
{
scsi_device_info *device = request->device;
SHOW_FLOW( 3, "request=%p", request );
ADD_CDL_LIST_HEAD( request, scsi_ccb, device->queued_reqs, );
}
static inline void scsi_add_req_queue_last( scsi_ccb *request )
{
scsi_device_info *device = request->device;
SHOW_FLOW( 3, "request=%p", request );
ADD_CDL_LIST_TAIL( request, scsi_ccb, device->queued_reqs, );
}
static inline void scsi_remove_req_queue( scsi_ccb *request )
{
scsi_device_info *device = request->device;
SHOW_FLOW( 3, "request=%p", request );
REMOVE_CDL_LIST( request, device->queued_reqs, );
}
// ignored, if device is already queued
static inline void scsi_add_device_queue_first( scsi_device_info *device )
{
SHOW_FLOW0( 3, "" );
if( DEVICE_IN_WAIT_QUEUE( device ))
return;
SHOW_FLOW0( 3, "was not in wait queue - adding" );
ADD_CDL_LIST_HEAD( device, scsi_device_info, device->bus->waiting_devices, waiting_ );
}
// ignored, if device is already queued
static inline void scsi_add_device_queue_last( scsi_device_info *device )
{
SHOW_FLOW0( 3, "" );
if( DEVICE_IN_WAIT_QUEUE( device ))
return;
SHOW_FLOW0( 3, "was not in wait queue - adding" );
ADD_CDL_LIST_TAIL( device, scsi_device_info, device->bus->waiting_devices, waiting_ );
}
// ignored, if device is not in queue
static inline void scsi_remove_device_queue( scsi_device_info *device )
{
SHOW_FLOW0( 3, "" );
if( !DEVICE_IN_WAIT_QUEUE( device ))
return;
SHOW_FLOW0( 3, "was in wait queue - removing from it" );
REMOVE_CDL_LIST( device, device->bus->waiting_devices, waiting_ );
// reset next link so we can see that it's not in device queue
device->waiting_next = NULL;
}
// set overflow bit of device; this will not remove device from bus queue!
// (multiple calls are ignored gracefully)
static inline void scsi_set_device_overflow( scsi_device_info *device )
{
device->lock_count += device->sim_overflow ^ 1;
device->sim_overflow = 1;
}
// set overflow bit of bus
// (multiple calls are ignored gracefully)
static inline void scsi_set_bus_overflow( scsi_bus_info *bus )
{
bus->lock_count += bus->sim_overflow ^ 1;
bus->sim_overflow = 1;
}
// clear overflow bit of device; this will not add device to bus queue!
// (multiple calls are ignored gracefully)
static inline void scsi_clear_device_overflow( scsi_device_info *device )
{
device->lock_count -= device->sim_overflow;
device->sim_overflow = 0;
}
// clear overflow bit of bus
// (multiple calls are ignored gracefully)
static inline void scsi_clear_bus_overflow( scsi_bus_info *bus )
{
bus->lock_count -= bus->sim_overflow;
bus->sim_overflow = 0;
}
// check whether bus has some pending requests it can process now
static inline bool scsi_can_service_bus( scsi_bus_info *bus )
{
// bus must not be blocked and requests pending
return (bus->lock_count == 0) & (bus->waiting_devices != NULL);
}
// unblock bus
// lock must be hold; service thread is not informed
static inline void scsi_unblock_bus_noresume( scsi_bus_info *bus, bool by_SIM )
{
if( bus->blocked[by_SIM] > 0 ) {
--bus->blocked[by_SIM];
--bus->lock_count;
} else {
panic( "Tried to unblock bus %d which wasn't blocked",
bus->path_id );
}
}
// unblock device
// lock must be hold; device is not added to queue and service thread is not informed
static inline void scsi_unblock_device_noresume( scsi_device_info *device, bool by_SIM )
{
if( device->blocked[by_SIM] > 0 ) {
--device->blocked[by_SIM];
--device->lock_count;
} else {
panic( "Tried to unblock device %d/%d/%d which wasn't blocked",
device->bus->path_id, device->target_id, device->target_lun );
}
}
// block bus
// lock must be hold
static inline void scsi_block_bus_nolock( scsi_bus_info *bus, bool by_SIM )
{
++bus->blocked[by_SIM];
++bus->lock_count;
}
// block device
// lock must be hold
static inline void scsi_block_device_nolock( scsi_device_info *device, bool by_SIM )
{
++device->blocked[by_SIM];
++device->lock_count;
// remove device from bus queue as it cannot be processed anymore
scsi_remove_device_queue( device );
}
void scsi_block_bus( scsi_bus_info *bus );
void scsi_unblock_bus( scsi_bus_info *bus );
void scsi_block_device( scsi_device_info *device );
void scsi_unblock_device( scsi_device_info *device );
void scsi_cont_send_bus( scsi_bus_info *bus );
void scsi_cont_send_device( scsi_device_info *device );
#endif
@@ -0,0 +1,177 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Creates temporary Scatter/Gather table if the peripheral
driver has provided a simple pointer only.
*/
#include "scsi_internal.h"
#include "KernelExport_ext.h"
#include <string.h>
#include <iovec.h>
locked_pool_cookie temp_sg_pool;
static bool
fill_temp_sg(scsi_ccb *ccb)
{
status_t res;
scsi_bus_info *bus = ccb->bus;
uint32 dma_boundary = bus->dma_params.dma_boundary;
uint32 max_sg_block_size = bus->dma_params.max_sg_block_size;
uint32 max_sg_blocks = min(bus->dma_params.max_sg_blocks, MAX_TEMP_SG_FRAGMENTS);
iovec vec = {
ccb->data,
ccb->data_len
};
size_t num_entries;
size_t mapped_len;
uint32 cur_idx;
physical_entry *temp_sg = (physical_entry *)ccb->sg_list;
res = get_iovec_memory_map(&vec, 1, 0, ccb->data_len, temp_sg, max_sg_blocks,
&num_entries, &mapped_len);
if (res != B_OK) {
SHOW_ERROR(2, "cannot create temporary S/G list for IO request (%s)", strerror(res));
return false;
}
if (mapped_len != ccb->data_len)
goto too_complex;
if (dma_boundary != ~0UL || ccb->data_len > max_sg_block_size) {
// S/G list may not be controller-compatible:
// we have to split offending entries
SHOW_FLOW(3, "Checking violation of dma boundary 0x%x and entry size 0x%x",
(int)dma_boundary, (int)max_sg_block_size);
for (cur_idx = 0; cur_idx < num_entries; ++cur_idx) {
addr_t max_len;
// calculate space upto next dma boundary crossing
max_len = (dma_boundary + 1) -
((addr_t)temp_sg[cur_idx].address & dma_boundary);
// restrict size per sg item
max_len = min(max_len, max_sg_block_size);
SHOW_FLOW(4, "addr=%p, size=%x, max_len=%x, idx=%d, num=%d",
temp_sg[cur_idx].address, (int)temp_sg[cur_idx].size,
(int)max_len, (int)cur_idx, (int)num_entries);
if (max_len < temp_sg[cur_idx].size) {
// split sg block
if (++num_entries > max_sg_blocks)
goto too_complex;
memmove(&temp_sg[cur_idx + 1], &temp_sg[cur_idx],
(num_entries - 1 - cur_idx) * sizeof(physical_entry));
temp_sg[cur_idx].size = max_len;
temp_sg[cur_idx + 1].address = (void *)((addr_t)temp_sg[cur_idx + 1].address + max_len);
temp_sg[cur_idx + 1].size -= max_len;
}
}
}
ccb->sg_cnt = num_entries;
return true;
too_complex:
SHOW_ERROR( 2, "S/G list to complex for IO request (max %d entries)",
MAX_TEMP_SG_FRAGMENTS );
return false;
}
/** create temporary SG for request */
bool
create_temp_sg(scsi_ccb *ccb)
{
physical_entry *temp_sg;
status_t res;
SHOW_FLOW(3, "ccb=%p, data=%p, data_len=%lu", ccb, ccb->data, ccb->data_len);
ccb->sg_list = temp_sg = locked_pool->alloc(temp_sg_pool);
res = lock_memory(ccb->data, ccb->data_len, B_DMA_IO
| ((ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN ? B_READ_DEVICE : 0));
if (res != B_OK) {
SHOW_ERROR(2, "cannot lock memory for IO request (%s)", strerror(res));
goto err;
}
if (fill_temp_sg(ccb))
// this is the success path
return true;
unlock_memory(ccb->data, ccb->data_len, B_DMA_IO
| ((ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN ? B_READ_DEVICE : 0));
err:
locked_pool->free(temp_sg_pool, temp_sg);
return false;
}
// cleanup temporary SG list
void uninit_temp_sg( void )
{
locked_pool->destroy( temp_sg_pool );
}
// destroy SG list buffer
void cleanup_tmp_sg( scsi_ccb *ccb )
{
status_t res;
SHOW_FLOW( 3, "ccb=%p, data=%p, data_len=%d",
ccb, ccb->data, (int)ccb->data_len );
res = unlock_memory( ccb->data, ccb->data_len, B_DMA_IO |
( (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN ? B_READ_DEVICE : 0 ));
if (res != B_OK) {
SHOW_FLOW0(3, "Cannot unlock previously locked memory!");
panic("Cannot unlock previously locked memory!");
}
locked_pool->free(temp_sg_pool, (physical_entry *)ccb->sg_list);
// restore previous state
ccb->sg_list = NULL;
}
/** create SG list buffer */
int
init_temp_sg(void)
{
temp_sg_pool = locked_pool->create(
MAX_TEMP_SG_FRAGMENTS * sizeof(physical_entry),
sizeof(physical_entry) - 1, 0,
B_PAGE_SIZE, MAX_TEMP_SG_LISTS, 1,
"scsi_temp_sg_pool", B_FULL_LOCK | B_CONTIGUOUS,
NULL, NULL, NULL);
if (temp_sg_pool == NULL)
return B_NO_MEMORY;
return B_OK;
}
@@ -0,0 +1,39 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Main file
*/
#include "scsi_internal.h"
#include <pnp_devfs.h>
locked_pool_interface *locked_pool;
device_manager_info *pnp;
fast_log_info *fast_log;
// Link to SCSI bus.
// SCSI device driver must have SCSI bus loaded, but it calls its functions
// directly instead via official interface, so this pointer is never read.
static module_info *scsi_bus_dummy;
module_dependency module_dependencies[] = {
{ DEVICE_MANAGER_MODULE_NAME, (module_info **)&pnp },
{ LOCKED_POOL_MODULE_NAME, (module_info **)&locked_pool },
{ FAST_LOG_MODULE_NAME, (module_info **)&fast_log },
{ SCSI_BUS_MODULE_NAME, &scsi_bus_dummy },
{}
};
_EXPORT
module_info *modules[] = {
(module_info *)&scsi_for_sim_module,
(module_info *)&scsi_bus_module,
(module_info *)&scsi_device_module,
(module_info *)&scsi_bus_raw_module,
NULL
};
@@ -0,0 +1,339 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Internal structures/definitions
*/
#ifndef __SCSI_INTERNAL_H__
#define __SCSI_INTERNAL_H__
#include <bus/SCSI.h>
#include <bus/scsi/scsi_cmds.h>
#include <locked_pool.h>
#include <device_manager.h>
#include <fast_log.h>
#define debug_level_error 4
#define debug_level_info 4
#define debug_level_flow 4
#define DEBUG_MSG_PREFIX "SCSI -- "
#include "wrapper.h"
//#define USE_FAST_LOG
#ifdef USE_FAST_LOG
#define FAST_LOG0( handle, event ) fast_log->log_0( handle, event )
#define FAST_LOG1( handle, event, param ) fast_log->log_1( handle, event, param )
#define FAST_LOG2( handle, event, param1, param2 ) fast_log->log_2( handle, event, param1, param2 )
#define FAST_LOG3( handle, event, param1, param2, param3 ) fast_log->log_3( handle, event, param1, param2, param3 )
#define FAST_LOGN( handle, event, num_params... ) fast_log->log_n( handle, event, num_params )
#else
#define FAST_LOG0( handle, event )
#define FAST_LOG1( handle, event, param )
#define FAST_LOG2( handle, event, param1, param2 )
#define FAST_LOG3( handle, event, param1, param2, param3 )
#define FAST_LOGN( handle, event, num_params... )
#endif
#include "scsi_lock.h"
#define MAX_PATH_ID 255
#define MAX_TARGET_ID 31
#define MAX_LUN_ID 7
// maximum number of fragments for temporary S/G lists
// for real SCSI controllers, there's no limit to transmission length
// but we need a limit - ATA transmits up to 128K, so we allow that
// (for massive data transmission, peripheral drivers should provide own
// SG list anyway)
// add one extra entry in case data is not page aligned
#define MAX_TEMP_SG_FRAGMENTS (128*1024 / B_PAGE_SIZE + 1)
// maximum number of temporary S/G lists
#define MAX_TEMP_SG_LISTS 32
// delay in µs before DMA buffer is cleaned up
#define SCSI_DMA_BUFFER_CLEANUP_DELAY 10*1000000
// buffer size for emulated SCSI commands that ATAPI cannot handle;
// for MODE SELECT 6, maximum size is 255 + header,
// for MODE SENSE 6, we use MODE SENSE 10 which can return 64 K,
// but as the caller has to live with the 255 + header restriction,
// we hope that this buffer is large enough
#define SCSI_ATAPI_BUFFER_SIZE 512
// name of pnp generator of path ids
#define SCSI_PATHID_GENERATOR "scsi/path_id"
// true, if SCSI device needs ATAPI emulation (ui8)
#define SCSI_DEVICE_IS_ATAPI_ITEM "scsi/is_atapi"
// true, if device requires auto-sense emulation (ui8)
#define SCSI_DEVICE_MANUAL_AUTOSENSE_ITEM "scsi/manual_autosense"
// name of internal scsi_bus_raw device driver
#define SCSI_BUS_RAW_MODULE_NAME "bus_managers/scsi/bus/raw"
// info about DPC
typedef struct scsi_dpc_info {
struct scsi_dpc_info *next;
bool registered; // true, if already/still in dpc list
void (*func)( void * );
void *arg;
} scsi_dpc_info;
// controller restrictions (see blkman.h)
typedef struct dma_params {
uint32 alignment;
uint32 max_blocks;
uint32 dma_boundary;
uint32 max_sg_block_size;
uint32 max_sg_blocks;
} dma_params;
// SCSI bus
typedef struct scsi_bus_info {
int lock_count; // sum of blocked[0..1] and sim_overflow
int blocked[2]; // depth of nested locks by bus manager (0) and SIM (1)
int left_slots; // left command queuing slots on HBA
bool sim_overflow; // 1, if SIM refused req because of bus queue overflow
uchar path_id; // SCSI path id
thread_id service_thread; // service thread
sem_id start_service; // released whenever service thread has work to do
bool shutting_down; // set to true to tell service thread to shut down
benaphore mutex; // used to synchronize changes in queueing and blocking
sem_id scan_lun_lock; // allocated whenever a lun is scanned
scsi_sim_interface *interface; // SIM interface
scsi_sim_cookie sim_cookie; // internal SIM cookie
spinlock_irq dpc_lock; // synchronizer for dpc list
scsi_dpc_info *dpc_list; // list of dpcs to execute
struct scsi_device_info *waiting_devices; // devices ready to receive requests
locked_pool_cookie ccb_pool; // ccb pool (one per bus)
pnp_node_handle node; // pnp node of bus
dma_params dma_params; // dma restrictions of controller
scsi_path_inquiry inquiry_data; // inquiry data as read on init
} scsi_bus_info;
// DMA buffer
typedef struct dma_buffer {
area_id area; // area of DMA buffer
uchar *address; // address of DMA buffer
uint32 size; // size of DMA buffer
area_id sg_list_area; // area of S/G list
physical_entry *sg_list; // address of S/G list
uint32 sg_cnt; // number of entries in S/G list
bool inuse; // true, if in use
bigtime_t last_use; // timestamp of last usage
area_id sg_orig; // area of S/G list to original data
physical_entry *sg_list_orig; // S/G list to original data
uint32 sg_cnt_max_orig; // maximum size (in entries)
uint32 sg_cnt_orig; // current size (in entries)
uchar *orig_data; // pointer to original data
const physical_entry *orig_sg_list; // original S/G list
uint32 orig_sg_cnt; // size of original S/G list
} dma_buffer;
// SCSI device
typedef struct scsi_device_info {
struct scsi_device_info *waiting_next;
struct scsi_device_info *waiting_prev;
bool manual_autosense : 1; // no autosense support
bool is_atapi : 1; // ATAPI device - needs some commands emulated
int lock_count; // sum of blocked[0..1] and sim_overflow
int blocked[2]; // depth of nested locks by bus manager (0) and SIM (1)
int sim_overflow; // 1, if SIM returned a request because of device queue overflow
int left_slots; // left command queuing slots for device
int total_slots; // total number of command queuing slots for device
scsi_ccb *queued_reqs; // queued requests, circularly doubly linked
// (scsi_insert_new_request depends on circular)
int64 last_sort; // last sort value (for elevator sort)
int32 valid; // access must be atomic!
scsi_bus_info *bus;
uchar target_id;
uchar target_lun;
scsi_ccb *auto_sense_request; // auto-sense request
scsi_ccb *auto_sense_originator; // request that auto-sense is
// currently requested for
area_id auto_sense_area; // area of auto-sense data and S/G list
uint8 emulation_map[256/8]; // bit field with index being command code:
// 1 indicates that this command is not supported
// and thus must be emulated
scsi_res_inquiry inquiry_data;
pnp_node_handle node; // device node
benaphore dma_buffer_lock; // lock between DMA buffer user and clean-up daemon
sem_id dma_buffer_owner; // to be acquired before using DMA buffer
dma_buffer dma_buffer; // DMA buffer
fast_log_handle log; // fast log connection
char name[30]; // name for fast log entries
// buffer used for emulating SCSI commands
char *buffer;
physical_entry *buffer_sg_list;
size_t buffer_sg_cnt;
size_t buffer_size;
area_id buffer_area;
sem_id buffer_sem;
} scsi_device_info;
enum {
ev_scsi_requeue_request = 1,
ev_scsi_resubmit_request,
ev_scsi_submit_autosense,
ev_scsi_finish_autosense,
ev_scsi_device_queue_overflow,
ev_scsi_request_finished,
ev_scsi_async_io,
ev_scsi_do_resend_request,
ev_copy_sg_data
};
// check whether device is in bus's wait queue
// we use the fact the queue is circular, so we don't need an explicit flag
#define DEVICE_IN_WAIT_QUEUE( device ) ((device)->waiting_next != NULL)
// state of ccb
enum {
SCSI_STATE_FREE = 0,
SCSI_STATE_INWORK = 1,
SCSI_STATE_QUEUED = 2,
SCSI_STATE_SENT = 3,
SCSI_STATE_FINISHED = 5,
} scsi_state;
extern locked_pool_interface *locked_pool;
extern device_manager_info *pnp;
extern fast_log_info *fast_log;
extern scsi_for_sim_interface scsi_for_sim_module;
extern scsi_bus_interface scsi_bus_module;
extern scsi_device_interface scsi_device_module;
extern struct pnp_devfs_driver_info scsi_bus_raw_module;
// bus_mgr.c
uchar scsi_inquiry_path( scsi_bus bus, scsi_path_inquiry *inquiry_data );
// ccb_mgr.c
scsi_ccb *scsi_alloc_ccb( scsi_device_info *device );
void scsi_free_ccb( scsi_ccb *ccb );
status_t scsi_init_ccb_alloc( scsi_bus_info *bus );
void scsi_uninit_ccb_alloc( scsi_bus_info *bus );
// device_mgr.c
status_t scsi_force_get_device( scsi_bus_info *bus,
uchar target_id, uchar target_lun, scsi_device_info **res_device );
void scsi_put_forced_device( scsi_device_info *device );
status_t scsi_register_device( scsi_bus_info *bus, uchar target_id,
uchar target_lun, scsi_res_inquiry *inquiry_data );
// device_scan.c
status_t scsi_scan_bus( scsi_bus_info *bus );
status_t scsi_scan_lun( scsi_bus_info *bus, uchar target_id, uchar target_lun );
// dpc.c
status_t scsi_alloc_dpc( scsi_dpc_info **dpc );
status_t scsi_free_dpc( scsi_dpc_info *dpc );
bool scsi_check_exec_dpc( scsi_bus_info *bus );
status_t scsi_schedule_dpc( scsi_bus_info *bus, scsi_dpc_info *dpc, /*int flags,*/
void (*func)( void *arg ), void *arg );
// scsi_io.c
void scsi_async_io( scsi_ccb *request );
void scsi_sync_io( scsi_ccb *request );
uchar scsi_term_io( scsi_ccb *ccb_to_terminate );
uchar scsi_abort( scsi_ccb *ccb_to_abort );
bool scsi_check_exec_service( scsi_bus_info *bus );
void scsi_done_io( scsi_ccb *ccb );
void scsi_requeue_request( scsi_ccb *request, bool bus_overflow );
void scsi_resubmit_request( scsi_ccb *request );
void scsi_request_finished( scsi_ccb *request, uint num_requests );
// sg_mgr.c
bool create_temp_sg( scsi_ccb *ccb );
void cleanup_tmp_sg( scsi_ccb *ccb );
int init_temp_sg( void );
void uninit_temp_sg( void );
// dma_buffer.c
void scsi_dma_buffer_daemon( void *dev, int counter );
void scsi_release_dma_buffer( scsi_ccb *request );
bool scsi_get_dma_buffer( scsi_ccb *request );
void scsi_dma_buffer_free( dma_buffer *buffer );
void scsi_dma_buffer_init( dma_buffer *buffer );
// queuing.c
// emulation.c
bool scsi_start_emulation( scsi_ccb *request );
void scsi_finish_emulation( scsi_ccb *request );
void scsi_free_emulation_buffer( scsi_device_info *device );
status_t scsi_init_emulation_buffer( scsi_device_info *device, size_t buffer_size );
#endif
@@ -0,0 +1,640 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Handling of SCSI I/O requests
*/
#include "scsi_internal.h"
#include "queuing.h"
#include <string.h>
/** put request back in queue because of device/bus overflow */
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;
FAST_LOG1( device->log, ev_scsi_requeue_request, (uint32)request );
SHOW_FLOW0( 3, "" );
if( request->state != SCSI_STATE_SENT ) {
panic( "Unsent ccb was request to requeue\n" );
return;
}
request->state = SCSI_STATE_QUEUED;
ACQUIRE_BEN( &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 );
// make sure it's the next request for this device
scsi_add_req_queue_first( request );
if( bus_overflow ) {
// bus has overflown
scsi_set_bus_overflow( bus );
// add device to queue as last - other devices may be waiting already
scsi_add_device_queue_last( device );
// don't change device overflow condition as the device has never seen
// this request
} else {
// device has overflown
scsi_set_device_overflow( device );
scsi_remove_device_queue( device );
// either, the device has refused the request, i.e. it was transmitted
// over the bus - in this case, the bus cannot be overloaded anymore;
// or, the driver detected that the device can not be able to process
// further requests, because the driver knows its maximum queue depth
// or something - in this case, the bus state hasn't changed, but the
// driver will tell us about any overflow when we submit the next
// request, so the overflow state will be fixed automatically
scsi_clear_bus_overflow( bus );
}
start_retry = !was_servicable && scsi_can_service_bus( bus );
RELEASE_BEN( &bus->mutex );
// submit requests to other devices in case bus was overloaded
if( start_retry )
release_sem_etc( bus->start_service, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
}
// restart request ASAP because something went wrong
void scsi_resubmit_request( scsi_ccb *request )
{
scsi_bus_info *bus = request->bus;
scsi_device_info *device = request->device;
bool was_servicable, start_retry;
FAST_LOG1( device->log, ev_scsi_resubmit_request, (uint32)request );
SHOW_FLOW0( 3, "" );
if( request->state != SCSI_STATE_SENT ) {
panic( "Unsent ccb was asked to get resubmitted\n" );
return;
}
request->state = SCSI_STATE_QUEUED;
ACQUIRE_BEN( &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 );
// if SIM reported overflow of device/bus, this should (hopefully) be over now
scsi_clear_device_overflow( device );
scsi_clear_bus_overflow( bus );
// we don't want to let anyone overtake this request
request->ordered = true;
// make it the next request submitted to SIM for this device
scsi_add_req_queue_first( request );
// if device is not blocked (anymore) add it to waiting list of bus
if( device->lock_count == 0 ) {
scsi_add_device_queue_first( device );
// as previous line does nothing if already queued, we force device
// to be the next one to get handled
bus->waiting_devices = device;
}
start_retry = !was_servicable && scsi_can_service_bus( bus );
RELEASE_BEN( &bus->mutex );
// let the service thread do the resubmit
if( start_retry )
release_sem_etc( bus->start_service, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
}
// submit autosense for request
static void submit_autosense( scsi_ccb *request )
{
scsi_device_info *device = request->device;
FAST_LOG1( device->log, ev_scsi_submit_autosense, (uint32)request );
//snooze( 1000000 );
SHOW_FLOW0( 3, "sending autosense" );
// we cannot use scsi_scsi_io but must insert it brute-force
// give SIM a well-defined first state
// WARNING: this is a short version of scsi_async_io, so if
// you change something there, do it here as well!
// no DMA buffer (we made sure that the data buffer fulfills all
// limitations)
request->buffered = false;
// don't let any request bypass us
request->ordered = true;
// initial SIM state for this request
request->sim_state = 0;
device->auto_sense_originator = request;
// make it next request to process
scsi_add_queued_request_first( device->auto_sense_request );
}
// finish special auto-sense request
static void finish_autosense( scsi_device_info *device )
{
scsi_ccb
*orig_request = device->auto_sense_originator,
*request = device->auto_sense_request;
FAST_LOG2( device->log, ev_scsi_finish_autosense, (uint32)request, (uint32)orig_request );
SHOW_FLOW0( 3, "" );
if( request->subsys_status == SCSI_REQ_CMP ) {
int sense_len;
// we got sense data -> copy it to sense buffer
sense_len = min( SCSI_MAX_SENSE_SIZE,
request->data_len - 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 {
// failed to get sense
orig_request->subsys_status = SCSI_AUTOSENSE_FAIL;
}
// inform peripheral driver
release_sem_etc( orig_request->completion_sem, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
}
// device refused request because command queue is full
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;
FAST_LOG2( device->log, ev_scsi_device_queue_overflow, (uint32)request, num_requests );
// set maximum number of concurrent requests to number of
// requests running when QUEUE FULL condition occurred - 1
// (the "1" is the refused request)
--num_requests;
// at least one request at once must be possible
if( num_requests < 1 )
num_requests = 1;
SHOW_INFO( 2, "Restricting device queue to %d requests", num_requests );
// update slot count
ACQUIRE_BEN( &bus->mutex );
diff_max_slots = device->total_slots - num_requests;
device->total_slots = num_requests;
device->left_slots -= diff_max_slots;
RELEASE_BEN( &bus->mutex );
// requeue request, blocking further device requests
scsi_requeue_request( request, false );
}
// finish scsi request
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;
FAST_LOG2( device->log, ev_scsi_request_finished, (uint32)request, num_requests );
SHOW_FLOW( 3, "%p", request );
if( request->state != SCSI_STATE_SENT ) {
panic( "Unsent ccb 0x%x was reported as done\n", request );
return;
}
if( request->subsys_status == SCSI_REQ_INPROG ) {
panic( "ccb 0x%xwith status \"Request in Progress\" was reported as done\n",
request );
return;
}
// check for queue overflow reported by device
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;
ACQUIRE_BEN( &bus->mutex );
was_servicable = scsi_can_service_bus( bus );
// do pseudo-autosense if device doesn't support it and
// device reported a check condition state and auto-sense haven't
// been retrieved by SIM
// (last test is implicit as SIM adds SCSI_AUTOSNS_VALID to subsys_status)
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=%x, manual_auto_sense=%d",
request->subsys_status, request->device_status, (int)request->flags,
device->manual_autosense );
}
if( do_autosense ) {
// queue auto-sense request after checking was_servicable but before
// releasing locks so no other request overtakes auto-sense
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 );
// if SIM reported overflow of device/bus, this should (hopefully) be over now
scsi_clear_device_overflow( device );
scsi_clear_bus_overflow( bus );
// if device is not blocked (anymore) and has pending requests,
// add it to waiting list of 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 );
RELEASE_BEN( &bus->mutex );
// tell service thread to submit new requests to SIM
// (do this ASAP to keep bus/device busy)
if( start_service )
release_sem_etc( bus->start_service, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
if( request->emulated )
scsi_finish_emulation( request );
// copy data from buffer and release it
if( request->buffered ) {
scsi_release_dma_buffer( request );
}
// special treatment for finished auto-sense
if( request == device->auto_sense_request )
finish_autosense( device );
else {
// tell peripheral driver about completion
if( !do_autosense )
release_sem_etc( request->completion_sem, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
}
}
// check whether request can be executed right now, enqueuing it if not,
// 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;
ACQUIRE_BEN( &bus->mutex );
// if device/bus is locked, or there are waiting requests
// or waiting devices (last condition makes sure we don't overtake
// requests that got queued because bus was full)
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 is saturated, block it
if( --bus->left_slots == 0 ) {
SHOW_FLOW0( 3, "bus is saturated, blocking further requests" );
scsi_block_bus_nolock( bus, false );
}
// if device saturated or blocking request, block device
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, "%Ld", device->last_sort );
}
execute = true;
}
RELEASE_BEN( &bus->mutex );
return execute;
}
// size of SCSI command according to function group
int func_group_len[8] = {
6, 10, 10, 0, 16, 12, 0, 0
};
/** execute scsi command asynchronously */
void
scsi_async_io(scsi_ccb *request)
{
scsi_bus_info *bus = request->bus;
//SHOW_FLOW( 0, "path_id=%d", bus->path_id );
//snooze( 1000000 );
// do some sanity tests first
if (request->state != SCSI_STATE_FINISHED)
panic("Passed ccb to scsi_action that isn't ready (state = %d)\n", request->state);
if (request->cdb_len < func_group_len[request->cdb[0] >> 5]) {
SHOW_ERROR(3, "invalid command len (%d instead of %d)",
request->cdb_len, func_group_len[request->cdb[0] >> 5]);
request->subsys_status = SCSI_REQ_INVALID;
goto err;
}
FAST_LOGN(request->device->log, ev_scsi_async_io,
func_group_len[request->cdb[0] >> 5] + 2,
(uint32)request, request->data_len,
request->cdb[0], request->cdb[1], request->cdb[2], request->cdb[3],
request->cdb[4], request->cdb[5], request->cdb[6], request->cdb[7],
request->cdb[8], request->cdb[9], request->cdb[10], request->cdb[11],
request->cdb[12], request->cdb[13], request->cdb[14], request->cdb[15]);
if (!request->device->valid) {
SHOW_ERROR0( 3, "device got removed" );
// device got removed meanwhile
request->subsys_status = SCSI_DEV_NOT_THERE;
goto err;
}
if ((request->flags & SCSI_DIR_MASK) != SCSI_DIR_NONE
&& request->sg_list == NULL && request->data_len > 0) {
SHOW_ERROR( 3, "Asynchronous SCSI I/O requires S/G list (data is %d bytes)",
(int)request->data_len );
request->subsys_status = SCSI_DATA_RUN_ERR;
goto err;
}
request->buffered = request->emulated = 0;
// make data DMA safe
// (S/G list must be created first to be able to verify DMA restrictions)
if ((request->flags & SCSI_DMA_SAFE) == 0 && request->data_len > 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;
}
}
// emulate command if not supported
if ((request->device->emulation_map[request->cdb[0] >> 3]
& (1 << (request->cdb[0] & 7))) != 0) {
request->emulated = true;
dprintf("emulation!\n");
if (!scsi_start_emulation(request)) {
SHOW_ERROR( 3, "cannot emulate SCSI command 0x%02x", request->cdb[0] );
goto err2;
}
}
// SCSI-1 uses 3 bits of command packet for LUN
// SCSI-2 uses identify message, but still needs LUN in command packet
// (though it won't fit, as LUNs can be 4 bits wide)
// SCSI-3 doesn't use command packet for LUN anymore
// ATAPI uses 3 bits of command packet for LUN
// currently, we always copy LUN into command packet as a safe bet
{
// abuse TUR to find proper spot in command packet for LUN
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);
// give SIM a well-defined first state
request->sim_state = 0;
// make sure device/bus is not blocked
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:
release_sem(request->completion_sem);
return;
}
/** execute SCSI command synchronously */
void
scsi_sync_io(scsi_ccb *request)
{
bool tmp_sg = false;
// create scatter-gather list if required
if ((request->flags & SCSI_DIR_MASK) != SCSI_DIR_NONE
&& request->sg_list == NULL && request->data_len > 0) {
tmp_sg = true;
dprintf("create temp request\n");
if (!create_temp_sg(request)) {
SHOW_ERROR0( 3, "data is too much fragmented - you should use s/g list" );
// ToDo: this means too much (fragmented) data
request->subsys_status = SCSI_DATA_RUN_ERR;
return;
}
}
scsi_async_io(request);
acquire_sem(request->completion_sem);
if (tmp_sg)
cleanup_tmp_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 ) {
// checking the validity of the request to abort is a nightmare
// this is just a beginning
return SCSI_REQ_INVALID;
}
ACQUIRE_BEN( &bus->mutex );
switch( req_to_abort->state ) {
case SCSI_STATE_FINISHED:
case SCSI_STATE_SENT:
RELEASE_BEN( &bus->mutex );
break;
case SCSI_STATE_QUEUED: {
bool was_servicable, start_retry;
was_servicable = scsi_can_service_bus( bus );
// remove request from device queue
scsi_remove_queued_request( req_to_abort );
start_retry = scsi_can_service_bus( bus ) && !was_servicable;
RELEASE_BEN( &bus->mutex );
req_to_abort->subsys_status = SCSI_REQ_ABORTED;
// finish emulation
if( req_to_abort->emulated )
scsi_finish_emulation( req_to_abort );
// release DMA buffer
if( req_to_abort->buffered )
scsi_release_dma_buffer( req_to_abort );
// tell peripheral driver about
release_sem_etc( req_to_abort->completion_sem, 1, 0/*B_DO_NOT_RESCHEDULE*/ );
if( start_retry )
release_sem( bus->start_service );
break; }
}
return SCSI_REQ_CMP;
}
// submit pending request (at most one!)
bool scsi_check_exec_service( scsi_bus_info *bus )
{
SHOW_FLOW0( 3, "" );
ACQUIRE_BEN( &bus->mutex );
if( scsi_can_service_bus( bus )) {
scsi_ccb *request;
scsi_device_info *device;
SHOW_FLOW0( 3, "servicing bus" );
//snooze( 1000000 );
// handle devices in round-robin-style
device = bus->waiting_devices;
bus->waiting_devices = bus->waiting_devices->waiting_next;
request = device->queued_reqs;
scsi_remove_queued_request( request );
// if bus is saturated, block it
if( --bus->left_slots == 0 ) {
SHOW_FLOW0( 3, "bus is saturated, blocking further requests" );
scsi_block_bus_nolock( bus, false );
}
// if device saturated or blocking request, block device
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, "%Ld", device->last_sort );
}
RELEASE_BEN( &bus->mutex );
FAST_LOG1( request->device->log, ev_scsi_do_resend_request, (uint32)request );
request->state = SCSI_STATE_SENT;
bus->interface->scsi_io( bus->sim_cookie, request );
return true;
}
RELEASE_BEN( &bus->mutex );
return false;
}
@@ -0,0 +1,53 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Special locks
The only one defined herein is a spinlock that automatically
disabled IRQs on enter and restores them on leave. Probably,
this should be made public as it's quite basic.
*/
#ifndef __SCSI_LOCK_H__
#define __SCSI_LOCK_H__
#include <KernelExport.h>
// enhanced spinlock that automatically disables irqs when lock is hold
typedef struct spinlock_irq {
spinlock lock; // normal spinlock
cpu_status prev_irq_state; // irq state before spinlock was entered
} spinlock_irq;
static inline void
spinlock_irq_init(spinlock_irq *lock)
{
lock->lock = 0;
}
static inline void
acquire_spinlock_irq(spinlock_irq *lock)
{
cpu_status prev_irq_state = disable_interrupts();
acquire_spinlock(&lock->lock);
lock->prev_irq_state = prev_irq_state;
}
static inline void
release_spinlock_irq(spinlock_irq *lock)
{
cpu_status prev_irq_state = lock->prev_irq_state;
release_spinlock(&lock->lock);
restore_interrupts(prev_irq_state);
}
#endif
@@ -0,0 +1,141 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
Part of Open SCSI bus manager
Interface for SIMs
Controllers use this interface to interact with bus manager.
*/
#include "scsi_internal.h"
#include "queuing.h"
#include <stdlib.h>
#include <string.h>
/** new scsi controller added
* in return, we register a new scsi bus node and let its fixed
* consumer (the SCSI device layer) automatically scan it for devices
*/
static status_t
scsi_controller_added(pnp_node_handle parent)
{
int path_id;
char *str, *controller_name;
SHOW_FLOW0( 4, "" );
if( pnp->get_attr_string( parent, PNP_DRIVER_TYPE, &str, false ) != B_OK )
return B_ERROR;
if (strcmp(str, SCSI_SIM_TYPE_NAME) != 0) {
free(str);
return B_ERROR;
}
free( str );
if( pnp->get_attr_string( parent, SCSI_DESCRIPTION_CONTROLLER_NAME,
&controller_name, false ) != B_OK )
{
pnp->get_attr_string( parent, PNP_DRIVER_DRIVER, &str, false );
SHOW_ERROR( 0, "Ignored controller managed by %s - controller name missing",
str );
return B_ERROR;
}
path_id = pnp->create_id( SCSI_PATHID_GENERATOR );
if( path_id < 0 ) {
SHOW_ERROR( 0, "Cannot register SCSI controller %s - out of path IDs",
controller_name );
free( controller_name );
return B_ERROR;
}
free(controller_name);
{
pnp_node_attr attrs[] =
{
// general information
{ PNP_DRIVER_DRIVER, B_STRING_TYPE, { string: SCSI_BUS_MODULE_NAME }},
{ PNP_DRIVER_TYPE, B_STRING_TYPE, { string: SCSI_BUS_TYPE_NAME }},
// we are a bus
{ PNP_BUS_IS_BUS, B_UINT8_TYPE, { ui8: 1 }},
// search for peripheral drivers after bus is fully scanned
{ PNP_BUS_DEFER_PROBE, B_UINT8_TYPE, {ui8: 1 }},
// remember who we are
// (could use the controller name, but probably some software would choke)
{ SCSI_BUS_PATH_ID_ITEM, B_UINT8_TYPE, { ui8: path_id }},
// tell PnP manager to clean up ID
{ PNP_MANAGER_ID_GENERATOR, B_STRING_TYPE, { string: SCSI_PATHID_GENERATOR }},
{ PNP_MANAGER_AUTO_ID, B_UINT32_TYPE, { ui32: path_id }},
// tell internal bus raw driver to register bus' device in devfs
{ PNP_DRIVER_FIXED_CONSUMER, B_STRING_TYPE, { string: SCSI_BUS_RAW_MODULE_NAME }},
{ NULL, 0 }
};
pnp_node_handle node;
return pnp->register_device( parent, attrs, NULL, &node );
}
}
static status_t
std_ops(int32 op, ...)
{
switch (op) {
case B_MODULE_INIT:
case B_MODULE_UNINIT:
return B_OK;
default:
return B_ERROR;
}
}
scsi_for_sim_interface scsi_for_sim_module =
{
{
{
SCSI_FOR_SIM_MODULE_NAME,
0,
std_ops
},
NULL,
NULL,
scsi_controller_added,
NULL
},
scsi_requeue_request,
scsi_resubmit_request,
scsi_request_finished,
scsi_alloc_dpc,
scsi_free_dpc,
scsi_schedule_dpc,
scsi_block_bus,
scsi_unblock_bus,
scsi_block_device,
scsi_unblock_device,
scsi_cont_send_bus,
scsi_cont_send_device
};
@@ -0,0 +1,139 @@
/*
** Copyright 2002/03, Thomas Kurschel. All rights reserved.
** Distributed under the terms of the OpenBeOS License.
*/
/*
VM helper functions.
Important assumption: get_memory_map must combine adjacent
physical pages, so contignous memory always leads to a S/G
list of length one.
*/
#include "KernelExport_ext.h"
#include "wrapper.h"
#include <vm.h>
#include <string.h>
/** get sg list of iovec
* TBD: this should be moved to somewhere in kernel
*/
status_t
get_iovec_memory_map(iovec *vec, size_t vec_count, size_t vec_offset, size_t len,
physical_entry *map, size_t max_entries, size_t *num_entries, size_t *mapped_len)
{
size_t cur_idx;
size_t left_len;
SHOW_FLOW(3, "vec_count=%lu, vec_offset=%lu, len=%lu, max_entries=%lu",
vec_count, vec_offset, len, max_entries);
// skip iovec blocks if needed
while (vec_count > 0 && vec_offset > vec->iov_len) {
vec_offset -= vec->iov_len;
--vec_count;
++vec;
}
for (left_len = len, cur_idx = 0; left_len > 0 && vec_count > 0 && cur_idx < max_entries;) {
char *range_start;
size_t range_len;
status_t res;
size_t cur_num_entries, cur_mapped_len;
uint32 tmp_idx;
SHOW_FLOW( 3, "left_len=%d, vec_count=%d, cur_idx=%d",
(int)left_len, (int)vec_count, (int)cur_idx );
// map one iovec
range_start = (char *)vec->iov_base + vec_offset;
range_len = min( vec->iov_len - vec_offset, left_len );
SHOW_FLOW( 3, "range_start=%x, range_len=%x",
(int)range_start, (int)range_len );
vec_offset = 0;
if ((res = get_memory_map(range_start, range_len, &map[cur_idx],
max_entries - cur_idx)) != B_OK) {
// according to docu, no error is ever reported - argh!
SHOW_ERROR(1, "invalid io_vec passed (%s)", strerror(res));
return res;
}
// stupid: get_memory_map does neither tell how many sg blocks
// are used nor whether there were enough sg blocks at all;
// -> determine that manually
cur_mapped_len = 0;
cur_num_entries = 0;
for (tmp_idx = cur_idx; tmp_idx < max_entries; ++tmp_idx) {
if (map[tmp_idx].size == 0)
break;
cur_mapped_len += map[tmp_idx].size;
++cur_num_entries;
}
if (cur_mapped_len == 0) {
panic("get_memory_map() returned empty list; left_len=%d, idx=%d/%d",
(int)left_len, (int)cur_idx, (int)max_entries);
SHOW_ERROR(2, "get_memory_map() returned empty list; left_len=%d, idx=%d/%d",
(int)left_len, (int)cur_idx, (int)max_entries);
return B_ERROR;
}
SHOW_FLOW( 3, "cur_num_entries=%d, cur_mapped_len=%x",
(int)cur_num_entries, (int)cur_mapped_len );
// try to combine with previous sg block
if (cur_num_entries > 0 && cur_idx > 0
&& map[cur_idx].address == (char *)map[cur_idx - 1].address + map[cur_idx - 1].size) {
SHOW_FLOW0( 3, "combine with previous chunk" );
map[cur_idx - 1].size += map[cur_idx].size;
memcpy(&map[cur_idx], &map[cur_idx + 1], (cur_num_entries - 1) * sizeof(map[0]));
--cur_num_entries;
}
cur_idx += cur_num_entries;
left_len -= cur_mapped_len;
// advance iovec if current one is described completely
if (cur_mapped_len == range_len) {
++vec;
--vec_count;
}
}
*num_entries = cur_idx;
*mapped_len = len - left_len;
SHOW_FLOW( 3, "num_entries=%d, mapped_len=%x",
(int)*num_entries, (int)*mapped_len );
return B_OK;
}
/** map main memory into virtual address space */
status_t
map_mainmemory(addr_t physicalAddress, void **_virtualAddress)
{
return vm_get_physical_page(physicalAddress, (addr_t *)_virtualAddress, PHYSICAL_PAGE_CAN_WAIT);
// ToDo: check if CAN_WAIT is correct
}
/** unmap main memory from virtual address space */
status_t
unmap_mainmemory(void *virtualAddress)
{
return vm_put_physical_page((addr_t)virtualAddress);
}
@@ -0,0 +1,89 @@
#ifndef _WRAPPER_H
#define _WRAPPER_H
#include <KernelExport.h>
#include <lock.h>
// benaphores
#define INIT_BEN(x, prefix) benaphore_init(x, prefix)
#define DELETE_BEN(x) benaphore_destroy(x)
#define ACQUIRE_BEN(x) benaphore_lock(x)
#define RELEASE_BEN(x) benaphore_unlock(x)
// debug output
#ifdef DEBUG_WAIT_ON_MSG
# define DEBUG_WAIT snooze( DEBUG_WAIT_ON_MSG );
#else
# define DEBUG_WAIT
#endif
#ifdef DEBUG_WAIT_ON_ERROR
# define DEBUG_WAIT_ERROR snooze( DEBUG_WAIT_ON_ERROR );
#else
# define DEBUG_WAIT_ERROR
#endif
#ifndef DEBUG_MAX_LEVEL_FLOW
# define DEBUG_MAX_LEVEL_FLOW 4
#endif
#ifndef DEBUG_MAX_LEVEL_INFO
# define DEBUG_MAX_LEVEL_INFO 4
#endif
#ifndef DEBUG_MAX_LEVEL_ERROR
# define DEBUG_MAX_LEVEL_ERROR 4
#endif
#ifndef DEBUG_MSG_PREFIX
# define DEBUG_MSG_PREFIX ""
#endif
#ifndef debug_level_flow
# define debug_level_flow 3
#endif
#ifndef debug_level_info
# define debug_level_info 2
#endif
#ifndef debug_level_error
# define debug_level_error 1
#endif
#define FUNC_NAME DEBUG_MSG_PREFIX __FUNCTION__ ": "
#define SHOW_FLOW(seriousness, format, param...) \
do { if( seriousness <= debug_level_flow && seriousness <= DEBUG_MAX_LEVEL_FLOW ) { \
dprintf( "%s"##format"\n", FUNC_NAME, param ); DEBUG_WAIT \
}} while( 0 )
#define SHOW_FLOW0(seriousness, format) \
do { if( seriousness <= debug_level_flow && seriousness <= DEBUG_MAX_LEVEL_FLOW ) { \
dprintf( "%s"##format"\n", FUNC_NAME); DEBUG_WAIT \
}} while( 0 )
#define SHOW_INFO(seriousness, format, param...) \
do { if( seriousness <= debug_level_info && seriousness <= DEBUG_MAX_LEVEL_INFO ) { \
dprintf( "%s"##format"\n", FUNC_NAME, param ); DEBUG_WAIT \
}} while( 0 )
#define SHOW_INFO0(seriousness, format) \
do { if( seriousness <= debug_level_info && seriousness <= DEBUG_MAX_LEVEL_INFO ) { \
dprintf( "%s"##format"\n", FUNC_NAME); DEBUG_WAIT \
}} while( 0 )
#define SHOW_ERROR(seriousness, format, param...) \
do { if( seriousness <= debug_level_error && seriousness <= DEBUG_MAX_LEVEL_ERROR ) { \
dprintf( "%s"##format"\n", FUNC_NAME, param ); DEBUG_WAIT_ERROR \
}} while( 0 )
#define SHOW_ERROR0(seriousness, format) \
do { if( seriousness <= debug_level_error && seriousness <= DEBUG_MAX_LEVEL_ERROR ) { \
dprintf( "%s"##format"\n", FUNC_NAME); DEBUG_WAIT_ERROR \
}} while( 0 )
#endif /* _BENAPHORE_H */