xref: /haiku/src/add-ons/kernel/drivers/disk/scsi/scsi_cd/scsi_cd.cpp (revision a4ef4a49150f118d47324242917a596a3f8f8bd5)
1 /*
2  * Copyright 2004-2008, Haiku, Inc. All rights reserved.
3  * Copyright 2002-2003, Thomas Kurschel. All rights reserved.
4  *
5  * Distributed under the terms of the MIT License.
6  */
7 
8 /*!	Peripheral driver to handle CD-ROM drives. To be more
9 	precisely, it supports CD-ROM and WORM drives (well -
10 	I've never _seen_ a WORM driver).
11 
12 	Much work is done by scsi_periph and block_io.
13 */
14 
15 
16 #include "scsi_cd.h"
17 
18 #include <stdlib.h>
19 #include <string.h>
20 
21 
22 //#define TRACE_CD_DISK
23 #ifdef TRACE_CD_DISK
24 #	define TRACE(x...) dprintf("scsi_cd: " x)
25 #else
26 #	define TRACE(x...) ;
27 #endif
28 
29 
30 static const uint8 kCDIcon[] = {
31 	0x6e, 0x63, 0x69, 0x66, 0x05, 0x05, 0x00, 0x02, 0x03, 0x06, 0x05, 0xb8,
32 	0x12, 0xa5, 0xbe, 0x03, 0xe1, 0x3d, 0xe7, 0x84, 0xb8, 0x02, 0x10, 0x49,
33 	0xf7, 0x9f, 0x49, 0xed, 0xd8, 0x00, 0xf1, 0xf1, 0xf1, 0x36, 0xd9, 0xdd,
34 	0xf4, 0x8a, 0x99, 0x96, 0xb9, 0xb4, 0xb8, 0xbe, 0xdb, 0xff, 0xf4, 0xf4,
35 	0xf4, 0x04, 0xeb, 0xd0, 0x02, 0x00, 0x06, 0x02, 0x3c, 0x92, 0xc0, 0x38,
36 	0x8f, 0x5f, 0xb8, 0x54, 0x50, 0x3c, 0x57, 0x63, 0x48, 0xd8, 0xdf, 0x48,
37 	0x89, 0x5b, 0x00, 0x41, 0x37, 0xa9, 0xff, 0xb9, 0xb9, 0xb9, 0x04, 0x01,
38 	0x7e, 0x04, 0x02, 0x04, 0x3f, 0x2c, 0x4e, 0x2c, 0x30, 0x2c, 0x22, 0x40,
39 	0x22, 0x34, 0x22, 0x4c, 0x3f, 0x54, 0x30, 0x54, 0x4e, 0x54, 0x5c, 0x40,
40 	0x5c, 0x4c, 0x5c, 0x34, 0x02, 0x04, 0x3f, 0x3a, 0x43, 0x3a, 0x3b, 0x3a,
41 	0x39, 0x3e, 0x39, 0x3c, 0x39, 0x40, 0x3f, 0x42, 0x3b, 0x42, 0x43, 0x42,
42 	0x45, 0x3e, 0x45, 0x40, 0x45, 0x3c, 0x02, 0x04, 0x4b, 0x3e, 0x4b, 0x3a,
43 	0x4b, 0x42, 0x3f, 0x46, 0x47, 0x46, 0x37, 0x46, 0x33, 0x3e, 0x33, 0x42,
44 	0x33, 0x3a, 0x3f, 0xbb, 0xf7, 0x37, 0xbb, 0xf7, 0x47, 0xbb, 0xf7, 0x02,
45 	0x04, 0x40, 0x2a, 0x54, 0x2a, 0x50, 0x2c, 0x5c, 0x40, 0x5c, 0x34, 0x5c,
46 	0x4c, 0x40, 0x56, 0x50, 0x54, 0x54, 0x56, 0x60, 0x40, 0x60, 0x4c, 0x60,
47 	0x34, 0x06, 0x0a, 0x04, 0x01, 0x03, 0x00, 0x0a, 0x00, 0x02, 0x00, 0x01,
48 	0x18, 0x15, 0xff, 0x01, 0x17, 0x84, 0x00, 0x04, 0x0a, 0x00, 0x02, 0x00,
49 	0x01, 0x18, 0x00, 0x15, 0x01, 0x17, 0x86, 0x00, 0x04, 0x0a, 0x01, 0x02,
50 	0x00, 0x02, 0x00, 0x0a, 0x02, 0x02, 0x02, 0x01, 0x00, 0x0a, 0x03, 0x01,
51 	0x02, 0x10, 0x01, 0x17, 0x82, 0x00, 0x04
52 };
53 
54 
55 static scsi_periph_interface *sSCSIPeripheral;
56 static device_manager_info *sDeviceManager;
57 
58 
59 #define SCSI_CD_STD_TIMEOUT 10
60 
61 
62 static status_t
63 update_capacity(cd_driver_info *info)
64 {
65 	TRACE("update_capacity()\n");
66 
67 	scsi_ccb *ccb = info->scsi->alloc_ccb(info->scsi_device);
68 	if (ccb == NULL)
69 		return B_NO_MEMORY;
70 
71 	status_t status = sSCSIPeripheral->check_capacity(
72 		info->scsi_periph_device, ccb);
73 
74 	info->scsi->free_ccb(ccb);
75 
76 	return status;
77 }
78 
79 
80 static status_t
81 get_geometry(cd_handle *handle, device_geometry *geometry)
82 {
83 	cd_driver_info *info = handle->info;
84 
85 	status_t status = update_capacity(info);
86 
87 	// it seems that Be expects B_GET_GEOMETRY to always succeed unless
88 	// the medium has been changed; e.g. if we report B_DEV_NO_MEDIA, the
89 	// info is ignored by the CDPlayer and CDBurner
90 	if (status == B_DEV_MEDIA_CHANGED)
91 		return B_DEV_MEDIA_CHANGED;
92 
93 	geometry->bytes_per_sector = info->block_size;
94 	geometry->sectors_per_track = 1;
95 	geometry->cylinder_count = info->capacity;
96 	geometry->head_count = 1;
97 	geometry->device_type = info->device_type;
98 	geometry->removable = info->removable;
99 
100 	// TBD: for all but CD-ROMs, read mode sense - medium type
101 	// (bit 7 of block device specific parameter for Optical Memory Block Device)
102 	// (same for Direct-Access Block Devices)
103 	// (same for write-once block devices)
104 	// (same for optical memory block devices)
105 	geometry->read_only = true;
106 	geometry->write_once = info->device_type == scsi_dev_WORM;
107 
108 	TRACE("scsi_disk: get_geometry(): %ld, %ld, %ld, %ld, %d, %d, %d, %d\n",
109 		geometry->bytes_per_sector, geometry->sectors_per_track,
110 		geometry->cylinder_count, geometry->head_count, geometry->device_type,
111 		geometry->removable, geometry->read_only, geometry->write_once);
112 
113 	return B_OK;
114 }
115 
116 
117 static status_t
118 get_toc(cd_driver_info *info, scsi_toc *toc)
119 {
120 	scsi_ccb *ccb;
121 	status_t res;
122 	scsi_cmd_read_toc *cmd;
123 	size_t dataLength;
124 	scsi_toc_general *short_response = (scsi_toc_general *)toc->toc_data;
125 
126 	TRACE("get_toc()\n");
127 
128 	ccb = info->scsi->alloc_ccb(info->scsi_device);
129 	if (ccb == NULL)
130 		return B_NO_MEMORY;
131 
132 	// first read number of tracks only
133 	ccb->flags = SCSI_DIR_IN;
134 
135 	cmd = (scsi_cmd_read_toc *)ccb->cdb;
136 
137 	memset(cmd, 0, sizeof(*cmd));
138 	cmd->opcode = SCSI_OP_READ_TOC;
139 	cmd->time = 1;
140 	cmd->format = SCSI_TOC_FORMAT_TOC;
141 	cmd->track = 1;
142 	cmd->allocation_length = B_HOST_TO_BENDIAN_INT16(sizeof(scsi_toc_general));
143 
144 	ccb->cdb_length = sizeof(*cmd);
145 
146 	ccb->sort = -1;
147 	ccb->timeout = SCSI_CD_STD_TIMEOUT;
148 
149 	ccb->data = toc->toc_data;
150 	ccb->sg_list = NULL;
151 	ccb->data_length = sizeof(toc->toc_data);
152 
153 	res = sSCSIPeripheral->safe_exec(info->scsi_periph_device, ccb);
154 	if (res != B_OK)
155 		goto err;
156 
157 	// then read all track infos
158 	// (little hint: number of tracks is last - first + 1;
159 	//  but scsi_toc_toc has already one track, so we get
160 	//  last - first extra tracks; finally, we want the lead-out as
161 	//  well, so we add an extra track)
162 	dataLength = (short_response->last - short_response->first + 1)
163 		* sizeof(scsi_toc_track) + sizeof(scsi_toc_toc);
164 	dataLength = min_c(dataLength, sizeof(toc->toc_data));
165 
166 	TRACE("  tracks: %d - %d, data length %d\n", short_response->first,
167 		short_response->last, (int)dataLength);
168 
169 	cmd->allocation_length = B_HOST_TO_BENDIAN_INT16(dataLength);
170 
171 	res = sSCSIPeripheral->safe_exec(info->scsi_periph_device, ccb);
172 
173 err:
174 	info->scsi->free_ccb(ccb);
175 
176 	return res;
177 }
178 
179 
180 static status_t
181 load_eject(cd_driver_info *info, bool load)
182 {
183 	TRACE("load_eject()\n");
184 
185 	scsi_ccb *ccb = info->scsi->alloc_ccb(info->scsi_device);
186 	if (ccb == NULL)
187 		return B_NO_MEMORY;
188 
189 	err_res result = sSCSIPeripheral->send_start_stop(
190 		info->scsi_periph_device, ccb, load, true);
191 
192 	info->scsi->free_ccb(ccb);
193 
194 	return result.error_code;
195 }
196 
197 
198 static status_t
199 get_position(cd_driver_info *info, scsi_position *position)
200 {
201 	scsi_cmd_read_subchannel cmd;
202 
203 	TRACE("get_position()\n");
204 
205 	memset(&cmd, 0, sizeof(cmd));
206 	cmd.opcode = SCSI_OP_READ_SUB_CHANNEL;
207 	cmd.time = 1;
208 	cmd.subq = 1;
209 	cmd.parameter_list = scsi_sub_channel_parameter_list_cd_pos;
210 	cmd.track = 0;
211 	cmd.allocation_length = B_HOST_TO_BENDIAN_INT16(sizeof(scsi_position));
212 
213 	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
214 		&cmd, sizeof(cmd), position, sizeof(*position), SCSI_DIR_IN);
215 }
216 
217 
218 static status_t
219 get_set_volume(cd_driver_info *info, scsi_volume *volume, bool set)
220 {
221 	scsi_cmd_mode_sense_6 cmd;
222 	scsi_mode_param_header_6 header;
223 	size_t len;
224 	void *buffer;
225 	scsi_modepage_audio	*page;
226 	status_t res;
227 
228 	TRACE("get_set_volume()\n");
229 
230 	// determine size of block descriptor
231 	memset(&cmd, 0, sizeof(cmd));
232 	cmd.opcode = SCSI_OP_MODE_SENSE_6;
233 	cmd.page_code = SCSI_MODEPAGE_AUDIO;
234 	cmd.page_control = SCSI_MODE_SENSE_PC_CURRENT;
235 	cmd.allocation_length = sizeof(header);
236 
237 	memset(&header, -2, sizeof(header));
238 
239 	res = sSCSIPeripheral->simple_exec(info->scsi_periph_device, &cmd,
240 		sizeof(cmd), &header, sizeof(header), SCSI_DIR_IN);
241 	if (res != B_OK)
242 		return res;
243 
244 	TRACE("  block_desc_len=%d", header.block_desc_length);
245 #if 0
246 	// ToDo: why this??
247 	return B_ERROR;
248 #endif
249 
250 	// retrieve param header, block descriptor and actual codepage
251 	len = sizeof(header) + header.block_desc_length
252 		+ sizeof(scsi_modepage_audio);
253 
254 	buffer = malloc(len);
255 	if (buffer == NULL)
256 		return B_NO_MEMORY;
257 
258 	memset(buffer, -1, sizeof(buffer));
259 
260 	cmd.allocation_length = len;
261 
262 	res = sSCSIPeripheral->simple_exec(info->scsi_periph_device, &cmd,
263 		sizeof(cmd), buffer, len, SCSI_DIR_IN);
264 	if (res != B_OK) {
265 		free(buffer);
266 		return res;
267 	}
268 
269 	TRACE("  mode_data_len=%d, block_desc_len=%d",
270 		((scsi_mode_param_header_6 *)buffer)->mode_data_length,
271 		((scsi_mode_param_header_6 *)buffer)->block_desc_length);
272 
273 	// find control page and retrieve values
274 	page = (scsi_modepage_audio *)((char *)buffer + sizeof(header)
275 		+ header.block_desc_length);
276 
277 	TRACE("  page=%p, codepage=%d", page, page->header.page_code);
278 
279 	if (!set) {
280 		volume->port0_channel = page->ports[0].channel;
281 		volume->port0_volume  = page->ports[0].volume;
282 		volume->port1_channel = page->ports[1].channel;
283 		volume->port1_volume  = page->ports[1].volume;
284 		volume->port2_channel = page->ports[2].channel;
285 		volume->port2_volume  = page->ports[2].volume;
286 		volume->port3_channel = page->ports[3].channel;
287 		volume->port3_volume  = page->ports[3].volume;
288 
289 #if 0
290 		SHOW_FLOW(3, "1: %d - %d", volume->port0_channel, volume->port0_volume);
291 		SHOW_FLOW(3, "2: %d - %d", volume->port1_channel, volume->port1_volume);
292 		SHOW_FLOW(3, "3: %d - %d", volume->port2_channel, volume->port2_volume);
293 		SHOW_FLOW(3, "4: %d - %d", volume->port3_channel, volume->port3_volume);
294 #endif
295 		res = B_OK;
296 	} else {
297 		scsi_cmd_mode_select_6 cmd;
298 
299 		if (volume->flags & 0x01)
300 			page->ports[0].channel = volume->port0_channel;
301 		if (volume->flags & 0x02)
302 			page->ports[0].volume = volume->port0_volume;
303 		if (volume->flags & 0x04)
304 			page->ports[1].channel = volume->port1_channel;
305 		if (volume->flags & 0x08)
306 			page->ports[1].volume = volume->port1_volume;
307 		if (volume->flags & 0x10)
308 			page->ports[2].channel = volume->port2_channel;
309 		if (volume->flags & 0x20)
310 			page->ports[2].volume = volume->port2_volume;
311 		if (volume->flags & 0x40)
312 			page->ports[3].channel = volume->port3_channel;
313 		if (volume->flags & 0x80)
314 			page->ports[3].volume = volume->port3_volume;
315 
316 		memset(&cmd, 0, sizeof(cmd));
317 		cmd.opcode = SCSI_OP_MODE_SELECT_6;
318 		cmd.pf = 1;
319 		cmd.param_list_length = sizeof(header) + header.block_desc_length
320 			+ sizeof(*page);
321 
322 		res = sSCSIPeripheral->simple_exec(info->scsi_periph_device,
323 			&cmd, sizeof(cmd), buffer, len, SCSI_DIR_OUT);
324 	}
325 
326 	free(buffer);
327 	return res;
328 }
329 
330 
331 /*!	Play audio cd; time is in MSF */
332 static status_t
333 play_msf(cd_driver_info *info, const scsi_play_position *position)
334 {
335 	scsi_cmd_play_msf cmd;
336 
337 	TRACE("play_msf(): %d:%d:%d-%d:%d:%d\n", position->start_m,
338 		position->start_s, position->start_f, position->end_m, position->end_s,
339 		position->end_f);
340 
341 	memset(&cmd, 0, sizeof(cmd));
342 
343 	cmd.opcode = SCSI_OP_PLAY_MSF;
344 	cmd.start_minute = position->start_m;
345 	cmd.start_second = position->start_s;
346 	cmd.start_frame = position->start_f;
347 	cmd.end_minute = position->end_m;
348 	cmd.end_second = position->end_s;
349 	cmd.end_frame = position->end_f;
350 
351 	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
352 		&cmd, sizeof(cmd), NULL, 0, 0);
353 }
354 
355 
356 /*! Play audio cd; time is in track/index */
357 static status_t
358 play_track_index(cd_driver_info *info, const scsi_play_track *buf)
359 {
360 	scsi_toc generic_toc;
361 	scsi_toc_toc *toc;
362 	status_t res;
363 	int start_track, end_track;
364 	scsi_play_position position;
365 
366 	TRACE("play_track_index(): %d-%d\n", buf->start_track, buf->end_track);
367 
368 	// the corresponding command PLAY AUDIO TRACK/INDEX	is deprecated,
369 	// so we have to simulate it by converting track to time via TOC
370 	res = get_toc(info, &generic_toc);
371 	if (res != B_OK)
372 		return res;
373 
374 	toc = (scsi_toc_toc *)&generic_toc.toc_data[0];
375 
376 	start_track = buf->start_track;
377 	end_track = buf->end_track;
378 
379 	if (start_track > toc->last_track)
380 		return B_BAD_INDEX;
381 
382 	if (end_track > toc->last_track)
383 		end_track = toc->last_track + 1;
384 
385 	if (end_track < toc->last_track + 1)
386 		++end_track;
387 
388 	start_track -= toc->first_track;
389 	end_track -= toc->first_track;
390 
391 	if (start_track < 0 || end_track < 0)
392 		return B_BAD_INDEX;
393 
394 	position.start_m = toc->tracks[start_track].start.time.minute;
395 	position.start_s = toc->tracks[start_track].start.time.second;
396 	position.start_f = toc->tracks[start_track].start.time.frame;
397 
398 	position.end_m = toc->tracks[end_track].start.time.minute;
399 	position.end_s = toc->tracks[end_track].start.time.second;
400 	position.end_f = toc->tracks[end_track].start.time.frame;
401 
402 	return play_msf(info, &position);
403 }
404 
405 
406 static status_t
407 stop_audio(cd_driver_info *info)
408 {
409 	scsi_cmd_stop_play cmd;
410 
411 	TRACE("stop_audio()\n");
412 
413 	memset( &cmd, 0, sizeof( cmd ));
414 	cmd.opcode = SCSI_OP_STOP_PLAY;
415 
416 	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
417 		&cmd, sizeof(cmd), NULL, 0, 0);
418 }
419 
420 
421 static status_t
422 pause_resume(cd_driver_info *info, bool resume)
423 {
424 	scsi_cmd_pause_resume cmd;
425 
426 	TRACE("pause_resume()\n");
427 
428 	memset(&cmd, 0, sizeof(cmd));
429 	cmd.opcode = SCSI_OP_PAUSE_RESUME;
430 	cmd.resume = resume;
431 
432 	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
433 		&cmd, sizeof(cmd), NULL, 0, 0);
434 }
435 
436 
437 static status_t
438 scan(cd_driver_info *info, const scsi_scan *buf)
439 {
440 	scsi_cmd_scan cmd;
441 	scsi_position curPos;
442 	scsi_cd_current_position *cdPos;
443 
444 	TRACE("scan(direction =% d)\n", buf->direction);
445 
446 	status_t res = get_position(info, &curPos);
447 	if (res != B_OK)
448 		return res;
449 
450 	cdPos = (scsi_cd_current_position *)((char *)&curPos
451 		+ sizeof(scsi_subchannel_data_header));
452 
453 	if (buf->direction == 0) {
454 		scsi_play_position playPos;
455 
456 		// to stop scan, we issue play command with "open end"
457 		playPos.start_m = cdPos->absolute_address.time.minute;
458 		playPos.start_s = cdPos->absolute_address.time.second;
459 		playPos.start_f = cdPos->absolute_address.time.frame;
460 		playPos.end_m = 99;
461 		playPos.end_s = 59;
462 		playPos.end_f = 24;
463 
464 		return play_msf(info, &playPos);
465 	}
466 
467 	memset(&cmd, 0, sizeof(cmd));
468 
469 	cmd.opcode = SCSI_OP_SCAN;
470 	cmd.direct = buf->direction < 0;
471 	cmd.start.time = cdPos->absolute_address.time;
472 	cmd.type = scsi_scan_msf;
473 
474 	/*
475 	tmp = (uint8 *)&cmd;
476 	dprintf("%d %d %d %d %d %d %d %d %d %d %d %d\n",
477 		tmp[0], tmp[1], tmp[2], tmp[3], tmp[4], tmp[5],
478 		tmp[6], tmp[7], tmp[8], tmp[9], tmp[10], tmp[11]);
479 	*/
480 
481 	return sSCSIPeripheral->simple_exec(info->scsi_periph_device,
482 		&cmd, sizeof(cmd), NULL, 0, 0);
483 }
484 
485 
486 static status_t
487 read_cd(cd_driver_info *info, const scsi_read_cd *readCD)
488 {
489 	scsi_cmd_read_cd *cmd;
490 	uint32 lba, length;
491 	scsi_ccb *ccb;
492 	status_t res;
493 
494 	// we use safe_exec instead of simple_exec as we want to set
495 	// the sorting order manually (only makes much sense if you grab
496 	// multiple tracks at once, but we are prepared)
497 	ccb = info->scsi->alloc_ccb(info->scsi_device);
498 
499 	if (ccb == NULL)
500 		return B_NO_MEMORY;
501 
502 	cmd = (scsi_cmd_read_cd *)ccb->cdb;
503 	memset(cmd, 0, sizeof(*cmd));
504 	cmd->opcode = SCSI_OP_READ_CD;
505 	cmd->sector_type = 1;
506 
507 	// skip first two seconds, they are lead-in
508 	lba = (readCD->start_m * 60 + readCD->start_s) * 75 + readCD->start_f
509 		- 2 * 75;
510 	length = (readCD->length_m * 60 + readCD->length_s) * 75 + readCD->length_f;
511 
512 	cmd->lba = B_HOST_TO_BENDIAN_INT32(lba);
513 	cmd->high_length = (length >> 16) & 0xff;
514 	cmd->mid_length = (length >> 8) & 0xff;
515 	cmd->low_length = length & 0xff;
516 
517 	cmd->error_field = scsi_read_cd_error_none;
518 	cmd->edc_ecc = 0;
519 	cmd->user_data = 1;
520 	cmd->header_code = scsi_read_cd_header_none;
521 	cmd->sync = 0;
522 	cmd->sub_channel_selection = scsi_read_cd_sub_channel_none;
523 
524 	ccb->cdb_length = sizeof(*cmd);
525 
526 	ccb->flags = SCSI_DIR_IN | SCSI_DIS_DISCONNECT;
527 	ccb->sort = lba;
528 	// are 10 seconds enough for timeout?
529 	ccb->timeout = 10;
530 
531 	// TODO: we pass a user buffer here!
532 	ccb->data = (uint8 *)readCD->buffer;
533 	ccb->sg_list = NULL;
534 	ccb->data_length = readCD->buffer_length;
535 
536 	res = sSCSIPeripheral->safe_exec(info->scsi_periph_device, ccb);
537 
538 	info->scsi->free_ccb(ccb);
539 
540 	return res;
541 }
542 
543 
544 static int
545 log2(uint32 x)
546 {
547 	int y;
548 
549 	for (y = 31; y >= 0; --y) {
550 		if (x == (1UL << y))
551 			break;
552 	}
553 
554 	return y;
555 }
556 
557 
558 static status_t
559 do_io(void* cookie, IOOperation* operation)
560 {
561 	cd_driver_info* info = (cd_driver_info*)cookie;
562 
563 	// TODO: this can go away as soon as we pushed the IOOperation to the upper
564 	// layers - we can then set scsi_periph::io() as callback for the scheduler
565 	size_t bytesTransferred;
566 	status_t status = sSCSIPeripheral->io(info->scsi_periph_device, operation,
567 		&bytesTransferred);
568 
569 	info->io_scheduler->OperationCompleted(operation, status, bytesTransferred);
570 	return status;
571 }
572 
573 
574 //	#pragma mark - device module API
575 
576 
577 static status_t
578 cd_init_device(void* _info, void** _cookie)
579 {
580 	cd_driver_info* info = (cd_driver_info*)_info;
581 
582 	// and get (initial) capacity
583 	scsi_ccb *request = info->scsi->alloc_ccb(info->scsi_device);
584 	if (request == NULL)
585 		return B_NO_MEMORY;
586 
587 	sSCSIPeripheral->check_capacity(info->scsi_periph_device, request);
588 	info->scsi->free_ccb(request);
589 
590 	*_cookie = info;
591 	return B_OK;
592 }
593 
594 
595 static void
596 cd_uninit_device(void* _cookie)
597 {
598 	cd_driver_info* info = (cd_driver_info*)_cookie;
599 
600 	delete info->io_scheduler;
601 	delete info->dma_resource;
602 }
603 
604 
605 static status_t
606 cd_open(void* _info, const char* path, int openMode, void** _cookie)
607 {
608 	cd_driver_info* info = (cd_driver_info*)_info;
609 
610 	cd_handle* handle = (cd_handle*)malloc(sizeof(cd_handle));
611 	if (handle == NULL)
612 		return B_NO_MEMORY;
613 
614 	handle->info = info;
615 
616 	status_t status = sSCSIPeripheral->handle_open(info->scsi_periph_device,
617 		(periph_handle_cookie)handle, &handle->scsi_periph_handle);
618 	if (status < B_OK) {
619 		free(handle);
620 		return status;
621 	}
622 
623 	*_cookie = handle;
624 	return B_OK;
625 }
626 
627 
628 static status_t
629 cd_close(void* cookie)
630 {
631 	cd_handle* handle = (cd_handle*)cookie;
632 	TRACE("close()\n");
633 
634 	sSCSIPeripheral->handle_close(handle->scsi_periph_handle);
635 	return B_OK;
636 }
637 
638 
639 static status_t
640 cd_free(void* cookie)
641 {
642 	cd_handle* handle = (cd_handle*)cookie;
643 	TRACE("free()\n");
644 
645 	sSCSIPeripheral->handle_free(handle->scsi_periph_handle);
646 	free(handle);
647 	return B_OK;
648 }
649 
650 
651 static status_t
652 cd_read(void* cookie, off_t pos, void* buffer, size_t* _length)
653 {
654 	cd_handle* handle = (cd_handle*)cookie;
655 	size_t length = *_length;
656 
657 	if (handle->info->capacity == 0)
658 		return B_DEV_NO_MEDIA;
659 
660 	IORequest request;
661 	status_t status = request.Init(pos, buffer, length, false, 0);
662 	if (status != B_OK)
663 		return status;
664 
665 	status = handle->info->io_scheduler->ScheduleRequest(&request);
666 	if (status != B_OK)
667 		return status;
668 
669 	status = request.Wait(0, 0);
670 	if (status == B_OK)
671 		*_length = length;
672 	else
673 		dprintf("cd_read(): request.Wait() returned: %s\n", strerror(status));
674 
675 	return status;
676 }
677 
678 
679 static status_t
680 cd_write(void* cookie, off_t pos, const void* buffer, size_t* _length)
681 {
682 	cd_handle* handle = (cd_handle*)cookie;
683 	size_t length = *_length;
684 
685 	if (handle->info->capacity == 0)
686 		return B_DEV_NO_MEDIA;
687 
688 	IORequest request;
689 	status_t status = request.Init(pos, (void*)buffer, length, true, 0);
690 	if (status != B_OK)
691 		return status;
692 
693 	status = handle->info->io_scheduler->ScheduleRequest(&request);
694 	if (status != B_OK)
695 		return status;
696 
697 	status = request.Wait(0, 0);
698 	if (status == B_OK)
699 		*_length = length;
700 	else
701 		dprintf("cd_write(): request.Wait() returned: %s\n", strerror(status));
702 
703 	return status;
704 }
705 
706 
707 static status_t
708 cd_io(void* cookie, io_request* request)
709 {
710 	cd_handle* handle = (cd_handle*)cookie;
711 
712 	if (handle->info->capacity == 0)
713 		return B_DEV_NO_MEDIA;
714 
715 	return handle->info->io_scheduler->ScheduleRequest(request);
716 }
717 
718 
719 static status_t
720 cd_ioctl(void* cookie, uint32 op, void* buffer, size_t length)
721 {
722 	cd_handle* handle = (cd_handle*)cookie;
723 	cd_driver_info *info = handle->info;
724 
725 	TRACE("ioctl(op = %lu)\n", op);
726 
727 	switch (op) {
728 		case B_GET_DEVICE_SIZE:
729 		{
730 			status_t status = update_capacity(info);
731 			if (status != B_OK)
732 				return status;
733 
734 			size_t size = info->capacity * info->block_size;
735 			return user_memcpy(buffer, &size, sizeof(size_t));
736 		}
737 
738 		case B_GET_GEOMETRY:
739 		{
740 			if (buffer == NULL /*|| length != sizeof(device_geometry)*/)
741 				return B_BAD_VALUE;
742 
743 		 	device_geometry geometry;
744 			status_t status = get_geometry(handle, &geometry);
745 			if (status != B_OK)
746 				return status;
747 
748 			return user_memcpy(buffer, &geometry, sizeof(device_geometry));
749 		}
750 
751 		case B_GET_ICON_NAME:
752 			return user_strlcpy((char*)buffer, "devices/drive-optical",
753 				B_FILE_NAME_LENGTH);
754 
755 		case B_GET_VECTOR_ICON:
756 		{
757 			device_icon iconData;
758 			if (length != sizeof(device_icon))
759 				return B_BAD_VALUE;
760 			if (user_memcpy(&iconData, buffer, sizeof(device_icon)) != B_OK)
761 				return B_BAD_ADDRESS;
762 
763 			if (iconData.icon_size >= (int32)sizeof(kCDIcon)) {
764 				if (user_memcpy(iconData.icon_data, kCDIcon,
765 						sizeof(kCDIcon)) != B_OK)
766 					return B_BAD_ADDRESS;
767 			}
768 
769 			iconData.icon_size = sizeof(kCDIcon);
770 			return user_memcpy(buffer, &iconData, sizeof(device_icon));
771 		}
772 
773 		case B_SCSI_GET_TOC:
774 			// TODO: we pass a user buffer here!
775 			return get_toc(info, (scsi_toc *)buffer);
776 
777 		case B_EJECT_DEVICE:
778 		case B_SCSI_EJECT:
779 			return load_eject(info, false);
780 
781 		case B_LOAD_MEDIA:
782 			return load_eject(info, true);
783 
784 		case B_SCSI_GET_POSITION:
785 		{
786 			if (buffer == NULL)
787 				return B_BAD_VALUE;
788 
789 			scsi_position position;
790 			status_t status = get_position(info, &position);
791 			if (status != B_OK)
792 				return status;
793 
794 			return user_memcpy(buffer, &position, sizeof(scsi_position));
795 		}
796 
797 		case B_SCSI_GET_VOLUME:
798 			// TODO: we pass a user buffer here!
799 			return get_set_volume(info, (scsi_volume *)buffer, false);
800 		case B_SCSI_SET_VOLUME:
801 			// TODO: we pass a user buffer here!
802 			return get_set_volume(info, (scsi_volume *)buffer, true);
803 
804 		case B_SCSI_PLAY_TRACK:
805 		{
806 			scsi_play_track track;
807 			if (user_memcpy(&track, buffer, sizeof(scsi_play_track)) != B_OK)
808 				return B_BAD_ADDRESS;
809 
810 			return play_track_index(info, &track);
811 		}
812 		case B_SCSI_PLAY_POSITION:
813 		{
814 			scsi_play_position position;
815 			if (user_memcpy(&position, buffer, sizeof(scsi_play_position))
816 					!= B_OK)
817 				return B_BAD_ADDRESS;
818 
819 			return play_msf(info, &position);
820 		}
821 
822 		case B_SCSI_STOP_AUDIO:
823 			return stop_audio(info);
824 		case B_SCSI_PAUSE_AUDIO:
825 			return pause_resume(info, false);
826 		case B_SCSI_RESUME_AUDIO:
827 			return pause_resume(info, true);
828 
829 		case B_SCSI_SCAN:
830 		{
831 			scsi_scan scanBuffer;
832 			if (user_memcpy(&scanBuffer, buffer, sizeof(scsi_scan)) != B_OK)
833 				return B_BAD_ADDRESS;
834 
835 			return scan(info, &scanBuffer);
836 		}
837 		case B_SCSI_READ_CD:
838 			// TODO: we pass a user buffer here!
839 			return read_cd(info, (scsi_read_cd *)buffer);
840 
841 		default:
842 			return sSCSIPeripheral->ioctl(handle->scsi_periph_handle, op,
843 				buffer, length);
844 	}
845 }
846 
847 
848 //	#pragma mark - scsi_periph callbacks
849 
850 
851 static void
852 cd_set_capacity(cd_driver_info* info, uint64 capacity, uint32 blockSize)
853 {
854 	TRACE("cd_set_capacity(info = %p, capacity = %Ld, blockSize = %ld)\n",
855 		info, capacity, blockSize);
856 
857 	// get log2, if possible
858 	uint32 blockShift = log2(blockSize);
859 
860 	if ((1UL << blockShift) != blockSize)
861 		blockShift = 0;
862 
863 	info->capacity = capacity;
864 
865 	if (info->block_size != blockSize) {
866 		if (capacity == 0) {
867 			// there is obviously no medium in the drive, don't try to update
868 			// the DMA resource
869 			return;
870 		}
871 
872 		if (info->block_size != 0) {
873 			dprintf("old %ld, new %ld\n", info->block_size, blockSize);
874 			panic("updating DMAResource not yet implemented...");
875 		}
876 
877 		// TODO: we need to replace the DMAResource in our IOScheduler
878 		status_t status = info->dma_resource->Init(info->node, blockSize, 1024,
879 			32);
880 		if (status != B_OK)
881 			panic("initializing DMAResource failed: %s", strerror(status));
882 
883 		info->io_scheduler = new(std::nothrow) IOScheduler(info->dma_resource);
884 		if (info->io_scheduler == NULL)
885 			panic("allocating IOScheduler failed.");
886 
887 		// TODO: use whole device name here
888 		status = info->io_scheduler->Init("scsi");
889 		if (status != B_OK)
890 			panic("initializing IOScheduler failed: %s", strerror(status));
891 
892 		info->io_scheduler->SetCallback(do_io, info);
893 	}
894 
895 	info->block_size = blockSize;
896 }
897 
898 
899 static void
900 cd_media_changed(cd_driver_info* info, scsi_ccb* request)
901 {
902 	// do a capacity check
903 	// TBD: is this a good idea (e.g. if this is an empty CD)?
904 	sSCSIPeripheral->check_capacity(info->scsi_periph_device, request);
905 }
906 
907 
908 scsi_periph_callbacks callbacks = {
909 	(void (*)(periph_device_cookie, uint64, uint32))cd_set_capacity,
910 	(void (*)(periph_device_cookie, scsi_ccb *))cd_media_changed
911 };
912 
913 
914 //	#pragma mark - driver module API
915 
916 
917 static float
918 cd_supports_device(device_node* parent)
919 {
920 	const char* bus;
921 	uint8 deviceType;
922 
923 	// make sure parent is really the SCSI bus manager
924 	if (sDeviceManager->get_attr_string(parent, B_DEVICE_BUS, &bus, false))
925 		return -1;
926 
927 	if (strcmp(bus, "scsi"))
928 		return 0.0;
929 
930 	// check whether it's really a CD-ROM or WORM
931 	if (sDeviceManager->get_attr_uint8(parent, SCSI_DEVICE_TYPE_ITEM,
932 			&deviceType, true) != B_OK
933 		|| (deviceType != scsi_dev_CDROM && deviceType != scsi_dev_WORM))
934 		return 0.0;
935 
936 	return 0.6;
937 }
938 
939 
940 /*!	Called whenever a new device was added to system;
941 	if we really support it, we create a new node that gets
942 	server by the block_io module
943 */
944 static status_t
945 cd_register_device(device_node* node)
946 {
947 	const scsi_res_inquiry* deviceInquiry = NULL;
948 	size_t inquiryLength;
949 	uint32 maxBlocks;
950 
951 	// get inquiry data
952 	if (sDeviceManager->get_attr_raw(node, SCSI_DEVICE_INQUIRY_ITEM,
953 			(const void**)&deviceInquiry, &inquiryLength, true) != B_OK
954 		|| inquiryLength < sizeof(deviceInquiry))
955 		return B_ERROR;
956 
957 	// get block limit of underlying hardware to lower it (if necessary)
958 	if (sDeviceManager->get_attr_uint32(node, B_DMA_MAX_TRANSFER_BLOCKS,
959 			&maxBlocks, true) != B_OK)
960 		maxBlocks = INT_MAX;
961 
962 	// using 10 byte commands, at most 0xffff blocks can be transmitted at once
963 	// (sadly, we cannot update this value later on if only 6 byte commands
964 	//  are supported, but the block_io module can live with that)
965 	maxBlocks = min_c(maxBlocks, 0xffff);
966 
967 	// ready to register
968 	device_attr attrs[] = {
969 		{"removable", B_UINT8_TYPE, {ui8: deviceInquiry->removable_medium}},
970 		{B_DMA_MAX_TRANSFER_BLOCKS, B_UINT32_TYPE, {ui32: maxBlocks}},
971 		{ NULL }
972 	};
973 
974 	return sDeviceManager->register_node(node, SCSI_CD_DRIVER_MODULE_NAME,
975 		attrs, NULL, NULL);
976 }
977 
978 
979 static status_t
980 cd_init_driver(device_node* node, void** _cookie)
981 {
982 	TRACE("cd_init_driver");
983 
984 	uint8 removable;
985 	status_t status = sDeviceManager->get_attr_uint8(node, "removable",
986 		&removable, false);
987 	if (status != B_OK)
988 		return status;
989 
990 	cd_driver_info* info = (cd_driver_info*)malloc(sizeof(cd_driver_info));
991 	if (info == NULL)
992 		return B_NO_MEMORY;
993 
994 	memset(info, 0, sizeof(cd_driver_info));
995 
996 	info->dma_resource = new(std::nothrow) DMAResource;
997 	if (info->dma_resource == NULL) {
998 		free(info);
999 		return B_NO_MEMORY;
1000 	}
1001 
1002 	info->node = node;
1003 	info->removable = removable;
1004 
1005 	// set capacity to zero, so it get checked on first opened handle
1006 	info->capacity = 0;
1007 	info->block_size = 0;
1008 
1009 	sDeviceManager->get_attr_uint8(node, SCSI_DEVICE_TYPE_ITEM,
1010 		&info->device_type, true);
1011 
1012 	device_node *parent = sDeviceManager->get_parent_node(node);
1013 	sDeviceManager->get_driver(parent, (driver_module_info**)&info->scsi,
1014 		(void**)&info->scsi_device);
1015 	sDeviceManager->put_node(parent);
1016 
1017 	status = sSCSIPeripheral->register_device((periph_device_cookie)info,
1018 		&callbacks, info->scsi_device, info->scsi, info->node,
1019 		info->removable, 10, &info->scsi_periph_device);
1020 	if (status != B_OK) {
1021 		free(info);
1022 		return status;
1023 	}
1024 
1025 	*_cookie = info;
1026 	return B_OK;
1027 }
1028 
1029 
1030 static void
1031 cd_uninit_driver(void* _cookie)
1032 {
1033 	cd_driver_info* info = (cd_driver_info*)_cookie;
1034 
1035 	sSCSIPeripheral->unregister_device(info->scsi_periph_device);
1036 	free(info);
1037 }
1038 
1039 
1040 static status_t
1041 cd_register_child_devices(void* _cookie)
1042 {
1043 	cd_driver_info* info = (cd_driver_info*)_cookie;
1044 
1045 	char* name = sSCSIPeripheral->compose_device_name(info->node, "disk/scsi");
1046 	if (name == NULL)
1047 		return B_ERROR;
1048 
1049 	status_t status = sDeviceManager->publish_device(info->node, name,
1050 		SCSI_CD_DEVICE_MODULE_NAME);
1051 
1052 	free(name);
1053 	return status;
1054 }
1055 
1056 
1057 module_dependency module_dependencies[] = {
1058 	{SCSI_PERIPH_MODULE_NAME, (module_info**)&sSCSIPeripheral},
1059 	{B_DEVICE_MANAGER_MODULE_NAME, (module_info**)&sDeviceManager},
1060 	{}
1061 };
1062 
1063 struct device_module_info sSCSICDDevice = {
1064 	{
1065 		SCSI_CD_DEVICE_MODULE_NAME,
1066 		0,
1067 		NULL
1068 	},
1069 
1070 	cd_init_device,
1071 	cd_uninit_device,
1072 	NULL,	// remove,
1073 
1074 	cd_open,
1075 	cd_close,
1076 	cd_free,
1077 	cd_read,
1078 	cd_write,
1079 	cd_io,
1080 	cd_ioctl,
1081 
1082 	NULL,	// select
1083 	NULL,	// deselect
1084 };
1085 
1086 struct driver_module_info sSCSICDDriver = {
1087 	{
1088 		SCSI_CD_DRIVER_MODULE_NAME,
1089 		0,
1090 		NULL
1091 	},
1092 
1093 	cd_supports_device,
1094 	cd_register_device,
1095 	cd_init_driver,
1096 	cd_uninit_driver,
1097 	cd_register_child_devices,
1098 	NULL,	// rescan
1099 	NULL,	// removed
1100 };
1101 
1102 module_info* modules[] = {
1103 	(module_info*)&sSCSICDDriver,
1104 	(module_info*)&sSCSICDDevice,
1105 	NULL
1106 };
1107