1 /*
2  * xhdi.cpp - XHDI like disk driver interface
3  *
4  * Copyright (c) 2002-2008 Petr Stehlik of ARAnyM dev team (see AUTHORS)
5  *
6  * This file is part of the ARAnyM project which builds a new and powerful
7  * TOS/FreeMiNT compatible virtual machine running on almost any hardware.
8  *
9  * ARAnyM is free software; you can redistribute it and/or modify
10  * it under the terms of the GNU General Public License as published by
11  * the Free Software Foundation; either version 2 of the License, or
12  * (at your option) any later version.
13  *
14  * ARAnyM is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with ARAnyM; if not, write to the Free Software
21  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22  */
23 
24 #include "sysdeps.h"
25 #include <cassert>
26 #include "cpu_emulation.h"
27 #include "xhdi.h"
28 #include "atari_rootsec.h"
29 #include "tools.h"
30 #include <errno.h>
31 #if (defined(X86_ASSEMBLY) || defined(X86_64_ASSEMBLY)) && defined(__SSE2__)
32 #include <emmintrin.h>
33 /* #define USE_SSE_BYTESWAP 1 */
34 #endif
35 
36 #define DEBUG 0
37 #include "debug.h"
38 
39 #ifndef HAVE_FSEEKO
40 #  define fseeko(a,b,c)	fseek(a,b,c)
41 #endif
42 #ifdef __BEOS__
43 extern "C" int fseeko(FILE *stream, off_t offset, int whence);
44 #endif
45 
46 #define XHDI_BLOCK_SIZE	512
47 
48 /* XHDI error codes */
49 #include "toserror.h"
50 
51 
XHDIDriver()52 XHDIDriver::XHDIDriver()
53 {
54 	init_disks();
55 }
56 
~XHDIDriver()57 XHDIDriver::~XHDIDriver()
58 {
59 	close_disks();
60 }
61 
reset()62 void XHDIDriver::reset()
63 {
64 	close_disks();
65 	init_disks();
66 }
67 
init_disks()68 void XHDIDriver::init_disks()
69 {
70 	// init all disks
71 	for(unsigned i=0; i<sizeof(disks)/sizeof(disks[0]); i++) {
72 		disks[i].present = false;
73 		disks[i].file = NULL;
74 	}
75 
76 	// setup disks array with copied disks and partitions values so that
77 	// user's changes in SETUP GUI don't affect the pending disk operations
78 	for(int i=0; i<DISKS; i++) {
79 		copy_scsidevice_settings(i, &bx_options.disks[i], disks+SCSI_START+i);
80 	}
81 	for(int i=0; i<2; i++) {
82 		copy_atadevice_settings(&bx_options.atadevice[0][i], disks+IDE_START+i);
83 	}
84 }
85 
close_disks()86 void XHDIDriver::close_disks()
87 {
88 	// close all open disks
89 	for(unsigned i=0; i<sizeof(disks)/sizeof(disks[0]); i++) {
90 		disk_t *disk = &disks[i];
91 		if (disk->file != NULL) {
92 #ifdef HAVE_FSYNC
93 			fsync(fileno(disk->file));
94 #endif
95 			fclose(disk->file);
96 			disk->file = NULL;
97 		}
98 	}
99 }
100 
copy_atadevice_settings(const bx_atadevice_options_t * src,disk_t * dest)101 void XHDIDriver::copy_atadevice_settings(const bx_atadevice_options_t *src, disk_t *dest)
102 {
103 	safe_strncpy(dest->path, src->path, sizeof(dest->path));
104 	safe_strncpy(dest->name, src->model, sizeof(dest->name));
105 	dest->present = (src->isCDROM ? false : src->present);	// CD-ROMs not supported via XHDI
106 	dest->readonly = src->readonly;
107 	dest->byteswap = src->byteswap;
108 	dest->sim_root = false;		// ATA devices are real disks
109 
110 	// check and remember disk size
111 	setDiskSizeInBlocks(dest);
112 }
113 
copy_scsidevice_settings(int index,const bx_scsidevice_options_t * src,disk_t * dest)114 void XHDIDriver::copy_scsidevice_settings(int index, const bx_scsidevice_options_t *src, disk_t *dest)
115 {
116 	safe_strncpy(dest->path, src->path, sizeof(dest->path));
117 	sprintf(dest->name, "PARTITION%d", index);
118 	dest->present = src->present;
119 	dest->readonly = src->readonly;
120 	dest->byteswap = src->byteswap;
121 	dest->sim_root = true;		// SCSI devices are simulated by prepending a virtual root sector to a single partition
122 
123 	// check and remember disk size
124 	setDiskSizeInBlocks(dest);
125 
126 	dest->partID[0] = src->partID[0];
127 	dest->partID[1] = src->partID[1];
128 	dest->partID[2] = src->partID[2];
129 }
130 
dev2disk(uint16 major,uint16 minor)131 disk_t *XHDIDriver::dev2disk(uint16 major, uint16 minor)
132 {
133 	if (minor != 0)
134 		return NULL;
135 
136 	disk_t *disk = NULL;
137 	if (major >= SCSI_START && major <= IDE_END) {
138 		disk = &disks[major];
139 		if (disk->present) {
140 			return disk;
141 		}
142 	}
143 
144 	return NULL;
145 }
146 
byteSwapBuf(uint8 * dst,int size)147 void XHDIDriver::byteSwapBuf(uint8 *dst, int size)
148 {
149 #ifdef USE_SSE_BYTESWAP
150 	__m128i A;
151 
152 	if ((((uintptr)dst) & 0xf) == 0)
153 	{
154 		while (size >= 16)
155 		{
156 			A = *((__m128i *)dst);
157 			*((__m128i *)dst) = _mm_or_si128(_mm_srli_epi16(A, 8), _mm_or_si128(_mm_slli_epi16(A, 8), _mm_setzero_si128()));
158 			dst += 16;
159 			size -= 16;
160 		}
161 	}
162 #endif
163 	while (size >= 2)
164 	{
165 		char tmp = *dst++;
166 		dst[-1] = *dst;
167 		*dst++ = tmp;
168 		size -= 2;
169 	}
170 }
171 
XHDrvMap()172 int32 XHDIDriver::XHDrvMap()
173 {
174 	D(bug("ARAnyM XHDrvMap"));
175 
176 	return 0;	// drive map
177 }
178 
XHInqDriver(uint16 bios_device,memptr name,memptr version,memptr company,wmemptr ahdi_version,wmemptr maxIPL)179 int32 XHDIDriver::XHInqDriver(uint16 bios_device, memptr name, memptr version,
180 					memptr company, wmemptr ahdi_version, wmemptr maxIPL)
181 {
182 	DUNUSED(bios_device);
183 	DUNUSED(name);
184 	DUNUSED(version);
185 	DUNUSED(company);
186 	DUNUSED(ahdi_version);
187 	DUNUSED(maxIPL);
188 	D(bug("ARAnyM XHInqDriver(bios_device=%u)", bios_device));
189 
190 	return TOS_EINVFN;
191 }
192 
193 
XHReadWrite(uint16 major,uint16 minor,uint16 rwflag,uint32 recno,uint16 count,memptr buf)194 int32 XHDIDriver::XHReadWrite(uint16 major, uint16 minor,
195 					uint16 rwflag, uint32 recno, uint16 count, memptr buf)
196 {
197 	D(bug("ARAnyM XH%s(%u.%u, recno=%lu, count=%u, buf=$%x)",
198 		(rwflag & 1) ? "Write" : "Read",
199 		major, minor, recno, count, buf));
200 
201 	disk_t *disk = dev2disk(major, minor);
202 	if (disk == NULL) {
203 		return TOS_EUNDEV;
204 	}
205 
206 	bool writing = (rwflag & 1);
207 	if (writing && disk->readonly) {
208 		return TOS_EACCDN;
209 	}
210 
211 	FILE *f = disk->file;
212 	if (f == NULL) {
213 		// TODO FIXME - if opening the IDE disk drives here then block the ATA emu layer
214 		// until the disk->path is closed here again! Otherwise bad data loss might occur
215 		// if both the direct PARTITION access and the ATA emu layer start writing to the same disk
216 		f = fopen(disk->path, disk->readonly ? "rb" : "r+b");
217 		if (f != NULL) {
218 			disk->file = f;
219 		}
220 		else {
221 			return TOS_EDRVNR;
222 		}
223 	}
224 
225 	if (disk->sim_root) {
226 		if (recno == 0 && count > 0) {
227 			if (!writing) {
228 				// simulate the root sector
229 				assert(sizeof(rootsector) == XHDI_BLOCK_SIZE);
230 				rootsector sector;
231 				memset(&sector, 0, sizeof(rootsector));
232 
233 				sector.hd_siz = SDL_SwapBE32(disk->size_blocks);
234 
235 				sector.part[0].flg = 1;
236 				if (disk->partID[0] == '$') {
237 					sector.part[0].id[0] = 0;
238 					sector.part[0].id[1] = 'D';
239 					char str[3] = {disk->partID[1], disk->partID[2], 0};
240 					sector.part[0].id[2] = strtol(str, NULL, 16);
241 				}
242 				else {
243 					sector.part[0].id[0] = disk->partID[0];
244 					sector.part[0].id[1] = disk->partID[1];
245 					sector.part[0].id[2] = disk->partID[2];
246 				}
247 				int start_sect = 1;
248 				sector.part[0].st = SDL_SwapBE32(start_sect);
249 				sector.part[0].siz = SDL_SwapBE32(disk->size_blocks - start_sect);
250 
251 				// zero out the other three partitions in PTBL
252 				for(int i=1; i<4; i++) {
253 					sector.part[i].flg = 0;
254 					sector.part[i].id[0] = 0;
255 					sector.part[i].id[1] = 0;
256 					sector.part[i].id[2] = 0;
257 					sector.part[i].st = 0;
258 					sector.part[i].siz = 0;
259 				}
260 
261 				Host2Atari_memcpy(buf, &sector, sizeof(sector));
262 			}
263 			// correct the count and buffer position
264 			count--;
265 			buf+=XHDI_BLOCK_SIZE;
266 			if (count == 0) {
267 				return writing ? TOS_EACCDN : TOS_E_OK;
268 			}
269 		}
270 
271 		// correct the offset to the partition
272 		if (recno > 0)
273 			recno--;
274 	}
275 
276 	off_t offset = (off_t)recno * XHDI_BLOCK_SIZE;
277 	if (fseeko(f, offset, SEEK_SET) != 0)
278 		return errnoHost2Mint(errno, TOS_EINVAL);
279 	if (count == 0)
280 		return writing ? TOS_EACCDN : TOS_E_OK;
281 
282 	memptr bytes = count * XHDI_BLOCK_SIZE;
283 	if (writing)
284 	{
285 #ifdef USE_SSE_BYTESWAP
286 		__attribute__((__aligned__(16))
287 #endif
288 		uint8 tempbuf[bytes];
289 
290 		memptr src_end = buf + bytes - 1;
291 		if (! ValidAtariAddr(buf, false, 1))
292 			BUS_ERROR(buf);
293 		if (! ValidAtariAddr(src_end, false, 1))
294 			BUS_ERROR(src_end);
295 		uint8 *src = Atari2HostAddr(buf);
296 		if (! disk->byteswap)
297 		{
298 			memcpy(tempbuf, src, bytes);
299 			byteSwapBuf(tempbuf, bytes);
300 			src = tempbuf;
301 		}
302 		if (fwrite(src, bytes, 1, f) != 1) {
303 			panicbug("nfXHDI: Error writing to device %u.%u (record=%d)", major, minor, recno);
304 			return TOS_EWRITF;
305 		}
306 	} else {
307 		memptr dst_end = buf + bytes - 1;
308 		if (! ValidAtariAddr(buf, true, 1))
309 			BUS_ERROR(buf);
310 		if (! ValidAtariAddr(dst_end, true, 1))
311 			BUS_ERROR(dst_end);
312 		uint8 *dst = Atari2HostAddr(buf);
313 		if (fread(dst, bytes, 1, f) != 1) {
314 			panicbug("nfXHDI: error reading device %u.%u (record=%d)", major, minor, recno);
315 			return TOS_EREADF;
316 		} else
317 		{
318 			if (! disk->byteswap)
319 				byteSwapBuf(dst, bytes);
320 		}
321 	}
322 	return TOS_E_OK;
323 }
324 
325 int32 XHDIDriver::XHInqTarget2(uint16 major, uint16 minor, lmemptr blocksize,
326 					lmemptr device_flags, memptr product_name, uint16 stringlen)
327 {
328 	D(bug("ARAnyM XHInqTarget2(%u.%u, product_name_len=%u)", major, minor, stringlen));
329 
330 	disk_t *disk = dev2disk(major, minor);
331 	if (disk == NULL)
332 		return TOS_EUNDEV;
333 
334 	if (blocksize) {
335 		WriteInt32(blocksize, XHDI_BLOCK_SIZE);
336 	}
337 
338 	if (device_flags) {
339 		WriteInt32(device_flags, 0);
340 	}
341 
342 	if (product_name && stringlen) {
343 		Host2AtariSafeStrncpy(product_name, disk->name, stringlen);
344 	}
345 
346 	return TOS_E_OK;
347 }
348 
349 int32 XHDIDriver::XHInqDev2(uint16 bios_device, wmemptr major, wmemptr minor,
350 					lmemptr start_sector, memptr bpb, lmemptr blocks,
351 					memptr partid)
352 {
353 	DUNUSED(bios_device);
354 	DUNUSED(major);
355 	DUNUSED(minor);
356 	DUNUSED(start_sector);
357 	DUNUSED(bpb);
358 	DUNUSED(blocks);
359 	DUNUSED(partid);
360 	D(bug("ARAnyM XHInqDev2(bios_device=%u)", bios_device));
361 
362 	return TOS_EINVFN;
363 }
364 
365 int32 XHDIDriver::XHGetCapacity(uint16 major, uint16 minor,
366 					lmemptr blocks, lmemptr blocksize)
367 {
368 	D(bug("ARAnyM XHGetCapacity(%u.%u, blocks=%lu, blocksize=%lu)", major, minor, blocks, blocksize));
369 
370 	disk_t *disk = dev2disk(major, minor);
371 	if (disk == NULL)
372 		return TOS_EUNDEV;
373 
374 	if (! setDiskSizeInBlocks(disk))
375 		return TOS_EDRVNR;
376 
377 	D(bug("XHGetCapacity in blocks = %ld\n", disk->size_blocks));
378 	if (blocks != 0)
379 		WriteAtariInt32(blocks, disk->size_blocks);
380 	if (blocksize != 0)
381 		WriteAtariInt32(blocksize, XHDI_BLOCK_SIZE);
382 	return TOS_E_OK;
383 }
384 
385 int32 XHDIDriver::dispatch(uint32 fncode)
386 {
387 	D(bug("ARAnyM XHDI(%u)", fncode));
388 	int32 ret;
389 	switch(fncode) {
390 		case  0: ret = 0x0130;	/* XHDI version */
391 				break;
392 
393 		case  1: ret = XHInqTarget2(
394 						getParameter(0), /* UWORD major */
395 						getParameter(1), /* UWORD minor */
396 						getParameter(2), /* ULONG *block_size */
397 						getParameter(3), /* ULONG *device_flags */
398 						getParameter(4), /* char  *product_name */
399 						             33  /* UWORD stringlen */
400 						);
401 				break;
402 
403 		case  6: ret = XHDrvMap();
404 				break;
405 
406 		case  7: ret = XHInqDev2(
407 						getParameter(0), /* UWORD bios_device */
408 						getParameter(1), /* UWORD *major */
409 						getParameter(2), /* UWORD *minor */
410 						getParameter(3), /* ULONG *start_sector */
411 						getParameter(4), /* BPB   *bpb */
412 						              0, /* ULONG *blocks */
413 						              0  /* char *partid */
414 						);
415 				break;
416 
417 		case  8: ret = XHInqDriver(
418 						getParameter(0), /* UWORD bios_device */
419 						getParameter(1), /* char  *name */
420 						getParameter(2), /* char  *version */
421 						getParameter(3), /* char  *company */
422 						getParameter(4), /* UWORD *ahdi_version */
423 						getParameter(5)  /* UWORD *maxIPL */
424 						);
425 				break;
426 
427 		case 10: ret = XHReadWrite(
428 						getParameter(0), /* UWORD major */
429 						getParameter(1), /* UWORD minor */
430 						getParameter(2), /* UWORD rwflag */
431 						getParameter(3), /* ULONG recno */
432 						getParameter(4), /* UWORD count */
433 						getParameter(5)  /* void *buf */
434 						);
435 				break;
436 
437 		case 11: ret = XHInqTarget2(
438 						getParameter(0), /* UWORD major */
439 						getParameter(1), /* UWORD minor */
440 						getParameter(2), /* ULONG *block_size */
441 						getParameter(3), /* ULONG *device_flags */
442 						getParameter(4), /* char  *product_name */
443 						getParameter(5)  /* UWORD stringlen */
444 						);
445 				break;
446 
447 		case 12: ret = XHInqDev2(
448 						getParameter(0), /* UWORD bios_device */
449 						getParameter(1), /* UWORD *major */
450 						getParameter(2), /* UWORD *minor */
451 						getParameter(3), /* ULONG *start_sector */
452 						getParameter(4), /* BPB   *bpb */
453 						getParameter(5), /* ULONG *blocks */
454 						getParameter(6)  /* char *partid */
455 						);
456 				break;
457 
458 		case 14: ret = XHGetCapacity(
459 						getParameter(0), /* UWORD major */
460 						getParameter(1), /* UWORD minor */
461 						getParameter(2), /* ULONG *blocks */
462 						getParameter(3)  /* ULONG *blocksize */
463 						);
464 				break;
465 
466 		case  2: /* XHReserve */
467 		case  3: /* XHLock */
468 		case  4: /* XHStop */
469 		case  5: /* XHEject */
470 		case  9: /* XHNewCookie */
471 		case 15: /* XHMediumChanged */
472 		case 16: /* XHMiNTInfo */
473 		case 17: /* XHDOSLimits */
474 		case 18: /* XHLastAccess */
475 		case 19: /* XHReaccess */
476 		default: ret = TOS_EINVFN;
477 				D(bug("Unimplemented ARAnyM XHDI function #%d", fncode));
478 				break;
479 	}
480 	D(bug("ARAnyM XHDI function returning with %d", ret));
481 	return ret;
482 }
483 
484 bool XHDIDriver::setDiskSizeInBlocks(disk_t *disk)
485 {
486 	disk->size_blocks = 0;
487 
488 	if (! disk->present)
489 		return false;
490 
491 	struct stat buf;
492 	long blocks = 0;
493 	if (stat(disk->path, &buf))
494 		return false;
495 
496 	if (S_ISBLK(buf.st_mode)) {
497 		int fd = open(disk->path, 0);
498 		if (fd < 0) {
499 			panicbug("open(%s) failed", disk->path);
500 			return false;
501 		}
502 		blocks = lseek(fd, 0, SEEK_END) / XHDI_BLOCK_SIZE;
503 		close(fd);
504 		D(bug("%ld blocks on %s", blocks, disk->path));
505 	}
506 	else {
507 		blocks = buf.st_size / XHDI_BLOCK_SIZE;
508 	}
509 
510 	if (disk->sim_root)
511 		blocks++;	// add the virtual master boot record
512 
513 	disk->size_blocks = blocks;
514 
515 	return true;
516 }
517 
518 /*
519 vim:ts=4:sw=4:
520 */
521