Copyright (c) Hyperion Entertainment and contributors.

Anatomy of a SATA Device Driver

From AmigaOS Documentation Wiki
Revision as of 00:19, 2 October 2016 by Steven Solie (talk | contribs)
Jump to navigation Jump to search

Introduction

This article describes the p5020sata.device and how it works. It is intended to be used in conjunction with the actual source code. The author will be going through this article at the AmiWest 2016 DevCon on October 8 and 9, 2016.

Initialization

Everything begins with a resident kickstart module. You can see a list of kickstart modules in the SYS:Kickstart/Kicklayout file. All of the modules listed in there will be loaded and linked when the system boots up. They become the system kernel and they are no loaded again until you do a cold reboot.

Creating a resident kickstart module is pretty simple. Here is the resident structure used by the p5020sata.device driver:

// Priority of resident kernel module
//
// Chosen to be lower than mounter.library which is at -45
// which means this device is initialized after mounter.library.
#define KMOD_PRIORITY -46
 
static struct Resident launch_restag USED =
{
	RTC_MATCHWORD,
	&launch_restag,
	(APTR)(&launch_restag + 1),
	RTF_NATIVE | RTF_COLDSTART,
	54,
	NT_UNKNOWN,
	KMOD_PRIORITY,
	DEVICE_NAME " launch",
	"$VER: " DEVICE_NAME " launch",
	launch_dev
};

The most important item to note is the choice of kickstart module priority from 127 to -128 in that order. Each kickstart module will be executed in the order of its priority. So if you have a kickstart module which dependent on another kickstart module you need to ensure the other module is executed first. In this case we need mounter.library to be available.

You can see the complete list of resident modules using Ranger in the Exec/Residents tab.

The entry point is named launch_dev and it looks like this:

APTR launch_dev(struct Library *lib, APTR seglist, struct ExecBase *exec)
{
	struct ExecIFace *IExec = (struct ExecIFace*)exec->MainInterface;
 
	IExec->Obtain();
 
	IExec->DebugPrintF("launch_dev() enter\n");
 
	struct SataDevice *device = create_sata_device(IExec);
 
	IExec->DebugPrintF("launch_dev() exit\n");
 
	IExec->Release();
 
	if (device == NULL)
	{
		return 0;
	}
	else
	{
		return (APTR)1;
	}
}

The create_sata_device() function is described below. The return value from the launch routine is either 0 for failure or 1 for success.

Testing Conundrum

Before we go any further we have to stop and think about testing. Performing a cold reboot each time we want to test our driver is going to become tedious very quickly.

The trick is remove our kickstart module from the Kicklayout file and launch it manually instead. The downside is that you can't use your driver to boot your system or you are back into conundrum territory. Booting using another device driver or USB are two ways to make it work.

This is one way to load the kickstart module manually via a shell command:

int main()
{
	BPTR seg = IDOS->LoadSeg("p5020sata.device.kmod");
	if (seg == ZERO)
	{
		IExec->DebugPrintF("LoadSeg() failed\n");
		return RETURN_FAIL;
	}
 
	struct Resident *res = NULL;
 
	int32 count = IDOS->GetSegListInfoTags(seg,
		GSLI_ResidentStruct, &res,
		TAG_END);
 
	if (count != 1 || res == NULL)
	{
		IExec->DebugPrintF("GetSegListInfoTags() failed\n");
		IDOS->UnLoadSeg(seg);
		return RETURN_FAIL;
	}
 
	APTR obj = IExec->InitResident(res, seg);
 
	if (obj == NULL)
	{
		IExec->DebugPrintF("InitResident() failed\n");
		IDOS->UnLoadSeg(seg);
		return RETURN_FAIL;
	}
 
	struct MsgPort *port = IExec->AllocSysObjectTags(ASOT_PORT, TAG_END);
 
	struct IOStdReq *req = IExec->AllocSysObjectTags(ASOT_IOREQUEST,
		ASOIOR_Size, sizeof(struct IOStdReq),
		ASOIOR_ReplyPort, port,
		TAG_END);
 
	// Test SATA port 0.
	test_sata_port(req, 0);
 
	// Test SATA port 1.
	test_sata_port(req, 1);
 
	IExec->FreeSysObject(ASOT_IOREQUEST, req);
	IExec->FreeSysObject(ASOT_PORT, port);
 
	IExec->DebugPrintF("*** Test Completed ***\n");
	IExec->DebugPrintF("Reboot to test again\n");
	IExec->Wait(0);
 
	return 0;
}

The most important call about is IExec->InitResident() which will end up triggering our launch routine we wrote above. That initializes the device and gets it ready. We then use the usual Exec device interface (MsgPort and IOStdReq) to talk to our device and exercise it.