The Möbius Operating System: Driver Book
HOME DOWNLOAD DOCUMENTATION SCREENSHOTS  

Example: PS/2 Mouse

Back: File System Drivers | Next: CD File System

DrvInit

As with any driver, execution starts at the DrvInit function:

bool DrvInit(driver_t* drv)
{
	drv->add_device = ps2AddDevice;
	return true;
}

DrvInit gets called when the driver is loaded. ps2mouse is a device driver, not a file system driver, so it sets add_device to point to its ps2AddDevice function, and returns.

ps2AddDevice

ps2mouse is an ISA driver, so it must be loaded explicitly by monitor. When this occurs, the kernel calls ps2AddDevice:

static void ps2AddDevice(driver_t* drv, const wchar_t* name, dev_config_t* cfg)
{

The first things it does are configure the keyboard controller, to enable the PS/2 port, and turn on the mouse. It sends a couple of pre-defined data strings to persuade the mouse to identify itself as wheeled, if possible.

/* enable the aux port */
kbdWrite(KEYB_CTRL, KCTRL_ENABLE_AUX);

TRACE0("[mouse] String 1\n");
for (ch = s1; *ch; ch++)
{
	kbdWrite(KEYB_CTRL, KCTRL_WRITE_AUX);
	kbdWriteRead(KEYB_PORT, *ch, KEYB_ACK);
}

TRACE0("[mouse] String 2\n");
for (ch = s2; *ch; ch++)
{
	kbdWrite(KEYB_CTRL, KCTRL_WRITE_AUX);
	kbdWriteRead(KEYB_PORT, *ch, KEYB_ACK);
}

msleep(10);

/* Identify mouse -- regular PS/2 mice should return zero here.
    Unfortunately, my Intellimouse PS/2 also returns zero unless it has
    been given the string 's2' above. Bochs doesn't support wheeled mice
    and panics when it receives the F6h above. Fix needed. */
TRACE0("[mouse] Identify\n");
kbdWrite(KEYB_CTRL, KCTRL_WRITE_AUX);
if (kbdWriteRead(KEYB_PORT, AUX_IDENTIFY, KEYB_ACK) != 0)
{
	wprintf(L"ps2mouse: no PS/2 mouse found\n");

	/* Disable the aux port */
	kbdWrite(KEYB_CTRL, KCTRL_DISABLE_AUX);
	return;
}

id = kbdRead();

Since it is an ISA driver, ps2mouse assumes that it will only ever be asked to operate with a standard keyboard controller, so the hardware port numbers are hard-coded. Those kbd routines look like this:

void kbdWrite(uint16_t port, uint8_t data)
{
	uint32_t timeout;
	uint8_t stat;

	for (timeout = 500000L; timeout != 0; timeout--)
	{
		stat = in(KEYB_CTRL);

		if ((stat & 0x02) == 0)
			break;
	}

	if (timeout != 0)
		out(port, data);
}

uint8_t kbdRead()
{
	unsigned long Timeout;
	uint8_t Stat, Data;

	for (Timeout = 50000L; Timeout != 0; Timeout--)
	{
		Stat = in(KEYB_CTRL);

		/* loop until 8042 output buffer full */
		if ((Stat & 0x01) != 0)
		{
			Data = in(KEYB_PORT);

			/* loop if parity error or receive timeout */
			if((Stat & 0xC0) == 0)
				return Data;
		}
	}

	return -1;
}

uint8_t kbdWriteRead(uint16_t port, uint8_t data, const char* expect)
{
	int RetVal;

	kbdWrite(port, data);
	for (; *expect; expect++)
	{
		RetVal = kbdRead();
		if ((uint8_t) *expect != RetVal)
		{
			TRACE2("[mouse] error: expected 0x%x, got 0x%x\n",
				*expect, RetVal);
			return RetVal;
		}
	}

	return 0;
}

Maybe the keyboard and ps2mouse drivers could share some code here, but it's not really important right now. There is no risk of the two drivers accessing the same hardware registers simultaneously since the device manager ensures that only one driver initialisation routine is running at any time.

msleep pauses execution for a number of milliseconds and looks like this:

static void msleep(unsigned ms)
{
	unsigned end;
	end = WrapSysUpTime() + ms;
	while (WrapSysUpTime() <= end)
		;
}

Note that it calls WrapSysUpTime, to obtain the number of milliseconds elapsed since the system booted. This is the kernel-mode implementation of the SysUpTime system call.

At this point ps2mouse knows that it has a PS/2 mouse attached. The variable id contains an identifying code. Now ps2mouse begins setting up its own data structures. If it hasn't already been assigned a device class, it gives itself one now. This identifies the device it's about to register as a mouse, and causes it to be given a path similar to /System/Devices/Classes/mouse0:

if (cfg->device_class == 0)
	cfg->device_class = 0x0902;

It sets up its Ps2Mouse object. This object will be passed through the cookie parameter to DevAddDevice, so ps2mouse will be able to refer to this object whenever it is called in the future:

ctx = malloc(sizeof(Ps2Mouse));
memset(ctx, 0, sizeof(Ps2Mouse));

TRACE1("[mouse] Detected device type %x\n", id);
ctx->has_wheel = id == 3;
ctx->bytes = 0;
memset(ctx->data, 0, sizeof(ctx->data));

Now it can finish initialising the mouse...

/* enable aux device (mouse) */
kbdWrite(KEYB_CTRL, KCTRL_WRITE_AUX);
kbdWriteRead(KEYB_PORT, AUX_ENABLE, KEYB_ACK);

...and register itself with the kernel:

TRACE0("PS/2 mouse driver installed\n");
ctx->device = DevAddDevice(drv, &ps2_vtbl, 0, name, cfg, ctx);
DevRegisterIrq(12, ctx->device);

ps2_vtbl is a constant global variable containing pointers to the functions ps2mouse defines:

static const device_vtbl_t ps2_vtbl =
{
	ps2DeleteDevice,
	ps2Request,
	ps2Isr,
};

ps2mouse does not issue any requests to any other devices, so it doesn't need finishio. Nor is it a bus driver, so it doesn't need claim_resources or remove_child.

ps2DeleteDevice

The Möbius kernel doesn't support removing devices yet, so ps2DeleteDevice is a stub. When implemented, it should deregister itself from the PS/2 port interrupt, shut down the mouse, and free any memory it allocated.

ps2Request

This function is called by the kernel when anything accesses the PS/2 mouse device:

static status_t ps2Request(device_t *device, request_t* req)
{

It takes two parameters:

  • device: the device object originally returned by DevAddDevice earlier
  • req: a pointer to a structure containing the request code and parameters

It returns a status_t; that is, an error code, or zero for success.

ps2mouse handles only the DEV_READ request code:

switch (req->code)
{
case DEV_READ:

Once it knows it has one of the DEV_ family of codes, it knows that a params_dev_t union follows req in memory. So it can cast req to request_dev_t and retrieve the parameters.

First it validates the parameters it has received. Mouse devices read their data into a series of fixed-size mouse_packet_t records, so the length of the data it has been asked for must be a multiple of the size of one of these records:

if (req_dev->params.dev_read.length % sizeof(mouse_packet_t))
{
	wprintf(L"%d: wrong size\n", req_dev->params.dev_read.length);
	return req->result = EBUFFER;
}

Now validatation has succeeded. ps2mouse can queue the new request:

io = DevQueueRequest(device, 
	&req_dev->header, 
	sizeof(*req_dev),
	req_dev->params.dev_read.pages,
	req_dev->params.dev_read.length);

Now some error checking...

if (io == NULL)
{
	return req->result = errno;
}

Make sure the length field is zeroed, because ps2mouse is going to be using it later to keep track of how many bytes we've read. Then return SIOPENDING to let the kernel (and, ultimately, whichever app asked for data from the mouse) that this is an asynchronous operation.

else
{
	io->length = 0;
	return SIOPENDING;
}

At the bottom of the function, any unknown codes (i.e. anything other than DEV_READ) are caught and an error code returned:

return req->result = ENOTIMPL;

ps2Isr

This function is called by the kernel whenever the PS/2 mouse interrupt, IRQ 12, is triggered by the hardware:

static bool ps2Isr(device_t *device, uint8_t irq)
{

First, check whether the keyboard controller really did issue the last interrupt:

if ((in(KEYB_CTRL) & 0x01) != 0)
{

If not, the function returns false. In this case, the kernel calls the interrupt routine for any other devices which might be handling IRQ 12. Next ps2Isr grabs its own cookie from the device object...

Ps2Mouse *mouse;
mouse = device->cookie;

...then reads a byte from the keyboard controller (i.e. the mouse):

mouse->data[mouse->bytes++] = in(KEYB_PORT);

If it has enough bytes (two-button mice send three bytes per packet, and three-button mice send four), it calls ps2StartIo which parses the data received from the hardware and gives it out to any requests that might be queued:

if (mouse->bytes >= 3 + mouse->has_wheel)
{
	ps2StartIo(mouse);
	mouse->bytes = 0;
}

return true;

ps2StartIo

Many Möbius device drivers contain a StartIo function. Its purpose is generally to look at any data recently received from the hardware, give it to any queued requests, and complete any requests which have received enough data.

void ps2StartIo(Ps2Mouse* ctx)
{

The first thing ps2StartIo does is extracts the important fields from the data sent by the mouse:

/* Extract the data from the bytes read.
    From svgalib ms.c. */
but = (ctx->data[0] & 0x04) >> 1 |	/* Middle */
      (ctx->data[0] & 0x02) >> 1 |	/* Right */
      (ctx->data[0] & 0x01) << 2;	/* Left */
dx = (ctx->data[0] & 0x10) ? ctx->data[1] - 256 : ctx->data[1];
dy = (ctx->data[0] & 0x20) ? -(ctx->data[2] - 256) : -ctx->data[2];
dw = (int) ((signed char) ctx->data[3]);

It applies a small amount of acceleration:

if (dx > 5 || dx < -5)
	dx *= 4;
if (dy > 5 || dy < -5)
	dy *= 4;

Finally, it loops through each of the requests queued on the device and passes them one copy of the last data:

for (io = ctx->device->io_first; io != NULL; io = next)
{
	pkt = (mouse_packet_t*) ((uint8_t*) MemMapPageArray(io->pages) + io->length);
	
	pkt->dx = dx;
	pkt->dy = dy;
	pkt->buttons = but;
	pkt->dwheel = dw;

	next = io->next;
	MemUnmapPageArray(io->pages);

	io->length += sizeof(*pkt);
	if (io->length >= ((request_dev_t*) io->req)->params.dev_read.length)
		DevFinishIo(ctx->device, io, 0);
}

DevFinishIo notifies the originator of the request (the device file system) that the request has finished, then frees the io structure.

Back: File System Drivers | Next: CD File System

Post a comment

From: