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