diff --git a/src/add-ons/kernel/drivers/disk/Jamfile b/src/add-ons/kernel/drivers/disk/Jamfile new file mode 100644 index 0000000000..28c5f5a5b8 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/Jamfile @@ -0,0 +1,3 @@ +SubDir OBOS_TOP src add-ons kernel drivers disk ; + +SubInclude OBOS_TOP src add-ons kernel drivers disk scsi ; diff --git a/src/add-ons/kernel/drivers/disk/scsi/Jamfile b/src/add-ons/kernel/drivers/disk/scsi/Jamfile new file mode 100644 index 0000000000..4cd3868265 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/Jamfile @@ -0,0 +1,4 @@ +SubDir OBOS_TOP src add-ons kernel drivers disk scsi ; + +SubInclude OBOS_TOP src add-ons kernel drivers disk scsi scsi_cd ; +SubInclude OBOS_TOP src add-ons kernel drivers disk scsi scsi_dsk ; diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/Jamfile b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/Jamfile new file mode 100644 index 0000000000..404aa59184 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/Jamfile @@ -0,0 +1,9 @@ +SubDir OBOS_TOP src add-ons kernel drivers disk scsi scsi_cd ; + +UsePrivateHeaders kernel ; + +KernelAddon scsi_cd : kernel drivers bin : + device.c + handle.c + scsi_cd.c + ; diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/device.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/device.c new file mode 100644 index 0000000000..9932fa5729 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/device.c @@ -0,0 +1,175 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +**/ + +/* + Part of Open SCSI Disk Driver + + Device management. +*/ + +#include "scsi_cd_int.h" + +#include + +#include +#include + + +status_t cd_init_device( pnp_node_handle node, void *user_cookie, void **cookie ) +{ + cd_device_info *device; + status_t res; + bool removable; + + SHOW_FLOW0( 3, "" ); + + device = (cd_device_info *)malloc( sizeof( *device )); + if( device == NULL ) + return B_NO_MEMORY; + + memset( device, 0, sizeof( *device )); + + device->blkman_device = user_cookie; + device->node = node; + + res = pnp->get_attr_uint8( node, "removable", + &removable, false ); + if( res != B_OK ) + goto err1; + + device->removable = removable; + + res = pnp->get_attr_uint8( node, SCSI_DEVICE_TYPE_ITEM, + &device->device_type, true ); + if( res != B_OK ) + goto err1; + + // we don't verify type - in worst case, the device doesn't work + + // register it everywhere + res = pnp->load_driver( pnp->get_parent( node ), NULL, (pnp_driver_info **)&device->scsi, + (void **)&device->scsi_device ); + if( res != B_OK ) + goto err2; + + res = scsi_periph->register_device( + (periph_device_cookie)device, &callbacks, device->scsi_device, + device->scsi, device->node, removable, &device->scsi_periph_device ); + if( res != B_OK ) + goto err3; + + // set capacity to zero, so it get checked on first opened handle + device->capacity = 0; + device->block_size = 0; + + SHOW_FLOW0( 3, "done" ); + + *cookie = device; + return B_OK; + +err3: + pnp->unload_driver( pnp->get_parent( node )); +err2: +err1: + free( device ); + return res; +} + + +status_t cd_uninit_device( cd_device_info *device ) +{ + scsi_periph->unregister_device( device->scsi_periph_device ); + pnp->unload_driver( pnp->get_parent( device->node )); + free( device ); + + return B_OK; +} + + +// called whenever a new device was added to system; +// if we really support it, we create a new node that gets +// server by blkdev +status_t cd_device_added( pnp_node_handle node ) +{ + char *str = NULL; + uint8 device_type; + scsi_res_inquiry *device_inquiry = NULL; + size_t inquiry_len; + char *name; + uint32 max_blocks; + + SHOW_FLOW0( 3, "" ); + + // make sure we can handle this parent device + if( pnp->get_attr_string( node, PNP_DRIVER_TYPE, &str, false ) != B_OK || + strcmp( str, SCSI_DEVICE_TYPE_NAME ) != 0 ) + goto err; + + // check whether it's really a CD-ROM or a WORM + if( pnp->get_attr_uint8( node, SCSI_DEVICE_TYPE_ITEM, + &device_type, true ) != B_OK || + (device_type != scsi_dev_CDROM && device_type != scsi_dev_WORM) ) + goto err; + + // get inquiry data + if( pnp->get_attr_raw( node, SCSI_DEVICE_INQUIRY_ITEM, + (void **)&device_inquiry, &inquiry_len, true ) != B_OK || + inquiry_len < sizeof( device_inquiry ) ) + goto err; + + // get block limit of underlying hardware to lower it (if necessary) + if( pnp->get_attr_uint32( node, BLKDEV_MAX_BLOCKS_ITEM, + &max_blocks, true ) != B_OK ) + max_blocks = INT_MAX; + + // using 10 byte commands, at most 0xffff blocks can be transmitted at once + // (sadly, we cannot update this value later on if only 6 byte commands + // are supported, but the blkdev can live with truncated transfers) + max_blocks = min( max_blocks, 0xffff ); + + name = scsi_periph->compose_device_name( node, "disk/scsi" ); + if( name == NULL ) { + SHOW_ERROR0( 2, "Cannot compose device name" ); + goto err; + } + + SHOW_FLOW( 3, "name=%s", name ); + + // ready to register + { + pnp_node_attr attrs[] = + { + { PNP_DRIVER_DRIVER, B_STRING_TYPE, { string: SCSI_CD_MODULE_NAME }}, + { PNP_DRIVER_TYPE, B_STRING_TYPE, { string: BLKDEV_TYPE_NAME }}, + // we always want blkdev on top of us + { PNP_DRIVER_FIXED_CONSUMER, B_STRING_TYPE, { string: BLKMAN_MODULE_NAME }}, + // tell blkdev whether the device is removable + { "removable", B_UINT8_TYPE, { ui8: device_inquiry->RMB }}, + // tell which name we want to have in devfs + { PNP_DEVFS_FILENAME, B_STRING_TYPE, { string: name }}, + // impose own max block restriction + { BLKDEV_MAX_BLOCKS_ITEM, B_UINT32_TYPE, { ui32: max_blocks }}, + { NULL } + }; + status_t res; + + res = pnp->register_device( node, attrs, NULL, &node ); + + free( name ); + free( str ); + free( device_inquiry ); + + return res; + } + +err: + if( str != NULL ) + free( str ); + + if( device_inquiry != NULL ) + free( device_inquiry ); + + return B_ERROR; +} diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/handle.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/handle.c new file mode 100644 index 0000000000..5e550e973d --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/handle.c @@ -0,0 +1,77 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + File handle management. +*/ + +#include "scsi_cd_int.h" + +#include + +status_t cd_open( cd_device_info *device, cd_handle_info **handle_out ) +{ + cd_handle_info *handle; + int res; + +// SHOW_FLOW0( 3, "" ); + + handle = (cd_handle_info *)malloc( sizeof( *handle )); + if( handle == NULL ) + return B_NO_MEMORY; + + handle->device = device; + + res = scsi_periph->handle_open( device->scsi_periph_device, + (periph_handle_cookie)handle, + &handle->scsi_periph_handle ); + if( res < 0 ) { + free( handle ); + return res; + } + + if( device->block_size == 0 || device->capacity == 0 ) { + scsi_ccb *request; + + request = device->scsi->alloc_ccb( device->scsi_device ); + + // don't care if no test was possible + if( request != NULL ) { + scsi_periph->check_capacity( device->scsi_periph_device, request ); + + device->scsi->free_ccb( request ); + } + } + +// SHOW_FLOW0( 3, "opened" ); + + *handle_out = handle; + return B_OK; +} + +status_t cd_close( cd_handle_info *handle ) +{ +// SHOW_FLOW0( 3, "" ); + + scsi_periph->handle_close( handle->scsi_periph_handle ); + +// SHOW_FLOW0( 3, "done" ); + + return B_OK; +} + +status_t cd_free( cd_handle_info *handle ) +{ +// SHOW_FLOW0( 3, "" ); + + scsi_periph->handle_free( handle->scsi_periph_handle ); + free( handle ); + +// SHOW_FLOW0( 3, "done" ); + + return B_OK; +} diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.c new file mode 100644 index 0000000000..179dcfdaa1 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.c @@ -0,0 +1,715 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI CD-ROM driver + + Main file. +*/ + +#include "scsi_cd_int.h" +#include +#include +#include + +extern blkdev_interface cd_interface; + +scsi_periph_interface *scsi_periph; +device_manager_info *pnp; +blkman_for_driver_interface *blkman; + + +#define SCSI_CD_STD_TIMEOUT 10 + +static status_t cd_read( cd_handle_info *handle, const phys_vecs *vecs, + off_t pos, size_t num_blocks, uint32 block_size, size_t *bytes_transferred ) +{ + return scsi_periph->read( handle->scsi_periph_handle, vecs, pos, + num_blocks, block_size, bytes_transferred, 10 ); +} + + +static status_t cd_write( cd_handle_info *handle, const phys_vecs *vecs, + off_t pos, size_t num_blocks, uint32 block_size, size_t *bytes_transferred ) +{ + return scsi_periph->write( handle->scsi_periph_handle, vecs, pos, + num_blocks, block_size, bytes_transferred, 10 ); +} + + +static status_t update_capacity( cd_device_info *device ) +{ + scsi_ccb *ccb; + status_t res; + + SHOW_FLOW0( 3, "" ); + + ccb = device->scsi->alloc_ccb( device->scsi_device ); + if( ccb == NULL ) + return B_NO_MEMORY; + + res = scsi_periph->check_capacity( device->scsi_periph_device, ccb ); + + device->scsi->free_ccb( ccb ); + + return res; +} + + +static status_t get_geometry( cd_handle_info *handle, void *buf, size_t len ) +{ + cd_device_info *device = handle->device; + device_geometry *geometry = (device_geometry *)buf; + status_t res; + + SHOW_FLOW0( 3, "" ); + + res = update_capacity( device ); + + // it seems that Be expects B_GET_GEOMETRY to always succeed unless + // the medium has been changed; e.g. if we report B_DEV_NO_MEDIA, the + // device is ignored by the CDPlayer and CDBurner + if( res == B_DEV_MEDIA_CHANGED ) + return res; + + geometry->bytes_per_sector = device->block_size; + geometry->sectors_per_track = 1; + geometry->cylinder_count = device->capacity; + geometry->head_count = 1; + geometry->device_type = device->device_type; + geometry->removable = device->removable; + + // TBD: for all but CD-ROMs, read mode sense - medium type + // (bit 7 of block device specific parameter for Optical Memory Block Device) + // (same for Direct-Access Block Devices) + // (same for write-once block devices) + // (same for optical memory block devices) + geometry->read_only = true; + geometry->write_once = device->device_type == scsi_dev_WORM; + + SHOW_FLOW( 3, "%ld, %ld, %ld, %ld, %d, %d, %d, %d", + geometry->bytes_per_sector, + geometry->sectors_per_track, + geometry->cylinder_count, + geometry->head_count, + geometry->device_type, + geometry->removable, + geometry->read_only, + geometry->write_once ); + + SHOW_FLOW0( 3, "done" ); + + return B_OK; +} + +static status_t get_toc( cd_device_info *device, scsi_toc *toc ) +{ + scsi_ccb *ccb; + status_t res; + scsi_cmd_read_toc *cmd; + size_t data_len; + scsi_toc_general *short_response = (scsi_toc_general *)toc->toc_data; + + SHOW_FLOW0( 0, "" ); + + ccb = device->scsi->alloc_ccb( device->scsi_device ); + + if( ccb == NULL ) + return B_NO_MEMORY; + + // first read number of tracks only + ccb->flags = SCSI_DIR_IN; + + cmd = (scsi_cmd_read_toc *)ccb->cdb; + + memset( cmd, 0, sizeof( *cmd )); + cmd->opcode = SCSI_OP_READ_TOC; + cmd->TIME = 1; + cmd->format = SCSI_TOC_FORMAT_TOC; + cmd->track = 1; + cmd->high_allocation_length = 0; + cmd->low_allocation_length = sizeof( scsi_toc_general ); + + ccb->cdb_len = sizeof( *cmd ); + + ccb->sort = -1; + ccb->timeout = SCSI_CD_STD_TIMEOUT; + + ccb->data = toc->toc_data; + ccb->sg_list = NULL; + ccb->data_len = sizeof( toc->toc_data ); + + res = scsi_periph->safe_exec( device->scsi_periph_device, ccb ); + if( res != B_OK ) + goto err; + + SHOW_FLOW( 0, "tracks: %d - %d", short_response->first, short_response->last ); + + // then read all track infos + // (little hint: number of tracks is last - first + 1; + // but scsi_toc_toc has already one track, so we get + // last - first extra tracks; finally, we want the lead-out as + // well, so we add an extra track) + data_len = (short_response->last - short_response->first + 1) + * sizeof( scsi_toc_track ) + sizeof( scsi_toc_toc ); + data_len = min( data_len, sizeof( toc->toc_data )); + + SHOW_FLOW( 0, "data_len: %d", (int)data_len ); + + cmd->high_allocation_length = data_len >> 8; + cmd->low_allocation_length = data_len & 0xff; + + res = scsi_periph->safe_exec( device->scsi_periph_device, ccb ); + +err: + device->scsi->free_ccb( ccb ); + + return res; +} + +static status_t load_eject( cd_device_info *device, bool load ) +{ + scsi_ccb *ccb; + err_res res; + + SHOW_FLOW0( 0, "" ); + + ccb = device->scsi->alloc_ccb( device->scsi_device ); + + res = scsi_periph->send_start_stop( device->scsi_periph_device, + ccb, load, true ); + + device->scsi->free_ccb( ccb ); + + return res.error_code; +} + +static status_t get_position( cd_device_info *device, scsi_position *position ) +{ + scsi_cmd_read_subchannel cmd; + + SHOW_FLOW0( 3, "" ); + + memset( &cmd, 0, sizeof( cmd )); + cmd.opcode = SCSI_OP_READ_SUB_CHANNEL; + cmd.TIME = 1; + cmd.SUBQ = 1; + cmd.parameter_list = scsi_sub_channel_parameter_list_cd_pos; + cmd.track = 0; + cmd.high_allocation_length = sizeof( *position ) >> 8; + cmd.low_allocation_length = sizeof( *position ) & 0xff; + + return scsi_periph->simple_exec( device->scsi_periph_device, + &cmd, sizeof( cmd ), + position, sizeof( *position ), SCSI_DIR_IN ); +} + +static status_t get_set_volume( cd_device_info *device, scsi_volume *volume, bool set ) +{ + scsi_cmd_mode_sense_6 cmd; + scsi_mode_param_header_6 header; + size_t len; + void *buffer; + scsi_modepage_audio *page; + status_t res; + + SHOW_FLOW0( 3, "" ); + + // determine size of block descriptor + memset( &cmd, 0, sizeof( cmd )); + cmd.opcode = SCSI_OP_MODE_SENSE_6; + cmd.page_code = SCSI_MODEPAGE_AUDIO; + cmd.PC = SCSI_MODE_SENSE_PC_CURRENT; + cmd.allocation_length = sizeof( header ); + + memset( &header, -2, sizeof( header )); + + res = scsi_periph->simple_exec( device->scsi_periph_device, &cmd, sizeof( cmd ), + &header, sizeof( header ), SCSI_DIR_IN ); + if( res != B_OK ) + return res; + + SHOW_FLOW( 0, "block_desc_len=%d", header.block_desc_len ); + return B_ERROR; + + // retrieve param header, block descriptor and actual codepage + len = sizeof( header ) + header.block_desc_len + + sizeof( scsi_modepage_audio ); + + buffer = malloc( len ); + if( buffer == NULL ) + return B_NO_MEMORY; + + memset( buffer, -1, sizeof( buffer )); + + cmd.allocation_length = len; + + res = scsi_periph->simple_exec( device->scsi_periph_device, &cmd, sizeof( cmd ), + buffer, len, SCSI_DIR_IN ); + if( res != B_OK ) { + free( buffer ); + return res; + } + + SHOW_FLOW( 3, "mode_data_len=%d, block_desc_len=%d", + ((scsi_mode_param_header_6 *)buffer)->mode_data_len, + ((scsi_mode_param_header_6 *)buffer)->block_desc_len ); + + // find control page and retrieve values + page = (scsi_modepage_audio *)((char *)buffer + sizeof( header ) + header.block_desc_len); + + SHOW_FLOW( 0, "page=%p, codepage=%d", page, page->header.page_code ); + + if( !set ) { + volume->port0_channel = page->ports[0].channel; + volume->port0_volume = page->ports[0].volume; + volume->port1_channel = page->ports[1].channel; + volume->port1_volume = page->ports[1].volume; + volume->port2_channel = page->ports[2].channel; + volume->port2_volume = page->ports[2].volume; + volume->port3_channel = page->ports[3].channel; + volume->port3_volume = page->ports[3].volume; + + SHOW_FLOW( 3, "1: %d - %d", volume->port0_channel, volume->port0_volume ); + SHOW_FLOW( 3, "2: %d - %d", volume->port1_channel, volume->port1_volume ); + SHOW_FLOW( 3, "3: %d - %d", volume->port2_channel, volume->port2_volume ); + SHOW_FLOW( 3, "4: %d - %d", volume->port3_channel, volume->port3_volume ); + + res = B_OK; + + } else { + scsi_cmd_mode_select_6 cmd; + + if( volume->flags & 1 ) + page->ports[0].channel = volume->port0_channel; + if( volume->flags & 2 ) + page->ports[0].volume = volume->port0_volume; + if( volume->flags & 4 ) + page->ports[1].channel = volume->port1_channel; + if( volume->flags & 8 ) + page->ports[1].volume = volume->port1_volume; + if( volume->flags & 0x10 ) + page->ports[2].channel = volume->port2_channel; + if( volume->flags & 0x20 ) + page->ports[2].volume = volume->port2_volume; + if( volume->flags & 0x40 ) + page->ports[3].channel = volume->port3_channel; + if( volume->flags & 0x80 ) + page->ports[3].volume = volume->port3_volume; + + memset( &cmd, 0, sizeof( cmd )); + cmd.opcode = SCSI_OP_MODE_SELECT_6; + cmd.PF = 1; + cmd.param_list_length = sizeof( header ) + header.block_desc_len + + sizeof( *page ); + + res = scsi_periph->simple_exec( device->scsi_periph_device, + &cmd, sizeof( cmd ), + buffer, len, SCSI_DIR_OUT ); + } + + free( buffer ); + return res; +} + + +// play audio cd; time is in MSF +static status_t play_msf( cd_device_info *device, scsi_play_position *buf ) +{ + scsi_cmd_play_msf cmd; + + SHOW_FLOW( 0, "%d:%d:%d-%d:%d:%d", + buf->start_m, buf->start_s, buf->start_f, + buf->end_m, buf->end_s, buf->end_f ); + + memset( &cmd, 0, sizeof( cmd )); + + cmd.opcode = SCSI_OP_PLAY_MSF; + cmd.start_minute = buf->start_m; + cmd.start_second = buf->start_s; + cmd.start_frame = buf->start_f; + cmd.end_minute = buf->end_m; + cmd.end_second = buf->end_s; + cmd.end_frame = buf->end_f; + + return scsi_periph->simple_exec( device->scsi_periph_device, + &cmd, sizeof( cmd ), + NULL, 0, 0 ); +} + + +// play audio cd; time is in track/index +static status_t play_track_index( cd_device_info *device, scsi_play_track *buf ) +{ + scsi_toc generic_toc; + scsi_toc_toc *toc; + status_t res; + int start_track, end_track; + scsi_play_position position; + + SHOW_FLOW( 0, "%d-%d", buf->start_track, buf->end_track ); + + // the corresponding command PLAY AUDIO TRACK/INDEX is deprecated, + // so we have to simulate it by converting track to time via TOC + res = get_toc( device, &generic_toc ); + if( res != B_OK ) + return res; + + toc = (scsi_toc_toc *)&generic_toc.toc_data[0]; + + start_track = buf->start_track; + end_track = buf->end_track; + + if( start_track > toc->last_track ) + return B_BAD_INDEX; + + if( end_track > toc->last_track ) + end_track = toc->last_track + 1; + + if( end_track < toc->last_track + 1) + ++end_track; + + start_track -= toc->first_track; + end_track -= toc->first_track; + + if( start_track < 0 || end_track < 0 ) + return B_BAD_INDEX; + + position.start_m = toc->tracks[start_track].start.time.minute; + position.start_s = toc->tracks[start_track].start.time.second; + position.start_f = toc->tracks[start_track].start.time.frame; + + position.end_m = toc->tracks[end_track].start.time.minute; + position.end_s = toc->tracks[end_track].start.time.second; + position.end_f = toc->tracks[end_track].start.time.frame; + + return play_msf( device, &position ); +} + +static status_t stop_audio( cd_device_info *device ) +{ + scsi_cmd_stop_play cmd; + + SHOW_FLOW0( 3, "" ); + + memset( &cmd, 0, sizeof( cmd )); + cmd.opcode = SCSI_OP_STOP_PLAY; + + return scsi_periph->simple_exec( device->scsi_periph_device, + &cmd, sizeof( cmd ), NULL, 0, 0 ); +} + +static status_t pause_resume( cd_device_info *device, bool resume ) +{ + scsi_cmd_pause_resume cmd; + + SHOW_FLOW0( 3, "" ); + + memset( &cmd, 0, sizeof( cmd )); + cmd.opcode = SCSI_OP_PAUSE_RESUME; + cmd.resume = resume; + + return scsi_periph->simple_exec( device->scsi_periph_device, + &cmd, sizeof( cmd ), NULL, 0, 0 ); +} + +static status_t scan( cd_device_info *device, scsi_scan *buf ) +{ + scsi_cmd_scan cmd; + scsi_position cur_pos; + scsi_cd_current_position *cd_pos; + status_t res; + /*uint8 *tmp;*/ + + SHOW_FLOW( 3, "direction=%d", buf->direction ); + + res = get_position( device, &cur_pos ); + if( res != B_OK ) + return res; + + cd_pos = (scsi_cd_current_position *)((char *)&cur_pos + + sizeof( scsi_subchannel_data_header )); + + if( buf->direction == 0 ) { + scsi_play_position play_pos; + + // to stop scan, we issue play command with "open end" + play_pos.start_m = cd_pos->absolute_address.time.minute; + play_pos.start_s = cd_pos->absolute_address.time.second; + play_pos.start_f = cd_pos->absolute_address.time.frame; + play_pos.end_m = 99; + play_pos.end_s = 59; + play_pos.end_f = 24; + + return play_msf( device, &play_pos ); + } + + memset( &cmd, 0, sizeof( cmd )); + + cmd.opcode = SCSI_OP_SCAN; + cmd.Direct = buf->direction < 0; + cmd.start.time = cd_pos->absolute_address.time; + cmd.type = scsi_scan_msf; + + /* + tmp = (uint8 *)&cmd; + dprintf( "%d %d %d %d %d %d %d %d %d %d %d %d\n", + tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5], + tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11] ); + */ + + return scsi_periph->simple_exec( device->scsi_periph_device, + &cmd, sizeof( cmd ), NULL, 0, 0 ); +} + + +static status_t read_cd( cd_device_info *device, scsi_read_cd *read_cd ) +{ + scsi_cmd_read_cd *cmd; + uint32 lba, length; + scsi_ccb *ccb; + status_t res; + + // we use safe_exec instead of simple_exec as we want to set + // the sorting order manually (only makes much sense if you grab + // multiple tracks at once, but we are prepared) + ccb = device->scsi->alloc_ccb( device->scsi_device ); + + if( ccb == NULL ) + return B_NO_MEMORY; + + cmd = (scsi_cmd_read_cd *)ccb->cdb; + memset( cmd, 0, sizeof( *cmd )); + cmd->opcode = SCSI_OP_READ_CD; + cmd->sector_type = 1; + + // skip first two seconds, they are lead-in + lba = (read_cd->start_m * 60 + read_cd->start_s) * 75 + read_cd->start_f + - 2 * 75; + + cmd->lba.top = (lba >> 24) & 0xff; + cmd->lba.high = (lba >> 16) & 0xff; + cmd->lba.mid = (lba >> 8) & 0xff; + cmd->lba.low = lba & 0xff; + + length = (read_cd->length_m * 60 + read_cd->length_s) * 75 + read_cd->length_f; + + cmd->high_length = (length >> 16) & 0xff; + cmd->mid_length = (length >> 8) & 0xff; + cmd->low_length = length & 0xff; + + cmd->error_field = scsi_read_cd_error_none; + cmd->EDC_ECC = 0; + cmd->user_data = 1; + cmd->header_code = scsi_read_cd_header_none; + cmd->SYNC = 0; + cmd->sub_channel_selection = scsi_read_cd_sub_channel_none; + + ccb->cdb_len = sizeof( *cmd ); + + ccb->flags = SCSI_DIR_IN | SCSI_DIS_DISCONNECT; + ccb->sort = lba; + // are 10 seconds enough for timeout? + ccb->timeout = 10; + + ccb->data = read_cd->buffer; + ccb->sg_list = NULL; + ccb->data_len = read_cd->buffer_length; + + res = scsi_periph->safe_exec( device->scsi_periph_device, ccb ); + + device->scsi->free_ccb( ccb ); + + return res; +} + + +static status_t cd_ioctl( cd_handle_info *handle, int op, void *buf, size_t len ) +{ + cd_device_info *device = handle->device; + status_t res; + + switch( op ) { + case B_GET_DEVICE_SIZE: + res = update_capacity( device ); + if( res == B_OK ) + (size_t *)buf = device->capacity * device->block_size; + break; + + case B_GET_GEOMETRY: + res = get_geometry( handle, buf, len ); + break; + + case B_GET_ICON: + res = scsi_periph->get_icon( icon_type_cd, (device_icon *)buf ); + break; + + case B_SCSI_GET_TOC: + res = get_toc( device, (scsi_toc *)buf ); + break; + + case B_EJECT_DEVICE: + case B_SCSI_EJECT: + res = load_eject( device, false ); + break; + + case B_LOAD_MEDIA: + res = load_eject( device, true ); + break; + + case B_SCSI_GET_POSITION: + res = get_position( device, buf ); + break; + + case B_SCSI_GET_VOLUME: + res = get_set_volume( device, buf, false ); + break; + + case B_SCSI_SET_VOLUME: + res = get_set_volume( device, buf, true ); + break; + + case B_SCSI_PLAY_TRACK: + res = play_track_index( device, buf ); + break; + + case B_SCSI_PLAY_POSITION: + res = play_msf( device, buf ); + break; + + case B_SCSI_STOP_AUDIO: + res = stop_audio( device ); + break; + + case B_SCSI_PAUSE_AUDIO: + res = pause_resume( device, false ); + break; + + case B_SCSI_RESUME_AUDIO: + res = pause_resume( device, true ); + break; + + case B_SCSI_SCAN: + res = scan( device, buf ); + break; + + case B_SCSI_READ_CD: + res = read_cd( device, buf ); + break; + + default: + res = scsi_periph->ioctl( handle->scsi_periph_handle, op, buf, len ); + break; + } + + SHOW_FLOW( 3, "%x: %s", op, strerror( res )); + + return res; +} + + +static int log2( uint32 x ) +{ + int y; + + for( y = 31; y >= 0; --y ) + if( x == ((uint32)1 << y) ) + break; + + return y; +} + + +static void cd_set_capacity( cd_device_info *device, uint64 capacity, + uint32 block_size ) +{ + uint32 ld_block_size; + + SHOW_FLOW( 3, "device=%p, capacity=%Ld, block_size=%ld", + device, capacity, block_size ); + + // get log2, if possible + ld_block_size = log2( block_size ); + + if( (1UL << ld_block_size) != block_size ) + ld_block_size = 0; + + device->capacity = capacity; + device->block_size = block_size; + + blkman->set_media_params( device->blkman_device, block_size, + ld_block_size, capacity ); +} + +static void cd_media_changed( cd_device_info *device, scsi_ccb *request ) +{ + // do a capacity check + // TBD: is this a good idea (e.g. if this is an empty CD)? + scsi_periph->check_capacity( device->scsi_periph_device, request ); +} + + +scsi_periph_callbacks callbacks = { + (void (*)( periph_device_cookie, + uint64, uint32 )) cd_set_capacity, + (void (*)( periph_device_cookie, scsi_ccb *)) cd_media_changed +}; + + +static status_t +std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +module_dependency module_dependencies[] = { + { SCSI_PERIPH_MODULE_NAME, (module_info **)&scsi_periph }, + { BLKMAN_FOR_DRIVER_MODULE_NAME, (module_info **)&blkman }, + { DEVICE_MANAGER_MODULE_NAME, (module_info **)&pnp }, + {} +}; + +blkdev_interface scsi_cd_module = { + { + { + SCSI_CD_MODULE_NAME, + 0, + std_ops + }, + + cd_init_device, + (status_t (*) (void *))cd_uninit_device, + cd_device_added, + NULL + }, + + (status_t (*)( blkdev_device_cookie, blkdev_handle_cookie * )) &cd_open, + (status_t (*)( blkdev_handle_cookie )) &cd_close, + (status_t (*)( blkdev_handle_cookie )) &cd_free, + + (status_t (*)( blkdev_handle_cookie, const phys_vecs *, + off_t, size_t, uint32, size_t * )) &cd_read, + (status_t (*)( blkdev_handle_cookie, const phys_vecs *, + off_t, size_t, uint32, size_t * )) &cd_write, + + (status_t (*)( blkdev_handle_cookie, int, void *, size_t )) &cd_ioctl, +}; + +#if !_BUILDING_kernel && !BOOT +_EXPORT +module_info *modules[] = +{ + (module_info *)&scsi_cd_module, + NULL +}; +#endif diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h new file mode 100644 index 0000000000..b7edd4bc29 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.h @@ -0,0 +1,23 @@ +/* +** Copyright 2003, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI CD Driver + + Peripheral driver to handle CD-ROM drives. To be more + precisely, it supports CD-ROM and WORM drives (well - + I've never _seen_ a WORM driver). + + Much work is done by scsi_periph and blkdev. +*/ + +#ifndef _SCSI_CD_H +#define _SCSI_CD_H + +#include + +#define SCSI_CD_MODULE_NAME "drivers/disk/scsi/scsi_cd/"SCSI_DEVICE_TYPE_NAME + +#endif diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd_int.h b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd_int.h new file mode 100644 index 0000000000..70d3cd5e2e --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd_int.h @@ -0,0 +1,60 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open IDE CD-ROM driver +*/ + +#include +#include "scsi_cd.h" +#include +#include + +#define debug_level_flow 0 +#define debug_level_error 3 +#define debug_level_info 3 + +#define DEBUG_MSG_PREFIX "SCSI_CD -- " + +#include "wrapper.h" + + +typedef struct cd_device_info { + pnp_node_handle node; + scsi_periph_device scsi_periph_device; + scsi_device scsi_device; + scsi_device_interface *scsi; + blkman_device blkman_device; + + uint64 capacity; + uint32 block_size; + + bool removable; + uint8 device_type; +} cd_device_info; + +typedef struct cd_handle_info { + scsi_periph_handle scsi_periph_handle; + cd_device_info *device; +} cd_handle_info; + +extern scsi_periph_interface *scsi_periph; +extern device_manager_info *pnp; +extern scsi_periph_callbacks callbacks; +extern blkman_for_driver_interface *blkman; + + +// device_mgr.c + +status_t cd_init_device( pnp_node_handle node, void *user_cookie, void **cookie ); +status_t cd_uninit_device( cd_device_info *device ); +status_t cd_device_added( pnp_node_handle node ); + + +// handle_mgr.c + +status_t cd_open( cd_device_info *device, cd_handle_info **handle_out ); +status_t cd_close( cd_handle_info *handle ); +status_t cd_free( cd_handle_info *handle ); diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/wrapper.h b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/wrapper.h new file mode 100644 index 0000000000..6793cb73be --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/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 */ diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/Jamfile b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/Jamfile new file mode 100644 index 0000000000..b2fda23f37 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/Jamfile @@ -0,0 +1,9 @@ +SubDir OBOS_TOP src add-ons kernel drivers disk scsi scsi_dsk ; + +UsePrivateHeaders kernel ; + +KernelAddon scsi_dsk : kernel drivers bin : + device.c + handle.c + scsi_dsk.c + ; diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/device.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/device.c new file mode 100644 index 0000000000..6b3f923614 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/device.c @@ -0,0 +1,173 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + Device management. +*/ + +#include "scsi_dsk_int.h" + +#include +#include +#include + + +status_t +das_init_device(pnp_node_handle node, void *user_cookie, void **cookie) +{ + das_device_info *device; + status_t res; + scsi_ccb *request; + + SHOW_FLOW0( 3, "" ); + + device = (das_device_info *)malloc(sizeof(*device)); + if (device == NULL) + return B_NO_MEMORY; + + memset(device, 0, sizeof(*device)); + + device->blkman_device = user_cookie; + device->node = node; + + res = pnp->get_attr_uint8(node, "removable", + &device->removable, false); + if (res != B_OK) + goto err1; + + // register it everywhere + res = pnp->load_driver(pnp->get_parent(node), NULL, (pnp_driver_info **)&device->scsi, + (void **)&device->scsi_device); + if (res != B_OK) + goto err2; + + res = scsi_periph->register_device((periph_device_cookie)device, &callbacks, + device->scsi_device, device->scsi, device->node, device->removable, + &device->scsi_periph_device); + if (res != B_OK) + goto err3; + + // and get (initial) capacity + request = device->scsi->alloc_ccb(device->scsi_device); + if (request == NULL) { + res = B_NO_MEMORY; + goto err4; + } + + scsi_periph->check_capacity(device->scsi_periph_device, request); + + device->scsi->free_ccb(request); + + SHOW_FLOW0(3, "done"); + + *cookie = device; + return B_OK; + +err4: + scsi_periph->unregister_device(device->scsi_periph_device); +err3: + pnp->unload_driver(pnp->get_parent(node)); +err2: +err1: + free(device); + return res; +} + + +status_t +das_uninit_device(das_device_info *device) +{ + scsi_periph->unregister_device(device->scsi_periph_device); + pnp->unload_driver(pnp->get_parent(device->node)); + free(device); + + return B_OK; +} + + +/** called whenever a new device was added to system; + * if we really support it, we create a new node that gets + * server by blkdev + */ + +status_t +das_device_added(pnp_node_handle node) +{ + char *str = NULL; + scsi_res_inquiry *device_inquiry = NULL; + uint8 device_type; + size_t inquiry_len; + char *name; + uint32 max_blocks; + + // make sure we can handle this parent device + if (pnp->get_attr_string(node, PNP_DRIVER_TYPE, &str, false) != B_OK + || strcmp(str, SCSI_DEVICE_TYPE_NAME) != 0) + goto err; + + // check whether it's really a Direct Access Device + if (pnp->get_attr_uint8(node, SCSI_DEVICE_TYPE_ITEM, &device_type, true) != B_OK + || device_type != scsi_dev_direct_access) + goto err; + + // get inquiry data + if (pnp->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM, + (void **)&device_inquiry, &inquiry_len, true) != B_OK + || inquiry_len < sizeof(device_inquiry)) + goto err; + + // get block limit of underlying hardware to lower it (if necessary) + if (pnp->get_attr_uint32(node, BLKDEV_MAX_BLOCKS_ITEM, &max_blocks, true) != B_OK) + max_blocks = INT_MAX; + + // using 10 byte commands, at most 0xffff blocks can be transmitted at once + // (sadly, we cannot update this value later on if only 6 byte commands + // are supported, but the blkdev can live with that) + max_blocks = min(max_blocks, 0xffff); + + name = scsi_periph->compose_device_name(node, "disk/scsi"); + if (name == NULL) { + SHOW_ERROR0(2, "Cannot compose device name"); + goto err; + } + + SHOW_FLOW(3, "name=%s", name); + + // ready to register + { + pnp_node_attr attrs[] = + { + { PNP_DRIVER_DRIVER, B_STRING_TYPE, { string: SCSI_DSK_MODULE_NAME }}, + { PNP_DRIVER_TYPE, B_STRING_TYPE, { string: BLKDEV_TYPE_NAME }}, + // we always want blkdev on top of us + { PNP_DRIVER_FIXED_CONSUMER, B_STRING_TYPE, { string: BLKMAN_MODULE_NAME }}, + // tell blkdev whether the device is removable + { "removable", B_UINT8_TYPE, { ui8: device_inquiry->RMB }}, + // tell which name we want to have in devfs + { PNP_DEVFS_FILENAME, B_STRING_TYPE, { string: name }}, + // impose own max block restriction + { BLKDEV_MAX_BLOCKS_ITEM, B_UINT32_TYPE, { ui32: max_blocks }}, + // in general, any disk can be a BIOS drive (even ZIP-disks) + { BLKDEV_IS_BIOS_DRIVE, B_UINT8_TYPE, { ui8: 1 }}, + { NULL } + }; + + status_t res = pnp->register_device(node, attrs, NULL, &node); + + free(name); + free(str); + free(device_inquiry); + + return res; + } + +err: + free(str); + free(device_inquiry); + + return B_ERROR; +} diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/handle.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/handle.c new file mode 100644 index 0000000000..dfc937df37 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/handle.c @@ -0,0 +1,64 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + File handle management. +*/ + +#include "scsi_dsk_int.h" + +#include + +status_t das_open( das_device_info *device, das_handle_info **handle_out ) +{ + das_handle_info *handle; + int res; + + SHOW_FLOW0( 3, "" ); + + handle = (das_handle_info *)malloc( sizeof( *handle )); + if( handle == NULL ) + return B_NO_MEMORY; + + handle->device = device; + + res = scsi_periph->handle_open( device->scsi_periph_device, + (periph_handle_cookie)handle, + &handle->scsi_periph_handle ); + if( res < 0 ) { + free( handle ); + return res; + } + + SHOW_FLOW0( 3, "opened" ); + + *handle_out = handle; + return B_OK; +} + +status_t das_close( das_handle_info *handle ) +{ + SHOW_FLOW0( 3, "" ); + + scsi_periph->handle_close( handle->scsi_periph_handle ); + + SHOW_FLOW0( 3, "done" ); + + return B_OK; +} + +status_t das_free( das_handle_info *handle ) +{ + SHOW_FLOW0( 3, "" ); + + scsi_periph->handle_free( handle->scsi_periph_handle ); + free( handle ); + + SHOW_FLOW0( 3, "done" ); + + return B_OK; +} diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/io.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/io.c new file mode 100644 index 0000000000..a6b36c2004 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/io.c @@ -0,0 +1,211 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + Everything doing the real input/output stuff. +*/ + +#include "scsi_dsk_int.h" + +#define DAS_STD_TIMEOUT 10 + + +// we don't want to inline this function - it's just not worth it +static int das_read_write( das_handle_info *handle, const phys_vecs *vecs, + off_t pos, size_t num_blocks, uint32 block_size, size_t *bytes_transferred, + bool write ); + + +status_t das_read( das_handle_info *handle, const phys_vecs *vecs, + off_t pos, size_t num_blocks, uint32 block_size, size_t *bytes_transferred ) +{ + return das_read_write( handle, vecs, pos, + num_blocks, block_size, bytes_transferred, false ); +} + + +status_t das_write( das_handle_info *handle, const phys_vecs *vecs, + off_t pos, size_t num_blocks, uint32 block_size, size_t *bytes_transferred ) +{ + return das_read_write( handle, vecs, pos, + num_blocks, block_size, bytes_transferred, true ); +} + + +// universal read/write function +static int das_read_write( das_handle_info *handle, const phys_vecs *vecs, + off_t pos64, size_t num_blocks, uint32 block_size, size_t *bytes_transferred, + bool write ) +{ + das_device_info *device = handle->device; + scsi_ccb *request; + err_res res; + int retries = 0; + int err; + uint32 pos = pos64; + + // don't test rw10_enabled restrictions - this flag may get changed + request = device->scsi->alloc_ccb( device->scsi_device ); + + if( request == NULL ) + return B_NO_MEMORY; + + do { + size_t num_bytes; + bool is_rw10; + + request->flags = write ? SCSI_DIR_OUT : SCSI_DIR_IN; + + // make sure we avoid 10 byte commands if they aren't supported + if( !device->rw10_enabled ) { + // restricting transfer is OK - the block manager will + // take care of transferring the rest + if( num_blocks > 0x100 ) + num_blocks = 0x100; + + // no way to break the 21 bit address limit + if( pos64 > 0x200000 ) { + err = B_BAD_VALUE; + goto abort; + } + + // don't allow transfer cross the 24 bit address limit + // (I'm not sure whether this is allowed, but this way we + // are sure to not ask for trouble) + num_blocks = min( num_blocks, 0x100000 - pos ); + } + + num_bytes = num_blocks * block_size; + + request->data = NULL; + request->sg_list = vecs->vec; + request->data_len = num_bytes; + request->sglist_cnt = vecs->num; + request->sort = pos; + request->timeout = DAS_STD_TIMEOUT; + // see whether daemon instructed us to post an ordered command; + // reset flag after read + request->flags = atomic_and( &device->next_tag_action, 0 ); + + SHOW_FLOW( 3, "ordered: %s", + (request->flags & SCSI_ORDERED_QTAG) == 0 ? "yes" : "no" ); + + // use 6 byte commands whenever possible + if( pos + num_blocks < 0x200000 && num_blocks <= 0x100 ) { + scsi_cmd_rw_6 *cmd = (scsi_cmd_rw_6 *)request->cdb; + + is_rw10 = false; + + memset( cmd, 0, sizeof( *cmd )); + cmd->opcode = write ? SCSI_OP_WRITE_6 : SCSI_OP_READ_6; + cmd->high_LBA = (pos >> 16) & 0x1f; + cmd->mid_LBA = (pos >> 8) & 0xff; + cmd->low_LBA = pos & 0xff; + cmd->length = num_blocks; + + request->cdb_len = sizeof( *cmd ); + } else { + scsi_cmd_rw_10 *cmd = (scsi_cmd_rw_10 *)request->cdb; + + is_rw10 = true; + + memset( cmd, 0, sizeof( *cmd )); + cmd->opcode = write ? SCSI_OP_WRITE_10 : SCSI_OP_READ_10; + cmd->RelAdr = 0; + cmd->FUA = 0; + cmd->DPO = 0; + + cmd->top_LBA = (pos >> 24) & 0xff; + cmd->high_LBA = (pos >> 16) & 0xff; + cmd->mid_LBA = (pos >> 8) & 0xff; + cmd->low_LBA = pos & 0xff; + + cmd->high_length = (num_blocks >> 8) & 0xff; + cmd->low_length = num_blocks & 0xff; + + request->cdb_len = sizeof( *cmd ); + } + + // last chance to detect errors that occured during concurrent accesses + err = handle->pending_error; + + if( err ) + goto abort; + + device->scsi->scsi_io( request ); + + acquire_sem( request->completion_sem ); + + // ask generic peripheral layer what to do now + res = scsi_periph->check_error( device->scsi_periph_device, request ); + + switch( res.action ) { + case err_act_ok: + *bytes_transferred = num_bytes - request->data_resid; + break; + + case err_act_start: + res = scsi_periph->send_start_stop( + device->scsi_periph_device, request, 1, device->removable ); + if( res.action == err_act_ok ) + res.action = err_act_retry; + break; + + case err_act_invalid_req: + // if this was a 10 byte command, the device probably doesn't + // support them, so disable them and retry + if( is_rw10 ) { + atomic_and( &device->rw10_enabled, 0 ); + res.action = err_act_retry; + } else + res.action = err_act_fail; + break; + } + + } while( + (res.action == err_act_retry && retries++ < 3) || + (res.action == err_act_many_retries && retries++ < 30 )); + + device->scsi->free_ccb( request ); + + // peripheral layer only created "read" error, so we have to + // map them to "write" errors if this was a write request + if( res.error_code == B_DEV_READ_ERROR && write ) + return B_DEV_WRITE_ERROR; + else + return res.error_code; + +abort: + device->scsi->free_ccb( request ); + + return err; +} + + + +// kernel daemon +// once in a minute, it sets a flag so that the next command is executed +// ordered; this way, we avoid starvation of SCSI commands inside the +// SCSI queuing system - the ordered command waits for all previous +// commands and thus no command can starve longer then a minute +void das_sync_queue_daemon( void *arg, int iteration ) +{ + das_device_info *device = (das_device_info *)arg; + + atomic_or( &device->next_tag_action, SCSI_ORDERED_QTAG ); +} + +void das_handle_set_error( das_handle_info *handle, status_t error_code ) +{ + handle->pending_error = error_code; +} + + +status_t das_handle_get_error( das_handle_info *handle ) +{ + return handle->pending_error; +} diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk.c b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk.c new file mode 100644 index 0000000000..9baf956c6b --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk.c @@ -0,0 +1,278 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + Main file. + + You'll find das_... all over the place. This stands for + "Direct Access Storage" which is the official SCSI name for + normal (floppy/hard/ZIP)-disk drives. +*/ + +#include "scsi_dsk_int.h" +#include +#include + +extern blkdev_interface das_interface; + +scsi_periph_interface *scsi_periph; +device_manager_info *pnp; +blkman_for_driver_interface *blkman; + + +static status_t +das_read(das_handle_info *handle, const phys_vecs *vecs, off_t pos, + size_t num_blocks, uint32 block_size, size_t *bytes_transferred) +{ + return scsi_periph->read(handle->scsi_periph_handle, vecs, pos, + num_blocks, block_size, bytes_transferred, 10); +} + + +static status_t +das_write(das_handle_info *handle, const phys_vecs *vecs, off_t pos, + size_t num_blocks, uint32 block_size, size_t *bytes_transferred) +{ + return scsi_periph->write(handle->scsi_periph_handle, vecs, pos, + num_blocks, block_size, bytes_transferred, 10); +} + + +static status_t +update_capacity(das_device_info *device) +{ + scsi_ccb *ccb; + status_t res; + + SHOW_FLOW0( 3, "" ); + + ccb = device->scsi->alloc_ccb(device->scsi_device); + if (ccb == NULL) + return B_NO_MEMORY; + + res = scsi_periph->check_capacity(device->scsi_periph_device, ccb); + + device->scsi->free_ccb(ccb); + + return res; +} + + +static status_t +get_geometry(das_handle_info *handle, void *buf, size_t len) +{ + das_device_info *device = handle->device; + device_geometry *geometry = (device_geometry *)buf; + status_t res; + + SHOW_FLOW0( 3, "" ); + + res = update_capacity(device); + + if (res < B_OK) + return res; + + geometry->bytes_per_sector = device->block_size; + geometry->sectors_per_track = 1; + geometry->cylinder_count = device->capacity; + geometry->head_count = 1; + geometry->device_type = B_DISK; + geometry->removable = device->removable; + + // TBD: for all but CD-ROMs, read mode sense - medium type + // (bit 7 of block device specific parameter for Optical Memory Block Device) + // (same for Direct-Access Block Devices) + // (same for write-once block devices) + // (same for optical memory block devices) + geometry->read_only = false; + geometry->write_once = false; + + SHOW_FLOW(3, "%ld, %ld, %ld, %ld, %d, %d, %d, %d", + geometry->bytes_per_sector, + geometry->sectors_per_track, + geometry->cylinder_count, + geometry->head_count, + geometry->device_type, + geometry->removable, + geometry->read_only, + geometry->write_once); + + SHOW_FLOW0(3, "done"); + + return B_OK; +} + + +static status_t +load_eject(das_device_info *device, bool load) +{ + scsi_ccb *ccb; + err_res res; + + SHOW_FLOW0( 0, "" ); + + ccb = device->scsi->alloc_ccb(device->scsi_device); + + res = scsi_periph->send_start_stop(device->scsi_periph_device, + ccb, load, true); + + device->scsi->free_ccb(ccb); + + return res.error_code; +} + + +static int +log2(uint32 x) +{ + int y; + + for (y = 31; y >= 0; --y) + if (x == ((uint32)1 << y)) + break; + + return y; +} + + +void +das_set_capacity(das_device_info *device, uint64 capacity, + uint32 block_size) +{ + uint32 ld_block_size; + + SHOW_FLOW(3, "device=%p, capacity=%Ld, block_size=%ld", + device, capacity, block_size); + + // get log2, if possible + ld_block_size = log2(block_size); + + if ((1UL << ld_block_size) != block_size) + ld_block_size = 0; + + device->capacity = capacity; + device->block_size = block_size; + + blkman->set_media_params(device->blkman_device, block_size, + ld_block_size, capacity); +} + + +static void +das_media_changed(das_device_info *device, scsi_ccb *request) +{ + // do a capacity check + // TBD: is this a good idea (e.g. if this is an empty CD)? + scsi_periph->check_capacity(device->scsi_periph_device, request); +} + + +scsi_periph_callbacks callbacks = { + (void (*)( periph_device_cookie, + uint64, uint32 )) das_set_capacity, + (void (*)( periph_device_cookie, scsi_ccb *)) das_media_changed +}; + + +static status_t +das_ioctl(das_handle_info *handle, int op, void *buf, size_t len) +{ + das_device_info *device = handle->device; + status_t res; + + SHOW_FLOW( 4, "%d", op ); + + switch( op ) { + case B_GET_DEVICE_SIZE: + res = update_capacity( device ); + if( res == B_OK ) + (size_t *)buf = device->capacity * device->block_size; + break; + + case B_GET_GEOMETRY: + res = get_geometry( handle, buf, len ); + break; + + case B_GET_ICON: + res = scsi_periph->get_icon( device->removable ? icon_type_floppy : icon_type_disk, + (device_icon *)buf ); + break; + + case B_EJECT_DEVICE: + case B_SCSI_EJECT: + res = load_eject( device, false ); + break; + + case B_LOAD_MEDIA: + res = load_eject( device, true ); + break; + + default: + res = scsi_periph->ioctl( handle->scsi_periph_handle, op, buf, len ); + } + + SHOW_FLOW( 4, "%s", strerror( res )); + + return res; +} + + +static status_t +std_ops(int32 op, ...) +{ + switch (op) { + case B_MODULE_INIT: + case B_MODULE_UNINIT: + return B_OK; + + default: + return B_ERROR; + } +} + + +module_dependency module_dependencies[] = { + { SCSI_PERIPH_MODULE_NAME, (module_info **)&scsi_periph }, + { BLKMAN_FOR_DRIVER_MODULE_NAME, (module_info **)&blkman }, + { DEVICE_MANAGER_MODULE_NAME, (module_info **)&pnp }, + {} +}; + +blkdev_interface scsi_dsk_module = { + { + { + SCSI_DSK_MODULE_NAME, + 0, + std_ops + }, + + das_init_device, + (status_t (*) (void *))das_uninit_device, + das_device_added, + NULL + }, + + (status_t (*)( blkdev_device_cookie, blkdev_handle_cookie * )) &das_open, + (status_t (*)( blkdev_handle_cookie )) &das_close, + (status_t (*)( blkdev_handle_cookie )) &das_free, + + (status_t (*)( blkdev_handle_cookie, const phys_vecs *, + off_t, size_t, uint32, size_t * )) &das_read, + (status_t (*)( blkdev_handle_cookie, const phys_vecs *, + off_t, size_t, uint32, size_t * )) &das_write, + + (status_t (*)( blkdev_handle_cookie, int, void *, size_t )) &das_ioctl, +}; + +#if !_BUILDING_kernel && !BOOT +_EXPORT +module_info *modules[] = +{ + (module_info *)&scsi_dsk_module, + NULL +}; +#endif diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk.h b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk.h new file mode 100644 index 0000000000..3de7442467 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk.h @@ -0,0 +1,22 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + Peripheral driver to handle any kind of SCSI disks, + i.e. hard disk and floopy disks (ZIP etc.) + + Much work is done by scsi_periph and blkdev. +*/ + +#ifndef _SCSI_DSK_H +#define _SCSI_DSK_H + +#include + +#define SCSI_DSK_MODULE_NAME "drivers/disk/scsi/scsi_dsk/"SCSI_DEVICE_TYPE_NAME + +#endif diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk_int.h b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk_int.h new file mode 100644 index 0000000000..4e1d9b7e52 --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/scsi_dsk_int.h @@ -0,0 +1,67 @@ +/* +** Copyright 2002/03, Thomas Kurschel. All rights reserved. +** Distributed under the terms of the OpenBeOS License. +*/ + +/* + Part of Open SCSI Disk Driver + + SCSI Direct Access Storage Device Driver (aka SCSI Disk Driver) +*/ + +#include +#include "scsi_dsk.h" +#include +#include + +#define debug_level_flow 4 +#define debug_level_error 4 +#define debug_level_info 4 + +#define DEBUG_MSG_PREFIX "SCSI_DSK -- " + +#include "wrapper.h" + + +typedef struct das_device_info { + pnp_node_handle node; + scsi_periph_device scsi_periph_device; + scsi_device scsi_device; + scsi_device_interface *scsi; + blkman_device blkman_device; + + uint64 capacity; + uint32 block_size; + + bool removable; // true, if device is removable +} das_device_info; + +typedef struct das_handle_info { + scsi_periph_handle scsi_periph_handle; + das_device_info *device; +} das_handle_info; + +extern scsi_periph_interface *scsi_periph; +extern device_manager_info *pnp; +extern scsi_periph_callbacks callbacks; +extern blkman_for_driver_interface *blkman; + + +// device_mgr.c + +status_t das_init_device(pnp_node_handle node, void *user_cookie, void **cookie); +status_t das_uninit_device(das_device_info *device); +status_t das_device_added(pnp_node_handle node); + + +// handle_mgr.c + +status_t das_open(das_device_info *device, das_handle_info **handle_out); +status_t das_close(das_handle_info *handle); +status_t das_free(das_handle_info *handle); + + +// scsi_dsk.c + +void das_set_capacity(das_device_info *device, uint64 capacity, + uint32 block_size); diff --git a/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/wrapper.h b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/wrapper.h new file mode 100644 index 0000000000..6793cb73be --- /dev/null +++ b/src/add-ons/kernel/drivers/disk/scsi/scsi_dsk/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 */