• Please review our updated Terms and Rules here

why am i getting these weird numbers returned after my ISR?

Mike Chambers

Veteran Member
Joined
Sep 2, 2006
Messages
2,621
so i'm taking over int 13h to add a virtual disk via TCP like i mentioned in another thread. a raw image that can by accessed sector-by-sector like a hard disk. fdisk can see my virtual disk a second fixed disk, but when i examine it it thinks it's only 10 MB in size. i've specified the geometry as 1024/16/63 CHS so it should be over 500 MB.

my handler checks the value of DL immediately, and if it matches 81h (or whatever drive number i'm taking control of) it continues on to my handling code, otherwise it does a _chain_intr to the original handler so that it is handled normally.

to try and see what the problem is i manually set AX to 0x1500 (get disk type function) and DX to 0x0081. here's what's returned:

AX = 0x0300 (what it should be, AH 3 returned says it's a hard disk)
CX = 0x0000
DX = 0x5104

when this function returns, CX-DX (<--should be a : but vbulletin turns it into a smiley) is actually used as a 32-bit value specifying the amount of 512 byte sectors on the disk in total. 0x00005104 = decimal 20740 times 512 = 10,618,880 bytes. about 10 MB.

if you have a look at my code (TCP stuff not added yet, i'm trying to get fdisk to see the proper size first) it is (at least to me, maybe i'm mistaken) clearly written so that CX-DX should be returning decimal 1,032,192. multiplied by 512, that's 528,482,304 bytes. 504 MB.

Code:
#include <stdio.h>
//#include <tcp.h>
#include <dos.h>
#include <stdlib.h>

//static tcp_Socket sock;
//tcp_Socket *s;

int status;
//word tickreturn;
char buffer[2049];
char outbuf[518];
char tmpsector[511];

//longword host;
int running = 0;

struct virtualDisk {
  int cyls;
  int heads;
  int spt;
} vdisk;

int usecyl = 0;
int usehead = 0;
int usesect = 1;
int triperr = 0;
int fakedrive;

char _huge *tsrstack;
char _huge *appstack;
char _huge *tsrbottom;

unsigned tsrsize;
int tmpval;
long tmplong;
long tmplong2;
int sectorcount;
int savestate = 0;
int savecarry = 0;
unsigned char far *BIOSarea = 0x400; //offset 0x75 from this is the byte holding the HDD count from BIOS
unsigned char far *tmpfarchar;

void (_interrupt _far *oldtimer)();
void _interrupt _far newtimer();
void (_interrupt _far *old13h)();
void _interrupt _far new13h( unsigned _es, unsigned _ds, unsigned _di,
			       unsigned _si, unsigned _bp, unsigned _sp,
			       unsigned _bx, unsigned _dx, unsigned _cx,
			       unsigned _ax, unsigned _ip, unsigned _cs,
			       unsigned flags );

void _interrupt _far new13h(unsigned _es, unsigned _ds,
			  unsigned _di, unsigned _si,
			  unsigned _bp, unsigned _sp,
			  unsigned _bx, unsigned _dx,
			  unsigned _cx, unsigned _ax,
			  unsigned _ip, unsigned _cs,
			  unsigned flags)
{
  if (_dx&&0xFF!=fakedrive) _chain_intr(old13h);
  _asm cli //disable other interrupts while we're working
  if ((flags&&1)==1) flags = flags - 1;
  switch(_ax>>8) {
    case 0x00: //RESET DISK DRIVE (always just return success)
      _ax = 0x0000;
      break;
    case 0x01: //CHECK DRIVE STATUS
      _ax = savestate;
      if (savecarry==1) flags = flags + 1;
      savecarry = 0;
      _asm sti //turn interrupts back on
      return;
    case 0x02: //READ SECTOR(S) INTO MEMORY
      _ax = _ax&&0xFF; // for now, let's just trick the caller into thinking we were successful
      break;
    case 0x08: //GET DISK PARAMETERS
      _ax = 0x0000;
      _cx = ((vdisk.cyls-1)<<6)+vdisk.spt;
      _dx = ((vdisk.heads-1)<<8)+BIOSarea[0x75];
      break;
    case 0x15: //GET DISK TYPE
      _ax = 0x0300;
      tmplong = vdisk.cyls * vdisk.heads * vdisk.spt;
      _dx = tmplong&&0xFFFF;
      _cx = tmplong>>8;
      break;
    default: //IF WE DONT SUPPORT THE COMMAND THE CALLER WANTS YET
      _ax = 0x0080; //WE TELL IT THERE WAS AN UNSPECIFIED ERROR
      flags = flags + 1;
  }
  savestate = _ax;
  savecarry = flags&&1;
  _asm sti //turn interrupts back on
}

void _interrupt _far newtimer() {
  (*oldtimer)();
  if (running==0) { //we need to take care to not allow re-entry
    running = 1;
//    tickreturn = tcp_tick(s);
    running = 0;
  }
}

int main() {
  printf("LAN Drive version blah blah blah (c)2010 Mike Chambers\n");
  printf("WATTCP library (c)1990, 1999 Erick Engelke\n\n");
  vdisk.cyls = 1024;
  vdisk.heads = 16;
  vdisk.spt = 63;

  printf("Initializing TCP/IP core... ");  // let's not worry about this for the moment
  //sock_init();
  //*oldtimer = _dos_getvect(0x1c);
  //_dos_setvect(0x1c, newtimer); //hook the timer interrupt to keep WATTCP processing

  printf("OK!\nConnecting to remote server... ");
  //host = resolve("192.168.1.2"); //not worrying about this either yet
  //status = tcp_open(s, 0, host, 5150, NULL);
  //sock_wait_established(s, 10, NULL, NULL);

  printf("OK!\nCurrent hard drive count: %u. (New count is %u.)\n", BIOSarea[0x75]++, BIOSarea[0x75]);
  fakedrive = 0x80+BIOSarea[0x75]-1; //figure out the value we look for in DL when int 13h is called
  printf("LAN Drive is simulating a hard disk at %xh.\n", fakedrive);
  printf("Virtual disk geometry: %u cyls, %u heads, %u sectors/track.\n", vdisk.cyls, vdisk.heads, vdisk.spt);

  printf("Installing TSR on interrupt 13h... ");

  old13h = _dos_getvect(0x13);
  _dos_setvect(0x13, new13h);

  /* Initialize stack and bottom of program. */
  _asm mov  WORD PTR tsrstack[0], sp
  _asm mov  WORD PTR tsrstack[2], ss
  FP_SEG( tsrbottom ) = _psp;
  FP_OFF( tsrbottom ) = 0;

  tsrsize = ((tsrstack - tsrbottom) >> 4) + 64;
  _dos_freemem(_psp);
  printf("OK!\n\nLAN Drive is now memory-resident. Returning to DOS...\n");
  _dos_keep(0, tsrsize);

  //sock_shutdown();
  return 0;

  sock_err:
  { } //i'll put stuff in here later to deal with socket errors. for now, this wont even be called.
}

i can't see where the problem lies. according to the QuickC help files to properly modify the register in an interrupt handler i simply change the value of _ax, _bx, etc and any changes will returned back to the caller.

is it a problem that i'm using the small memory model or something like that? this is frustrating. oh, and i've used INTSPY to watch every int 13h call that fdisk makes, and NONE of them are a function that my code doesn't support yet, so it's not that.



EDIT: P.S. i shelled out $65 for the WATTCP programmer's reference yesterday, now i get to have all kinds of fun. :)

yet another edit: do you think it might be any difference to make the handler simply a void far (with no passed args) instead of void interrupt far, and then manually pop/push the values from/to the stack?
 
Last edited:
Code:
      tmplong = vdisk.cyls * vdisk.heads * vdisk.spt;
      _dx = tmplong&&0xFFFF;
      _cx = tmplong>>8;
      break;

Mike, pour yourself another cup of coffee and tell me if that's what you really mean.

Don't you mean:
Code:
      tmplong = (long) vdisk.cyls * vdisk.heads * vdisk.spt;
      _dx = tmplong & 0xFFFF;
      _cx = tmplong >> 16;
      break;

I'd probably make tmplong a local unsigned long to your interrupt routine also--just good form not to use a global statically allocated variable when one isn't intended. Also, you can use the clearer _enable() and _disable() intrinsics instead of the _asm cli and _asm sti assembler statements.

There are probably other issues, but that one jumped out at me.
 
if you could see how hard i'm facepalming right now... wow, yeah. that was ditsy of me. so used to shifting single bytes i must have typed that in without thinking. let me fire up the ol' 286 lappy here again and change that. also, the (long) is necessary? i thought it intrinsically converted the type. d'oh! as you can see, i'm still getting used to C.

EDIT: just changed it, but it still returns the same for some reason. it's like my values are being overwritten for some reason. also, just so you know i have disabled the stack and pointer checking compiler options. i doubt it'll be any different, but i'm going to compile this with borland turbo c 3.0 instead of QuickC 2.51 just for kicks. can't hurt.
 
Last edited:
I'd probably make tmplong a local unsigned long to your interrupt routine also--just good form not to use a global statically allocated variable when one isn't intended. Also, you can use the clearer _enable() and _disable() intrinsics instead of the _asm cli and _asm sti assembler statements.

yeah, that's how i wanted to do it with the local unsigned long but QuickC is goofy when the MS extensions are enabled instead of ANSI C. it doesn't want me to make a local one. i might switch this whole thing over to borland soon, it makes larger executables but the MS compiler has some quirks. (for example, it'll throw me some warnings during a compile but then if i close the IDE and re-open it, then recompile the exact same code again i might get no warnings this time)
 
I'm rooting for TurboC ;-) I couldn't follow the code that well but if you step through it and if you have a register debug window you might see where it's getting overwritten or if it does pull the data back correctly and as expected.
 
Mike, go back and look at your code carefully. I think you got a brain fart confusing & and &&. && always returns 0 or 1, but & is a logical AND. So the statement

Code:
if (_dx&&0xFF!=fakedrive) _chain_intr(old13h);

Makes no sense. Sometimes it's better to define a macro to avoid things like this. For example:

#define HIBYTE(x) ((unsigned) (x)>> 8 )
 
the reason i started messing around with QuickC was the sweet feature of being able to link C OBJs with QuickBASIC OBJs. you can call C functions from QB, but not vice versa because C looks for a leading underscore in the procedure name.... so i wrote a prog that added underscores in compiled QB OBJs because i'm stubborn like that. i think this virtual network disk prog can be really useful, i have a lot of old 8088s but not enough hard drives to go around.

plus there's the benefit of being able to use the raw images it works with directly inside of QEMU. how handy is that?
 
Mike, go back and look at your code carefully. I think you got a brain fart confusing & and &&. && always returns 0 or 1, but & is a logical AND. So the statement

Code:
if (_dx&&0xFF!=fakedrive) _chain_intr(old13h);

Makes no sense. Sometimes it's better to define a macro to avoid things like this. For example:

#define HIBYTE(x) ((unsigned) (x)>> 8 )

!!!!! see, there's my BASIC background messing me up again. i would have done IF (DX AND fakedrive) <> fakedrive THEN in BASIC, which would work. i thought it was the equivalent operator here.

so i guess i should do the same throughout the code. i used && a number of times in calculations.
 
That's the beauty :)huh:) of C--it'll compile the darnedest things.

Dennis Ritchey used to moderate a USENET group back in the pre-web days on C, when the internet was a lot more civil. He'd post "what does it do?" quizzes. Some were real humdingers, including the correct answer being "I don't know."
 
hahaha it works! at least on my 486 laptop with DR-DOS 7.03. i'll move it back over to the 286 laptop and see if it works on MS-DOS 6.22 on there. i was getting tired of the greyscale LCD and compile times.

plus the 486 lappy has a BUILT IN PRINTER how sweet is that? best $2 i ever spent. thanks shopgoodwill.com! and thanks chuck... i'd be getting nowhere if you werent around lol. i'll be looking for serious beta testers for this sometime soon. anybody interested in a program like this? i know there's the MS net client 3.0 for DOS software, but that's a redirector and it has some limitations. (plus crazy RAM usage)

this prog will have the advantage of looking like a true physical hard disk to everything. first things first, but i am planning on LBA extension support later on. an additional idea i had about that was possibly being able to act as a drive overlay program for real hard drives under an old BIOS, kind of like EZ-Drive (but less infuriating?)
 
Last edited:
Yeah, sure that would be pretty cool to play with!

You can put me down for "interested" and "Beta test" if you want :)
 
noted. :)

hey, Chuck. i was thinking, some days ago you were talking about how i would have to have my code relocate itself in RAM to the top of conventional memory then adjust the install RAM value in the BIOS data area. it's much more complicated than i wanted to deal with, so i had this thought.. what do you think of this?

when the PC boots a to DOS partition, the boot sector code hands off control to the OS by loading the first 3 sectors of IO.SYS into memory and jumping to it. that code then loads the rest of the file which takes care of detecting hardware (i.e. disks) ... it then sets up the drive letters based on the partitions it found. i'm not sure if i can just manually load the first 3 sectors and just jump to that because it might still go ahead and make DOS forgot that my code is loaded and it would get overwritten, but if that's the case then with some research and/or careful reverse-engineering (disassemble and examine) it might be possible to pinpoint the exact code that takes care of enumerating partitions and setting up drive letters and add equivalent functionality to LAN Drive.

....or am i too optimistic?
 
I don't know, Mike. It sounds pretty scary, particularly when it comes to different DOS versions. You can look at the code in MSBOOT.ASM to see what it entails (I assume that you have the MSDOS 6.0 source).

Observe that a lot of network cards support booting over the net, so you might want to investigate making your code relocatable anyway--someoen may want to put it in a ROM.
 
Just wanna say this has awesome potential... could almost have a fleet of old computers running off a common network storage. No need to worry about getting legacy hard disks / controllers in the works...
 
i just ordered a few books. one of which is Undocumented DOS which mike brutman suggested, along with 3 others i found on amazon that look pretty tasty:

Advanced M. S.-DOS Programming: The Microsoft Guide for Assembly Language and C. Programmers

Writing DOS Device Drivers in C (this one is only rated 2/5 stars, but I'm sure there are useful tidbits in there)

Writing MS-DOS Device Drivers




i was able to get a packet driver loaded into the memory i reserved, and was able to run it but after an int 19 although the PD remains in memory and programs see it, it can't actually communicate with the network card. i was talking with erick engelke (WATTCP programmer) about this and he says that even though most PDs are .COM files they still allocate memory elsewhere from DOS in other segments.

my thought was that this can be made to work anyway by hooking int 21h and intercepting the allocate/deallocate memory calls to make the PD use exactly the segments i want but that is really just starting to get way too complicated so i'm going to go the .SYS file device driver route. i downloaded the PC-DOS 7 technical reference which has a huge chapter on device driver programming. pretty straight-forward really.

i wish i was doing all this 20 years ago because i'd be rich. unfortunately i was 6 then. :\
 
It's an excellent reason to write code like this in assembler or at least redo the startup code so you know what's being done. MS always (to the best of my knowledge) provided the startup source for just such reasons. Take a look at the PDMAIN.ASM program, for example, in my PDOS driver. It even contains its own printf() that sends output to the serial port, so you can watch the debug messages as the driver operates.
 
good point. i've been going through your redirector code btw. it's very well written, good stuff.
 
Back
Top