VOGONS


Writing DOS device drivers in C

Topic actions

Reply 40 of 54, by hyoenmadan

User metadata
Rank Member
Rank
Member
Oerg866 wrote on 2021-08-21, 13:55:

Yeah, fair point. I was rubbed wrongly mostly by the first two paragraphs of BloodyCactus' post and this gem. It's not needed and rude (and far from the first time I've seen unwelcoming behavior on this site...).

Yes, that's my gem.
My reasoning behind it are 2 things:
- Harder to configure the toolset for these rare corner cases and debug.
- Abusing stuff like unreal mode from a DOS mode resident driver code, any other than the memory manager ones, results in drivers which are incompatible with DOSExtenders, such as Windows 3.x and extended games/applications.

But that was before knowing better what you were trying to do. If your driver will be just a "hardware initializer" type (aka, only set some MSRs and then unload), then is fine I guess...

Although generally these (hardware initializers) are done as executables which can be called from DOS prompt besides being used from config.sys.

Reply 41 of 54, by Oerg866

User metadata
Rank Member
Rank
Member

It's fine that you had reasons for it, but you should generally not be so presumptuous or at least put what you wrote above in the initial response. 😞

- Harder to configure the toolset for these rare corner cases and debug.

I mean, there is no easy toolchain to set up for DOS I believe 😜 Even DJGPP has its problems in that regard (I wrote an installer for a custom Win98 distro with it and had a few issues).

- Abusing stuff like unreal mode from a DOS mode resident driver code

This is all boring old real mode stuff.

Although generally these (hardware initializers) are done as executables which can be called from DOS prompt besides being used from config.sys.

That's what the DOS Internals framework outputs (EXEs which can be DEVICE= loaded). I do have the option in it to stay resident, and the example shows how to do interrupt hooks and such, buuuuuuut that's for some dark times far far away for other projects 😜

Best regards,
Eric

Reply 42 of 54, by BloodyCactus

User metadata
Rank Oldbie
Rank
Oldbie
Oerg866 wrote on 2021-08-21, 11:14:

Please stop being so condescending, I'm an embedded software engineer (got my own office and everything!) and I'm a reasonably capable programmer trying to learn a new (old) thing - the attitude in these types of threads make that very hard so I usually don't bother posting here.

thats ok, I've been programming for like, 30,33,35? years. good luck with your endevours and I hope you succeed. I've removed my unnecessary stuff.

--/\-[ Stu : Bloody Cactus :: [ https://bloodycactus.com :: http://kråketær.com ]-/\--

Reply 43 of 54, by jakethompson1

User metadata
Rank Oldbie
Rank
Oldbie
Oerg866 wrote on 2021-08-21, 17:28:

I mean, there is no easy toolchain to set up for DOS I believe 😜 Even DJGPP has its problems in that regard (I wrote an installer for a custom Win98 distro with it and had a few issues).

The only DOS C compiler that doesn't use a DOS extender that I've personally used is Visual C++ 1.52c. However, I think this recipe would work for any DOS compiler when developing a simple .SYS driver.

First, you don't want the C runtime code included. That is the pre-main() startup code BloodyCactus was referring to. For the CL compiler looks like that option is /Ln.

Next you would want to use the Tiny memory model (everything in one happy segment cs=ds=ss) which matches how a .SYS would be loaded. Then, you would want to output a .COM, not an .EXE. A .COM has no header, it's just a block of data that gets put in memory and jumped to.

Next, you would prefix the .COM with the correct DOS driver header. Here's an overview of what they look like. http://www.delorie.com/djgpp/doc/rbinter/it/46/16.html

If I were attempting this personally, I would configure the linker to output a .MAP file--that would help you determine the addresses of your Strategy() and Interrupt() routines that need to be placed into the header. Don't forget that .COM offsets are offset by 100h (because a Program Segment Prefix with the command line, etc. goes before the loaded file) but when the .SYS gets loaded, those Strategy() and Interrupt() routines need to be file offsets.

Reply 44 of 54, by Jo22

User metadata
Rank l33t++
Rank
l33t++
Oerg866 wrote on 2021-08-21, 13:55:

TL;DR: I have a working framework for this from DOS internals, and I am making progress. And doing this in C is NOT up for debate 😜

If you must use C, then please consider use ing K&R C, at least. That's for real men and it is less bloated, also. 😉
If you leave alout stdio.h and other stuff, the program code will be even smaller.

Btw, my favorite oldschool C compiler is MIX Power C.
It can use different memory models for EXE files and is able to generate code for 8086, 808186, 80286.
It also has x87 support, if memory serves.
And can handle both K&R C and ANSI-C.
Anyway, this thing is from the late 1980s.
It may not provide all that comfy stuff people are used to by now. 😉

"Time, it seems, doesn't flow. For some it's fast, for some it's slow.
In what to one race is no time at all, another race can rise and fall..." - The Minstrel

//My video channel//

Reply 45 of 54, by Oerg866

User metadata
Rank Member
Rank
Member

Hello everyone,

to show that in principle the framework and idea of doing things works, here is my first test version.

https://github.com/oerg866/k6init/releases/do … 0.01/K6INIT.EXE

It checks for a supported CPU, finds LFB regions and memory size using VBE queries and sets MTRR registers in the CPU accordingly to enable write combining for them. On my machine this works properly and SetK6 confirms WC enabled for my GeForce 2 GTS LFB.

I still have an absudrly hard time wrapping my head about how PROCs work and what the deal with segmentation and near/far is. So my way of doing things are probably wrong, but I'm getting there.

Reply 46 of 54, by Oerg866

User metadata
Rank Member
Rank
Member
jakethompson1 wrote on 2021-08-21, 18:58:
The only DOS C compiler that doesn't use a DOS extender that I've personally used is Visual C++ 1.52c. However, I think this rec […]
Show full quote
Oerg866 wrote on 2021-08-21, 17:28:

I mean, there is no easy toolchain to set up for DOS I believe 😜 Even DJGPP has its problems in that regard (I wrote an installer for a custom Win98 distro with it and had a few issues).

The only DOS C compiler that doesn't use a DOS extender that I've personally used is Visual C++ 1.52c. However, I think this recipe would work for any DOS compiler when developing a simple .SYS driver.

First, you don't want the C runtime code included. That is the pre-main() startup code BloodyCactus was referring to. For the CL compiler looks like that option is /Ln.

Next you would want to use the Tiny memory model (everything in one happy segment cs=ds=ss) which matches how a .SYS would be loaded. Then, you would want to output a .COM, not an .EXE. A .COM has no header, it's just a block of data that gets put in memory and jumped to.

Next, you would prefix the .COM with the correct DOS driver header. Here's an overview of what they look like. http://www.delorie.com/djgpp/doc/rbinter/it/46/16.html

If I were attempting this personally, I would configure the linker to output a .MAP file--that would help you determine the addresses of your Strategy() and Interrupt() routines that need to be placed into the header. Don't forget that .COM offsets are offset by 100h (because a Program Segment Prefix with the command line, etc. goes before the loaded file) but when the .SYS gets loaded, those Strategy() and Interrupt() routines need to be file offsets.

I completely overlooked this post. Thanmk you for your thorough explanation -- although the framework provided by DOS internals already does all of this, it's good to know what goes on under the hood.

-Eric

Reply 47 of 54, by Oerg866

User metadata
Rank Member
Rank
Member

unknown.png

stuff is starting to work 😀

It's actually really not so bad once the hard stuff is out of the way. Like spending 10 hours debugging implausible return values until you figure out that you cannot return a 32-bit value in a proc ( the upper 16 bit will be cut off) 😠 Thus proving - coming to an older world from a modern world where the compiler does half the work for you is a challenge, haha.

EDIT: replaced big picture by screenshot

This works correctly (to my surprise) and SETK6 confirms MTRRs being configured as intended and PCPBENCH and quake obtain a massive speed boost. Yay 😀

Reply 48 of 54, by kjliew

User metadata
Rank Oldbie
Rank
Oldbie
Oerg866 wrote on 2021-08-25, 22:07:

Like spending 10 hours debugging implausible return values until you figure out that you cannot return a 32-bit value in a proc ( the upper 16 bit will be cut off) 😠 Thus proving - coming to an older world from a modern world where the compiler does half the work for you is a challenge, haha.

That's what I called the 'cult' of 16-bit C ABI. 32-bit value is returned in DX:AX instead of EAX or RAX for modern flat model x86 C programming. Or, R0/W0/X0 if you are coming from ARM/AArch64 background. 😉

Reply 49 of 54, by Exploit

User metadata
Rank Newbie
Rank
Newbie
Oerg866 wrote on 2021-08-19, 00:03:
... […]
Show full quote

...

While in principle this does work, I can compile and link an executable file that I can load both as a .SYS and as a .EXE, I'm shocked to find out that this compiler, released 5 years after the processor's introduction, does not support 386 code generation -- so that's out.

Then I tried MSC8.00's compiler and while it does have 386 code generation, the inline assembler (which I need for a few specific pentium+ things) does not support 386 instructions, which is mentioned in the help files.

Was that ever fixed? Was there a later version that can produce DOS code and maybe can inline 386 instructions?
...

Real life example why you should never write DOS drivers for 386 only and later:

MSCDEX.EXE from MS-DOS was written for 8086 CPUs and many CD-ROM drivers for the drives were also written for 8086 CPUs.
Therefore, with the proprietary original drivers for CD-ROM drives and the proprietary MS-DOS from Microsoft, you can even use them on an old IBM PC with an 8088 processor.

However, the big mistake that was made in the FreeDOS project was that the free counterpart to MSCDEX.EXE, which is called SHSUCDX.COM, was written for the 386 processor.
This means you cannot boot the FreeDOS Live CD on 286 processors and 8088 processors. Reading doesn't work either.
As a workaround, you have to create a boot disk and then use MSCDEX.EXE from Microsoft instead of SHSUCDX.COM.

Reply 50 of 54, by Exploit

User metadata
Rank Newbie
Rank
Newbie
Oerg866 wrote on 2021-08-15, 23:55:

Yes, I am intellectually capable of doing it, but I could think of many many different ways I'd rather spend my time 😜

I don't want to miss the time and effort I spent learning x86 assembler. I learned a lot from this that high-level languages like C hide from you.

Reply 51 of 54, by maxtherabbit

User metadata
Rank l33t
Rank
l33t
Exploit wrote on 2023-11-13, 13:52:
Real life example why you should never write DOS drivers for 386 only and later: […]
Show full quote
Oerg866 wrote on 2021-08-19, 00:03:
... […]
Show full quote

...

While in principle this does work, I can compile and link an executable file that I can load both as a .SYS and as a .EXE, I'm shocked to find out that this compiler, released 5 years after the processor's introduction, does not support 386 code generation -- so that's out.

Then I tried MSC8.00's compiler and while it does have 386 code generation, the inline assembler (which I need for a few specific pentium+ things) does not support 386 instructions, which is mentioned in the help files.

Was that ever fixed? Was there a later version that can produce DOS code and maybe can inline 386 instructions?
...

Real life example why you should never write DOS drivers for 386 only and later:

MSCDEX.EXE from MS-DOS was written for 8086 CPUs and many CD-ROM drivers for the drives were also written for 8086 CPUs.
Therefore, with the proprietary original drivers for CD-ROM drives and the proprietary MS-DOS from Microsoft, you can even use them on an old IBM PC with an 8088 processor.

However, the big mistake that was made in the FreeDOS project was that the free counterpart to MSCDEX.EXE, which is called SHSUCDX.COM, was written for the 386 processor.
This means you cannot boot the FreeDOS Live CD on 286 processors and 8088 processors. Reading doesn't work either.
As a workaround, you have to create a boot disk and then use MSCDEX.EXE from Microsoft instead of SHSUCDX.COM.

There is a version of SHSUCDX that doesn't require 386 called SHSUX86 or something like that

Reply 52 of 54, by Exploit

User metadata
Rank Newbie
Rank
Newbie
maxtherabbit wrote on 2023-11-13, 14:54:

There is a version of SHSUCDX that doesn't require 386 called SHSUX86 or something like that

That's interesting.
Could you tell me the exact name?

I couldn't find a 8086 version of SHSUCDX on github:
https://github.com/adoxa/shsucd/blob/master/readme.txt