1 /* 2 * Copyright 2013, Jérôme Duval, korli@users.berlios.de. 3 * Distributed under the terms of the MIT License. 4 */ 5 6 7 #include "VirtioSCSIPrivate.h" 8 9 #include <new> 10 #include <stdlib.h> 11 #include <strings.h> 12 13 #include <util/AutoLock.h> 14 15 16 const char * 17 get_feature_name(uint32 feature) 18 { 19 switch (feature) { 20 case VIRTIO_SCSI_F_INOUT: 21 return "in out"; 22 case VIRTIO_SCSI_F_HOTPLUG: 23 return "hotplug"; 24 case VIRTIO_SCSI_F_CHANGE: 25 return "change"; 26 } 27 return NULL; 28 } 29 30 31 VirtioSCSIController::VirtioSCSIController(device_node *node) 32 : 33 fNode(node), 34 fVirtio(NULL), 35 fVirtioDevice(NULL), 36 fStatus(B_NO_INIT), 37 fRequest(NULL), 38 fEventDPC(NULL) 39 { 40 CALLED(); 41 42 B_INITIALIZE_SPINLOCK(&fInterruptLock); 43 fInterruptCondition.Init(this, "virtio scsi transfer"); 44 45 if (gSCSI->alloc_dpc(&fEventDPC) != B_OK) 46 return; 47 48 // get the Virtio device from our parent's parent 49 device_node *parent = gDeviceManager->get_parent_node(node); 50 device_node *virtioParent = gDeviceManager->get_parent_node(parent); 51 gDeviceManager->put_node(parent); 52 53 gDeviceManager->get_driver(virtioParent, (driver_module_info **)&fVirtio, 54 (void **)&fVirtioDevice); 55 gDeviceManager->put_node(virtioParent); 56 57 fVirtio->negociate_features(fVirtioDevice, 58 VIRTIO_SCSI_F_CHANGE /*VIRTIO_SCSI_F_HOTPLUG*/, 59 &fFeatures, &get_feature_name); 60 61 fStatus = fVirtio->read_device_config(fVirtioDevice, 0, &fConfig, 62 sizeof(struct virtio_scsi_config)); 63 if (fStatus != B_OK) 64 return; 65 66 fConfig.sense_size = VIRTIO_SCSI_SENSE_SIZE; 67 fConfig.cdb_size = VIRTIO_SCSI_CDB_SIZE; 68 69 fVirtio->write_device_config(fVirtioDevice, 70 offsetof(struct virtio_scsi_config, sense_size), &fConfig.sense_size, 71 sizeof(fConfig.sense_size)); 72 fVirtio->write_device_config(fVirtioDevice, 73 offsetof(struct virtio_scsi_config, cdb_size), &fConfig.sense_size, 74 sizeof(fConfig.cdb_size)); 75 76 fRequest = new(std::nothrow) VirtioSCSIRequest(true); 77 if (fRequest == NULL) { 78 fStatus = B_NO_MEMORY; 79 return; 80 } 81 82 ::virtio_queue virtioQueues[3]; 83 fStatus = fVirtio->alloc_queues(fVirtioDevice, 3, virtioQueues); 84 if (fStatus != B_OK) { 85 ERROR("queue allocation failed (%s)\n", strerror(fStatus)); 86 return; 87 } 88 89 fControlVirtioQueue = virtioQueues[0]; 90 fEventVirtioQueue = virtioQueues[1]; 91 fRequestVirtioQueue = virtioQueues[2]; 92 93 for (uint32 i = 0; i < VIRTIO_SCSI_NUM_EVENTS; i++) 94 _SubmitEvent(i); 95 96 fStatus = fVirtio->setup_interrupt(fVirtioDevice, NULL, this); 97 if (fStatus != B_OK) { 98 ERROR("interrupt setup failed (%s)\n", strerror(fStatus)); 99 return; 100 } 101 102 103 } 104 105 106 VirtioSCSIController::~VirtioSCSIController() 107 { 108 CALLED(); 109 delete fRequest; 110 111 gSCSI->free_dpc(fEventDPC); 112 } 113 114 115 status_t 116 VirtioSCSIController::InitCheck() 117 { 118 return fStatus; 119 } 120 121 122 void 123 VirtioSCSIController::SetBus(scsi_bus bus) 124 { 125 fBus = bus; 126 } 127 128 129 void 130 VirtioSCSIController::PathInquiry(scsi_path_inquiry *info) 131 { 132 info->hba_inquiry = SCSI_PI_TAG_ABLE; 133 info->hba_misc = 0; 134 info->sim_priv = 0; 135 info->initiator_id = VIRTIO_SCSI_INITIATOR_ID; 136 info->hba_queue_size = fConfig.cmd_per_lun != 0 ? fConfig.cmd_per_lun : 1; 137 memset(info->vuhba_flags, 0, sizeof(info->vuhba_flags)); 138 139 strlcpy(info->sim_vid, "Haiku", SCSI_SIM_ID); 140 strlcpy(info->hba_vid, "VirtIO", SCSI_HBA_ID); 141 142 strlcpy(info->sim_version, "1.0", SCSI_VERS); 143 strlcpy(info->hba_version, "1.0", SCSI_VERS); 144 strlcpy(info->controller_family, "Virtio", SCSI_FAM_ID); 145 strlcpy(info->controller_type, "Virtio", SCSI_TYPE_ID); 146 } 147 148 149 void 150 VirtioSCSIController::GetRestrictions(uint8 targetID, bool *isATAPI, 151 bool *noAutoSense, uint32 *maxBlocks) 152 { 153 *isATAPI = false; 154 *noAutoSense = true; 155 *maxBlocks = fConfig.cmd_per_lun; 156 } 157 158 159 uchar 160 VirtioSCSIController::ResetDevice(uchar targetID, uchar targetLUN) 161 { 162 return SCSI_REQ_CMP; 163 } 164 165 166 status_t 167 VirtioSCSIController::ExecuteRequest(scsi_ccb *ccb) 168 { 169 status_t result = fRequest->Start(ccb); 170 if (result != B_OK) 171 return result; 172 173 if (ccb->cdb[0] == SCSI_OP_REQUEST_SENSE && fRequest->HasSense()) { 174 TRACE("request sense\n"); 175 fRequest->RequestSense(); 176 fRequest->Finish(false); 177 return B_OK; 178 } 179 180 if (ccb->target_id > fConfig.max_target) { 181 ERROR("invalid target device\n"); 182 fRequest->SetStatus(SCSI_TID_INVALID); 183 fRequest->Finish(false); 184 return B_BAD_INDEX; 185 } 186 187 if (ccb->target_lun > fConfig.max_lun) { 188 ERROR("invalid lun device\n"); 189 fRequest->SetStatus(SCSI_LUN_INVALID); 190 fRequest->Finish(false); 191 return B_BAD_INDEX; 192 } 193 194 if (ccb->cdb_length > VIRTIO_SCSI_CDB_SIZE) { 195 fRequest->SetStatus(SCSI_REQ_INVALID); 196 fRequest->Finish(false); 197 return B_BAD_VALUE; 198 } 199 200 bool isOut = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_OUT; 201 bool isIn = (ccb->flags & SCSI_DIR_MASK) == SCSI_DIR_IN; 202 203 // TODO check feature inout if request is bidirectional 204 205 fRequest->SetTimeout(ccb->timeout > 0 ? ccb->timeout * 1000 * 1000 206 : VIRTIO_SCSI_STANDARD_TIMEOUT); 207 208 uint32 inCount = (isIn ? ccb->sg_count : 0) + 1; 209 uint32 outCount = (isOut ? ccb->sg_count : 0) + 1; 210 physical_entry entries[inCount + outCount]; 211 fRequest->FillRequest(inCount, outCount, entries); 212 213 { 214 InterruptsSpinLocker locker(fInterruptLock); 215 fExpectsInterrupt = true; 216 fInterruptCondition.Add(&fInterruptConditionEntry); 217 } 218 219 fVirtio->queue_request_v(fRequestVirtioQueue, entries, 220 outCount, inCount, VirtioSCSIController::_RequestCallback, NULL); 221 222 result = fInterruptConditionEntry.Wait(B_RELATIVE_TIMEOUT, 223 fRequest->Timeout()); 224 225 { 226 InterruptsSpinLocker locker(fInterruptLock); 227 fExpectsInterrupt = false; 228 } 229 230 if (result != B_OK) 231 return result; 232 233 return fRequest->Finish(false); 234 } 235 236 237 uchar 238 VirtioSCSIController::AbortRequest(scsi_ccb *request) 239 { 240 return SCSI_REQ_CMP; 241 } 242 243 244 uchar 245 VirtioSCSIController::TerminateRequest(scsi_ccb *request) 246 { 247 return SCSI_REQ_CMP; 248 } 249 250 251 status_t 252 VirtioSCSIController::Control(uint8 targetID, uint32 op, void *buffer, 253 size_t length) 254 { 255 CALLED(); 256 return B_DEV_INVALID_IOCTL; 257 } 258 259 260 void 261 VirtioSCSIController::_RequestCallback(void* driverCookie, void* cookie) 262 { 263 CALLED(); 264 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 265 controller->_RequestInterrupt(); 266 } 267 268 269 void 270 VirtioSCSIController::_RequestInterrupt() 271 { 272 SpinLocker locker(fInterruptLock); 273 fInterruptCondition.NotifyAll(); 274 } 275 276 277 278 void 279 VirtioSCSIController::_EventCallback(void* driverCookie, void* cookie) 280 { 281 CALLED(); 282 VirtioSCSIController* controller = (VirtioSCSIController*)driverCookie; 283 struct virtio_scsi_event* event = (struct virtio_scsi_event*)cookie; 284 controller->_EventInterrupt(event); 285 } 286 287 288 void 289 VirtioSCSIController::_EventInterrupt(struct virtio_scsi_event* event) 290 { 291 CALLED(); 292 TRACE("events %#x\n", event->event); 293 if ((event->event & VIRTIO_SCSI_T_EVENTS_MISSED) != 0) { 294 ERROR("events missed\n"); 295 } else switch (event->event) { 296 case VIRTIO_SCSI_T_TRANSPORT_RESET: 297 ERROR("transport reset\n"); 298 break; 299 case VIRTIO_SCSI_T_ASYNC_NOTIFY: 300 ERROR("async notify\n"); 301 break; 302 case VIRTIO_SCSI_T_PARAM_CHANGE: 303 { 304 uint16 sense = (event->reason >> 8) 305 | ((event->reason & 0xff) << 8); 306 if (sense == SCSIS_ASC_CAPACITY_DATA_HAS_CHANGED) { 307 ERROR("capacity data has changed for %x:%x\n", event->lun[1], 308 event->lun[2] << 8 | event->lun[3]); 309 gSCSI->schedule_dpc(fBus, fEventDPC, _RescanChildBus, this); 310 } else 311 ERROR("param change, unknown reason\n"); 312 break; 313 } 314 default: 315 ERROR("unknown event %#x\n", event->event); 316 break; 317 } 318 } 319 320 321 void 322 VirtioSCSIController::_SubmitEvent(uint32 eventNumber) 323 { 324 CALLED(); 325 struct virtio_scsi_event* event = &fEventBuffers[eventNumber]; 326 bzero(event, sizeof(struct virtio_scsi_event)); 327 328 physical_entry entry; 329 get_memory_map(event, sizeof(struct virtio_scsi_event), &entry, 1); 330 331 fVirtio->queue_request_v(fEventVirtioQueue, &entry, 332 0, 1, VirtioSCSIController::_EventCallback, event); 333 } 334 335 336 void 337 VirtioSCSIController::_RescanChildBus(void *cookie) 338 { 339 CALLED(); 340 VirtioSCSIController* controller = (VirtioSCSIController*)cookie; 341 device_node *childNode = NULL; 342 const device_attr attrs[] = { { NULL } }; 343 if (gDeviceManager->get_next_child_node(controller->fNode, attrs, 344 &childNode) != B_OK) { 345 ERROR("couldn't find the child node for %p\n", controller->fNode); 346 return; 347 } 348 349 gDeviceManager->rescan_node(childNode); 350 TRACE("rescan done %p\n", childNode); 351 gDeviceManager->put_node(childNode); 352 } 353