/* * Copyright 2004-2006, Haiku, Inc. All RightsReserved. * Copyright 2002/03, Thomas Kurschel. All rights reserved. * * Distributed under the terms of the MIT License. */ /* 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 /** 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); mutex_destroy(&device->dma_buffer_lock); delete_sem(device->dma_buffer_owner); 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); if (target_lun != 0) dprintf("WARNING: SCSI target %d lun %d getting restrictions without lun\n", target_id, target_lun); // 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, B_DMA_MAX_TRANSFER_BLOCKS, &orig_max_blocks, true); max_blocks = std::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]; device_attr attrs[] = { // connection { SCSI_DEVICE_TARGET_ID_ITEM, B_UINT8_TYPE, { .ui8 = target_id }}, { SCSI_DEVICE_TARGET_LUN_ITEM, B_UINT8_TYPE, { .ui8 = target_lun }}, // inquiry data (used for both identification and information) { SCSI_DEVICE_INQUIRY_ITEM, B_RAW_TYPE, { .raw = { inquiry_data, sizeof( *inquiry_data ) }}}, // 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 { B_DEVICE_BUS, B_STRING_TYPE, { .string = "scsi" }}, // extra restriction of maximum number of blocks per transfer { B_DMA_MAX_TRANSFER_BLOCKS, 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 } }; 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)); return pnp->register_node(bus->node, SCSI_DEVICE_MODULE_NAME, attrs, NULL, NULL); } return B_OK; } // create data structure for a device static scsi_device_info * scsi_create_device(device_node *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); mutex_init(&device->dma_buffer_lock, "dma_buffer"); device->dma_buffer_owner = create_sem(1, "dma_buffer"); if (device->dma_buffer_owner < 0) goto err; register_kernel_daemon(scsi_dma_buffer_daemon, device, 5 * 10); return device; err: mutex_destroy(&device->dma_buffer_lock); 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; unsigned 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_32_BIT_FULL_LOCK, B_KERNEL_READ_AREA | B_KERNEL_WRITE_AREA); // TODO: Use B_FULL_LOCK, if addresses >= 4 GB are supported! if (device->auto_sense_area < 0) goto err; request->data = buffer; request->data_length = SCSI_MAX_SENSE_SIZE; request->sg_list = (physical_entry *)(buffer + SCSI_MAX_SENSE_SIZE); request->sg_count = 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_length = sizeof(*cmd); memset(cmd, 0, sizeof(*cmd)); cmd->opcode = SCSI_OP_REQUEST_SENSE; cmd->lun = device->target_lun; cmd->allocation_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(device_node *node, void **cookie) { const scsi_res_inquiry *inquiry_data = NULL; uint8 target_id, target_lun, path_id; scsi_bus_info *bus; scsi_device_info *device; status_t res; 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, (const void **)&inquiry_data, &inquiry_data_len, false) != B_OK || inquiry_data_len != sizeof(*inquiry_data)) { return B_ERROR; } { device_node *parent = pnp->get_parent_node(node); pnp->get_driver(parent, NULL, (void **)&bus); pnp->put_node(parent); } device = scsi_create_device(node, bus, target_id, target_lun); if (device == NULL) return B_NO_MEMORY; // never mind if there is no path - it might be an emulated controller path_id = (uint8)-1; pnp->get_attr_uint8(node, SCSI_BUS_PATH_ID_ITEM, &path_id, true); 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); } *cookie = device; return B_OK; err: scsi_free_device(device); return res; } static void scsi_uninit_device(scsi_device_info *device) { SHOW_FLOW0(3, ""); scsi_free_device(device); } static void scsi_device_removed(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) { device_attr attrs[] = { { SCSI_DEVICE_TARGET_ID_ITEM, B_UINT8_TYPE, { .ui8 = target_id }}, { SCSI_DEVICE_TARGET_LUN_ITEM, B_UINT8_TYPE, { .ui8 = target_lun }}, { NULL } }; device_node *node; status_t res; driver_module_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 = NULL; pnp->get_next_child_node(bus->node, attrs, &node); SHOW_FLOW(3, "%p", node); if (node != NULL) { // TODO: have a second look a this one! // there is one - get it res = pnp->get_driver(node, &driver_interface, (void **)&device); if (res != B_OK) pnp->put_node(node); } 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->put_node(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 scsi_ioctl(scsi_device_info *device, uint32 op, void *buffer, size_t length) { if (device->bus->interface->ioctl != NULL) { return device->bus->interface->ioctl(device->bus->sim_cookie, device->target_id, op, buffer, length); } return B_DEV_INVALID_IOCTL; } static status_t std_ops(int32 op, ...) { switch (op) { case B_MODULE_INIT: { // 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. module_info *dummy; return get_module(SCSI_BUS_MODULE_NAME, &dummy); } case B_MODULE_UNINIT: return put_module(SCSI_BUS_MODULE_NAME); default: return B_ERROR; } } scsi_device_interface scsi_device_module = { { { SCSI_DEVICE_MODULE_NAME, 0, std_ops }, NULL, // supported devices NULL, // register node scsi_init_device, (void (*)(void *)) scsi_uninit_device, NULL, // register child devices NULL, // rescan (void (*)(void *)) scsi_device_removed }, scsi_alloc_ccb, scsi_free_ccb, scsi_async_io, scsi_sync_io, scsi_abort, scsi_reset_device, scsi_term_io, scsi_ioctl, };