/* * Copyright 2004-2007, Haiku, Inc. All RightsReserved. * Copyright 2002/03, Thomas Kurschel. All rights reserved. * * Distributed under the terms of the MIT License. */ /*! 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 #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_TEST_UNIT_READY; worker_req->sg_list = NULL; worker_req->data = NULL; worker_req->data_length = 0; worker_req->cdb_length = sizeof(*cmd); worker_req->timeout = 0; worker_req->sort = -1; worker_req->flags = SCSI_DIR_NONE; 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_length = sizeof(*new_inquiry_data); worker_req->cdb_length = 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->removable_medium, 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 (std::min((int)cmd->allocation_length, new_inquiry_data->additional_length + 4) >= (int)offsetof(scsi_res_inquiry, _res74)) { int i, previousStandard = -1; for (i = 0; i < 8; ++i) { int standard = B_BENDIAN_TO_HOST_INT16( new_inquiry_data->version_descriptor[i]); // omit standards reported twice if (standard != previousStandard && standard != 0) SHOW_INFO(3, "standard: %04x", standard); previousStandard = standard; } } //snooze( 1000000 ); /* { unsigned int i; for( i = 0; i < worker_req->data_length - 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 res = scsi_register_device(bus, target_id, target_lun, &new_inquiry_data); if (res == B_NAME_IN_USE) { SHOW_FLOW0(3, "name in use"); if (scsi_force_get_device(bus, target_id, target_lun, &device) != B_OK) return B_OK; // the device was already registered, let's tell our child to rescan it device_node *childNode = NULL; const device_attr attrs[] = { { NULL } }; if (pnp->get_next_child_node(bus->node, attrs, &childNode) == B_OK) { pnp->rescan_node(childNode); pnp->put_node(childNode); } scsi_put_forced_device(device); } 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) { scsi_path_inquiry inquiry; SHOW_FLOW0( 3, "" ); // get ID of initiator (i.e. controller) uchar res = scsi_inquiry_path(bus, &inquiry); if (res != SCSI_REQ_CMP) return B_ERROR; uint 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 (uint target_id = 0; target_id < bus->max_target_count; ++target_id) { 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 (uint lun = 0; lun < bus->max_lun_count; ++lun) { SHOW_FLOW(3, "lun: %d", lun); status_t status = 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 && status != B_OK) break; } } SHOW_FLOW0(3, "done"); return B_OK; }