xref: /haiku/src/add-ons/kernel/generic/scsi_periph/removable.cpp (revision a1e8da410153bb137c3358d5bff3028902da2081)
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 
17 void
periph_media_changed(scsi_periph_device_info * device,scsi_ccb * request)18 periph_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 
69 void
periph_media_changed_public(scsi_periph_device_info * device)70 periph_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 
87 static err_res
send_tur(scsi_periph_device_info * device,scsi_ccb * request)88 send_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 
114 static err_res
wait_for_ready(scsi_periph_device_info * device,scsi_ccb * request)115 wait_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 
147 status_t
periph_get_media_status(scsi_periph_handle_info * handle)148 periph_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 
190 err:
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 */
200 err_res
periph_send_start_stop(scsi_periph_device_info * device,scsi_ccb * request,bool start,bool withLoadEject)201 periph_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