Revision 46
Added by markw over 11 years ago
firmware/Makefile | ||
---|---|---|
BASE = zpu-elf
|
||
CC = $(BASE)-gcc
|
||
LD = $(BASE)-gcc
|
||
AS = $(BASE)-as
|
||
CP = $(BASE)-objcopy
|
||
DUMP = $(BASE)-objdump
|
||
|
||
# we use mincrt0.s from here
|
||
STARTUP_DIR = .
|
||
|
||
# we fetch ROM prologue / epilogue from here
|
||
RTL_DIR = $(ZPUFLEXDIR)/RTL/
|
||
|
||
|
||
BUILD_DIR=zpu_obj
|
||
|
||
#MINSTARTUP_SRC = mincrt0.s
|
||
MINSTARTUP_SRC = mycrt0.s
|
||
MINSTARTUP_OBJ = $(patsubst $(STARTUP_DIR)/%.s,$(BUILD_DIR)/%.o,$(MINSTARTUP_SRC))
|
||
|
||
MAIN_PRJ = JustStartAtari
|
||
MAIN_SRC = main.c regs.c atari_drive_emulator.c pokey/uart.c hexdump.c printf/printf.c fat/pff_file.c fat/pff.c common/utils.c sd_direct/diskio_mmc.c sd_direct/spi.c sd_direct/mmc.c
|
||
#gcc -g -O0 -DLITTLE_ENDIAN test_drive.c atari_drive_emulator.c native/uart.c hexdump.c printf/printf.c fat/pff_file.c fat/pff.c common/utils.c native/diskio_image.c -I. -Iprintf -Ifat -Icommon
|
||
#MAIN_SRC = stuff.c
|
||
MAIN_OBJ = $(COMMON_OBJ) $(patsubst %.c,$(BUILD_DIR)/%.o,$(MAIN_SRC))
|
||
|
||
LINKMAP = ./standalone_simple.ld
|
||
|
||
|
||
# Commandline options for each tool.
|
||
|
||
#ZPUOPTS= -mno-poppcrel -mno-pushspadd -mno-callpcrel -mno-shortop -mno-neg # No-neg requires bugfixed toolchain
|
||
#Include everything -> need to include emulation rom...
|
||
ZPUOPTS =
|
||
CFLAGS = -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -Os $(ZPUOPTS) -DDISABLE_UART_RX
|
||
|
||
LFLAGS = -nostartfiles -Wl,--relax -g -Os
|
||
#LFLAGS = -nostartfiles -Os
|
||
|
||
|
||
# Our target.
|
||
all: $(BUILD_DIR) $(MAIN_PRJ).bin $(MAIN_PRJ).rpt
|
||
|
||
clean:
|
||
rm -f $(BUILD_DIR)/*.o *.hex *.elf *.map *.lst *.srec $(MAIN_PRJ).rom *~ */*.o *.bin
|
||
|
||
|
||
# Convert ELF binary to bin file.
|
||
%.bin: %.elf
|
||
$(CP) -O binary $< $@
|
||
|
||
%.rpt: %.elf
|
||
echo >$@ -n "End of code:\t"
|
||
$(DUMP) -x $< | grep >>$@ _romend
|
||
echo >>$@ -n "Start of BSS:\t"
|
||
$(DUMP) -x $< | grep >>$@ __bss_start__
|
||
echo >>$@ -n "End of BSS:\t"
|
||
$(DUMP) -x $< | grep >>$@ __bss_end__
|
||
cat $@
|
||
|
||
# Link - this produces an ELF binary.
|
||
|
||
$(MAIN_PRJ).elf: $(MINSTARTUP_OBJ) $(MAIN_OBJ)
|
||
$(LD) $(LFLAGS) -T $(LINKMAP) -o $@ $+ $(LIBS)
|
||
|
||
$(BUILD_DIR)/%.o: %.c Makefile
|
||
mkdir -p `dirname $@`
|
||
$(CC) $(CFLAGS) -o $@ -c $<
|
||
|
||
$(BUILD_DIR)/%.o: %.s
|
||
$(AS) -o $@ $<
|
||
|
||
$(BUILD_DIR)/%.o: $(STARTUP_DIR)/%.s
|
||
$(AS) -o $@ $<
|
||
|
||
$(BUILD_DIR):
|
||
mkdir $(BUILD_DIR)
|
||
|
firmware/actions.c | ||
---|---|---|
int bit_set(int var, int bit)
|
||
{
|
||
return (((1<<bit)&var)!=0);
|
||
}
|
||
|
||
void actions()
|
||
{
|
||
unsigned int i = 0;
|
||
//unsigned volatile char * store = 0xf00000; // SDRAM - fails!!
|
||
unsigned volatile char * store = 0xf80000; // SRAM...
|
||
unsigned volatile char * store2 = 0xfc0000; // custom chips...
|
||
|
||
// cold start (need to clear a few key locations to make OS cold start)
|
||
// file selector (where applicable)
|
||
// options (where applicable)
|
||
|
||
int keys = *zpu_in;
|
||
|
||
if (bit_set(keys,0))
|
||
{
|
||
coldstart();
|
||
}
|
||
else if (bit_set(keys,1))
|
||
{
|
||
set_pause_6502(1);
|
||
freeze();
|
||
menu_options();
|
||
restore();
|
||
set_pause_6502(0);
|
||
}
|
||
else if (bit_set(keys,2))
|
||
{
|
||
set_pause_6502(1);
|
||
freeze();
|
||
menu_fileselector();
|
||
coldstart();
|
||
}
|
||
}
|
||
|
||
void menu_options()
|
||
{
|
||
// title
|
||
// memory
|
||
// rom
|
||
// turbo
|
||
// disks
|
||
// exit/reboot
|
||
|
||
// simple state machine for menu, so I set up a small data structure then it just runs from that...
|
||
}
|
||
|
||
void menu_fileselector()
|
||
{
|
||
// title
|
||
// loads of stuff, filtered by type
|
||
// directories can be selected
|
||
// initial directory set, after that starts where it was left
|
||
}
|
||
|
firmware/atari_drive_emulator.c | ||
---|---|---|
#include "atari_drive_emulator.h"
|
||
|
||
#include "uart.h"
|
||
#include "pause.h"
|
||
#include "simplefile.h"
|
||
|
||
#include "printf.h"
|
||
#include "integer.h"
|
||
|
||
#define send_ACK() USART_Transmit_Byte('A');
|
||
#define send_NACK() USART_Transmit_Byte('N');
|
||
#define send_CMPL() USART_Transmit_Byte('C');
|
||
#define send_ERR() USART_Transmit_Byte('E');
|
||
|
||
/* BiboDos needs at least 50us delay before ACK */
|
||
#define DELAY_T2_MIN wait_us(100);
|
||
|
||
/* the QMEG OS needs at least 300usec delay between ACK and complete */
|
||
#define DELAY_T5_MIN wait_us(300);
|
||
|
||
/* QMEG OS 3 needs a delay of 150usec between complete and data */
|
||
#define DELAY_T3_PERIPH wait_us(150);
|
||
|
||
#define speedslow 0x28
|
||
#define speedfast 0x6
|
||
#define XEX_SECTOR_SIZE 128
|
||
|
||
#define MAX_DRIVES 4
|
||
|
||
struct SimpleFile * drives[MAX_DRIVES];
|
||
|
||
struct ATRHeader
|
||
{
|
||
u16 wMagic;
|
||
u16 wPars;
|
||
u16 wSecSize;
|
||
u08 btParsHigh;
|
||
u32 dwCRC;
|
||
} __attribute__((packed));
|
||
struct ATRHeader atr_header;
|
||
int speed;
|
||
|
||
int badcommandcount;
|
||
int commandcount;
|
||
int opendrive;
|
||
|
||
unsigned char atari_sector_buffer[256];
|
||
|
||
unsigned char get_checksum(unsigned char* buffer, u16 len);
|
||
|
||
#define TWOBYTESTOWORD(ptr,val) (*((u08*)(ptr)) = val&0xff);(*(1+(u08*)(ptr)) = (val>>8)&0xff);
|
||
|
||
void processCommand();
|
||
void USART_Send_cmpl_and_atari_sector_buffer_and_check_sum(unsigned short len);
|
||
void clearAtariSectorBuffer()
|
||
{
|
||
int i=256;
|
||
while (--i)
|
||
atari_sector_buffer[i] = 0;
|
||
}
|
||
|
||
int offset;
|
||
int xex_loader;
|
||
int xex_size;
|
||
uint8_t boot_xex_loader[179] = {
|
||
0x72,0x02,0x5f,0x07,0xf8,0x07,0xa9,0x00,0x8d,0x04,0x03,0x8d,0x44,0x02,0xa9,0x07,
|
||
0x8d,0x05,0x03,0xa9,0x70,0x8d,0x0a,0x03,0xa9,0x01,0x8d,0x0b,0x03,0x85,0x09,0x60,
|
||
0x7d,0x8a,0x48,0x20,0x53,0xe4,0x88,0xd0,0xfa,0x68,0xaa,0x8c,0x8e,0x07,0xad,0x7d,
|
||
0x07,0xee,0x8e,0x07,0x60,0xa9,0x93,0x8d,0xe2,0x02,0xa9,0x07,0x8d,0xe3,0x02,0xa2,
|
||
0x02,0x20,0xda,0x07,0x95,0x43,0x20,0xda,0x07,0x95,0x44,0x35,0x43,0xc9,0xff,0xf0,
|
||
0xf0,0xca,0xca,0x10,0xec,0x30,0x06,0xe6,0x45,0xd0,0x02,0xe6,0x46,0x20,0xda,0x07,
|
||
0xa2,0x01,0x81,0x44,0xb5,0x45,0xd5,0x43,0xd0,0xed,0xca,0x10,0xf7,0x20,0xd2,0x07,
|
||
0x4c,0x94,0x07,0xa9,0x03,0x8d,0x0f,0xd2,0x6c,0xe2,0x02,0xad,0x8e,0x07,0xcd,0x7f,
|
||
0x07,0xd0,0xab,0xee,0x0a,0x03,0xd0,0x03,0xee,0x0b,0x03,0xad,0x7d,0x07,0x0d,0x7e,
|
||
0x07,0xd0,0x8e,0x20,0xd2,0x07,0x6c,0xe0,0x02,0x20,0xda,0x07,0x8d,0xe0,0x02,0x20,
|
||
0xda,0x07,0x8d,0xe1,0x02,0x2d,0xe0,0x02,0xc9,0xff,0xf0,0xed,0xa9,0x00,0x8d,0x8e,
|
||
0x07,0xf0,0x82 };
|
||
// relokacni tabulka neni potreba, meni se vsechny hodnoty 0x07
|
||
// (melo by byt PRESNE 20 vyskytu! pokud je jich vic, pak bacha!!!)
|
||
|
||
void byteswap(WORD * inw)
|
||
{
|
||
#ifndef LITTLE_ENDIAN
|
||
unsigned char * in = (unsigned char *)inw;
|
||
unsigned char temp = in[0];
|
||
in[0] = in[1];
|
||
in[1] = temp;
|
||
#endif
|
||
}
|
||
|
||
struct command
|
||
{
|
||
u08 deviceId;
|
||
u08 command;
|
||
u08 aux1;
|
||
u08 aux2;
|
||
u08 chksum;
|
||
} __attribute__((packed));
|
||
void getCommand(struct command * cmd)
|
||
{
|
||
int expchk;
|
||
|
||
//printf("Waiting for command\n");
|
||
//USART_Data_Ready();
|
||
while (0 == USART_Command_Line());
|
||
//printf("Init:");
|
||
//printf("%d",*zpu_sio);
|
||
USART_Init(speed+6);
|
||
//printf("%d",speed);
|
||
//printf("\n");
|
||
while (1 == USART_Command_Line())
|
||
{
|
||
actions();
|
||
}
|
||
cmd->deviceId = USART_Receive_Byte();
|
||
cmd->command = USART_Receive_Byte();
|
||
cmd->aux1 = USART_Receive_Byte();
|
||
cmd->aux2 = USART_Receive_Byte();
|
||
cmd->chksum = USART_Receive_Byte();
|
||
while (0 == USART_Command_Line())
|
||
{
|
||
actions();
|
||
}
|
||
printf("cmd:");
|
||
//printf("Gone high\n");
|
||
atari_sector_buffer[0] = cmd->deviceId;
|
||
atari_sector_buffer[1] = cmd->command;
|
||
atari_sector_buffer[2] = cmd->aux1;
|
||
atari_sector_buffer[3] = cmd->aux2;
|
||
expchk = get_checksum(&atari_sector_buffer[0],4);
|
||
|
||
//printf("Device id:");
|
||
printf("%d",cmd->deviceId);
|
||
//printf("\n");
|
||
//printf("command:");
|
||
printf("%d",cmd->command);
|
||
//printf("\n");
|
||
//printf("aux1:");
|
||
printf("%d",cmd->aux1);
|
||
//printf("\n");
|
||
//printf("aux2:");
|
||
printf("%d",cmd->aux2);
|
||
//printf("\n");
|
||
//printf("chksum:");
|
||
printf("%d",cmd->chksum);
|
||
printf("%d",expchk);
|
||
|
||
if (expchk!=cmd->chksum || USART_Framing_Error())
|
||
{
|
||
printf("ERR ");
|
||
//wait_us(1000000);
|
||
if (speed == speedslow)
|
||
{
|
||
speed = speedfast;
|
||
printf("SPDF");
|
||
printf("%d",speed);
|
||
}
|
||
else
|
||
{
|
||
speed = speedslow;
|
||
printf("SPDS");
|
||
printf("%d",speed);
|
||
}
|
||
}
|
||
printf("\n");
|
||
|
||
DELAY_T2_MIN;
|
||
}
|
||
|
||
int compare_ext(char const * filename, char const * ext)
|
||
{
|
||
int dot = 0;
|
||
|
||
while (1)
|
||
{
|
||
if (filename[dot] == '\0')
|
||
break;
|
||
if (filename[dot] != '.')
|
||
{
|
||
++dot;
|
||
continue;
|
||
}
|
||
if (filename[dot+1] == ext[0])
|
||
if (filename[dot+2] == ext[1])
|
||
if (filename[dot+3] == ext[2])
|
||
{
|
||
return 1;
|
||
break;
|
||
}
|
||
break;
|
||
}
|
||
|
||
return 0;
|
||
}
|
||
|
||
// Called whenever file changed
|
||
void set_drive_status(int driveNumber, struct SimpleFile * file)
|
||
{
|
||
int read = 0;
|
||
int xfd = 0;
|
||
|
||
drives[driveNumber] = 0;
|
||
|
||
if (!file) return;
|
||
|
||
// Read header
|
||
read = 0;
|
||
file_seek(file,0);
|
||
file_read(file,(unsigned char *)&atr_header, 16, &read);
|
||
if (read!=16)
|
||
{
|
||
printf("Could not read header\n");
|
||
return; //while(1);
|
||
}
|
||
byteswap(&atr_header.wMagic);
|
||
byteswap(&atr_header.wPars);
|
||
byteswap(&atr_header.wSecSize);
|
||
/*printf("\nHeader:");
|
||
printf("%d",atr_header.wMagic);
|
||
plotnext(toatarichar(' '));
|
||
printf("%d",atr_header.wPars);
|
||
plotnext(toatarichar(' '));
|
||
printf("%d",atr_header.wSecSize);
|
||
plotnext(toatarichar(' '));
|
||
printf("%d",atr_header.btParsHigh);
|
||
plotnext(toatarichar(' '));
|
||
printf("%d",atr_header.dwCRC);
|
||
printf("\n");
|
||
*/
|
||
|
||
xex_loader = 0;
|
||
xfd = compare_ext(file_name(file),"XFD") || compare_ext(file_name(file),"xfd");
|
||
|
||
if (xfd == 1)
|
||
{
|
||
printf("XFD ");
|
||
// build a fake atr header
|
||
offset = 0;
|
||
atr_header.wMagic = 0x296;
|
||
atr_header.wPars = file_size(file)/16;
|
||
atr_header.wSecSize = 0x80;
|
||
}
|
||
else if (atr_header.wMagic == 0xFFFF) // XEX
|
||
{
|
||
int i;
|
||
printf("XEX ");
|
||
offset = -256;
|
||
xex_loader = 1;
|
||
atr_header.wMagic = 0xffff;
|
||
xex_size = file_size(file);
|
||
atr_header.wPars = xex_size/16;
|
||
atr_header.wSecSize = XEX_SECTOR_SIZE;
|
||
}
|
||
else if (atr_header.wMagic == 0x296) // ATR
|
||
{
|
||
printf("ATR ");
|
||
offset = 16;
|
||
}
|
||
else
|
||
{
|
||
printf("Unknown file type");
|
||
return;
|
||
}
|
||
|
||
if (atr_header.wSecSize == 0x80)
|
||
{
|
||
if (atr_header.wPars>(720*128/16))
|
||
printf("MD ");
|
||
else
|
||
printf("SD ");
|
||
}
|
||
else if (atr_header.wSecSize == 0x100)
|
||
{
|
||
printf("DD ");
|
||
}
|
||
else if (atr_header.wSecSize < 0x100)
|
||
{
|
||
printf("XD ");
|
||
}
|
||
else
|
||
{
|
||
printf("BAD sector size");
|
||
return;
|
||
}
|
||
printf("%d",atr_header.wPars);
|
||
printf("0\n");
|
||
|
||
drives[driveNumber] = file;
|
||
}
|
||
|
||
void init_drive_emulator()
|
||
{
|
||
int i;
|
||
|
||
commandcount = 0;
|
||
badcommandcount = 0;
|
||
opendrive = -1;
|
||
speed = speedslow;
|
||
USART_Init(speed+6);
|
||
for (i=0; i!=MAX_DRIVES; ++i)
|
||
{
|
||
drives[i] = 0;
|
||
}
|
||
}
|
||
|
||
void run_drive_emulator()
|
||
{
|
||
while (1)
|
||
{
|
||
processCommand();
|
||
actions();
|
||
}
|
||
}
|
||
|
||
/////////////////////////
|
||
|
||
void processCommand()
|
||
{
|
||
struct command command;
|
||
|
||
getCommand(&command);
|
||
|
||
++commandcount;
|
||
/*FIXME if (commandcount==4 && (4==(4&(*zpu_switches))))
|
||
{
|
||
printf("Paused\n");
|
||
pause_6502(1);
|
||
while(1);
|
||
}*/
|
||
/*if (badcommandcount==8)
|
||
{
|
||
printf("Stuck?\n");
|
||
pause_6502(1);
|
||
while(1);
|
||
}*/
|
||
|
||
if (command.deviceId >= 0x31 && command.deviceId < 0x34)
|
||
{
|
||
int sent = 0;
|
||
int drive = 0;
|
||
struct SimpleFile * file = 0;
|
||
|
||
drive = command.deviceId&0xf -1;
|
||
printf("Drive:");
|
||
printf("%d",drive);
|
||
if (drive!=opendrive)
|
||
{
|
||
if (drive<MAX_DRIVES)
|
||
{
|
||
opendrive = drive;
|
||
}
|
||
}
|
||
|
||
if (drive<0 || !drives[drive])
|
||
{
|
||
//USART_Transmit_Mode();
|
||
//send_NACK();
|
||
//USART_Wait_Transmit_Complete();
|
||
//wait_us(100); // Wait for transmission to complete - Pokey bug, gets stuck active...
|
||
//USART_Receive_Mode();
|
||
|
||
printf("Drive not present");
|
||
return;
|
||
}
|
||
|
||
file = drives[opendrive];
|
||
|
||
switch (command.command)
|
||
{
|
||
case 0x3f:
|
||
{
|
||
printf("Speed:");
|
||
int sector = ((int)command.aux1) + (((int)command.aux2&0x7f)<<8);
|
||
USART_Transmit_Mode();
|
||
send_ACK();
|
||
clearAtariSectorBuffer();
|
||
atari_sector_buffer[0] = speedfast;
|
||
hexdump_pure(atari_sector_buffer,1);
|
||
USART_Send_cmpl_and_atari_sector_buffer_and_check_sum(1);
|
||
sent = 1;
|
||
if (sector == 0)
|
||
{
|
||
speed = speedfast;
|
||
printf("SPDF");
|
||
printf("%d",speed);
|
||
}
|
||
else
|
||
{
|
||
speed = speedslow;
|
||
printf("SPDS");
|
||
printf("%d",speed);
|
||
}
|
||
}
|
||
case 0x53:
|
||
{
|
||
unsigned char status;
|
||
printf("Stat:");
|
||
USART_Transmit_Mode();
|
||
send_ACK();
|
||
clearAtariSectorBuffer();
|
||
|
||
status = 0x10; // Motor on;
|
||
status |= 0x08; // write protected; // no write support yet...
|
||
if (atr_header.wSecSize == 0x80) // normal sector size
|
||
{
|
||
if (atr_header.wPars>(720*128/16))
|
||
{
|
||
status |= 0x80; // medium density - or a strange one...
|
||
}
|
||
}
|
||
else
|
||
{
|
||
status |= 0x20; // 256 byte sectors
|
||
}
|
||
atari_sector_buffer[0] = status;
|
||
atari_sector_buffer[1] = 0xff;
|
||
atari_sector_buffer[2] = 0xe0;
|
||
atari_sector_buffer[3] = 0x0;
|
||
hexdump_pure(atari_sector_buffer,4); // Somehow with this...
|
||
USART_Send_cmpl_and_atari_sector_buffer_and_check_sum(4);
|
||
sent = 1;
|
||
printf("%d",atari_sector_buffer[0]); // and this... The wrong checksum is sent!!
|
||
printf(":done\n");
|
||
}
|
||
break;
|
||
case 0x50: // write
|
||
case 0x57: // write with verify
|
||
default:
|
||
// TODO
|
||
//USART_Transmit_Mode();
|
||
//send_NACK();
|
||
//USART_Wait_Transmit_Complete();
|
||
//USART_Receive_Mode();
|
||
break;
|
||
case 0x52: // read
|
||
{
|
||
int sector = ((int)command.aux1) + (((int)command.aux2&0x7f)<<8);
|
||
int sectorSize = 0;
|
||
int read = 0;
|
||
int location =0;
|
||
|
||
USART_Transmit_Mode();
|
||
send_ACK();
|
||
printf("Sector:");
|
||
printf("%d",sector);
|
||
printf(":");
|
||
if(xex_loader) //n_sector>0 && //==0 se overuje hned na zacatku
|
||
{
|
||
//sektory xex bootloaderu, tj. 1 nebo 2
|
||
u08 i,b;
|
||
u08 *spt, *dpt;
|
||
int file_sectors;
|
||
|
||
//file_sectors se pouzije pro sektory $168 i $169 (optimalizace)
|
||
//zarovnano nahoru, tj. =(size+124)/125
|
||
file_sectors = ((xex_size+(u32)(XEX_SECTOR_SIZE-3-1))/((u32)XEX_SECTOR_SIZE-3));
|
||
|
||
printf("XEX ");
|
||
|
||
if (sector<=2)
|
||
{
|
||
printf("boot ");
|
||
|
||
spt= &boot_xex_loader[(u16)(sector-1)*((u16)XEX_SECTOR_SIZE)];
|
||
dpt= atari_sector_buffer;
|
||
i=XEX_SECTOR_SIZE;
|
||
do
|
||
{
|
||
b=*spt++;
|
||
//relokace bootloaderu z $0700 na jine misto
|
||
//TODO if (b==0x07) b+=bootloader_relocation;
|
||
*dpt++=b;
|
||
i--;
|
||
} while(i);
|
||
}
|
||
else
|
||
if(sector==0x168)
|
||
{
|
||
printf("numtobuffer ");
|
||
//vrati pocet sektoru diskety
|
||
//byty 1,2
|
||
goto set_number_of_sectors_to_buffer_1_2;
|
||
}
|
||
else
|
||
if(sector==0x169)
|
||
{
|
||
printf("name ");
|
||
//fatGetDirEntry(FileInfo.vDisk.file_index,5,0);
|
||
//fatGetDirEntry(FileInfo.vDisk.file_index,0); //ale musi to posunout o 5 bajtu doprava
|
||
|
||
{
|
||
u08 i,j;
|
||
for(i=j=0;i<8+3;i++)
|
||
{
|
||
/*if( ((xex_name[i]>='A' && xex_name[i]<='Z') ||
|
||
(xex_name[i]>='0' && xex_name[i]<='9')) )
|
||
{
|
||
//znak je pouzitelny na Atari
|
||
atari_sector_buffer[j]=xex_name[i];
|
||
j++;
|
||
}*/
|
||
if ( (i==7) || (i==8+2) )
|
||
{
|
||
for(;j<=i;j++) atari_sector_buffer[j]=' ';
|
||
}
|
||
}
|
||
//posune nazev z 0-10 na 5-15 (0-4 budou systemova adresarova data)
|
||
//musi pozpatku
|
||
for(i=15;i>=5;i--) atari_sector_buffer[i]=atari_sector_buffer[i-5];
|
||
//a pak uklidi cely zbytek tohoto sektoru
|
||
for(i=5+8+3;i<XEX_SECTOR_SIZE;i++)
|
||
atari_sector_buffer[i]=0x00;
|
||
}
|
||
|
||
//teprve ted muze pridat prvnich 5 bytu na zacatek nulte adresarove polozky (pred nazev)
|
||
//atari_sector_buffer[0]=0x42; //0
|
||
//jestlize soubor zasahuje do sektoru cislo 1024 a vic,
|
||
//status souboru je $46 misto standardniho $42
|
||
atari_sector_buffer[0]=(file_sectors>(0x400-0x171))? 0x46 : 0x42; //0
|
||
|
||
TWOBYTESTOWORD(atari_sector_buffer+3,0x0171); //3,4
|
||
set_number_of_sectors_to_buffer_1_2:
|
||
TWOBYTESTOWORD(atari_sector_buffer+1,file_sectors); //1,2
|
||
}
|
||
else
|
||
if(sector>=0x171)
|
||
{
|
||
printf("data ");
|
||
file_seek(file,((u32)sector-0x171)*((u32)XEX_SECTOR_SIZE-3));
|
||
file_read(file,&atari_sector_buffer[0], XEX_SECTOR_SIZE-3, &read);
|
||
|
||
if(read<(XEX_SECTOR_SIZE-3))
|
||
sector=0; //je to posledni sektor
|
||
else
|
||
sector++; //ukazatel na dalsi
|
||
|
||
atari_sector_buffer[XEX_SECTOR_SIZE-3]=((sector)>>8); //nejdriv HB !!!
|
||
atari_sector_buffer[XEX_SECTOR_SIZE-2]=((sector)&0xff); //pak DB!!! (je to HB,DB)
|
||
atari_sector_buffer[XEX_SECTOR_SIZE-1]=read;
|
||
}
|
||
printf(" sending\n");
|
||
|
||
sectorSize = XEX_SECTOR_SIZE;
|
||
}
|
||
else
|
||
{
|
||
location = offset;
|
||
if (sector>3)
|
||
{
|
||
sector-=4;
|
||
location += 128*3;
|
||
location += sector*atr_header.wSecSize;
|
||
sectorSize = atr_header.wSecSize;
|
||
}
|
||
else
|
||
{
|
||
location += 128*(sector-1);
|
||
sectorSize = 128;
|
||
}
|
||
printf("%d",location);
|
||
printf("\n");
|
||
file_seek(file,location);
|
||
file_read(file,&atari_sector_buffer[0], sectorSize, &read);
|
||
}
|
||
|
||
//topofscreen();
|
||
//hexdump_pure(atari_sector_buffer,sectorSize);
|
||
//printf("Sending\n");
|
||
USART_Send_cmpl_and_atari_sector_buffer_and_check_sum(sectorSize);
|
||
sent = 1;
|
||
|
||
//pause_6502(1);
|
||
//hexdump_pure(0x10000+0x400,128);
|
||
unsigned char chksumreceive = 0; //get_checksum(0x10000+0x400, sectorSize);
|
||
printf(" receive:");
|
||
printf("%d",chksumreceive);
|
||
printf("\n");
|
||
//pause_6502(1);
|
||
//while(1);
|
||
}
|
||
|
||
break;
|
||
}
|
||
|
||
//wait_us(100); // Wait for transmission to complete - Pokey bug, gets stuck active...
|
||
|
||
if (sent)
|
||
USART_Wait_Transmit_Complete();
|
||
USART_Receive_Mode();
|
||
}
|
||
else
|
||
{
|
||
++badcommandcount;
|
||
}
|
||
}
|
||
|
||
unsigned char get_checksum(unsigned char* buffer, u16 len)
|
||
{
|
||
u16 i;
|
||
u08 sumo,sum;
|
||
sum=sumo=0;
|
||
for(i=0;i<len;i++)
|
||
{
|
||
sum+=buffer[i];
|
||
if(sum<sumo) sum++;
|
||
sumo = sum;
|
||
}
|
||
return sum;
|
||
}
|
||
|
||
void USART_Send_Buffer(unsigned char *buff, u16 len)
|
||
{
|
||
while(len>0) { USART_Transmit_Byte(*buff++); len--; }
|
||
}
|
||
|
||
void USART_Send_cmpl_and_atari_sector_buffer_and_check_sum(unsigned short len)
|
||
{
|
||
u08 check_sum;
|
||
printf("(send:");
|
||
printf("%d",len);
|
||
|
||
DELAY_T5_MIN;
|
||
send_CMPL();
|
||
|
||
// Hias: changed to 100us so that Qmeg3 works again with the
|
||
// new bit-banging transmission code
|
||
DELAY_T3_PERIPH;
|
||
|
||
check_sum = 0;
|
||
USART_Send_Buffer(atari_sector_buffer,len);
|
||
// tx_checksum is updated by bit-banging USART_Transmit_Byte,
|
||
// so we can skip separate calculation
|
||
check_sum = get_checksum(atari_sector_buffer,len);
|
||
USART_Transmit_Byte(check_sum);
|
||
//hexdump_pure(atari_sector_buffer,len);
|
||
printf(":chk:");
|
||
printf("%d",check_sum);
|
||
printf(")");
|
||
}
|
firmware/atari_drive_emulator.h | ||
---|---|---|
#pragma once
|
||
|
||
// In principle the drive emulator itself just needs to have access to files from somewhere and then serves requests from the Atari.
|
||
// So it doesn't need to depend on fat, just needs a way of reading the specified 'file'
|
||
// So entry points are:
|
||
// i) Provide function ptr to: fetch data, check file size
|
||
// ii) Notify when disk has been changed/removed
|
||
// iii) Drive - called frequently so we can respond to commands received from Pokey
|
||
|
||
// To speak to the Atari we need:
|
||
// a) Command line
|
||
// b) Pokey
|
||
// Both these are mapped into zpu config regs
|
||
|
||
void actions(); // this is called whenever possible - should be quick
|
||
|
||
void init_drive_emulator();
|
||
void run_drive_emulator(); // Blocks. Pokey at its fastest is 6 cycles * 10 bits per byte. i.e. 60 cycles at 1.79MHz.
|
||
|
||
// To remove a disk, set file to null
|
||
// For a read-only disk, just have no write function!
|
||
struct SimpleFile;
|
||
void set_drive_status(int driveNumber, struct SimpleFile * file);
|
||
|
firmware/build | ||
---|---|---|
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/uart.o -c pokey/uart.c
|
||
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/hexdump.o -c hexdump.c
|
||
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/printf.o -c printf/printf.c
|
||
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/pff_file.o -c fat/pff_file.c
|
||
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/pff.o -c fat/pff.c
|
||
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/utils.o -c common/utils.c
|
||
zpu-elf-gcc -I. -Isd_direct -Iprintf -Ifat -Icommon -c -g -O2 -DDISABLE_UART_RX -o zpu_obj/diskio_mmc.o -c diskio_mmc.c
|
||
zpu-elf-gcc -nostartfiles -Wl,--relax -g -Os -T ./standalone_simple.ld -o JustStartAtari.elf mycrt0.s zpu_obj/main.o zpu_obj/regs.o zpu_obj/atari_drive_emulator.o zpu_obj/pokey/uart.o zpu_obj/hexdump.o zpu_obj/printf/printf.o zpu_obj/fat/pff_file.o zpu_obj/fat/pff.o zpu_obj/common/utils.o zpu_obj/diskio_mmc.o
|
||
zpu-elf-objcopy -O binary JustStartAtari.elf JustStartAtari.bin
|
||
echo >JustStartAtari.rpt -n "End of code:\t"
|
||
zpu-elf-objdump -x JustStartAtari.elf | grep >>JustStartAtari.rpt _romend
|
||
echo >>JustStartAtari.rpt -n "Start of BSS:\t"
|
||
zpu-elf-objdump -x JustStartAtari.elf | grep >>JustStartAtari.rpt __bss_start__
|
||
echo >>JustStartAtari.rpt -n "End of BSS:\t"
|
||
zpu-elf-objdump -x JustStartAtari.elf | grep >>JustStartAtari.rpt __bss_end__
|
||
cat JustStartAtari.rpt
|
||
|
||
firmware/build_native_drive | ||
---|---|---|
gcc -g -O0 -DLITTLE_ENDIAN test_drive.c atari_drive_emulator.c native/uart.c hexdump.c printf/printf.c fat/pff_file.c fat/pff.c common/utils.c native/diskio_image.c -I. -Iprintf -Ifat -Icommon
|
||
firmware/build_native_file_test | ||
---|---|---|
gcc -g -O0 test_file.c fat/pff_file.c fat/pff.c common/utils.c native/diskio_image.c -I. -Ifat -Icommon
|
||
firmware/hexdump.c | ||
---|---|---|
#include "hexdump.h"
|
||
#include "printf.h"
|
||
|
||
void hexdump_pure(void const * str, int length)
|
||
{
|
||
for (;length>0;--length)
|
||
{
|
||
unsigned char val= *(unsigned char *)str++;
|
||
printf("%02x",val);
|
||
}
|
||
}
|
firmware/build_native_joystick | ||
---|---|---|
gcc -g -O0 test_joy.c joystick.c native/regs.c -I.
|
||
firmware/common/integer.h | ||
---|---|---|
#ifndef _INTEGER
|
||
#define _INTEGER
|
||
|
||
/* These types must be 16-bit, 32-bit or larger integer */
|
||
typedef int INT;
|
||
typedef unsigned int UINT;
|
||
|
||
/* These types must be 8-bit integer */
|
||
typedef char CHAR;
|
||
typedef unsigned char UCHAR;
|
||
typedef unsigned char BYTE;
|
||
typedef unsigned char u08;
|
||
typedef unsigned char uint8_t;
|
||
|
||
/* These types must be 16-bit integer */
|
||
typedef short SHORT;
|
||
typedef unsigned short USHORT;
|
||
typedef unsigned short WORD;
|
||
typedef unsigned short WCHAR;
|
||
typedef unsigned short u16;
|
||
|
||
/* These types must be 32-bit integer */
|
||
typedef long LONG;
|
||
typedef unsigned long ULONG;
|
||
typedef unsigned long DWORD;
|
||
typedef unsigned int u32;
|
||
|
||
#define EEMEM
|
||
|
||
#endif
|
firmware/common/utils.c | ||
---|---|---|
int strcmp(char const * a, char const * b)
|
||
{
|
||
while (*a || *b)
|
||
{
|
||
if (*a<*b)
|
||
return -1;
|
||
else if (*a>*b)
|
||
return 1;
|
||
|
||
++a;
|
||
++b;
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
void strcpy(char * dest, char const * src)
|
||
{
|
||
while (*dest++=*src++);
|
||
}
|
||
|
||
int strlen(char const * a)
|
||
{
|
||
int count;
|
||
for (count=0; *a; ++a,++count);
|
||
return count;
|
||
}
|
||
|
firmware/joystick.h | ||
---|---|---|
#pragma once
|
||
|
||
struct joystick_status
|
||
{
|
||
char x_;
|
||
char y_;
|
||
char fire_;
|
||
};
|
||
|
||
enum JoyWait {WAIT_QUIET, WAIT_FIRE, WAIT_MOVE, WAIT_EITHER};
|
||
|
||
void joystick_poll(struct joystick_status * status);
|
||
void joystick_wait(struct joystick_status * status, enum JoyWait waitFor);
|
||
|
firmware/mif_file_stuff/rom_prologue.vhd | ||
---|---|---|
DEPTH = 4096; % Memory depth and width are required %
|
||
% DEPTH is the number of addresses %
|
||
WIDTH = 32; % WIDTH is the number of bits of data per word %
|
||
% DEPTH and WIDTH should be entered as decimal numbers %
|
||
ADDRESS_RADIX = HEX; % Address and value radixes are required %
|
||
DATA_RADIX = HEX; % Enter BIN, DEC, HEX, OCT, or UNS; unless %
|
||
% otherwise specified, radixes = HEX %
|
||
-- Specify values for addresses, which can be single address or range
|
||
CONTENT
|
||
BEGIN
|
firmware/mycrt0.s | ||
---|---|---|
/* Startup code for ZPU
|
||
Copyright (C) 2005 Free Software Foundation, Inc.
|
||
|
||
This file 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 2, or (at your option) any
|
||
later version.
|
||
|
||
In addition to the permissions in the GNU General Public License, the
|
||
Free Software Foundation gives you unlimited permission to link the
|
||
compiled version of this file with other programs, and to distribute
|
||
those programs without any restriction coming from the use of this
|
||
file. (The General Public License restrictions do apply in other
|
||
respects; for example, they cover modification of the file, and
|
||
distribution when not linked into another program.)
|
||
|
||
This file 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 this program; see the file COPYING. If not, write to
|
||
the Free Software Foundation, 59 Temple Place - Suite 330,
|
||
Boston, MA 02111-1307, USA. */
|
||
.file "mincrt0.S"
|
||
|
||
|
||
/* Minimal startup code, usable where the core is complete enough not to require emulated instructions */
|
||
|
||
|
||
.section ".fixed_vectors","ax"
|
||
; KLUDGE!!! we remove the executable bit to avoid relaxation
|
||
.section ".fixed_vectors","a"
|
||
|
||
.macro fixedim value
|
||
im \value
|
||
.endm
|
||
|
||
.macro jmp address
|
||
fixedim \address
|
||
poppc
|
||
.endm
|
||
|
||
.macro fast_neg
|
||
not
|
||
im 1
|
||
add
|
||
.endm
|
||
|
||
.macro mult1bit
|
||
; create mask of lowest bit in A
|
||
loadsp 8 ; A
|
||
im 1
|
||
and
|
||
im -1
|
||
add
|
||
not
|
||
loadsp 8 ; B
|
||
and
|
||
add ; accumulate in C
|
||
|
||
; shift B left 1 bit
|
||
loadsp 4 ; B
|
||
addsp 0
|
||
storesp 8 ; B
|
||
|
||
; shift A right 1 bit
|
||
loadsp 8 ; A
|
||
flip
|
||
addsp 0
|
||
flip
|
||
storesp 12 ; A
|
||
.endm
|
||
|
||
.macro cimpl funcname
|
||
; save R0
|
||
im _memreg
|
||
load
|
||
|
||
; save R1
|
||
im _memreg+4
|
||
load
|
||
|
||
; save R2
|
||
im _memreg+8
|
||
load
|
||
|
||
loadsp 20
|
||
loadsp 20
|
||
|
||
fixedim \funcname
|
||
call
|
||
|
||
; destroy arguments on stack
|
||
storesp 0
|
||
storesp 0
|
||
|
||
im _memreg
|
||
load
|
||
|
||
; poke the result into the right slot
|
||
storesp 24
|
||
|
||
; restore R2
|
||
im _memreg+8
|
||
store
|
||
|
||
; restore R1
|
||
im _memreg+4
|
||
store
|
||
|
||
; restore r0
|
||
im _memreg
|
||
store
|
||
|
||
|
||
storesp 4
|
||
poppc
|
||
.endm
|
||
|
||
|
||
.globl _start
|
||
_start:
|
||
jmp _premain
|
||
|
||
/* vectors */
|
||
/* instruction emulation code */
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
nop
|
||
|
||
# opcode 34
|
||
# offset 0x0000 0040
|
||
.balign 32,0
|
||
_loadh:
|
||
loadsp 4
|
||
; by not masking out bit 0, we cause a memory access error
|
||
; on unaligned access
|
||
im ~0x2
|
||
and
|
||
load
|
||
|
||
; mult 8
|
||
loadsp 8
|
||
im 3
|
||
and
|
||
fast_neg
|
||
im 2
|
||
add
|
||
im 3
|
||
ashiftleft
|
||
; shift right addr&3 * 8
|
||
lshiftright
|
||
im 0xffff
|
||
and
|
||
storesp 8
|
||
|
||
poppc
|
||
|
||
# opcode 35
|
||
# offset 0x0000 0060
|
||
.balign 32,0
|
||
_storeh:
|
||
loadsp 4
|
||
; by not masking out bit 0, we cause a memory access error
|
||
; on unaligned access
|
||
im ~0x2
|
||
and
|
||
load
|
||
|
||
; mask
|
||
im 0xffff
|
||
loadsp 12
|
||
im 3
|
||
and
|
||
fast_neg
|
||
im 2
|
||
add
|
||
im 3
|
||
ashiftleft
|
||
ashiftleft
|
||
not
|
||
|
||
and
|
||
|
||
loadsp 12
|
||
im 0xffff
|
||
|
||
nop
|
||
|
||
fixedim _storehtail
|
||
poppc
|
||
|
||
|
||
# opcode 36
|
||
# offset 0x0000 0080
|
||
.balign 32,0
|
||
_lessthan:
|
||
loadsp 8
|
||
fast_neg
|
||
loadsp 8
|
||
add
|
||
|
||
; DANGER!!!!
|
||
; 0x80000000 will overflow when negated, so we need to mask
|
||
; the result above with the compare positive to negative
|
||
; number case
|
||
loadsp 12
|
||
loadsp 12
|
||
not
|
||
and
|
||
not
|
||
and
|
||
|
||
|
||
; handle case where we are comparing a negative number
|
||
; and positve number. This can underflow. E.g. consider 0x8000000 < 0x1000
|
||
loadsp 12
|
||
not
|
||
loadsp 12
|
||
and
|
||
|
||
or
|
||
|
||
|
||
|
||
flip
|
||
im 1
|
||
and
|
||
|
||
|
||
storesp 12
|
||
storesp 4
|
||
poppc
|
||
|
||
|
||
# opcode 37
|
||
# offset 0x0000 00a0
|
||
.balign 32,0
|
||
_lessthanorequal:
|
||
loadsp 8
|
||
loadsp 8
|
||
lessthan
|
||
loadsp 12
|
||
loadsp 12
|
||
eq
|
||
or
|
||
|
||
storesp 12
|
||
storesp 4
|
||
poppc
|
||
|
||
|
||
# opcode 38
|
||
# offset 0x0000 00c0
|
||
.balign 32,0
|
||
_ulessthan:
|
||
; fish up arguments
|
||
loadsp 4
|
||
loadsp 12
|
||
|
||
/* low: -1 if low bit dif is negative 0 otherwise: neg (not x&1 and (y&1))
|
||
x&1 y&1 neg (not x&1 and (y&1))
|
||
1 1 0
|
||
1 0 0
|
||
0 1 -1
|
||
0 0 0
|
||
|
||
*/
|
||
loadsp 4
|
||
not
|
||
loadsp 4
|
||
and
|
||
im 1
|
||
and
|
||
neg
|
||
|
||
|
||
/* high: upper 31-bit diff is only wrong when diff is 0 and low=-1
|
||
high=x>>1 - y>>1 + low
|
||
|
||
extremes
|
||
|
||
0000 - 1111:
|
||
low= neg(not 0 and 1) = 1111 (-1)
|
||
high=000+ neg(111) +low = 000 + 1001 + low = 1000
|
||
OK
|
||
|
||
1111 - 0000
|
||
low=neg(not 1 and 0) = 0
|
||
high=111+neg(000) + low = 0111
|
||
OK
|
||
|
||
|
||
*/
|
||
loadsp 8
|
||
|
||
flip
|
||
addsp 0
|
||
flip
|
||
|
||
loadsp 8
|
||
|
||
flip
|
||
addsp 0
|
||
flip
|
||
|
||
sub
|
||
|
||
; if they are equal, then the last bit decides...
|
||
add
|
||
|
||
/* test if negative: result = flip(diff) & 1 */
|
||
flip
|
||
im 1
|
||
and
|
||
|
||
; destroy a&b which are on stack
|
||
storesp 4
|
||
storesp 4
|
||
|
||
storesp 12
|
||
storesp 4
|
||
poppc
|
||
|
||
# opcode 39
|
||
# offset 0x0000 00e0
|
||
.balign 32,0
|
||
_ulessthanorequal:
|
||
loadsp 8
|
||
loadsp 8
|
||
ulessthan
|
||
loadsp 12
|
||
loadsp 12
|
||
eq
|
||
or
|
||
|
||
storesp 12
|
||
storesp 4
|
||
poppc
|
||
|
||
|
||
# opcode 40
|
||
# offset 0x0000 0100
|
||
.balign 32,0
|
||
.globl _swap
|
||
_swap:
|
||
breakpoint ; tbd
|
||
|
||
# opcode 41
|
||
# offset 0x0000 0120
|
||
.balign 32,0
|
||
_slowmult:
|
||
im _slowmultImpl
|
||
poppc
|
||
|
||
# opcode 42
|
||
# offset 0x0000 0140
|
||
.balign 32,0
|
||
_lshiftright:
|
||
loadsp 8
|
||
flip
|
||
|
||
loadsp 8
|
||
ashiftleft
|
||
flip
|
||
|
||
storesp 12
|
||
storesp 4
|
||
|
||
poppc
|
||
|
||
|
||
# opcode 43
|
||
# offset 0x0000 0160
|
||
.balign 32,0
|
||
_ashiftleft:
|
||
loadsp 8
|
||
|
||
loadsp 8
|
||
im 0x1f
|
||
and
|
||
fast_neg
|
||
im _ashiftleftEnd
|
||
add
|
||
poppc
|
||
|
||
|
||
|
||
# opcode 44
|
||
# offset 0x0000 0180
|
||
.balign 32,0
|
||
_ashiftright:
|
||
loadsp 8
|
||
loadsp 8
|
||
lshiftright
|
||
|
||
; handle signed value
|
||
im -1
|
||
loadsp 12
|
||
im 0x1f
|
||
and
|
||
lshiftright
|
||
not ; now we have an integer on the stack with the signed
|
||
; bits in the right position
|
||
|
||
; mask these bits with the signed bit.
|
||
loadsp 16
|
||
not
|
||
flip
|
||
im 1
|
||
and
|
||
im -1
|
||
add
|
||
|
||
and
|
||
|
||
; stuff in the signed bits...
|
||
or
|
||
|
||
; store result into correct stack slot
|
||
storesp 12
|
||
|
||
; move up return value
|
||
storesp 4
|
||
poppc
|
||
|
||
# opcode 45
|
||
# offset 0x0000 01a0
|
||
.balign 32,0
|
||
_call:
|
||
; fn
|
||
loadsp 4
|
||
|
||
; return address
|
||
loadsp 4
|
||
|
||
; store return address
|
||
storesp 12
|
||
|
||
; fn to call
|
||
storesp 4
|
||
|
||
pushsp ; flush internal stack
|
||
popsp
|
||
|
||
poppc
|
||
|
||
_storehtail:
|
||
|
||
and
|
||
loadsp 12
|
||
im 3
|
||
and
|
||
fast_neg
|
||
im 2
|
||
add
|
||
im 3
|
||
ashiftleft
|
||
nop
|
||
ashiftleft
|
||
|
||
or
|
Also available in: Unified diff
First cut of reworked firmware. Just runs gunpowder charlie.atr drive emulation!