710 lines
15 KiB
C
710 lines
15 KiB
C
/* Copyright 1994,1996-2003,2005,2007,2009 Alain Knaff.
|
|
* This file is part of mtools.
|
|
*
|
|
* Mtools is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* Mtools is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with Mtools. If not, see <http://www.gnu.org/licenses/>.
|
|
*
|
|
* Io to an xdf disk
|
|
*
|
|
* written by:
|
|
*
|
|
* Alain L. Knaff
|
|
* alain@knaff.lu
|
|
*
|
|
*/
|
|
|
|
|
|
#include "sysincludes.h"
|
|
#ifdef OS_linux
|
|
#include "msdos.h"
|
|
#include "mtools.h"
|
|
#include "devices.h"
|
|
#include "xdf_io.h"
|
|
|
|
/* Algorithms can't be patented */
|
|
|
|
typedef struct sector_map {
|
|
unsigned int head:1;
|
|
unsigned int size:7;
|
|
} sector_map_t;
|
|
|
|
|
|
static struct {
|
|
unsigned char track_size;
|
|
unsigned int track0_size:7;
|
|
unsigned int rootskip:1;
|
|
unsigned char rate;
|
|
sector_map_t map[9];
|
|
} xdf_table[]= {
|
|
{
|
|
19, 16, 0, 0,
|
|
{ {0,3}, {0,6}, {1,2}, {0,2}, {1,6}, {1,3}, {0,0} }
|
|
},
|
|
{
|
|
23, 19, 0, 0,
|
|
{ {0,3}, {0,4}, {1,6}, {0,2}, {1,2}, {0,6}, {1,4}, {1,3}, {0,0} }
|
|
},
|
|
{
|
|
46, 37, 1, 0x43,
|
|
{ {0,3}, {0,4}, {0,5}, {0,7}, {1,3}, {1,4}, {1,5}, {1,7}, {0,0} }
|
|
},
|
|
{
|
|
24, 20, 1, 0,
|
|
{ {0,5}, {1,6}, {0,6}, {1, 5} }
|
|
},
|
|
{
|
|
48, 41, 1, 0,
|
|
{ {0,6}, {1,7}, {0,7}, {1, 6} }
|
|
}
|
|
};
|
|
|
|
#define NUMBER(x) (sizeof(x)/sizeof(x[0]))
|
|
|
|
typedef struct {
|
|
unsigned char begin; /* where it begins */
|
|
unsigned char end;
|
|
unsigned char sector;
|
|
unsigned char sizecode;
|
|
|
|
unsigned int dirty:1;
|
|
unsigned int phantom:2;
|
|
unsigned int valid:1;
|
|
unsigned int head:1;
|
|
} TrackMap_t;
|
|
|
|
|
|
|
|
typedef struct Xdf_t {
|
|
Class_t *Class;
|
|
int refs;
|
|
Stream_t *Next;
|
|
Stream_t *Buffer;
|
|
|
|
int fd;
|
|
char *buffer;
|
|
|
|
int current_track;
|
|
|
|
sector_map_t *map;
|
|
|
|
int track_size;
|
|
int track0_size;
|
|
int sector_size;
|
|
unsigned int FatSize;
|
|
unsigned int RootDirSize;
|
|
TrackMap_t *track_map;
|
|
|
|
unsigned char last_sector;
|
|
unsigned char rate;
|
|
|
|
unsigned int stretch:1;
|
|
unsigned int rootskip:1;
|
|
signed int drive:4;
|
|
} Xdf_t;
|
|
|
|
typedef struct {
|
|
unsigned char head;
|
|
unsigned char sector;
|
|
unsigned char ptr;
|
|
} Compactify_t;
|
|
|
|
|
|
static int analyze_reply(RawRequest_t *raw_cmd, int do_print)
|
|
{
|
|
int ret, bytes, newbytes;
|
|
|
|
bytes = 0;
|
|
while(1) {
|
|
ret = analyze_one_reply(raw_cmd, &newbytes, do_print);
|
|
bytes += newbytes;
|
|
switch(ret) {
|
|
case 0:
|
|
return bytes;
|
|
case 1:
|
|
raw_cmd++;
|
|
break;
|
|
case -1:
|
|
if(bytes)
|
|
return bytes;
|
|
else
|
|
return 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
static int send_cmd(int fd, RawRequest_t *raw_cmd, int nr,
|
|
const char *message, int retries)
|
|
{
|
|
int j;
|
|
int ret=-1;
|
|
|
|
if(!nr)
|
|
return 0;
|
|
for (j=0; j< retries; j++){
|
|
switch(send_one_cmd(fd, raw_cmd, message)) {
|
|
case -1:
|
|
return -1;
|
|
case 1:
|
|
j++;
|
|
continue;
|
|
case 0:
|
|
break;
|
|
}
|
|
if((ret=analyze_reply(raw_cmd, j)) > 0)
|
|
return ret; /* ok */
|
|
}
|
|
if(j > 1 && j == retries) {
|
|
fprintf(stderr,"Too many errors, giving up\n");
|
|
return 0;
|
|
}
|
|
return -1;
|
|
}
|
|
|
|
|
|
|
|
#define REC (This->track_map[ptr])
|
|
#define END(x) (This->track_map[(x)].end)
|
|
#define BEGIN(x) (This->track_map[(x)].begin)
|
|
|
|
static int add_to_request(Xdf_t *This, int ptr,
|
|
RawRequest_t *request, int *nr,
|
|
int direction, Compactify_t *compactify)
|
|
{
|
|
#if 0
|
|
if(direction == MT_WRITE) {
|
|
printf("writing %d: %d %d %d %d [%02x]\n",
|
|
ptr, This->current_track,
|
|
REC.head, REC.sector, REC.sizecode,
|
|
*(This->buffer + ptr * This->sector_size));
|
|
} else
|
|
printf(" load %d.%d\n", This->current_track, ptr);
|
|
#endif
|
|
if(REC.phantom) {
|
|
if(direction== MT_READ)
|
|
memset(This->buffer + ptr * This->sector_size, 0,
|
|
128 << REC.sizecode);
|
|
return 0;
|
|
}
|
|
|
|
if(*nr &&
|
|
RR_SIZECODE(request+(*nr)-1) == REC.sizecode &&
|
|
compactify->head == REC.head &&
|
|
compactify->ptr + 1 == ptr &&
|
|
compactify->sector +1 == REC.sector) {
|
|
RR_SETSIZECODE(request+(*nr)-1, REC.sizecode);
|
|
} else {
|
|
if(*nr)
|
|
RR_SETCONT(request+(*nr)-1);
|
|
RR_INIT(request+(*nr));
|
|
RR_SETDRIVE(request+(*nr), This->drive);
|
|
RR_SETRATE(request+(*nr), This->rate);
|
|
RR_SETTRACK(request+(*nr), This->current_track);
|
|
RR_SETPTRACK(request+(*nr),
|
|
This->current_track << This->stretch);
|
|
RR_SETHEAD(request+(*nr), REC.head);
|
|
RR_SETSECTOR(request+(*nr), REC.sector);
|
|
RR_SETSIZECODE(request+(*nr), REC.sizecode);
|
|
RR_SETDIRECTION(request+(*nr), direction);
|
|
RR_SETDATA(request+(*nr),
|
|
(caddr_t) This->buffer + ptr * This->sector_size);
|
|
(*nr)++;
|
|
}
|
|
compactify->ptr = ptr;
|
|
compactify->head = REC.head;
|
|
compactify->sector = REC.sector;
|
|
return 0;
|
|
}
|
|
|
|
|
|
static void add_to_request_if_invalid(Xdf_t *This, int ptr,
|
|
RawRequest_t *request, int *nr,
|
|
Compactify_t *compactify)
|
|
{
|
|
if(!REC.valid)
|
|
add_to_request(This, ptr, request, nr, MT_READ, compactify);
|
|
|
|
}
|
|
|
|
|
|
static void adjust_bounds(Xdf_t *This, off_t *begin, off_t *end)
|
|
{
|
|
/* translates begin and end from byte to sectors */
|
|
*begin = *begin / This->sector_size;
|
|
*end = (*end + This->sector_size - 1) / This->sector_size;
|
|
}
|
|
|
|
|
|
static __inline__ int try_flush_dirty(Xdf_t *This)
|
|
{
|
|
int ptr, nr, bytes;
|
|
RawRequest_t requests[100];
|
|
Compactify_t compactify;
|
|
|
|
if(This->current_track < 0)
|
|
return 0;
|
|
|
|
nr = 0;
|
|
for(ptr=0; ptr < This->last_sector; ptr=REC.end)
|
|
if(REC.dirty)
|
|
add_to_request(This, ptr,
|
|
requests, &nr,
|
|
MT_WRITE, &compactify);
|
|
#if 1
|
|
bytes = send_cmd(This->fd,requests, nr, "writing", 4);
|
|
if(bytes < 0)
|
|
return bytes;
|
|
#else
|
|
bytes = 0xffffff;
|
|
#endif
|
|
for(ptr=0; ptr < This->last_sector; ptr=REC.end)
|
|
if(REC.dirty) {
|
|
if(bytes >= REC.end - REC.begin) {
|
|
bytes -= REC.end - REC.begin;
|
|
REC.dirty = 0;
|
|
} else
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
static int flush_dirty(Xdf_t *This)
|
|
{
|
|
int ret;
|
|
|
|
while((ret = try_flush_dirty(This))) {
|
|
if(ret < 0)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int load_data(Xdf_t *This, off_t begin, off_t end, int retries)
|
|
{
|
|
int ptr, nr, bytes;
|
|
RawRequest_t requests[100];
|
|
Compactify_t compactify;
|
|
|
|
adjust_bounds(This, &begin, &end);
|
|
|
|
ptr = begin;
|
|
nr = 0;
|
|
for(ptr=REC.begin; ptr < end ; ptr = REC.end)
|
|
add_to_request_if_invalid(This, ptr, requests, &nr,
|
|
&compactify);
|
|
bytes = send_cmd(This->fd,requests, nr, "reading", retries);
|
|
if(bytes < 0)
|
|
return bytes;
|
|
ptr = begin;
|
|
for(ptr=REC.begin; ptr < end ; ptr = REC.end) {
|
|
if(!REC.valid) {
|
|
if(bytes >= REC.end - REC.begin) {
|
|
bytes -= REC.end - REC.begin;
|
|
REC.valid = 1;
|
|
} else if(ptr > begin)
|
|
return ptr * This->sector_size;
|
|
else
|
|
return -1;
|
|
}
|
|
}
|
|
return end * This->sector_size;
|
|
}
|
|
|
|
static void mark_dirty(Xdf_t *This, off_t begin, off_t end)
|
|
{
|
|
int ptr;
|
|
|
|
adjust_bounds(This, &begin, &end);
|
|
|
|
ptr = begin;
|
|
for(ptr=REC.begin; ptr < end ; ptr = REC.end) {
|
|
REC.valid = 1;
|
|
if(!REC.phantom)
|
|
REC.dirty = 1;
|
|
}
|
|
}
|
|
|
|
|
|
static int load_bounds(Xdf_t *This, off_t begin, off_t end)
|
|
{
|
|
off_t lbegin, lend;
|
|
int endp1, endp2;
|
|
|
|
lbegin = begin;
|
|
lend = end;
|
|
|
|
adjust_bounds(This, &lbegin, &lend);
|
|
|
|
if(begin != BEGIN(lbegin) * This->sector_size &&
|
|
end != BEGIN(lend) * This->sector_size &&
|
|
lend < END(END(lbegin)))
|
|
/* contiguous end & begin, load them in one go */
|
|
return load_data(This, begin, end, 4);
|
|
|
|
if(begin != BEGIN(lbegin) * This->sector_size) {
|
|
endp1 = load_data(This, begin, begin, 4);
|
|
if(endp1 < 0)
|
|
return endp1;
|
|
}
|
|
|
|
if(end != BEGIN(lend) * This->sector_size) {
|
|
endp2 = load_data(This, end, end, 4);
|
|
if(endp2 < 0)
|
|
return BEGIN(lend) * This->sector_size;
|
|
}
|
|
return lend * This->sector_size;
|
|
}
|
|
|
|
|
|
static int fill_t0(Xdf_t *This, int ptr, unsigned int size,
|
|
int *sector, int *head)
|
|
{
|
|
int n;
|
|
|
|
for(n = 0; n < size; ptr++,n++) {
|
|
REC.head = *head;
|
|
REC.sector = *sector + 129;
|
|
REC.phantom = 0;
|
|
(*sector)++;
|
|
if(!*head && *sector >= This->track0_size - 8) {
|
|
*sector = 0;
|
|
*head = 1;
|
|
}
|
|
}
|
|
return ptr;
|
|
}
|
|
|
|
|
|
static int fill_phantoms(Xdf_t *This, int ptr, unsigned int size)
|
|
{
|
|
int n;
|
|
|
|
for(n = 0; n < size; ptr++,n++)
|
|
REC.phantom = 1;
|
|
return ptr;
|
|
}
|
|
|
|
static void decompose(Xdf_t *This, int where, int len, off_t *begin,
|
|
off_t *end, int boot)
|
|
{
|
|
int ptr, track;
|
|
sector_map_t *map;
|
|
int lbegin, lend;
|
|
|
|
track = where / This->track_size / 1024;
|
|
|
|
*begin = where - track * This->track_size * 1024;
|
|
*end = where + len - track * This->track_size * 1024;
|
|
maximize(*end, This->track_size * 1024);
|
|
|
|
if(This->current_track == track && !boot)
|
|
/* already OK, return immediately */
|
|
return;
|
|
if(!boot)
|
|
flush_dirty(This);
|
|
This->current_track = track;
|
|
|
|
if(track) {
|
|
for(ptr=0, map=This->map; map->size; map++) {
|
|
/* iterate through all sectors */
|
|
lbegin = ptr;
|
|
lend = ptr + (128 << map->size) / This->sector_size;
|
|
for( ; ptr < lend ; ptr++) {
|
|
REC.begin = lbegin;
|
|
REC.end = lend;
|
|
|
|
REC.head = map->head;
|
|
REC.sector = map->size + 128;
|
|
REC.sizecode = map->size;
|
|
|
|
REC.valid = 0;
|
|
REC.dirty = 0;
|
|
REC.phantom = 0;
|
|
}
|
|
}
|
|
REC.begin = REC.end = ptr;
|
|
} else {
|
|
int sector, head;
|
|
|
|
head = 0;
|
|
sector = 0;
|
|
|
|
for(ptr=boot; ptr < 2 * This->track_size; ptr++) {
|
|
REC.begin = ptr;
|
|
REC.end = ptr+1;
|
|
|
|
REC.sizecode = 2;
|
|
|
|
REC.valid = 0;
|
|
REC.dirty = 0;
|
|
}
|
|
|
|
/* boot & 1st fat */
|
|
ptr=fill_t0(This, 0, 1 + This->FatSize, §or, &head);
|
|
|
|
/* second fat */
|
|
ptr=fill_phantoms(This, ptr, This->FatSize);
|
|
|
|
/* root dir */
|
|
ptr=fill_t0(This, ptr, This->RootDirSize, §or, &head);
|
|
|
|
/* "bad sectors" at the beginning of the fs */
|
|
ptr=fill_phantoms(This, ptr, 5);
|
|
|
|
if(This->rootskip)
|
|
sector++;
|
|
|
|
/* beginning of the file system */
|
|
ptr = fill_t0(This, ptr,
|
|
(This->track_size - This->FatSize) * 2 -
|
|
This->RootDirSize - 6,
|
|
§or, &head);
|
|
}
|
|
This->last_sector = ptr;
|
|
}
|
|
|
|
|
|
static int xdf_read(Stream_t *Stream, char *buf, mt_off_t where, size_t len)
|
|
{
|
|
off_t begin, end;
|
|
size_t len2;
|
|
DeclareThis(Xdf_t);
|
|
|
|
decompose(This, truncBytes32(where), len, &begin, &end, 0);
|
|
len2 = load_data(This, begin, end, 4);
|
|
len2 -= begin;
|
|
maximize(len, len2);
|
|
memcpy(buf, This->buffer + begin, len);
|
|
return end - begin;
|
|
}
|
|
|
|
static int xdf_write(Stream_t *Stream, char *buf, mt_off_t where, size_t len)
|
|
{
|
|
off_t begin, end;
|
|
size_t len2;
|
|
DeclareThis(Xdf_t);
|
|
|
|
decompose(This, truncBytes32(where), len, &begin, &end, 0);
|
|
len2 = load_bounds(This, begin, end);
|
|
smaximize(end, (off_t)len2);
|
|
len2 -= begin;
|
|
sizemaximize(len, (off_t)len2);
|
|
memcpy(This->buffer + begin, buf, len);
|
|
mark_dirty(This, begin, end);
|
|
return end - begin;
|
|
}
|
|
|
|
static int xdf_flush(Stream_t *Stream)
|
|
{
|
|
DeclareThis(Xdf_t);
|
|
|
|
return flush_dirty(This);
|
|
}
|
|
|
|
static int xdf_free(Stream_t *Stream)
|
|
{
|
|
DeclareThis(Xdf_t);
|
|
Free(This->track_map);
|
|
Free(This->buffer);
|
|
return close(This->fd);
|
|
}
|
|
|
|
|
|
static int check_geom(struct device *dev, int media, union bootsector *boot)
|
|
{
|
|
int sect;
|
|
|
|
if(media >= 0xfc && media <= 0xff)
|
|
return 1; /* old DOS */
|
|
|
|
if (!IS_MFORMAT_ONLY(dev)) {
|
|
if(compare(dev->sectors, 19) &&
|
|
compare(dev->sectors, 23) &&
|
|
compare(dev->sectors, 24) &&
|
|
compare(dev->sectors, 46) &&
|
|
compare(dev->sectors, 48))
|
|
return 1;
|
|
|
|
/* check against contradictory info from configuration file */
|
|
if(compare(dev->heads, 2))
|
|
return 1;
|
|
}
|
|
|
|
/* check against info from boot */
|
|
if(boot) {
|
|
sect = WORD(nsect);
|
|
if((sect != 19 && sect != 23 && sect != 24 &&
|
|
sect != 46 && sect != 48) ||
|
|
(!IS_MFORMAT_ONLY(dev) && compare(dev->sectors, sect)) ||
|
|
WORD(nheads) !=2)
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void set_geom(union bootsector *boot, struct device *dev)
|
|
{
|
|
/* fill in config info to be returned to user */
|
|
dev->heads = 2;
|
|
dev->use_2m = 0xff;
|
|
if(boot) {
|
|
dev->sectors = WORD(nsect);
|
|
if(WORD(psect))
|
|
dev->tracks = WORD(psect) / dev->sectors / 2;
|
|
}
|
|
}
|
|
|
|
static int config_geom(Stream_t *Stream UNUSEDP, struct device *dev,
|
|
struct device *orig_dev UNUSEDP, int media,
|
|
union bootsector *boot)
|
|
{
|
|
if(check_geom(dev, media, boot))
|
|
return 1;
|
|
set_geom(boot,dev);
|
|
return 0;
|
|
}
|
|
|
|
static Class_t XdfClass = {
|
|
xdf_read,
|
|
xdf_write,
|
|
xdf_flush,
|
|
xdf_free,
|
|
config_geom,
|
|
0, /* get_data */
|
|
0, /* pre-allocate */
|
|
0, /* get_dosConvert */
|
|
0 /* discard */
|
|
};
|
|
|
|
Stream_t *XdfOpen(struct device *dev, char *name,
|
|
int mode, char *errmsg, struct xdf_info *info)
|
|
{
|
|
Xdf_t *This;
|
|
off_t begin, end;
|
|
union bootsector *boot;
|
|
unsigned int type;
|
|
|
|
if(dev && (!SHOULD_USE_XDF(dev) || check_geom(dev, 0, 0)))
|
|
return NULL;
|
|
|
|
This = New(Xdf_t);
|
|
if (!This)
|
|
return NULL;
|
|
|
|
This->Class = &XdfClass;
|
|
This->sector_size = 512;
|
|
This->stretch = 0;
|
|
|
|
precmd(dev);
|
|
This->fd = open(name, mode | dev->mode | O_EXCL | O_NDELAY);
|
|
if(This->fd < 0) {
|
|
#ifdef HAVE_SNPRINTF
|
|
snprintf(errmsg,199,"xdf floppy: open: \"%s\"", strerror(errno));
|
|
#else
|
|
sprintf(errmsg,"xdf floppy: open: \"%s\"", strerror(errno));
|
|
#endif
|
|
goto exit_0;
|
|
}
|
|
closeExec(This->fd);
|
|
|
|
This->drive = GET_DRIVE(This->fd);
|
|
if(This->drive < 0)
|
|
goto exit_1;
|
|
|
|
/* allocate buffer */
|
|
This->buffer = (char *) malloc(96 * 512);
|
|
if (!This->buffer)
|
|
goto exit_1;
|
|
|
|
This->current_track = -1;
|
|
This->track_map = (TrackMap_t *)
|
|
calloc(96, sizeof(TrackMap_t));
|
|
if(!This->track_map)
|
|
goto exit_2;
|
|
|
|
/* lock the device on writes */
|
|
if (lock_dev(This->fd, mode == O_RDWR, dev)) {
|
|
#ifdef HAVE_SNPRINTF
|
|
snprintf(errmsg,199,"xdf floppy: device \"%s\" busy:",
|
|
dev->name);
|
|
#else
|
|
sprintf(errmsg,"xdf floppy: device \"%s\" busy:",
|
|
dev->name);
|
|
#endif
|
|
goto exit_3;
|
|
}
|
|
|
|
/* Before reading the boot sector, assume dummy values suitable
|
|
* for reading at least the boot sector */
|
|
This->track_size = 11;
|
|
This->track0_size = 6;
|
|
This->rate = 0;
|
|
This->FatSize = 9;
|
|
This->RootDirSize = 1;
|
|
decompose(This, 0, 512, &begin, &end, 0);
|
|
if (load_data(This, 0, 1, 1) < 0 ) {
|
|
This->rate = 0x43;
|
|
if(load_data(This, 0, 1, 1) < 0)
|
|
goto exit_3;
|
|
}
|
|
|
|
boot = (union bootsector *) This->buffer;
|
|
This->FatSize = WORD(fatlen);
|
|
This->RootDirSize = WORD(dirents)/16;
|
|
This->track_size = WORD(nsect);
|
|
for(type=0; type < NUMBER(xdf_table); type++) {
|
|
if(xdf_table[type].track_size == This->track_size) {
|
|
This->map = xdf_table[type].map;
|
|
This->track0_size = xdf_table[type].track0_size;
|
|
This->rootskip = xdf_table[type].rootskip;
|
|
This->rate = xdf_table[type].rate;
|
|
break;
|
|
}
|
|
}
|
|
if(type == NUMBER(xdf_table))
|
|
goto exit_3;
|
|
|
|
if(info) {
|
|
info->RootDirSize = This->RootDirSize;
|
|
info->FatSize = This->FatSize;
|
|
info->BadSectors = 5;
|
|
}
|
|
decompose(This, 0, 512, &begin, &end, 1);
|
|
|
|
This->refs = 1;
|
|
This->Next = 0;
|
|
This->Buffer = 0;
|
|
if(dev)
|
|
set_geom(boot, dev);
|
|
return (Stream_t *) This;
|
|
|
|
exit_3:
|
|
Free(This->track_map);
|
|
exit_2:
|
|
Free(This->buffer);
|
|
exit_1:
|
|
close(This->fd);
|
|
exit_0:
|
|
Free(This);
|
|
return NULL;
|
|
}
|
|
|
|
#endif
|
|
|
|
/* Algorithms can't be patented */
|
|
|