The Human68K driver development thread

Started by lydux, July 10, 2014, 02:29:47 AM

Previous topic - Next topic

lydux

Some time ago, when I created my ERSA board, I've start writting a DDK that allow me to develop a Human68k driver using my C toolchain. This follow the thread about controlling LHES with a joystick (http://nfggames.com/forum2/index.php?topic=5317.0) where Kamiboy ask how to write a driver. 

So I've decided to share with you some of my study and work.
Here is small zip archive containing some part of my DDK and (unfinished) samples :
http://nfggames.com/x68000/Development/libraries/human68k-ddk-0.1.zip

It does not contains everything I've written ! (My hard drive is a mess of source codes... :p )
Also, it's more linux friendly than Code::Blocks, but I remember having compiled the WINDRV sample on Code::Blocks.
At least, files are correctly commented and you should find the necessary to build a simple driver. So, consider this as a reference actually.
I'll try to create a repository on my github page soon.

Note : the following contains very hard stuff ! I might be confusing sometimes, sorry in advance. Don't hesitate to ask for help if you do not understand something. Thank you.



Terminology :
Byte : 1 byte (8 bits)
Word : 2 bytes (16 bits)
Long : 4 bytes (32 bits)
Char : 1 character = 1 byte
All data types are CPU specific : M68K family. So, byte ordering is always "Big Endian" and a pointer is 32 bits (long).

Compilation flags, libraries and startup code
Normally, any traditional executable requires some initializations step before reaching the "main" subroutine such as the process memory allocator, environment variables, command line processing, etc... This code object (crt0.o) is transparent for you, is provided by the libc (newlib) and automatically linked with the whole final objects by the toolchain linker specs. However, such initialization step is meaningless for driver ! Well... At least for .SYS drivers. You might have noticed that drivers may also be .X files ? In fact they contain both driver AND executable code.
So depending on your wish :
  - if you're going for .X containing both executable and driver, no need to change anything.
  - if you're going for just a .SYS driver, you need to add "-nostartfiles" to the linker flags. This will avoid the link against crt0.o and will reduce the final executable size. However, the linker will complain about a missing "_start" symbol. Look at samples in my DDK archive, you'll find a "crtstub.S" that'll add a dummy _start one.

When Human68k load drivers by CONFIG.SYS, the kernel hasn't totally finished initializing itself. Some libc functions will not be available. For example, COMMAND.X is not loaded yet, so you'll lack "setenv" and "getenv" functions. Also, you can consider a driver as a part of the kernel ; that means the overall RAM consummed by the kernel is not yet known ! The libc memory allocator can't be used, so no malloc available !
Because many of such functions can't be used with driver, I recommand not to use my toolchain libc by adding "-nostdlib" to the linker flags.
The zip archive contains some stripped down version of usefull libc's functions to help you instead. (look at the include folder).
That's all about startup and libc.

About libraries, you'll probably find interest in iocscalls and doscalls, they remain fully valid with drivers. You can safelly add "-liocs -ldos" to library link.

Human68k driver loading and structure
First, please note that Human68k drivers really look like to MS-DOS ones. So chances are MS-DOS drivers documentations are also valid to Human68K !
When kernel try to load a driver, the very first thing it must encounter is an header containing informations on how to load it and which kind of driver it is. The very first driver header must be located right after the XFile header which is 0x40 bytes length. Here is this header structure :

  • next header (long) : A single .SYS or .X file may contains more than one drivers, so this a pointer to the next driver header within this file. Set this member to -1 (0xFFFFFFFF) if this driver entry is the last one in the chain.
  • attributes (word): This 16 bits (1 word) member are flags that give informations about the driver behavior. Valid flags are :
      - 0x8000 : Character device driver (any driver that'll NOT talk to storage device, generally a disk containing a FAT filesystem). Unset this for a block device driver.
      - 0x4000 : IOCTL capable device. IOCTL are device specific commands that the kernel do not understand, and do not care anyway. These commands are available to userspace for a dedicated application, the kernel act as a bridge between the driver and user application. A good example of a IOCTL command is a media ejection, available for us on floppy drives or CDROM, but not on hard drive. Thinks about an application like "eject a:".
      - 0x2000 : Remote device driver. This flag is an unique Human68k feature. When communicating to a driver having this flag setted, the kernel will deploy alternate functions that work at an higher level than block devices : filesystems. This allow the developper to use alternate way to access file on a device that the kernel do not normally understand. Think about eventual FAT32, or drive mapping over RS232/TCP. Emulators WINDRV use this flag.
      - 0x0040 : Special IOCTL. (?)
      - 0x0020 : Raw mode. (?)
      - 0x0008 : Clock device. Use internally by the kernel to specify that the driver talk to an RTC or timer device.
      - 0x0004 : NUL device. Use internally by the kernel. A dummy driver.
      - 0x0002 : STDOUT device. The driver will redefine the stdout, for example : screen, serial output, printers,...
      - 0x0001 : STDIN device. The driver will redefine the stdin : keyboard or serial input.
  • strategy routine (long): A pointer to the very first routine the kernel will execute ! This will initiate the driver loading.
  • interrupt routine (long): A pointer to a routine that will perform the communication between the kernel and the device.
  • driver name (8 chars): An unique identifier, also used by userspace application when executing IOCTL commands.

Strategy and interrupt routines
These 2 routines are the only ones used by the kernel to communicate with the driver.
Upon header validation, the kernel will allocate a few bytes in its own memory that'll be used later as a communication interface. This small memory block is fixed in size, will never move, and will remain forever during the OS lifetime. We call it the "Request Packet". Once allocated, it's absolute location is set into cpu register A5 and will imediatly jump to the "Strategy routine". On Human68k, the only purpose of this routine is to store the value contained in the register A5 somewhere in the driver uninitialized data space (the complex and long word to say a "variable" in C.  :P Or BSS segment in binary terminology.) and return. Then, the strategy routine can totally be forgotten, it will never be reused.
Next is the "Interrupt routine", and is the most important one. All kernel request to driver will be passed by modifying the request packet, and then execute the interrupt routine.
This routine is responsible of analyzing the request packet, and proceed the request.

The Request Packet
The small allocated kernel memory area will change on every kernel request. Only the first 13 bytes is a fixed structure describing the request itself, and the remaining are the request parameters. Note that the some Request Packet structure members are filled by the kernel (input), others are by the drivers as the result of the request (output).
Request packet structure header (fixed first 13 bytes) :

  • Request length (byte, input) : This request packet length, while the full allocated request packet is fixed once and for all, the amount of parameters and their size change depending on the requested command code.
  • Requested unit (byte, input) : The unit number this packet target. An unique loaded driver might handle more than one device. Ex : the FDD driver handle 2 or 4 floppy drives.
  • Request command code (byte, input) : The kernel request command code that the interrupt routine MUST analyze and dispatch/proceed. See "Generic command codes and parameters" and "Extend command codes" bellow.
  • Result status (word, output) : These are combination of return flags notifying the success or failure of the request. On failure, this manifest by the famous centered white box with a message, and the user is invited to type "A"bort, "R"etry or "I"gnore. A driver must always fill this member on every command execution ! See "Command execution result flags" bellow for a list of acceptable flags.
  • Reserved (8 bytes) : Reserved area, don't touch it !

Command execution result flags :
This list contains all acceptable values to fill the result status flags in the request packet header.
S_**** flags will display (or hide) the "A"bort, "R"etry and "I"gnore line, and E_**** will show an appropriate error message.
"Result status" member can be any combination of S_**** flags (ORed together), but only one E_**** can be set.

  • 0x1000 - S_ABORT : "A"bort the whole request.
  • 0x2000 - S_RETRY : Allow to "R"etry the request.
  • 0x4000 - S_IGNORE : The failed request can be safelly "I"gnored.
  • 0x0000 - E_OK : No error
  • 0x0001 - E_UNIT : The unit is unknown
  • 0x0002 - E_NOTRDY : Unit isn't ready
  • 0x0003 - E_CMD : Unknow command code
  • 0x0004 - E_CRC : CRC error
  • 0x0005 - E_LENGTH : Bad length
  • 0x0006 - E_SEEK :  Seek Error
  • 0x0007 - E_MEDIA :  Unknown Media
  • 0x0008 - E_NOTFND : Sector Not Found
  • 0x0009 - E_PAPER : No Paper
  • 0x000A - E_WRITE : Write Fault
  • 0x000B - E_READ : Read Fault
  • 0x000C - E_FAILURE : General Failure
  • 0x000D - E_WRPRT : Write Protect
  • 0x000E - E_DISK : Disk Change


lydux

Generic command codes and parameters
Each possible request command code in the request packet header have their own set of parameters. Again some are filled by the kernel, and others by the driver.
Note that some request command code are available only for character device drivers, some only for block device drivers and some on both. Remote device drivers have their own set of command code.
Command parameters structure always start at position 14 within the request packet.

0x00 - Initialize
Command ID : 0x00 (C_INIT)
Availability : Block/Character
Description :
This is the initialize command and is called only once after driver loading. On initialize, the driver must perform eventual device detection and initialization, specify the number of unit it will handle, process its own arguments passed by CONFIG.SYS, and specify its eventual memory heap size. On return from this command, this finish the whole driver initialization process, and the kernel will continue to the next driver or continue its execution.
Input parameters :




PositionTypeDescription
18LongPointer to a null terminated string containing arguments passed via CONFIG.SYS
22ByteBlock device driver only. Not sure of the meaning, but this probably contains the drive number the kernel want to initialize.
Output parameters :





PositionTypeDescription
13ByteBlock device driver only. The amount of "units" this driver will handle. Ex : amount of partitions in all detected hard drives
14LongPointer to the end of the used memory space. This memory space include every segment sizes used by the driver : TEXT, DATA, BSS, as well as eventual HEAP. A very important parameter ! Any wrong value written here will crash the kernel. Normally, my toolchain provides a symbol usable for this parameter : "_end". "_end" points to the last byte of the relocated BSS segment, so you can directly pass its address if you don't use HEAP, or pass "_end" address + the amount of memory you need for HEAP. See DDK samples for more infos.
18LongBlock device driver only. Pointer to an array of BPB (Bios Parameter Block). Basically, a BPB describes the FAT volume on the physical disk, so determine the size of a partition or media. For more information about BPB, see wikipedia : http://en.wikipedia.org/wiki/BIOS_parameter_block
Note that the X68000 BPB is slightly different than PC BPB. I will describe it later.

0x01 - Media Check
Command ID : 0x01 (C_MEDIACHK)
Availability : Block
Description :Before performing IO operations on a block device, the kernel will process this command code to ensure the target media is still available and ready.
Input parameters :



PositionTypeDescription
13ByteLast known media type ID. See standard media type list bellow.
Output parameters :



PositionTypeDescription
14ByteMedia status. Possible values are :

  • -1 - M_CHANGED : Media was changed
  • 0 - M_DONT_KNOW : Media state unknown
  • 1 - M_NOT_CHANGED : Media still in and ready
Human68K standard media type list :
Note : this list isn't exhaustive ! A driver can provide its own set. So, a program or driver should never rely on these values to determine the media type.
I will not explain them, names pretty much speak for themselves.

  • 0xE0 - MD_2DD_10
  • 0xE5 - MD_1D_9
  • 0xE6 - MD_2D_9
  • 0xE7 - MD_1D_8
  • 0xE8 - MD_2D_8
  • 0xEA - MD_2HT
  • 0xEB - MD_2HS
  • 0xEC - MD_2HDE
  • 0xEE - MD_1DD_9
  • 0xEF - MD_1DD_8
  • 0xF4 - MD_DAT
  • 0xF5 - MD_CDROM
  • 0xF6 - MD_MO
  • 0xF7 - MD_SCSIHD
  • 0xF8 - MD_SASIHD
  • 0xF9 - MD_RAMDISK
  • 0xFA - MD_2HQ
  • 0xFB - MD_2DD_8
  • 0xFC - MD_2DD_9
  • 0xFD - MD_2HC
  • 0xFE - MD_2HD

0x02 - Build BPB
Command ID : 0x02 (C_BLDBPB)
Availability : Block
Description :This command is proceed when a media have previously been reported as "changed" by the "Media Check" command code. It's used to fetch the newer unit BPB.
Input parameters :none
Output parameters :



PositionTypeDescription
18LongPointer to the requested unit BPB

0x03 - IOCTL input
Command ID : 0x03 (C_IOCTLIN)
Availability : Block/Character
Description :Request the driver to read raw arbitrary datas from the specified unit. This request is initiated by an userspace application (IOCTL).
Input parameters :




PositionTypeDescription
14LongPointer to a buffer that'll contain readed data from the unit. The driver is responsible of filling this buffer.
18LongBuffer size
Output parameters :none

0x04 - Input (read)
Command ID : 0x04 (C_INPUT)
Availability : Block/Character
Description :Request the driver to read data from the specified unit. This request is initiated by the kernel.
Input parameters :






PositionTypeDescription
13ByteBlock device driver only. Media type.
14LongPointer to a buffer that'll contain the readed data from the unit.The driver is responsible of filling this buffer.
18Long
Block device driver : number of sector to read. A sector length is normally specified by the BPB.
Character device driver : Buffer size.
22LongBlock device driver only : logical start sector to read from.
Output parameters :none

Note for block device driver : as the kernel is responsible for handling the FAT, you just have to seek to the specified start sector location and read the wanted amount of data from there according to the requested amount of sectors and the BPB. However, the driver is still responsible of the logical to physical partition translation ! So, seek correctly.

0x05 - Read no wait
Command ID : 0x05 (C_NDREAD)
Availability : Character
Description :To check furthermore. A strange command code that allow a driver to send back a device prereaded byte. Maybe dedicated command to some simple interrupt driven device.
Input parameters :none
Output parameters :



PositionTypeDescription
13BytePrereaded byte

0x05 - Drive control/Status packet
Command ID : 0x05 (C_DRVCTL)
Availability : Block
Description : To check furthermore. This seems to be really X68000 specific floppy drive command, or some kind of kernel hack by Human68k developpers. It does some kind of IOCTL, but internally to the kernel to dis/allow and control of FDD ejection, LEDs or media write protection control...

0x06 - Input status
Command ID : 0x06 (C_ISTAT)
Availability : Character
Description :Kernel perform this command before the INPUT one to check if reading on the specified unit is actually possible or not. There is no parameter. Just fill the result flags in the request packet header accordingly.
Input parameters :none
Output parameters :none

0x07 - Input flush
Command ID : 0x07 (C_IFLUSH)
Availability : Character
Description :Request the driver to flush the specified unit input buffer. There is no parameter. Just fill the result flags in the request packet header accordingly.
Input parameters :none
Output parameters :none

0x08 - Output (write)
Command ID : 0x08 (C_OUTPUT)
Availability : Block/Character
Description :Request the driver to write data to a specified unit. This request is initiated by the kernel.
Input parameters :





PositionTypeDescription
14LongPointer to a buffer that contains data to write to the unit.
18Long
Block device driver : number of sector to write. A sector length is normally specified by the BPB.
Character device driver : Buffer size.
22LongBlock device driver only : logical start sector to write to.
Output parameters :none

Note for block device driver : as the kernel is responsible for handling the FAT, you just have to seek to the specified start sector location and read the wanted amount of data from there according to the requested amount of sectors and the BPB. However, the driver is still responsible of the logical to physical partition translation ! So, seek correctly.

0x09 - Output with verify
Command ID : 0x09 (C_OUTVFY)
Availability : Block/Character
Description : Same as "0x08 - Output (write)", but the driver must also read the data buffer back to ensure the data as been correctly written.
Input parameters :same as the "0x08 - Output (write)"

0x0A - Output status
Command ID : 0x0A (C_OSTAT)
Availability : Character
Description :Kernel perform this command before the OUTPUT one to check if writing to the specified unit is actually possible or not. There is no parameter. Just fill the result flags in the request packet header accordingly.
Input parameters :none
Output parameters :none

0x0B - Output flush
Command ID : 0x0B (C_OFLUSH)
Availability : Character
Description :Request the driver to flush the specified unit output buffer. There is no parameter. Just fill the result flags in the request packet header accordingly.
Input parameters :none
Output parameters :none

0x0C - IOCTL out
Command ID : 0x0C (C_IOCTLOUT)
Availability : Block/Character
Description :Request the driver to write raw arbitrary datas to the specified unit. This request is initiated by an userspace application (IOCTL).
Input parameters :




PositionTypeDescription
14LongPointer to a buffer that'll contain data to write to the unit. The userspace program is responsible of filling this buffer.
18LongBuffer size
Output parameters :none

0x13 - Generic IOCTL
Command ID : 0x13 (C_GENIOCTL)
Availability : Block/Character
Description :Perform a IOCTL command understood only by the driver and an userspace program.
Input parameters :




PositionTypeDescription
14LongOptional pointer to a buffer. That buffer is totally specific to the IOCTL command ID.
18WordIOCTL command ID. There is no restriction to this value.
Output parameters :none


Extend command code
These following request command codes are only performed when the driver header have the "attribute" flag set with the value 0x2000 (Remote device driver).
I actually do not know very much about them but they seems to be really filesystem level.
Here is all known command ID :

  • 0x40 - CR_INIT
  • 0x41 - CR_SEARCH_DIR
  • 0x42 - CR_CREATE_DIR
  • 0x43 - CR_DELETE_DIR
  • 0x44 - CR_RENAME_FILE
  • 0x45 - CR_DELETE_FILE
  • 0x46 - CR_CHMOD
  • 0x47 - CR_FILES
  • 0x48 - CR_NFILES
  • 0x49 - CR_CREATE
  • 0x4A - CR_OPEN
  • 0x4B - CR_CLOS
  • 0x4C - CR_READ
  • 0x4D - CR_WRITE
  • 0x4E - CR_SEEK
  • 0x4F - CR_TIMEMOD
  • 0x50 - CR_GETCAP
  • 0x51 - CR_CONTROL
  • 0x52 - CR_BUILD_BPB
  • 0x53 - CR_IOCTL_IN
  • 0x54 - CR_IOCTL_OUT
  • 0x55 - CR_IOCTL_SPECIAL
  • 0x56 - CR_ABORT
  • 0x57 - CR_MEDIA_CHECK
  • 0x58 - CR_LOCK
I do have parameters. I will write them later.


BIOS Parameter Block (BPB)
I will not explain this structure as there is plenty of informations about it on the internet.
Starting from wikipedia : http://en.wikipedia.org/wiki/BIOS_parameter_block
Just read everything you can about FAT12 and FAT16. 95% of these also apply to Human68k.
Most of difference is the byte ordering, which is big endian instead of little endian. The structure is also a bit different. From my ddk.h file :

struct bpb {
  UWORD bpb_nbyte; /* Bytes per sector */
  UBYTE bpb_nsector; /* Sectors per allocation unit */
  UBYTE bpb_nfat; /* Number of FATs */
  UWORD bpb_nreserved; /* Number of reserved sectors */
  UWORD bpb_ndirent; /* Number of root directory entries */
  UWORD bpb_nsize; /* Size in sectors (16 bits) */
  UBYTE bpb_mdesc; /* MEDIA Descriptor Byte */
  UBYTE bpb_nfsect; /* Number of sector per FAT */
  ULONG bpb_huge; /* Size in sectors if bpb_nsize == 0 (32 bits) */
};

See ? Only the "number of FAT" and "number of reserved sectors" members are swapped.

lydux

#2
More to come : a simple C sample...
I guess... push this to the wiki might be a good idea...  :P

eidis

 Hi Lydux !

This looks fantastic ! I will merge it with the Wiki tomorrow. Keep up the good work !

Keep the scene alive !
Eidis
X68000 personal computer is called, "X68K" or "no good good" is called, is the PC that are loved by many people today.

kamiboy

#4
Well, I am scheduled for a trip to Thailand on the morrow. So, if I do not venture into the red light district and wash up as a bloated corpse on a beach somewhere I'll dig into this once I return.

If I successfully manage to write a driver that can override interrupt calls I have another fun project I'd like to try out, in addition to the joystick control venture that is.

eidis

 Hi Lydux and Kamiboy !

Lydux,

Please have a look and let me know if something needs to be improved.

Writing Drivers
http://gamesx.com/wiki/doku.php?id=x68000:writing_drivers

Kamiboy,

Best of luck ! Some say that they have temples which offer spiritual enlightenment. I do not know if it works for writing software as well ;)

Keep the scene alive !
Eidis
X68000 personal computer is called, "X68K" or "no good good" is called, is the PC that are loved by many people today.

lydux

Thanks Eidis ! That's good. I'll modify missing parts later.