1/*
2 * Copyright 2021 David Sebek, dasebek@gmail.com
3 * Copyright 2008-2013 Axel D��rfler, axeld@pinc-software.de
4 * Copyright 2002/03 Thomas Kurschel
5 * All rights reserved. Distributed under the terms of the MIT License.
6 */
7
8
9/*!	Peripheral driver to handle any kind of SCSI disks,
10	i.e. hard disk and floopy disks (ZIP etc.)
11
12	Much work is done by scsi_periph and block_io.
13
14	You'll find das_... all over the place. This stands for
15	"Direct Access Storage" which is the official SCSI name for
16	normal (floppy/hard/ZIP)-disk drives.
17*/
18
19
20#include "scsi_disk.h"
21
22#include <string.h>
23#include <stdlib.h>
24
25#include <AutoDeleter.h>
26
27#include <fs/devfs.h>
28#include <util/fs_trim_support.h>
29
30#include "dma_resources.h"
31#include "IORequest.h"
32#include "IOSchedulerSimple.h"
33
34
35//#define TRACE_SCSI_DISK
36#ifdef TRACE_SCSI_DISK
37#	define TRACE(x...) dprintf("scsi_disk: " x)
38#else
39#	define TRACE(x...) ;
40#endif
41
42
43static const uint8 kDriveIcon[] = {
44	0x6e, 0x63, 0x69, 0x66, 0x08, 0x03, 0x01, 0x00, 0x00, 0x02, 0x00, 0x16,
45	0x02, 0x3c, 0xc7, 0xee, 0x38, 0x9b, 0xc0, 0xba, 0x16, 0x57, 0x3e, 0x39,
46	0xb0, 0x49, 0x77, 0xc8, 0x42, 0xad, 0xc7, 0x00, 0xff, 0xff, 0xd3, 0x02,
47	0x00, 0x06, 0x02, 0x3c, 0x96, 0x32, 0x3a, 0x4d, 0x3f, 0xba, 0xfc, 0x01,
48	0x3d, 0x5a, 0x97, 0x4b, 0x57, 0xa5, 0x49, 0x84, 0x4d, 0x00, 0x47, 0x47,
49	0x47, 0xff, 0xa5, 0xa0, 0xa0, 0x02, 0x00, 0x16, 0x02, 0xbc, 0x59, 0x2f,
50	0xbb, 0x29, 0xa7, 0x3c, 0x0c, 0xe4, 0xbd, 0x0b, 0x7c, 0x48, 0x92, 0xc0,
51	0x4b, 0x79, 0x66, 0x00, 0x7d, 0xff, 0xd4, 0x02, 0x00, 0x06, 0x02, 0x38,
52	0xdb, 0xb4, 0x39, 0x97, 0x33, 0xbc, 0x4a, 0x33, 0x3b, 0xa5, 0x42, 0x48,
53	0x6e, 0x66, 0x49, 0xee, 0x7b, 0x00, 0x59, 0x67, 0x56, 0xff, 0xeb, 0xb2,
54	0xb2, 0x03, 0xa7, 0xff, 0x00, 0x03, 0xff, 0x00, 0x00, 0x04, 0x01, 0x80,
55	0x07, 0x0a, 0x06, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x5a, 0x3e, 0x5a,
56	0x31, 0x39, 0x25, 0x0a, 0x04, 0x22, 0x3c, 0x44, 0x4b, 0x5a, 0x31, 0x39,
57	0x25, 0x0a, 0x04, 0x44, 0x4b, 0x44, 0x5b, 0x5a, 0x3e, 0x5a, 0x31, 0x0a,
58	0x04, 0x22, 0x3c, 0x22, 0x49, 0x44, 0x5b, 0x44, 0x4b, 0x08, 0x02, 0x27,
59	0x43, 0xb8, 0x14, 0xc1, 0xf1, 0x08, 0x02, 0x26, 0x43, 0x29, 0x44, 0x0a,
60	0x05, 0x44, 0x5d, 0x49, 0x5d, 0x60, 0x3e, 0x5a, 0x3b, 0x5b, 0x3f, 0x08,
61	0x0a, 0x07, 0x01, 0x06, 0x00, 0x0a, 0x00, 0x01, 0x00, 0x10, 0x01, 0x17,
62	0x84, 0x00, 0x04, 0x0a, 0x01, 0x01, 0x01, 0x00, 0x0a, 0x02, 0x01, 0x02,
63	0x00, 0x0a, 0x03, 0x01, 0x03, 0x00, 0x0a, 0x04, 0x01, 0x04, 0x10, 0x01,
64	0x17, 0x85, 0x20, 0x04, 0x0a, 0x06, 0x01, 0x05, 0x30, 0x24, 0xb3, 0x99,
65	0x01, 0x17, 0x82, 0x00, 0x04, 0x0a, 0x05, 0x01, 0x05, 0x30, 0x20, 0xb2,
66	0xe6, 0x01, 0x17, 0x82, 0x00, 0x04
67};
68
69
70static scsi_periph_interface* sSCSIPeripheral;
71static device_manager_info* sDeviceManager;
72
73
74static status_t
75update_capacity(das_driver_info* device)
76{
77	TRACE("update_capacity()\n");
78
79	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
80	if (ccb == NULL)
81		return B_NO_MEMORY;
82
83	status_t status = sSCSIPeripheral->check_capacity(
84		device->scsi_periph_device, ccb);
85
86	device->scsi->free_ccb(ccb);
87
88	return status;
89}
90
91
92static status_t
93get_geometry(das_handle* handle, device_geometry* geometry)
94{
95	das_driver_info* info = handle->info;
96
97	status_t status = update_capacity(info);
98	if (status != B_OK)
99		return status;
100
101	devfs_compute_geometry_size(geometry, info->capacity, info->block_size);
102	geometry->bytes_per_physical_sector = info->physical_block_size;
103
104	geometry->device_type = B_DISK;
105	geometry->removable = info->removable;
106
107	// TBD: for all but CD-ROMs, read mode sense - medium type
108	// (bit 7 of block device specific parameter for Optical Memory Block Device)
109	// (same for Direct-Access Block Devices)
110	// (same for write-once block devices)
111	// (same for optical memory block devices)
112	geometry->read_only = false;
113	geometry->write_once = false;
114
115	TRACE("scsi_disk: get_geometry(): %" B_PRId32 ", %" B_PRId32 ", %" B_PRId32
116		", %" B_PRId32 ", %d, %d, %d, %d, %" B_PRId32 "\n", geometry->bytes_per_sector,
117		geometry->sectors_per_track, geometry->cylinder_count,
118		geometry->head_count, geometry->device_type,
119		geometry->removable, geometry->read_only, geometry->write_once,
120		geometry->bytes_per_physical_sector);
121
122	return B_OK;
123}
124
125
126static status_t
127load_eject(das_driver_info *device, bool load)
128{
129	TRACE("load_eject()\n");
130
131	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
132	if (ccb == NULL)
133		return B_NO_MEMORY;
134
135	err_res result = sSCSIPeripheral->send_start_stop(
136		device->scsi_periph_device, ccb, load, true);
137
138	device->scsi->free_ccb(ccb);
139
140	return result.error_code;
141}
142
143
144static status_t
145synchronize_cache(das_driver_info *device)
146{
147	TRACE("synchronize_cache()\n");
148
149	scsi_ccb *ccb = device->scsi->alloc_ccb(device->scsi_device);
150	if (ccb == NULL)
151		return B_NO_MEMORY;
152
153	err_res result = sSCSIPeripheral->synchronize_cache(
154		device->scsi_periph_device, ccb);
155
156	device->scsi->free_ccb(ccb);
157
158	return result.error_code;
159}
160
161
162static status_t
163trim_device(das_driver_info* device, fs_trim_data* trimData)
164{
165	TRACE("trim_device()\n");
166
167	trimData->trimmed_size = 0;
168
169	scsi_ccb* request = device->scsi->alloc_ccb(device->scsi_device);
170	if (request == NULL)
171		return B_NO_MEMORY;
172
173	scsi_block_range* blockRanges = (scsi_block_range*)
174		malloc(trimData->range_count * sizeof(*blockRanges));
175	if (blockRanges == NULL)
176		return B_NO_MEMORY;
177
178	MemoryDeleter deleter(blockRanges);
179
180	for (uint32 i = 0; i < trimData->range_count; i++) {
181		uint64 startBytes = trimData->ranges[i].offset;
182		uint64 sizeBytes = trimData->ranges[i].size;
183		uint32 blockSize = device->block_size;
184
185		// Align to a block boundary so we don't discard blocks
186		// that could also contain some other data
187		uint64 blockOffset = startBytes % blockSize;
188		if (blockOffset == 0) {
189			blockRanges[i].lba = startBytes / blockSize;
190			blockRanges[i].size = sizeBytes / blockSize;
191		} else {
192			blockRanges[i].lba = startBytes / blockSize + 1;
193			blockRanges[i].size = (sizeBytes - (blockSize - blockOffset))
194				/ blockSize;
195		}
196	}
197
198	// Check ranges against device capacity and make them fit
199	for (uint32 i = 0; i < trimData->range_count; i++) {
200		if (blockRanges[i].lba >= device->capacity) {
201			dprintf("trim_device(): range offset (LBA) %" B_PRIu64
202				" exceeds device capacity %" B_PRIu64 "\n",
203				blockRanges[i].lba, device->capacity);
204			return B_BAD_VALUE;
205		}
206		uint64 maxSize = device->capacity - blockRanges[i].lba;
207		blockRanges[i].size = min_c(blockRanges[i].size, maxSize);
208	}
209
210	uint64 trimmedBlocks;
211	status_t status = sSCSIPeripheral->trim_device(device->scsi_periph_device,
212		request, blockRanges, trimData->range_count, &trimmedBlocks);
213
214	device->scsi->free_ccb(request);
215	// Some blocks may have been trimmed even if trim_device returns a failure
216	trimData->trimmed_size = trimmedBlocks * device->block_size;
217
218	return status;
219}
220
221
222static status_t
223do_io(void* cookie, IOOperation* operation)
224{
225	das_driver_info* info = (das_driver_info*)cookie;
226
227	// TODO: this can go away as soon as we pushed the IOOperation to the upper
228	// layers - we can then set scsi_periph::io() as callback for the scheduler
229	size_t bytesTransferred;
230	status_t status = sSCSIPeripheral->io(info->scsi_periph_device, operation,
231		&bytesTransferred);
232
233	info->io_scheduler->OperationCompleted(operation, status, bytesTransferred);
234	return status;
235}
236
237
238//	#pragma mark - device module API
239
240
241static status_t
242das_init_device(void* _info, void** _cookie)
243{
244	das_driver_info* info = (das_driver_info*)_info;
245
246	// and get (initial) capacity
247	scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device);
248	if (request == NULL)
249		return B_NO_MEMORY;
250
251	sSCSIPeripheral->check_capacity(info->scsi_periph_device, request);
252	info->scsi->free_ccb(request);
253
254	*_cookie = info;
255	return B_OK;
256}
257
258
259static void
260das_uninit_device(void* _cookie)
261{
262	das_driver_info* info = (das_driver_info*)_cookie;
263
264	delete info->io_scheduler;
265}
266
267
268static status_t
269das_open(void* _info, const char* path, int openMode, void** _cookie)
270{
271	das_driver_info* info = (das_driver_info*)_info;
272
273	das_handle* handle = (das_handle*)malloc(sizeof(das_handle));
274	if (handle == NULL)
275		return B_NO_MEMORY;
276
277	handle->info = info;
278
279	status_t status = sSCSIPeripheral->handle_open(info->scsi_periph_device,
280		(periph_handle_cookie)handle, &handle->scsi_periph_handle);
281	if (status < B_OK) {
282		free(handle);
283		return status;
284	}
285
286	*_cookie = handle;
287	return B_OK;
288}
289
290
291static status_t
292das_close(void* cookie)
293{
294	das_handle* handle = (das_handle*)cookie;
295	TRACE("close()\n");
296
297	sSCSIPeripheral->handle_close(handle->scsi_periph_handle);
298	return B_OK;
299}
300
301
302static status_t
303das_free(void* cookie)
304{
305	das_handle* handle = (das_handle*)cookie;
306	TRACE("free()\n");
307
308	sSCSIPeripheral->handle_free(handle->scsi_periph_handle);
309	free(handle);
310	return B_OK;
311}
312
313
314static status_t
315das_io(void *cookie, io_request *request)
316{
317	das_handle* handle = (das_handle*)cookie;
318
319	return handle->info->io_scheduler->ScheduleRequest(request);
320}
321
322
323static status_t
324das_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
325{
326	das_handle* handle = (das_handle*)cookie;
327	das_driver_info* info = handle->info;
328
329	TRACE("ioctl(op = %" B_PRIu32 ")\n", op);
330
331	switch (op) {
332		case B_GET_DEVICE_SIZE:
333		{
334			status_t status = update_capacity(info);
335			if (status != B_OK)
336				return status;
337
338			size_t size = info->capacity * info->block_size;
339			return user_memcpy(buffer, &size, sizeof(size_t));
340		}
341
342		case B_GET_GEOMETRY:
343		{
344			if (buffer == NULL || length > sizeof(device_geometry))
345				return B_BAD_VALUE;
346
347		 	device_geometry geometry;
348			status_t status = get_geometry(handle, &geometry);
349			if (status != B_OK)
350				return status;
351
352			return user_memcpy(buffer, &geometry, length);
353		}
354
355		case B_GET_ICON_NAME:
356			// TODO: take device type into account!
357			return user_strlcpy((char*)buffer, info->removable
358				? "devices/drive-removable-media" : "devices/drive-harddisk",
359				B_FILE_NAME_LENGTH);
360
361		case B_GET_VECTOR_ICON:
362		{
363			// TODO: take device type into account!
364			device_icon iconData;
365			if (length != sizeof(device_icon))
366				return B_BAD_VALUE;
367			if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
368				return B_BAD_ADDRESS;
369
370			if (iconData.icon_size >= (int32)sizeof(kDriveIcon)) {
371				if (user_memcpy(iconData.icon_data, kDriveIcon,
372						sizeof(kDriveIcon)) != B_OK)
373					return B_BAD_ADDRESS;
374			}
375
376			iconData.icon_size = sizeof(kDriveIcon);
377			return user_memcpy(buffer, &iconData, sizeof(device_icon));
378		}
379
380		case B_EJECT_DEVICE:
381		case B_SCSI_EJECT:
382			return load_eject(info, false);
383
384		case B_LOAD_MEDIA:
385			return load_eject(info, true);
386
387		case B_FLUSH_DRIVE_CACHE:
388			return synchronize_cache(info);
389
390		case B_TRIM_DEVICE:
391		{
392			// We know the buffer is kernel-side because it has been
393			// preprocessed in devfs
394			ASSERT(IS_KERNEL_ADDRESS(buffer));
395			return trim_device(info, (fs_trim_data*)buffer);
396		}
397
398		default:
399			return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
400				buffer, length);
401	}
402}
403
404
405//	#pragma mark - scsi_periph callbacks
406
407
408static void
409das_set_capacity(das_driver_info* info, uint64 capacity, uint32 blockSize, uint32 physicalBlockSize)
410{
411	TRACE("das_set_capacity(device = %p, capacity = %" B_PRIu64
412		", blockSize = %" B_PRIu32 ")\n", info, capacity, blockSize);
413
414	info->capacity = capacity;
415
416	if (info->block_size != blockSize) {
417		if (info->block_size != 0) {
418			dprintf("old %" B_PRId32 ", new %" B_PRId32 "\n", info->block_size,
419				blockSize);
420			panic("updating DMAResource not yet implemented...");
421		}
422
423		// TODO: we need to replace the DMAResource in our IOScheduler
424		status_t status = info->dma_resource->Init(info->node, blockSize, 1024,
425			32);
426		if (status != B_OK)
427			panic("initializing DMAResource failed: %s", strerror(status));
428
429		info->io_scheduler = new(std::nothrow) IOSchedulerSimple(
430			info->dma_resource);
431		if (info->io_scheduler == NULL)
432			panic("allocating IOScheduler failed.");
433
434		// TODO: use whole device name here
435		status = info->io_scheduler->Init("scsi");
436		if (status != B_OK)
437			panic("initializing IOScheduler failed: %s", strerror(status));
438
439		info->io_scheduler->SetCallback(do_io, info);
440	}
441
442	info->block_size = blockSize;
443	info->physical_block_size = physicalBlockSize;
444}
445
446
447static void
448das_media_changed(das_driver_info *device, scsi_ccb *request)
449{
450	// do a capacity check
451	// TODO: is this a good idea (e.g. if this is an empty CD)?
452	sSCSIPeripheral->check_capacity(device->scsi_periph_device, request);
453}
454
455
456scsi_periph_callbacks callbacks = {
457	(void (*)(periph_device_cookie, uint64, uint32, uint32))das_set_capacity,
458	(void (*)(periph_device_cookie, scsi_ccb *))das_media_changed
459};
460
461
462//	#pragma mark - driver module API
463
464
465static float
466das_supports_device(device_node *parent)
467{
468	const char *bus;
469	uint8 deviceType;
470
471	// make sure parent is really the SCSI bus manager
472	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
473		return -1;
474
475	if (strcmp(bus, "scsi"))
476		return 0.0;
477
478	// check whether it's really a Direct Access Device
479	if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM,
480			&deviceType, true) != B_OK || deviceType != scsi_dev_direct_access)
481		return 0.0;
482
483	return 0.6;
484}
485
486
487/*!	Called whenever a new device was added to system;
488	if we really support it, we create a new node that gets
489	server by the block_io module
490*/
491static status_t
492das_register_device(device_node *node)
493{
494	const scsi_res_inquiry *deviceInquiry = NULL;
495	size_t inquiryLength;
496	uint32 maxBlocks;
497
498	// get inquiry data
499	if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM,
500			(const void **)&deviceInquiry, &inquiryLength, true) != B_OK
501		|| inquiryLength < sizeof(scsi_res_inquiry))
502		return B_ERROR;
503
504	// get block limit of underlying hardware to lower it (if necessary)
505	if (sDeviceManager->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS,
506			&maxBlocks, true) != B_OK)
507		maxBlocks = INT_MAX;
508
509	// using 10 byte commands, at most 0xffff blocks can be transmitted at once
510	// (sadly, we cannot update this value later on if only 6 byte commands
511	//  are supported, but the block_io module can live with that)
512	maxBlocks = min_c(maxBlocks, 0xffff);
513
514	// ready to register
515	device_attr attrs[] = {
516		{ B_DEVICE_PRETTY_NAME, B_STRING_TYPE, { .string = "SCSI Disk" }},
517		// tell block_io whether the device is removable
518		{"removable", B_UINT8_TYPE, {.ui8 = deviceInquiry->removable_medium}},
519		// impose own max block restriction
520		{B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, {.ui32 = maxBlocks}},
521		{ NULL }
522	};
523
524	return sDeviceManager->register_node(node, SCSI_DISK_DRIVER_MODULE_NAME,
525		attrs, NULL, NULL);
526}
527
528
529static status_t
530das_init_driver(device_node *node, void **cookie)
531{
532	TRACE("das_init_driver");
533
534	uint8 removable;
535	status_t status = sDeviceManager->get_attr_uint8(node, "removable",
536		&removable, false);
537	if (status != B_OK)
538		return status;
539
540	das_driver_info* info = (das_driver_info*)malloc(sizeof(das_driver_info));
541	if (info == NULL)
542		return B_NO_MEMORY;
543
544	memset(info, 0, sizeof(*info));
545
546	info->dma_resource = new(std::nothrow) DMAResource;
547	if (info->dma_resource == NULL) {
548		free(info);
549		return B_NO_MEMORY;
550	}
551
552	info->node = node;
553	info->removable = removable;
554
555	device_node* parent = sDeviceManager->get_parent_node(node);
556	sDeviceManager->get_driver(parent, (driver_module_info **)&info->scsi,
557		(void **)&info->scsi_device);
558	sDeviceManager->put_node(parent);
559
560	status = sSCSIPeripheral->register_device((periph_device_cookie)info,
561		&callbacks, info->scsi_device, info->scsi, info->node,
562		info->removable, 10, &info->scsi_periph_device);
563	if (status != B_OK) {
564		delete info->dma_resource;
565		free(info);
566		return status;
567	}
568
569	*cookie = info;
570	return B_OK;
571}
572
573
574static void
575das_uninit_driver(void *_cookie)
576{
577	das_driver_info* info = (das_driver_info*)_cookie;
578
579	sSCSIPeripheral->unregister_device(info->scsi_periph_device);
580	delete info->dma_resource;
581	free(info);
582}
583
584
585static status_t
586das_register_child_devices(void* _cookie)
587{
588	das_driver_info* info = (das_driver_info*)_cookie;
589	status_t status;
590
591	char* name = sSCSIPeripheral->compose_device_name(info->node,
592		"disk/scsi");
593	if (name == NULL)
594		return B_ERROR;
595
596	status = sDeviceManager->publish_device(info->node, name,
597		SCSI_DISK_DEVICE_MODULE_NAME);
598
599	free(name);
600	return status;
601}
602
603
604static status_t
605das_rescan_child_devices(void* _cookie)
606{
607	das_driver_info* info = (das_driver_info*)_cookie;
608	uint64 capacity = info->capacity;
609	update_capacity(info);
610	if (info->capacity != capacity)
611		sSCSIPeripheral->media_changed(info->scsi_periph_device);
612	return B_OK;
613}
614
615
616
617module_dependency module_dependencies[] = {
618	{SCSI_PERIPH_MODULE_NAME, (module_info**)&sSCSIPeripheral},
619	{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
620	{}
621};
622
623struct device_module_info sSCSIDiskDevice = {
624	{
625		SCSI_DISK_DEVICE_MODULE_NAME,
626		0,
627		NULL
628	},
629
630	das_init_device,
631	das_uninit_device,
632	NULL, //das_remove,
633
634	das_open,
635	das_close,
636	das_free,
637	NULL,	// read
638	NULL,	// write
639	das_io,
640	das_ioctl,
641
642	NULL,	// select
643	NULL,	// deselect
644};
645
646struct driver_module_info sSCSIDiskDriver = {
647	{
648		SCSI_DISK_DRIVER_MODULE_NAME,
649		0,
650		NULL
651	},
652
653	das_supports_device,
654	das_register_device,
655	das_init_driver,
656	das_uninit_driver,
657	das_register_child_devices,
658	das_rescan_child_devices,
659	NULL,	// removed
660};
661
662module_info* modules[] = {
663	(module_info*)&sSCSIDiskDriver,
664	(module_info*)&sSCSIDiskDevice,
665	NULL
666};
667