blob: d84fd5316da78fbbd1e80813ee50f3c1373c00c1 [file] [log] [blame]
/* 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 {
struct Stream_t head;
int fd;
char *buffer;
bool track_valid;
uint8_t current_track;
sector_map_t *map;
uint32_t track_size;
int track0_size;
uint16_t sector_size;
uint8_t FatSize;
uint16_t 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, unsigned char ptr,
RawRequest_t *request, int *nr,
int direction, Compactify_t *compactify)
{
#if 0
if(direction == MT_WRITE) {
printf("writing %d: %u %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.%u\n", This->current_track, ptr);
#endif
if(REC.phantom) {
if(direction== MT_READ)
memset(This->buffer + ptr * This->sector_size, 0,
128u << 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, unsigned char 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, uint32_t ibegin, uint32_t iend,
uint8_t *begin, uint8_t *end)
{
/* translates begin and end from byte to sectors */
*begin = (uint8_t) (ibegin / This->sector_size);
*end = (uint8_t) ((iend + This->sector_size - 1) / This->sector_size);
}
static __inline__ int try_flush_dirty(Xdf_t *This)
{
unsigned char ptr;
int nr, bytes;
RawRequest_t requests[100];
Compactify_t compactify;
if(!This->track_valid)
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 ssize_t load_data(Xdf_t *This, uint32_t ibegin, uint32_t iend,
int retries)
{
unsigned char ptr;
int nr, bytes;
RawRequest_t requests[100];
Compactify_t compactify;
unsigned char begin, end;
adjust_bounds(This, ibegin, iend, &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, uint32_t ibegin, uint32_t iend)
{
int ptr;
unsigned char begin, end;
adjust_bounds(This, ibegin, iend, &begin, &end);
ptr = begin;
for(ptr=REC.begin; ptr < end ; ptr = REC.end) {
REC.valid = 1;
if(!REC.phantom)
REC.dirty = 1;
}
}
static ssize_t load_bounds(Xdf_t *This, uint32_t begin, uint32_t end)
{
unsigned char lbegin, lend;
adjust_bounds(This, begin, end, &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) {
ssize_t ret = load_data(This, begin, begin, 4);
if(ret < 0)
return ret;
}
if(end != BEGIN(lend) * This->sector_size) {
ssize_t ret = load_data(This, end, end, 4);
if(ret < 0)
return BEGIN(lend) * This->sector_size;
}
return lend * This->sector_size;
}
/* Fill out a map that is just sufficient to read boot sector */
static void fill_boot(Xdf_t *This)
{
uint8_t ptr=0;
REC.head = 0;
REC.sector = 129;
REC.phantom = 0;
REC.begin = ptr;
REC.end = ptr+1;
REC.sizecode = 2;
REC.valid = 0;
REC.dirty = 0;
This->last_sector=1;
This->current_track=0;
}
static uint8_t fill_t0(Xdf_t *This, uint8_t ptr, unsigned int size,
uint8_t *sector, uint8_t *head)
{
unsigned 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 uint8_t fill_phantoms(Xdf_t *This, uint8_t ptr, uint8_t size)
{
unsigned int n;
for(n = 0; n < size; ptr++,n++)
REC.phantom = 1;
return ptr;
}
static int decompose(Xdf_t *This, mt_off_t iwhere, size_t len,
uint32_t *begin, uint32_t *end, uint8_t boot)
{
uint8_t ptr;
sector_map_t *map;
uint8_t lbegin, lend;
uint32_t track_size = This->track_size * 1024;
smt_off_t track = (smt_off_t) iwhere / track_size;
uint32_t where = (smt_off_t) iwhere % track_size;
*begin = where;
if(where + len > track_size)
*end = track_size;
else
*end = (uint32_t) (where + len);
if(This->current_track == track && !boot)
/* already OK, return immediately */
return 0;
if(!boot)
flush_dirty(This);
if(track >= 80)
return -1;
This->current_track = (uint8_t) track;
This->track_valid = true;
if(track) {
for(ptr=0, map=This->map; map->size; map++) {
/* iterate through all sectors */
lbegin = ptr;
lend = ptr +
(uint8_t) ((128u<<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 {
uint8_t 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, &sector, &head);
/* second fat */
ptr=fill_phantoms(This, ptr, This->FatSize);
/* root dir */
ptr=fill_t0(This, ptr, This->RootDirSize, &sector, &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,
&sector, &head);
}
This->last_sector = ptr;
return 0;
}
static ssize_t xdf_pread(Stream_t *Stream, char *buf,
mt_off_t where, size_t len)
{
uint32_t begin, end;
ssize_t ret;
DeclareThis(Xdf_t);
if(decompose(This, truncBytes32(where), len, &begin, &end, 0) < 0)
/* Read beyond end of device */
return 0;
ret = load_data(This, begin, end, 4);
if(ret < 0 || (size_t) ret < begin)
return -1;
maximize(len, (size_t) ret - begin);
memcpy(buf, This->buffer + begin, len);
return (ssize_t) (end - begin);
}
static ssize_t xdf_pwrite(Stream_t *Stream, char *buf,
mt_off_t where, size_t len)
{
uint32_t begin, end;
ssize_t len2;
DeclareThis(Xdf_t);
if(decompose(This, truncBytes32(where), len, &begin, &end, 0) < 0) {
/* Write beyond end of device */
errno = EFBIG;
return -1;
}
len2 = load_bounds(This, begin, end);
if(len2 < 0)
return -1;
maximize(end, (uint32_t)len2);
len2 -= begin;
maximize(len, (size_t) len2);
memcpy(This->buffer + begin, buf, len);
mark_dirty(This, begin, end);
return (ssize_t) (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(Xdf_t *This, struct device *dev)
{
unsigned int sect;
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(This) {
sect = This->track_size;
if((sect != 19 && sect != 23 && sect != 24 &&
sect != 46 && sect != 48) ||
(!IS_MFORMAT_ONLY(dev) && compare(dev->sectors, sect)))
return 1;
}
return 0;
}
static void set_geom(Xdf_t *This, struct device *dev)
{
/* fill in config info to be returned to user */
dev->heads = 2;
dev->use_2m = 0xff;
dev->sectors = (uint16_t) This->track_size;
dev->tracks = 80;
}
static int config_geom(Stream_t *Stream UNUSEDP, struct device *dev,
struct device *orig_dev UNUSEDP)
{
DeclareThis(Xdf_t);
if(check_geom(This, dev))
return 1;
set_geom(This, dev);
return 0;
}
static Class_t XdfClass = {
0,
0,
xdf_pread,
xdf_pwrite,
xdf_flush,
xdf_free,
config_geom,
0, /* get_data */
0, /* pre-allocate */
0, /* get_dosConvert */
0 /* discard */
};
Stream_t *XdfOpen(struct device *dev, const char *name,
int mode, char *errmsg, struct xdf_info *info)
{
Xdf_t *This;
uint32_t begin, end;
union bootsector *boot;
unsigned int type;
uint16_t fatSize;
if(dev && ((!SHOULD_USE_XDF(dev) && !getenv("MTOOLS_USE_XDF")) ||
check_geom(NULL, dev)))
return NULL;
This = New(Xdf_t);
if (!This)
return NULL;
init_head(&This->head, &XdfClass, NULL);
This->sector_size = 512;
This->stretch = 0;
precmd(dev);
This->fd = open(name,
((mode | dev->mode) & ~O_ACCMODE) |
O_EXCL | O_NDELAY | O_RDWR);
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->track_valid = false;
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 just the boot sector */
fill_boot(This);
This->rate = 0;
if (load_data(This, 0, 1, 4) < 0 ) {
This->rate = 0x43;
if(load_data(This, 0, 1, 4) < 0)
goto exit_3;
}
boot = (union bootsector *) This->buffer;
fatSize = WORD(fatlen);
if(fatSize > UINT8_MAX) {
fprintf(stderr, "Fat size %d too large\n", fatSize);
exit(1);
}
This->FatSize = (uint8_t) fatSize;
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);
if(dev)
set_geom(This, dev);
return &This->head;
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 */