From 1450572a5d28572db7a58bb04c1148522f137878 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Axel=20D=C3=B6rfler?= Date: Mon, 7 Jun 2004 00:55:49 +0000 Subject: [PATCH] Added SCSI bus manager written by Thomas Kurschel. git-svn-id: file:///srv/svn/repos/haiku/trunk/current@7776 a95241bf-73f2-0310-859d-f6bbb57e9c96 --- src/add-ons/kernel/bus_managers/scsi/Jamfile | 23 + .../bus_managers/scsi/KernelExport_ext.h | 45 ++ .../kernel/bus_managers/scsi/bus_raw.c | 195 ++++++ src/add-ons/kernel/bus_managers/scsi/busses.c | 346 ++++++++++ src/add-ons/kernel/bus_managers/scsi/ccb.c | 116 ++++ .../kernel/bus_managers/scsi/device_scan.c | 288 ++++++++ .../kernel/bus_managers/scsi/devices.c | 541 +++++++++++++++ .../kernel/bus_managers/scsi/dl_list.h | 88 +++ .../kernel/bus_managers/scsi/dma_buffer.c | 519 ++++++++++++++ src/add-ons/kernel/bus_managers/scsi/dpc.c | 105 +++ .../kernel/bus_managers/scsi/emulation.c | 581 ++++++++++++++++ .../kernel/bus_managers/scsi/queuing.c | 393 +++++++++++ .../kernel/bus_managers/scsi/queuing.h | 189 ++++++ .../kernel/bus_managers/scsi/scatter_gather.c | 177 +++++ src/add-ons/kernel/bus_managers/scsi/scsi.c | 39 ++ .../kernel/bus_managers/scsi/scsi_internal.h | 339 ++++++++++ .../kernel/bus_managers/scsi/scsi_io.c | 640 ++++++++++++++++++ .../kernel/bus_managers/scsi/scsi_lock.h | 53 ++ .../kernel/bus_managers/scsi/sim_interface.c | 141 ++++ .../kernel/bus_managers/scsi/virtual_memory.c | 139 ++++ .../kernel/bus_managers/scsi/wrapper.h | 89 +++ 21 files changed, 5046 insertions(+) create mode 100644 src/add-ons/kernel/bus_managers/scsi/Jamfile create mode 100644 src/add-ons/kernel/bus_managers/scsi/KernelExport_ext.h create mode 100644 src/add-ons/kernel/bus_managers/scsi/bus_raw.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/busses.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/ccb.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/device_scan.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/devices.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/dl_list.h create mode 100644 src/add-ons/kernel/bus_managers/scsi/dma_buffer.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/dpc.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/emulation.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/queuing.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/queuing.h create mode 100644 src/add-ons/kernel/bus_managers/scsi/scatter_gather.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/scsi.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/scsi_internal.h create mode 100644 src/add-ons/kernel/bus_managers/scsi/scsi_io.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/scsi_lock.h create mode 100644 src/add-ons/kernel/bus_managers/scsi/sim_interface.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/virtual_memory.c create mode 100644 src/add-ons/kernel/bus_managers/scsi/wrapper.h diff --git a/src/add-ons/kernel/bus_managers/scsi/Jamfile b/src/add-ons/kernel/bus_managers/scsi/Jamfile new file mode 100644 index 0000000000..dc3b70833f --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/Jamfile @@ -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 + ; + diff --git a/src/add-ons/kernel/bus_managers/scsi/KernelExport_ext.h b/src/add-ons/kernel/bus_managers/scsi/KernelExport_ext.h new file mode 100644 index 0000000000..0c0219222d --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/KernelExport_ext.h @@ -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 +#include + + +// 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 diff --git a/src/add-ons/kernel/bus_managers/scsi/bus_raw.c b/src/add-ons/kernel/bus_managers/scsi/bus_raw.c new file mode 100644 index 0000000000..2b16405ca9 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/bus_raw.c @@ -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 +#include + +#include +#include +#include + + +// 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 +}; diff --git a/src/add-ons/kernel/bus_managers/scsi/busses.c b/src/add-ons/kernel/bus_managers/scsi/busses.c new file mode 100644 index 0000000000..cca5dffe1f --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/busses.c @@ -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 +#include +#include + + +// 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, +}; diff --git a/src/add-ons/kernel/bus_managers/scsi/ccb.c b/src/add-ons/kernel/bus_managers/scsi/ccb.c new file mode 100644 index 0000000000..3694b53180 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/ccb.c @@ -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); +} + diff --git a/src/add-ons/kernel/bus_managers/scsi/device_scan.c b/src/add-ons/kernel/bus_managers/scsi/device_scan.c new file mode 100644 index 0000000000..f1431c6df5 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/device_scan.c @@ -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 +#include + + +// 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; +} diff --git a/src/add-ons/kernel/bus_managers/scsi/devices.c b/src/add-ons/kernel/bus_managers/scsi/devices.c new file mode 100644 index 0000000000..68cd34fb2f --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/devices.c @@ -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 + +#include +#include +#include + + +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 +}; diff --git a/src/add-ons/kernel/bus_managers/scsi/dl_list.h b/src/add-ons/kernel/bus_managers/scsi/dl_list.h new file mode 100644 index 0000000000..36a0d22da9 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/dl_list.h @@ -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 diff --git a/src/add-ons/kernel/bus_managers/scsi/dma_buffer.c b/src/add-ons/kernel/bus_managers/scsi/dma_buffer.c new file mode 100644 index 0000000000..f83bc1bc92 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/dma_buffer.c @@ -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 + + +// 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; +} diff --git a/src/add-ons/kernel/bus_managers/scsi/dpc.c b/src/add-ons/kernel/bus_managers/scsi/dpc.c new file mode 100644 index 0000000000..91d953fc35 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/dpc.c @@ -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 +#include + + +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; +} diff --git a/src/add-ons/kernel/bus_managers/scsi/emulation.c b/src/add-ons/kernel/bus_managers/scsi/emulation.c new file mode 100644 index 0000000000..0697274675 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/emulation.c @@ -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 + + +// 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; +} diff --git a/src/add-ons/kernel/bus_managers/scsi/queuing.c b/src/add-ons/kernel/bus_managers/scsi/queuing.c new file mode 100644 index 0000000000..dbf8f54976 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/queuing.c @@ -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 ); +} diff --git a/src/add-ons/kernel/bus_managers/scsi/queuing.h b/src/add-ons/kernel/bus_managers/scsi/queuing.h new file mode 100644 index 0000000000..c757fcbe15 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/queuing.h @@ -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 + +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 diff --git a/src/add-ons/kernel/bus_managers/scsi/scatter_gather.c b/src/add-ons/kernel/bus_managers/scsi/scatter_gather.c new file mode 100644 index 0000000000..bfc1beac0d --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/scatter_gather.c @@ -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 +#include + +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; +} + diff --git a/src/add-ons/kernel/bus_managers/scsi/scsi.c b/src/add-ons/kernel/bus_managers/scsi/scsi.c new file mode 100644 index 0000000000..10b5519a04 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/scsi.c @@ -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 + +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 +}; diff --git a/src/add-ons/kernel/bus_managers/scsi/scsi_internal.h b/src/add-ons/kernel/bus_managers/scsi/scsi_internal.h new file mode 100644 index 0000000000..c592d3a372 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/scsi_internal.h @@ -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 +#include +#include +#include +#include + +#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 diff --git a/src/add-ons/kernel/bus_managers/scsi/scsi_io.c b/src/add-ons/kernel/bus_managers/scsi/scsi_io.c new file mode 100644 index 0000000000..8a799142ca --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/scsi_io.c @@ -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 + + +/** 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; +} diff --git a/src/add-ons/kernel/bus_managers/scsi/scsi_lock.h b/src/add-ons/kernel/bus_managers/scsi/scsi_lock.h new file mode 100644 index 0000000000..7913673b25 --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/scsi_lock.h @@ -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 + + +// 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 diff --git a/src/add-ons/kernel/bus_managers/scsi/sim_interface.c b/src/add-ons/kernel/bus_managers/scsi/sim_interface.c new file mode 100644 index 0000000000..0362d733da --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/sim_interface.c @@ -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 +#include + + +/** 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 +}; diff --git a/src/add-ons/kernel/bus_managers/scsi/virtual_memory.c b/src/add-ons/kernel/bus_managers/scsi/virtual_memory.c new file mode 100644 index 0000000000..0a18b3d3df --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/virtual_memory.c @@ -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 +#include + + +/** 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); +} + diff --git a/src/add-ons/kernel/bus_managers/scsi/wrapper.h b/src/add-ons/kernel/bus_managers/scsi/wrapper.h new file mode 100644 index 0000000000..6793cb73be --- /dev/null +++ b/src/add-ons/kernel/bus_managers/scsi/wrapper.h @@ -0,0 +1,89 @@ +#ifndef _WRAPPER_H +#define _WRAPPER_H + +#include +#include + + +// 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 */