X68000 Programming chapter 6, TRS programs.

Introduction:

Human68k is an MS-DOS like operating system and as such it isn’t multi-tasking, or it is?

Humand68K provides us with the capability to do something that resembles multitasking.

We can build a TSR program which stands for Terminate and Stay Resident.

A TSR program is one that starts, does some setups and terminates but instead of being tear down from memory it stays.

The setups are normally to install an interruption that runs a sub routine or function.

We can do things like run the function when the interruption of pressing certain keyboard keys triggers or we could be watching some printer event.

Human68k has a place in memory with a vector that points to the different system routines when certain interrupts happen.

A program doesn’t need to be made resident or TSR to exploit these interruptions, in fact most of the video games utilize them to track things like the vertical or horizontal scan-line and so do graphic effects or just time the frames.

But a TSR program is only useful by using these interruptions.

Once a TSR program is in memory and runs when triggered by interruptions we can still use the operating system as if it was multitasked.

In this article I’m going show the instructions, the data structures, how to make a program resident and a mechanism to communicate with it through a command line interface.

The instructions:

The key instructions that make it possible are the following:

struct _psp* _dos_getpdb()			//get the Program Segment Prefix

void* _dos_intvcs(int intno, void* jobadr)	//Set vector handling address	

void _dos_keeppr (int prglen, int code)		//Terminate and reside

Along the way we will see a bunch of other instructions but these three are the important ones.

We will be following the source code at:

https://github.com/FedericoTech/X68KTutorials/blob/main/blogresi/main.c

The program in memory:

We first need to understand how a program are stored in memory.
Here is a chart that shows how a program is stored in memory and the functions to manipulate the different aspects.

When we run a program or stays resident, the operating system keeps track of it with two data structures. One structure is struct _psp that stands for Program Segment Prefix (PSP) and the other is struct _mep which might stand for Memory Entry Pointer (MEP).

Program Segment Prefix (PSP) data structure.

The Program Segment Prefix is a data structure where MS-DOS like operating systems keep track of the different parameters of the program.

Parameters such pointers to the command line string, the environment variables, the filename and path, OS flags, the heap, the User Stack Pointer and Supervisor Stack Pointer, the exit, error and Ctrl+C routines, etc.

The declaration of the Program Segment Prefix structure is like this:

There are a few ways to get the pointer to this data structure:

struct _psp {
    char *env;					// Pointer to environment variables.
    void *exit;					// Pointer to the Exit routine.
    void *ctrlc;				// Pointer to the CTRL+C routine
    void *errexit;				// Pointer to the Error routine.
    char *comline;				// Pointer to the command line data structure
    unsigned char handle[12];		
    void *bss;					// Pointer to the Block Started by Symbol
    void *heap;					// Pointer to the heap
    void *stack;				// Pointer to the stack
    void *usp;					// User Stack Pointer
    void *ssp;					// Supervisor Stack Pointer
    unsigned short sr;			// Status Register
    unsigned short abort_sr;	// backup of Status Register when interrupted (perhaps)
    void *abort_ssp;			// Supervisor Stack Pointer backup when interrupted (perhaps)
    void *trap10;			
    void *trap11;
    void *trap12;
    void *trap13;
    void *trap14;
    unsigned int_ osflg;		// OS flags
    unsigned char reserve_1[28];	// (1.b module number, 3.b unused, 1.l Memory management pointer for loaded child process, 5.l unused)
    char exe_path[68];			// path to the exec
    char exe_name[24];			// name of the exec
    char reserve_2[36];
};

The struct _psp* _dos_getpdb(); function.

Some compilers provide pointers to it:

The address extern int _PSP; in integer format.

The pointer extern struct _psp *_procp; in pointer format.

Memory Entry Pointer (MEP) data structure.

The Memory Entry Pointer is a double linked list that relates the program with other programs in memory, it sits right before the PSP, and looks like this:

struct _mep {
    void *prev_mp;
    void *parent_mp;
    void *block_end;
    void *next_mp;
};

struct _mep has 4 pointers and it’s 16 bytes long. 4 bytes per pointer.

The first field void *prev_mp points to the struct _mep of the previous program.
The second field void *parent_mp points to the struct _mep of the program that started this program, normally Command.com.
The third field void *block_end points to the last byte of the program in memory.
The forth field void *next_mp points to the struct _mep of the next program.

There are a few ways to get the MEP data structure:

The first and foremost is by getting the PSP address with the function struct _psp* _dos_getpdb(); and subtract the size of MEP which is 16 bytes.

struct _mep* mep = (struct _mep*)((int) _dos_getpdb() - sizeof(struct _mep));

or combinations with the PSP pointers should the compiler makes then available:

struct _mep* mep = (struct _mep*) (_PSP  - sizeof(struct _mep));

struct _mep* mep = (struct _mep*) ((int) _psp  - sizeof(struct _mep));

Some compilers give straight forward access trough a pointer.

extern struct _mep *_memcp;	// direct access to it

Some compilers also give straight away access to void *block_end with a pointer or an int.

extern void *_last; // pointer format

extern int _HEND; //integer format

The program.

There is not much to say, the program seats right after PSP all the way to void *block_end.

Finding the resident program.

Our program will have two parts, the resident part and the non resident part.

The resident part is just a function that executes a task when triggered by an interruption.

The non-resident part gives us an interface to setup and tear down the resident part.

When the non-resident part starts will explore the program list to find out whether the resident part is already setup in order to safely set it up or tear it down.

When exploring the program list we are going to look for a sequence of bytes, a word, that the resident program will have right after the MEP and PSP structures and at the beginning of the program so that we can find it.

In this example I’ll use the struct _psp* _dos_getpdb(); rather than the pointers because they are not available in all the toolchains.

#ifdef __MARIKO_CC__
	#include 
	#include 
#else
	#include 
	#include 

	//substitutions for Lyndux toolchain
	#define interrupt __attribute__ ((interrupt_handler))
	#define _mep dos_mep
	#define _psp dos_psp

	// _dos_getpdb() is broken
	extern int _PSP;
	#define _dos_getpdb() (void*)_PSP
#endif

int main(int argc, char *argv[])
{
	struct _psp* psp = _dos_getpdb();
	struct _mep* mep = (struct _mep*)((int) psp - sizeof(struct _mep));
	void* start_program = (void *)((int) psp + sizeof(struct _psp));
	void* end_address = mep->block_end;
}

Because it’s running on the non-resident part, this is for sure the last program in the list so we need to explore the programs behind it. The address to the previous program is in the field prev_mp This is also the address of the its MEP struct.

As it belongs to the current program we can manipulate it without any supervisor privilege so we can store it in an int var straight away:

resident_start_addr =  (unsigned int) mep-> prev_mp;

Alternatively, as the address is in the very first field we can just do this:

resident_start_addr =  (unsigned int) mep;

Now we need to loop all until the resident_start_addr is 0, but because we will be reading areas of memory outside our program we need to either activate supervisor privileges with int _dos_super(long stack); or using functions that allows us to get the content of those addresses.

Using int _dos_super(long stack); is easier but in this example I decided to use the other functions so that we get to know them.

The loop will look like this:

while(resident_start_addr != 0){
	// we read a long word with _iocs_b_lpeek so that we don't need to go Super
	resident_start_addr = _iocs_b_lpeek((void *) resident_start_addr);
	// check something and exit the loop
}

So we use the function int _iocs_b_lpeek (const void *addr); to get the int size content of the address given.

Now we have the ability to explore the program list but we need to find something that identifies the resident program.

As afore mentioned, in this example, the strategy is to place something in the program memory that we can find relative to the program that identifies it if present.

This is a very low level solution that I found in an assembly program and I managed to replicate in C.

As we saw so far we can get the PSP data structure. In front of it is the MEP data structure and behind it is the program.

We can hardcode some information right at the beginning of the program so that we can find it next time.

This information will be a C string variable whose space is set in assembly code.

For this to work, if the program we are building has more object files, the file where the main function is has to be first in the compilation and linking commands, otherwise we will be able to set up the TSR but not find it in memory.

Another consideration is that these assembly has to be before any initialization or implementation.
We can do forward declarations or declare variable but if we initialize them we also lose the place.

The code is the following:

extern char keyword[4]; // declaration only

asm("	_keyword:	.ds.b	4	    "); //this is the actual storage.
int found = 0;	//flag in case we find the resident program.
int main(int argc, char *argv[])
{
	unsigned int resident_start_addr;
	struct _mep* mep = (struct _mep*)((int) _dos_getpdb() - sizeof(struct _mep));
	resident_start_addr =  (unsigned int) mep-> prev_mp;

As you may notice, the assembly variable name is _keyword: whereas the variable in our C doesn’t have the preceding underscore. This may differ in other toolchains and need to add the underscore.

If we have followed the steps correctly we will have the variable keyword stored right at the beginning of the program so we can find it always.

The keyword variable is for us to set this identification word before we make the program resident.

The identification word should be a word that can’t be mistaken with an assembly opcode so it’s guaranteed we won’t find the same combination of bytes at the beginning of other programs and to prevent the CPU from trying to execute it.

In this example “MMV” is a safe word as there is no such combination of bytes in the M68000 instructions set.

Back to loop we now search for this word:

extern char keyword[4]; // declaration only

asm("	_keyword:	.ds.b	4	    "); //this is the actual storage.
int found = 0;	//flag in case we find the resident program.
int main(int argc, char *argv[])
{
	unsigned int resident_start_addr;
	struct _mep* mep = (struct _mep*)((int) _dos_getpdb() - sizeof(struct _mep));
	resident_start_addr =  (unsigned int) mep-> prev_mp;

	while(resident_start_addr != 0){
		char keyword[4] = {0}; // we will copy the first 4 bytes found in other programs in this var.

		// we read a long word with _iocs_b_lpeek so that we don't need to go Super
		resident_start_addr = _iocs_b_lpeek((void *) resident_start_addr);

		//we capture the address of the beginning of the program
		void * beginning_of_program = (void *)resident_start_addr + sizeof(struct _mep) + sizeof(struct _psp);

		//we copy three byte in our buffer. we use _iocs_b_memstr so that we don't need to go Super
		_iocs_b_memstr(
			beginning_of_program,
			keyword,
			3
		);

		//if we find the process
		if(strncmp(“MMV”, keyword, 3) == 0){
			found = 1;
			break;
		}
	}

The algorithm above calculates the program starting address by adding the sizes of MEP and PSP and copies 3 bytes in the buffer keyword. Then we compare it to the word MMV and if found, we flag it and exit the loop.

It seems that in order to access certain data we need to be calculating offsets back and forth all the time, by getting the PSP address, then subtracting the size of MEP to get its address or to add the size of PSP in order to get the start address of the program in order to reach the keyword string. However there is a more direct way.

We can use an struct to map all.

struct resident {
	struct {
		struct _mep mep; //16   bytes
		struct _psp psp; //240 bytes
	} procc;    //256 bytes
	char keyword[4];    //here the program starts
}

The first time we will still need to get the pointer to PSP and subtract the size of MEP (16 bytes). But if we cast MEP’s pointer into (struct resident *), now we can access all the fields straight away, and inside the loop where we explore the program list, we can cast the address with this type too.

Let’s do the changes:

struct resident {
	struct {
		struct _mep mep; //16   bytes
		struct _psp psp; //240 bytes
	} procc;    //256 bytes
	char keyword[4];    //here the program starts
};

extern char keyword[4];

asm("	_keyword:	.ds.b	4	    "); //this is the actual storage.
int found = 0;	//flag in case we find the resident program.
int main(int argc, char *argv[])
{
	unsigned int resident_start_addr;
	struct resident * _resident, * resident_aux;

	struct _psp* psp = _dos_getpdb();

	_resident = (struct resident *)((unsigned int) psp - sizeof(struct _mep));

	resident_start_addr = (unsigned int) _resident->procc.mep.prev_mp;
	while(resident_start_addr != 0){
		char keyword[4] = {0}; // we will copy the first 4 bytes found in other programs in this var.

		void* beginning_of_program;

		// we read a long word with _iocs_b_lpeek so that we don't need to go Super
 		resident_start_addr = _iocs_b_lpeek((void *) resident_start_addr);

		//we capture the address of the beginning of the program
		resident_aux = (struct resident *) resident_start_addr;

		//we copy three byte in our buffer. we use _iocs_b_memstr so that we don't need to go Super
		_iocs_b_memstr(
			resident_aux->keyword,
			keyword,
			3
		);

		//if we find the process
		if(strncmp("MMV", keyword, 3) == 0){
			found = 1;
			break;
		}
	}

	if(found) {
		// what is next?
	}
};

The task

The purpose of making a program TSR is to do a task in the back ground.

We need to implement a function. This function isn’t any function but has to be an interrupt.

If the compiler provides the library we can do #include <interrupt.h> and the declare the function like this:

#include <interrupt.h>

void interrupt process_start();

If the compiler doesn’t have the interrupt library we still can declare it this way:

void __attribute__ ((interrupt_handler)) process_start();

This function is going to be trigger by some interruption of the system.

It might be that the interruption triggers very little, only when we hit certain key, or very often as the vertical scan line interruption 60 times per second or even faster every millisecond.

If the function takes too long to execute it might happen that interrupt itself interrupts it and starts a new execution instance and the whole thing enters a deadlock and eventually the system crashes. This is a racing condition.

In order to prevent this we can have a flag that acts as a mutex for mutual exclusion so that if the new interruption call finds the mutex set to 0 it means that the previous execution is still on so that it finishes and the control comes returns to the point where it got interrupted.

The mutex flag has to be volatile so that we tell the compiler that to change it has side effects and it doesn’t optimize it out.

The task in this example is a counter that shows in the top most left corner in yellow color every second.

 int counter;
int volatile mutex = 1;

//the resident program is this interrupt
void interrupt process_start() //it has to be an interrupt
{
	//we check whether the interrupt is still being processed
	if(mutex){
		mutex = 0; //we hold the mutex

		//only do things every 100 cycles
        	if(++counter % 100 == 0){
			//we get the current position of the cursor
			int cursor_pos = _iocs_b_locate(-1, -1);

			//we get the current colour.
			char previous_color = _iocs_b_color(-1);


			//we move the cursor to the top left corner
			_iocs_b_locate(0, 0);

			//we change the colour
			_iocs_b_color(2 + 4 + 8);

			printf("Count %d\n", counter);


			//we re establish cursor's position
			_iocs_b_locate((cursor_pos >> 16) & 0xffff, cursor_pos & 0xffff);

			_iocs_b_color(previous_color);
        	}

		mutex = 1;
	}
}

Making the program TSR.

So, now we have a way to find the resident instance of our program and the task but we haven’t made it resident yet.

We need to set up an interruption with the instruction void* _dos_intvcs(int intno, void* jobadr) and exit the program with void _dos_keeppr (int prglen, int code).

The instruction void* _dos_intvcs(int intno, void* jobadr) takes the number of interruption from the vector of interrupts and the address to our task. As a return it gives us the current address of the interruption.

In this URL you can find at the end the table of interruptions and pick one to use: https://www.chibiakumas.com/68000/x68000.php

In my example I’m using the interrupt 0x45, UserInterrupt: MFP Timer-C (Mouse/cursor/FDD control, etc.) which triggers 100 times per second.

When setting up the interruption we need to keep the address it returns so that we can restore it when the resident part, the one in the background ends.

In my example I’m storing the returned address in the pointer oldvector which I’m also using as a flag in the place of found.

It is not necessary to do it this way, you can use a normal variable but in my example I’m using another variable declared with assembly after the keyword variable. Therefore I’ll include it in struct resident.

The instruction void _dos_keeppr (int prglen, int code) takes the size of the program as first parameter and the exit code as second.

The length of the program is from the end of PSP to the address pointed by the field void *block_end in MEP.

There are various ways to work out the size:

struct _psp* psp = _dos_getpdb();

struct _mep* mep = (struct _mep*)((unsigned int) psp - sizeof(struct _mep));

unsigned int start_program_adders =  (unsigned int) psp + sizeof(struct _psp);

_dos_keeppr((unsigned int) mep-> block_end -  start_program_adders, 0);

If the compiler allows it we can just do:

unsigned int start_program_adders = _PSP + sizeof(struct _psp);

_dos_keeppr(_HEND – start_program_adders, 0);

To make the program resident then we do this sequence of instructions:

unsigned int resident_addr, end_addr;
//the program starts right where the keyword is.
resident_addr = (unsigned int) &_resident->keyword;
//where is where the program ends
end_addr = (unsigned int) _resident->procc.mep.block_end;      

//we set the keyword
_iocs_b_memstr(
	KEYWORDS,
	&keyword,
	3
);

//we get the current vector address
oldvector = _dos_intvcs(TIMER_C, process_start);

_dos_c_print("resident program started\r\n");

//we make the program resident
_dos_keeppr(
	end_addr - resident_addr,   //memory from beginning of the program
	0                           //exit code.
);

If the program is already resident, though, we are going to restore the interrupt, stop it and tear it down.

The code to do that is this:

// we remove it

_dos_intvcs(TIMER_C, oldvector); //we reestablish 0x45

_dos_mfree(&resident_aux->procc.psp); //psp_addr

_dos_c_print("resident program stopped\r\n");

Our final version looks like this:

#ifdef __MARIKO_CC__
	#include <doslib.h>
	#include <iocslib.h>
#else
	#include <dos.h>
	#include <iocs.h>
	#include <stdio.h>

	//substitutions for Lyndux toolchain

	#define interrupt __attribute__ ((interrupt_handler))
	#define _mep dos_mep
	#define _psp dos_psp
	#define oldvector _oldvector

	// _dos_getpdb() is broken
	extern int _PSP;
	#define _dos_getpdb() (void*)_PSP
#endif

#define 	TIMER_C 	0x45
#define 	KEYWORDS	"MMV"


struct resident {
	struct {
		struct _mep mep; //16   bytes
		struct _psp psp; //240 bytes
	} procc;    //256 bytes
	char keyword[4];    //here the program starts
	void * oldvector;
};

extern char keyword[4];
extern void * oldvector;

void interrupt process_start();	//forward declaration valid before the assembly.

//these have to be the very first thing
asm( "	_keyword:	.ds.b	4		" ); // zero initialized 4 byte storage for keyword
asm( "	_oldvector:	.dc.l	0		" ); // oldvector address also initialized by 0

int main(int argc, char *argv[])
{
	struct resident * _resident, * resident_aux;

	int resident_start_addr;
	struct _psp* psp = _dos_getpdb();

	_resident = (struct resident *)((unsigned int) psp - sizeof(struct _mep));

	resident_start_addr = (unsigned int) _resident->procc.mep.prev_mp;

	while(resident_start_addr != 0){
		char keyword[4] = {0}; // we will copy the first 4 bytes found in other programs in this var.
		void* beginning_of_program;
		// we read a long word with _iocs_b_lpeek so that we don't need to go Super
		resident_start_addr = _iocs_b_lpeek((void *) resident_start_addr);

		//we capture the address of the beginning of the program
		resident_aux = (struct resident *) resident_start_addr;

		//we copy three byte in our buffer. we use _iocs_b_memstr so that we don't need to go Super
		_iocs_b_memstr(
			resident_aux->keyword,
			keyword,
			3
		);

		//if we find the process
		if(strncmp(KEYWORDS, keyword, 3) == 0){
			//we copy the current value of the offset 4 from the beginning of the program which is the  oldvector declared in assembly.
			oldvector =  (void *) _iocs_b_lpeek(&resident_aux->oldvector);
			break;
		}
	}

	// if found...
	if(oldvector) {
		// we remove it
		_dos_intvcs(TIMER_C, oldvector); //we reestablish 0x45
		_dos_mfree(&resident_aux->procc.psp); //psp_addr
		_dos_c_print("resident program stopped\r\n");
	//if not found
	} else {
		unsigned int resident_addr, end_addr;
		//the program starts right where the keyword is.
		resident_addr = (unsigned int) &_resident->keyword;			
		//where is where the program ends
		end_addr = (unsigned int) _resident->procc.mep.block_end;	  

		//we set the keyword
		_iocs_b_memstr(
			KEYWORDS,
			&keyword,
			3
		);

		//we get the current vector address
		oldvector = _dos_intvcs(TIMER_C, process_start);

		_dos_c_print("resident program started\r\n");

		//we make the program resident
		_dos_keeppr(
			end_addr - resident_addr,   //memory from beginning of the program
			0						   //exit code.
		);
	}
}

int counter;
int volatile mutex = 1;

//the resident program is this interrupt
void interrupt process_start() //it has to be an interrupt
{
	//we check whether the interrupt is still being processed
	if(mutex){
		mutex = 0; //we hold the mutex

		//only do things every 200 cycles
		if(++counter % 100 == 0){
			//we get the current position of the cursor
			int cursor_pos = _iocs_b_locate(-1, -1);

			//we get the current colour.
			char previous_color = _iocs_b_color(-1);


			//we move the cursor to the top left corner
			_iocs_b_locate(0, 0);

			//we change the colour
			_iocs_b_color(2 + 4 + 8);

			printf("Count %d\n", counter);

			//we re establish cursor's position
			_iocs_b_locate((cursor_pos >> 16) & 0xffff, cursor_pos & 0xffff);

			//we re establish text colour
			_iocs_b_color(previous_color);
		}

		mutex = 1;
	}
}

So, now if we call the executable for the first time it will set up a counter on the screen that will be counting as we are still able to do something else, and when called a second time it’s going to find the resident instance and stop it.

I hope you enjoy this tutorial.

Leave a comment