1/*
2 * Copyright 2004-2011, Haiku, Inc. All RightsReserved.
3 * Copyright 2002-2003, Thomas Kurschel. All rights reserved.
4 *
5 * Distributed under the terms of the MIT License.
6 */
7
8
9//!	Handling of removable media.
10
11
12#include "scsi_periph_int.h"
13
14#include <string.h>
15
16
17void
18periph_media_changed(scsi_periph_device_info *device, scsi_ccb *request)
19{
20	uint32 backup_flags;
21	uint8 backup_cdb[SCSI_MAX_CDB_SIZE];
22	uchar backup_cdb_len;
23	int64 backup_sort;
24	bigtime_t backup_timeout;
25	uchar *backup_data;
26	const physical_entry *backup_sg_list;
27	uint16 backup_sg_count;
28	uint32 backup_data_len;
29
30	// if there is no hook, the driver doesn't handle removal devices
31	if (!device->removable) {
32		SHOW_ERROR0( 1, "Driver doesn't support medium changes, but there occured one!?" );
33		return;
34	}
35
36	// when medium has changed, tell all handles
37	periph_media_changed_public(device);
38
39	// the peripheral driver may need a fresh ccb; sadly, we cannot allocate one
40	// as this may lead to a deadlock if all ccb are in use already; thus, we
41	// have to backup all relevant data of current ccb and use it instead of a
42	// new one - not pretty but working (and performance is not an issue in this
43	// path)
44	backup_flags = request->flags;
45	memcpy(backup_cdb, request->cdb, SCSI_MAX_CDB_SIZE);
46	backup_cdb_len = request->cdb_length;
47	backup_sort = request->sort;
48	backup_timeout = request->timeout;
49	backup_data = request->data;
50	backup_sg_list = request->sg_list;
51	backup_sg_count = request->sg_count;
52	backup_data_len = request->data_length;
53
54	if (device->callbacks->media_changed != NULL)
55		device->callbacks->media_changed(device->periph_device, request);
56
57	request->flags = backup_flags;
58	memcpy(request->cdb, backup_cdb, SCSI_MAX_CDB_SIZE);
59	request->cdb_length = backup_cdb_len;
60	request->sort = backup_sort;
61	request->timeout = backup_timeout;
62	request->data = backup_data;
63	request->sg_list = backup_sg_list;
64	request->sg_count = backup_sg_count;
65	request->data_length = backup_data_len;
66}
67
68
69void
70periph_media_changed_public(scsi_periph_device_info *device)
71{
72	scsi_periph_handle_info *handle;
73
74	mutex_lock(&device->mutex);
75
76	// when medium has changed, tell all handles
77	// (this must be atomic for each handle!)
78	for (handle = device->handles; handle; handle = handle->next)
79		handle->pending_error = B_DEV_MEDIA_CHANGED;
80
81	mutex_unlock(&device->mutex);
82}
83
84
85/** send TUR */
86
87static err_res
88send_tur(scsi_periph_device_info *device, scsi_ccb *request)
89{
90	scsi_cmd_tur *cmd = (scsi_cmd_tur *)request->cdb;
91
92	request->flags = SCSI_DIR_NONE | SCSI_ORDERED_QTAG;
93
94	request->data = NULL;
95	request->sg_list = NULL;
96	request->data_length = 0;
97	request->timeout = device->std_timeout;
98	request->sort = -1;
99	request->sg_list = NULL;
100
101	memset(cmd, 0, sizeof(*cmd));
102	cmd->opcode = SCSI_OP_TEST_UNIT_READY;
103
104	request->cdb_length = sizeof(*cmd);
105
106	device->scsi->sync_io(request);
107
108	return periph_check_error(device, request);
109}
110
111
112/** wait until device is ready */
113
114static err_res
115wait_for_ready(scsi_periph_device_info *device, scsi_ccb *request)
116{
117	int retries = 0;
118
119	while (true) {
120		err_res res;
121
122		// we send TURs until the device is OK or all hope is lost
123		res = send_tur(device, request);
124
125		switch (res.action) {
126			case err_act_ok:
127				return MK_ERROR(err_act_ok, B_OK);
128
129			case err_act_retry:
130				if (++retries >= 3)
131					return MK_ERROR(err_act_fail, res.error_code);
132				break;
133
134			case err_act_many_retries:
135				if (++retries >= 30)
136					return MK_ERROR(err_act_fail, res.error_code);
137				break;
138
139			default:
140				SHOW_FLOW( 3, "action: %x, error: %x", (int)res.action, (int)res.error_code);
141				return res;
142		}
143	}
144}
145
146
147status_t
148periph_get_media_status(scsi_periph_handle_info *handle)
149{
150	scsi_periph_device_info *device = handle->device;
151	scsi_ccb *request;
152	err_res res;
153	status_t err;
154
155	mutex_lock(&device->mutex);
156
157	// removal requests are returned to exactly one handle
158	// (no real problem, as noone check medias status "by mistake")
159	if (device->removal_requested) {
160		device->removal_requested = false;
161		err = B_DEV_MEDIA_CHANGE_REQUESTED;
162		goto err;
163	}
164
165	// if there is a pending error (read: media has changed), return once per handle
166	err = handle->pending_error;
167	if (err != B_OK) {
168		handle->pending_error = B_OK;
169		goto err;
170	}
171
172	SHOW_FLOW0( 3, "" );
173
174	mutex_unlock(&device->mutex);
175
176	// finally, ask the device itself
177
178	request = device->scsi->alloc_ccb(device->scsi_device);
179	if (request == NULL)
180		return B_NO_MEMORY;
181
182	res = wait_for_ready(device, request);
183
184	device->scsi->free_ccb(request);
185
186	SHOW_FLOW(3, "error_code: %x", (int)res.error_code);
187
188	return res.error_code;
189
190err:
191	mutex_unlock(&device->mutex);
192	return err;
193}
194
195
196/*!	Send START/STOP command to device
197	start - true for start, false for stop
198	withLoadEject - if true, then lock drive on start and eject on stop
199*/
200err_res
201periph_send_start_stop(scsi_periph_device_info *device, scsi_ccb *request,
202	bool start, bool withLoadEject)
203{
204	scsi_cmd_ssu *cmd = (scsi_cmd_ssu *)request->cdb;
205
206	// this must be ordered, so all previous commands are really finished
207	request->flags = SCSI_DIR_NONE | SCSI_ORDERED_QTAG;
208
209	request->data = NULL;
210	request->sg_list = NULL;
211	request->data_length = 0;
212	request->timeout = device->std_timeout;
213	request->sort = -1;
214	request->sg_list = NULL;
215
216	memset(cmd, 0, sizeof(*cmd));
217	cmd->opcode = SCSI_OP_START_STOP;
218	// we don't want to poll; we give a long timeout instead
219	// (well - the default timeout _is_ large)
220	cmd->immediately = 0;
221	cmd->start = start;
222	cmd->load_eject = withLoadEject;
223
224	request->cdb_length = sizeof(*cmd);
225
226	device->scsi->sync_io(request);
227
228	return periph_check_error(device, request);
229}
230