1/*
2 * Copyright 2004-2011, Haiku, Inc. All rights reserved.
3 * Copyright 2002-2004, Thomas Kurschel. All rights reserved.
4 *
5 * Distributed under the terms of the MIT License.
6 */
7
8/*
9	PIO data transmission
10
11	This file is more difficult then you might expect as the SCSI system
12	uses physical addresses everywhere which have to be mapped into
13	virtual address space during transmission. Additionally, during ATAPI
14	commands we may have to transmit more data then exist because the
15	data len specified by the command doesn't need to be the same as
16	of the data buffer provided.
17
18	The handling of S/G entries of odd size may look superfluous as the
19	SCSI bus manager can take care of that. In general, this would be possible
20	as most controllers need even alignment for DMA as well, but some can
21	handle _any_ S/G list and it wouldn't be sensitive to enforce stricter
22	alignement just for some rare PIO transmissions.
23
24	Little hint for the meaning of "transferred": this is the number of bytes
25	sent over the bus. For read-transmissions, this may be one more then copied
26	into the buffer (the extra byte read is stored in device->odd_byte), for
27	write-transmissions, this may be one less (the waiting byte is pending in
28	device->odd_byte).
29
30	In terms of error handling: we don't bother checking transmission of every
31	single byte via read/write_pio(). At least at the end of the request, when
32	the status bits are verified, we will see that something has gone wrong.
33
34	TBD: S/G entries may have odd start address. For non-Intel architecture
35	we either have to copy data to an aligned buffer or have to modify
36	PIO-handling in controller drivers.
37*/
38
39#include "ide_internal.h"
40#include "ide_sim.h"
41
42#include <thread.h>
43#include <vm/vm.h>
44
45#include <string.h>
46
47
48// internal error code if scatter gather table is too short
49#define ERR_TOO_BIG	(B_ERRORS_END + 1)
50
51
52/*! Prepare PIO transfer */
53void
54prep_PIO_transfer(ide_device_info *device, ide_qrequest *qrequest)
55{
56	SHOW_FLOW0(4, "");
57
58	device->left_sg_elem = qrequest->request->sg_count;
59	device->cur_sg_elem = qrequest->request->sg_list;
60	device->cur_sg_ofs = 0;
61	device->has_odd_byte = false;
62	qrequest->request->data_resid = qrequest->request->data_length;
63}
64
65
66/*! Transfer virtually continuous data */
67static inline status_t
68transfer_PIO_virtcont(ide_device_info *device, uint8 *virtualAddress,
69	int length, bool write, int *transferred)
70{
71	ide_bus_info *bus = device->bus;
72	ide_controller_interface *controller = bus->controller;
73	void * channel_cookie = bus->channel_cookie;
74
75	if (write) {
76		// if there is a byte left from last chunk, transmit it together
77		// with the first byte of the current chunk (IDE requires 16 bits
78		// to be transmitted at once)
79		if (device->has_odd_byte) {
80			uint8 buffer[2];
81
82			buffer[0] = device->odd_byte;
83			buffer[1] = *virtualAddress++;
84
85			controller->write_pio(channel_cookie, (uint16 *)buffer, 1, false);
86
87			--length;
88			*transferred += 2;
89		}
90
91		controller->write_pio(channel_cookie, (uint16 *)virtualAddress,
92			length / 2, false);
93
94		// take care if chunk size was odd, which means that 1 byte remains
95		virtualAddress += length & ~1;
96		*transferred += length & ~1;
97
98		device->has_odd_byte = (length & 1) != 0;
99
100		if (device->has_odd_byte)
101			device->odd_byte = *virtualAddress;
102	} else {
103		// if we read one byte too much last time, push it into current chunk
104		if (device->has_odd_byte) {
105			*virtualAddress++ = device->odd_byte;
106			--length;
107		}
108
109		SHOW_FLOW(4, "Reading PIO to %p, %d bytes", virtualAddress, length);
110
111		controller->read_pio(channel_cookie, (uint16 *)virtualAddress,
112			length / 2, false);
113
114		// take care of odd chunk size;
115		// in this case we read 1 byte to few!
116		virtualAddress += length & ~1;
117		*transferred += length & ~1;
118
119		device->has_odd_byte = (length & 1) != 0;
120
121		if (device->has_odd_byte) {
122			uint8 buffer[2];
123
124			// now read the missing byte; as we have to read 2 bytes at once,
125			// we'll read one byte too much
126			controller->read_pio(channel_cookie, (uint16 *)buffer, 1, false);
127
128			*virtualAddress = buffer[0];
129			device->odd_byte = buffer[1];
130
131			*transferred += 2;
132		}
133	}
134
135	return B_OK;
136}
137
138
139/*! Transmit physically continuous data */
140static inline status_t
141transfer_PIO_physcont(ide_device_info *device, addr_t physicalAddress,
142	int length, bool write, int *transferred)
143{
144	// we must split up chunk into B_PAGE_SIZE blocks as we can map only
145	// one page into address space at once
146	while (length > 0) {
147		addr_t virtualAddress;
148		void* handle;
149		int page_left, cur_len;
150		status_t err;
151		Thread* thread = thread_get_current_thread();
152
153		SHOW_FLOW(4, "Transmitting to/from physical address %lx, %d bytes left",
154			physicalAddress, length);
155
156		thread_pin_to_current_cpu(thread);
157		if (vm_get_physical_page_current_cpu(physicalAddress, &virtualAddress,
158				&handle) != B_OK) {
159			thread_unpin_from_current_cpu(thread);
160			// ouch: this should never ever happen
161			set_sense(device, SCSIS_KEY_HARDWARE_ERROR, SCSIS_ASC_INTERNAL_FAILURE);
162			return B_ERROR;
163		}
164
165		// if chunks starts in the middle of a page, we have even less then
166		// a page left
167		page_left = B_PAGE_SIZE - physicalAddress % B_PAGE_SIZE;
168
169		SHOW_FLOW(4, "page_left=%d", page_left);
170
171		cur_len = min_c(page_left, length);
172
173		SHOW_FLOW(4, "cur_len=%d", cur_len);
174
175		err = transfer_PIO_virtcont(device, (uint8 *)virtualAddress,
176			cur_len, write, transferred);
177
178		vm_put_physical_page_current_cpu(virtualAddress, handle);
179		thread_unpin_from_current_cpu(thread);
180
181		if (err != B_OK)
182			return err;
183
184		length -= cur_len;
185		physicalAddress += cur_len;
186	}
187
188	return B_OK;
189}
190
191
192/*! Transfer PIO block from/to buffer */
193static inline int
194transfer_PIO_block(ide_device_info *device, int length, bool write, int *transferred)
195{
196	// data is usually split up into multiple scatter/gather blocks
197	while (length > 0) {
198		int left_bytes, cur_len;
199		status_t err;
200
201		if (device->left_sg_elem == 0)
202			// ups - buffer too small (for ATAPI data, this is OK)
203			return ERR_TOO_BIG;
204
205		// we might have transmitted part of a scatter/entry already!
206		left_bytes = device->cur_sg_elem->size - device->cur_sg_ofs;
207
208		cur_len = min_c(left_bytes, length);
209
210		err = transfer_PIO_physcont(device,
211			(addr_t)device->cur_sg_elem->address + device->cur_sg_ofs,
212			cur_len, write, transferred);
213
214		if (err != B_OK)
215			return err;
216
217		if (left_bytes <= length) {
218			// end of one scatter/gather block reached
219			device->cur_sg_ofs = 0;
220			++device->cur_sg_elem;
221			--device->left_sg_elem;
222		} else {
223			// still in the same block
224			device->cur_sg_ofs += cur_len;
225		}
226
227		length -= cur_len;
228	}
229
230	return B_OK;
231}
232
233
234/*! Write zero data (required for ATAPI if we ran out of data) */
235
236static void
237write_discard_PIO(ide_device_info *device, int length)
238{
239	ide_bus_info *bus = device->bus;
240	uint8 buffer[32];
241
242	memset(buffer, 0, sizeof(buffer));
243
244	// we transmit 32 zero-bytes at once
245	// (not very efficient but easy to implement - you get what you deserve
246	//  when you don't provide enough buffer)
247	while (length > 0) {
248		int cur_len;
249
250		// if device asks for odd number of bytes, append an extra byte to
251		// make length even (this is the "length + 1" term)
252		cur_len = min_c(length + 1, (int)(sizeof(buffer))) / 2;
253
254		bus->controller->write_pio(bus->channel_cookie, (uint16 *)buffer, cur_len, false);
255
256		length -= cur_len * 2;
257	}
258}
259
260
261/*! Read PIO data and discard it (required for ATAPI if buffer was too small) */
262static void
263read_discard_PIO(ide_device_info *device, int length)
264{
265	ide_bus_info *bus = device->bus;
266	uint8 buffer[32];
267
268	// discard 32 bytes at once	(see write_discard_PIO)
269	while (length > 0) {
270		int cur_len;
271
272		// read extra byte if length is odd (that's the "length + 1")
273		cur_len = min_c(length + 1, (int)sizeof(buffer)) / 2;
274
275		bus->controller->read_pio(bus->channel_cookie, (uint16 *)buffer, cur_len, false);
276
277		length -= cur_len * 2;
278	}
279}
280
281
282/*! write PIO data
283	return: there are 3 possible results
284	NO_ERROR - everything's nice and groovy
285	ERR_TOO_BIG - data buffer was too short, remaining data got discarded
286	B_ERROR - something serious went wrong, sense data was set
287*/
288status_t
289write_PIO_block(ide_qrequest *qrequest, int length)
290{
291	ide_device_info *device = qrequest->device;
292	int transferred;
293	status_t err;
294
295	transferred = 0;
296	err = transfer_PIO_block(device, length, true, &transferred);
297
298	qrequest->request->data_resid -= transferred;
299
300	if (err != ERR_TOO_BIG)
301		return err;
302
303	// there may be a pending odd byte - transmit that now
304	if (qrequest->device->has_odd_byte) {
305		uint8 buffer[2];
306
307		buffer[0] = device->odd_byte;
308		buffer[1] = 0;
309
310		device->has_odd_byte = false;
311
312		qrequest->request->data_resid -= 1;
313		transferred += 2;
314
315		device->bus->controller->write_pio(device->bus->channel_cookie, (uint16 *)buffer, 1, false);
316	}
317
318	// "transferred" may actually be larger then length because the last odd-byte
319	// is sent together with an extra zero-byte
320	if (transferred >= length)
321		return err;
322
323	// Ouch! the device asks for data but we haven't got any left.
324	// Sadly, this behaviour is OK for ATAPI packets, but there is no
325	// way to tell the device that we don't have any data left;
326	// only solution is to send zero bytes, though it's BAD BAD BAD
327	write_discard_PIO(qrequest->device, length - transferred);
328	return ERR_TOO_BIG;
329}
330
331
332/*!	read PIO data
333	return: see write_PIO_block
334*/
335status_t
336read_PIO_block(ide_qrequest *qrequest, int length)
337{
338	ide_device_info *device = qrequest->device;
339	int transferred;
340	status_t err;
341
342	transferred = 0;
343	err = transfer_PIO_block(qrequest->device, length, false, &transferred);
344
345	qrequest->request->data_resid -= transferred;
346
347	// if length was odd, there's an extra byte waiting in device->odd_byte
348	if (device->has_odd_byte) {
349		// discard byte
350		device->has_odd_byte = false;
351		// adjust res_id as the extra byte didn't reach the buffer
352		++qrequest->request->data_resid;
353	}
354
355	if (err != ERR_TOO_BIG)
356		return err;
357
358	// the device returns more data then the buffer can store;
359	// for ATAPI this is OK - we just discard remaining bytes (there
360	// is no way to tell ATAPI about that, but we "only" waste time)
361
362	// perhaps discarding the extra odd-byte was sufficient
363	if (transferred >= length)
364		return err;
365
366	SHOW_FLOW(3, "discarding after %d bytes", transferred);
367	read_discard_PIO(qrequest->device, length - transferred);
368	return ERR_TOO_BIG;
369}
370