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:
@@ -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,
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
Reference in New Issue
Block a user