Tutorial: Making a new disk device driver

Discussion about writing software for VS1005 and the VSOS Operating System. Also posts about VS1005-related hardware design and device drivers should be posted here.
Post Reply
User avatar
Panu
VLSI Staff
Posts: 2780
Joined: Tue 2010-06-22 13:43

Tutorial: Making a new disk device driver

Post by Panu » Mon 2012-08-13 11:18

[Edit] This posting is quite old, it's for the VSOS2 so it doesn't even use the dynamically loadable libraries of VSOS3 (DL3's). But the main point here is what operations are needed in a disk device driver (BlockRead, BlockWrite and IOCTL_RESTART) and how to write them. That hasn't changed much over the years so it's still a good read.


Hi, all!

My task for the next couple of days is to make a new disk drive device driver to help me verify that the read and write operations of VSOS FAT filesystem handler are correct.

My plan is to make a new "disk drive" which in fact is implemented as a Windows program, which has a 16 meg array of RAM, and the reads and writes to that "disk" are done via the UART serial port of the VS1005. I can then see the reads and writes on the Windows program, and examine the state of the "disk" after each operation.

I'll document the steps I'm taking to make this device driver here, so that you can hopefully follow them if you ever need to make a new disk device driver for the VSOS.
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

User avatar
Panu
VLSI Staff
Posts: 2780
Joined: Tue 2010-06-22 13:43

Step 1. Create a skeleton solution

Post by Panu » Mon 2012-08-13 11:33

First step is to make a new solution. I'll use the "Main Menu" VSOS template, because it's quite small and it has some basic functionality like buttons for the touch screen, which may be useful in testing.

I've also already made a solution from the VS1005 solution - VSOS kernel 0.2 template, because I'll need to copy-and-paste some files from the kernel.

- Create new solution from the MainMenu template
- Copy devSdSpi.c and devSdSpi.h from the kernel, rename them to devSerialDisk.c, devSerialDisk.h (the h file comes from libvs1005g) and add them to the solution.
- String-replace all "SdSpi" to "SerialDisk" in both the C and H files.
- add new global u_int16 __nextDeviceInstange = 1234 to devSerialDisk.c (actually, this should be a kernel interface global... maybe in VSOS 0.3) (__nextDeviceInstance is a counter to give an incrementing ID for each device, so that the OS is able to check if the device for a file is no longer valid (SD card changed etc))
- Remove all SD card specific stuff from the files
- Remove all references to "ph" to be sure.
- Add a few comments...

The result is a new block device driver devSerialDisk, which compiles but does not do any reads or writes yet. The next task will be to add them.

Here is the skeleton disk driver:

devSerialDisk.h:

Code: Select all

#ifndef DEVSERIALDISK_H
#define DEVSERIALDISK_H

typedef struct devSerialDiskHardwareInfoStruct {
	u_int16 dummy;
} devSerialDiskHwInfo;

ioresult DevSerialDiskCreate (register __i0 DEVICE *dev, const void *name, u_int16 extraInfo);
ioresult DevSerialDiskInput(register __i0 DEVICE *dev, u_int16 port, void *buf, u_int16 bytes);
ioresult DevSerialDiskOutput(register __i0 DEVICE *dev, u_int16 port, void *buf, u_int16 bytes);	
ioresult DevSerialDiskBlockRead(register __i0 DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data);
ioresult DevSerialDiskBlockWrite(register __i0 DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data);
ioresult DevSerialDiskDelete(register __i0 DEVICE *dev);
const char* DevSerialDiskIdentify(register __i0 void *obj, char *buf, u_int16 bufsize);
ioresult DevSerialDiskIoctl(register __i0 DEVICE *dev, s_int16 request, char *argp);

#endif
devSerialDisk.c:

Code: Select all

/// \file devSerialDisk.c VsOS Device driver for serial simulated disk drive (skeleton only)
/// \author Panu-Kristian Poiksalo

#include <stdlib.h>
#include <string.h>
#include "vsos.h"
#include "devSerialDisk.h"
#include "vo_fat.h"	
#include "vs1005g.h"
#include "mmcCommands.h"
#include "devboard.h"
//#include "forbid_stdout.h"
#include "vo_stdio.h"

u_int16 __nextDeviceInstance = 1234;

const DEVICE devSerialDiskDefaults = {	
	0, //u_int16   flags; //< present, block/char
	DevSerialDiskIdentify, //char*    (*Identify)(void *obj, char *buf, u_int16 bufsize);	 
	DevSerialDiskCreate, //ioresult (*Create)(DEVICE *dev, char *name, struct filesystem_descriptor *fs);
	(ioresult(*)(DEVICE*))CommonOkResultFunction, //ioresult (*Delete)(DEVICE *dev);
	DevSerialDiskIoctl, //ioresult (*Ioctl)(DEVICE *dev, s_int16 request, char *argp); //Start, Stop, Flush, Check Media
	// Stream operations
	0,
	0,
	// Block device operations
	DevSerialDiskBlockRead, //ioresult (*BlockRead)(DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data);
	DevSerialDiskBlockWrite, //ioresult (*BlockWrite)(DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data);
	// Stream operations
	//DevSerialDiskInput, //ioresult (*Input)(DEVICE *dev, u_int16 port, void *buf, u_int16 bytes);
	//DevSerialDiskOutput, //ioresult (*Output)(DEVICE *dev, u_int16 port, void *buf, u_int16 bytes);
	//FILESYSTEM *fs;
	//DIRECTORY *root; //Zero if the device cannot hold directories
	//u_int16  deviceInstance;
	//u_int16  deviceInfo[10]; // For filesystem use, size TBD	
};

void PrintBuffer(u_int16 *buf); //debug function in vo_fat.c


char* DevSerialDiskIdentify(register __i0 void *obj, char *buf, u_int16 bufsize){
	return "Serial Disk";
}

	
ioresult DevSerialDiskCreate (register __i0 DEVICE *dev, void *name, u_int16 extraInfo) {
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;	
	memcpy(dev, &devSerialDiskDefaults, sizeof(*dev));	
	dev->deviceInstance = __nextDeviceInstance++;	
	return dev->Ioctl(dev, IOCTL_RESTART, 0);
}



ioresult DevSerialDiskBlockRead(register __i0 DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data){
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;
	if (data == 0) {
		return SysError("Null Pointer");
	}
	if (!__F_CAN_SEEK_AND_READ(dev)) {
		return SysError("Seek");
	}
	
	while (blocks) {
				
		memset(data, 0, 256*blocks);
		// Do something to read 512 bytes (256 words) of data from sector "firstBlock"
		
		blocks--;					
		data += (512/2);
		firstBlock++;
	}
	return S_OK;
}


ioresult DevSerialDiskBlockWrite(register __i0 DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data){
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;
	u_int16 errorCount=0;
	
	if (!__F_CAN_SEEK_AND_WRITE(dev)) {
		return S_ERROR;
	}
	if (data == 0) {
		return SysError("Null Pointer");
	}

	while (blocks) {
		
		// Do something to write 512 bytes (256 words) of data to sector "firstBlock"

		data += 256;
		firstBlock++;
		blocks--;		
	}	
	return S_OK;
}


ioresult DevSerialDiskIoctl(register __i0 DEVICE *dev, s_int16 request, char *argp) {
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;
	u_int16 i, n, cmd;

	switch (request) {
		case IOCTL_START_FRAME:			
			break;
		
		case IOCTL_END_FRAME:
			break;

		case IOCTL_RESTART: 
			dev->flags = __MASK_PRESENT | __MASK_OPEN | __MASK_SEEKABLE | __MASK_READABLE | __MASK_WRITABLE;
			dev->fs = StartFileSystem (dev, "0"); // Find a filesystem, which understands this device			
			break;
					
	default:
		return S_ERROR;
		break;
	}			
	return S_OK;
}
		

Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

User avatar
Panu
VLSI Staff
Posts: 2780
Joined: Tue 2010-06-22 13:43

Step 2. Add UART functionality

Post by Panu » Mon 2012-08-13 12:15

Since my driver is going to use the serial port, next I'll add some functionality to handle the UART in VS1005. I'll make new source files uart1005.h and uart1005.c to hold the functions I'll need to use the UART.

I open the VS1005 datasheet and find the explanation for the UART in chapter 9.14. From there I'll make the functions to handle the UART...

To speed up compiling, I add the following post-build command to project properties:
copy $(PROJNAME).APP F:\
which will copy the app to my SD card automatically after compiling.

Writing the code was really easy, here are the resulting UART functions:

uart1005.h:

Code: Select all

/// \file uart1005.h UART handling functions for VS1005
/// \author Panu-Kristian Poiksalo, VLSI Solution Oy 2012

#ifndef UART1005_H
#define UART1005_H
#include <vsos.h>

// Blocking put char to UART
void UartPutChar(char c);
// Blocking read char from UART
char UartGetChar();
// Non-blocking read char from UART, will return -1 if no char is received
int UartPeekChar();
/// initialize the VS1005 UART (incomplete)
ioresult UartInit();
/// Write bytes to UART
ioresult UartWrite (u_int16 *data, u_int16 bytes);
/// Write a char* string to UART
ioresult UartPutString(char *s);
/// Read bytes from UART
ioresult UartRead (u_int16 *data, u_int16 bytes);

#endif
uart1005.c:

Code: Select all

/// \file uart1005.c UART handling functions for VS1005
/// \author Panu-Kristian Poiksalo, VLSI Solution Oy 2012

#include "uart1005.h"
#include <vsos.h>
#include <vs1005g.h>
#include <minifat.h>

/// initialize the VS1005 UART (incomplete)
ioresult UartInit() {
	/// should put baud rate setting here, now it will just use the default 115200 bps.
	/// Disable UART RX interrupt, this will disable VS3EMU ROM monitor connection
	PERIP(INT_ENABLEL0) &= ~(INTF_UART_RX);
	PERIP(INT_ENABLEH0) &= ~(INTF_UART_RX);
}

// Blocking put char to UART
void UartPutChar(char c){
	while (PERIP(UART_STATUS) & UART_ST_TXFULL) {
		// Wait for the UART to become ready
		// for multitasking, it would be clean to Yield or Sleep here to allow other tasks to run
	}
	PERIP(UART_DATA) = c;	
}

// Blocking read char from UART
char UartGetChar(){
	char c;
	while (!(PERIP(UART_STATUS) & UART_ST_RXFULL)) {
		// Wait for data to arrive
		// A timeout here would be a neat feature, now it will wait forever.
	}
	return PERIP(UART_DATA);
}

// Non-blocking read char from UART, will return -1 if no char is received
int UartPeekChar() {
	if (!(PERIP(UART_STATUS) & UART_ST_RXFULL)) {
		return -1;
	}
	return PERIP(UART_DATA);
}


/// Write bytes to UART
ioresult UartWrite (u_int16 *data, u_int16 bytes) {
	u_int16 i;
	u_int16 b = 0;
	for (i=0; i<bytes; i++) {
		MemCopyPackedBigEndian(&b, 1, data, i, 1);
		UartPutChar(b);
	}		
	return S_OK;
}

/// Write a char* string to UART
ioresult UartPutString(char *s) {
	while (*s) {
		UartPutChar(*s++);
	}
	return S_OK;
}

/// Read bytes from UART
ioresult UartRead (u_int16 *data, u_int16 bytes) {
	u_int16 i;
	u_int16 b;
	for (i=0; i<bytes; i++) {
		b = UartGetChar();
		MemCopyPackedBigEndian(data, i, &b, 1, 1);
	}		
	return S_OK;
}
Next I'll make the Windows program that simulates a disk...
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

User avatar
Panu
VLSI Staff
Posts: 2780
Joined: Tue 2010-06-22 13:43

Step 3. Add the BlockRead and BlockWrite functionality

Post by Panu » Mon 2012-08-13 14:29

Ok, my simple PC software is working after fixing a couple of data format issues, it loads a disk image at startup and handles block read and write operations.
DISKREAD.png
My PC program simulates a hard disk over an UART cable
DISKREAD.png (116.32 KiB) Viewed 3214 times
Next I will add the functionality to the VSOS device driver to communicate with this program.

To make a working VSOS block device driver, it needs working implementations of BlockRead and BlockWrite methods. And when the device is (re)initialized, it should call InitFileSystem to make VSOS try to attach a filesystem (FAT) handler to the device. The place to add this call is the IOCTL_RESTART handler. After these additions, the driver is now capable of opening and reading files from my simulated disk. I'll test writing next.

The source code for the device driver now is:
devSerialDisk.c:

Code: Select all

/// \file devSerialDisk.c VsOS Device driver for serial simulated disk drive (first working version)
/// \author Panu-Kristian Poiksalo

#include <stdlib.h>
#include <string.h>
#include "vsos.h"
#include "devSerialDisk.h"
#include "vo_fat.h"	
#include "vs1005g.h"
#include "mmcCommands.h"
#include "devboard.h"
//#include "forbid_stdout.h"
#include "vo_stdio.h"
#include "uart1005.h"

u_int16 __nextDeviceInstance = 1234;

const DEVICE devSerialDiskDefaults = {	
	0, //u_int16   flags; //< present, block/char
	DevSerialDiskIdentify, //char*    (*Identify)(void *obj, char *buf, u_int16 bufsize);	 
	DevSerialDiskCreate, //ioresult (*Create)(DEVICE *dev, char *name, struct filesystem_descriptor *fs);
	(ioresult(*)(DEVICE*))CommonOkResultFunction, //ioresult (*Delete)(DEVICE *dev);
	DevSerialDiskIoctl, //ioresult (*Ioctl)(DEVICE *dev, s_int16 request, char *argp); //Start, Stop, Flush, Check Media
	// Stream operations
	0,
	0,
	// Block device operations
	DevSerialDiskBlockRead, //ioresult (*BlockRead)(DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data);
	DevSerialDiskBlockWrite, //ioresult (*BlockWrite)(DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data);
	// Stream operations
	//DevSerialDiskInput, //ioresult (*Input)(DEVICE *dev, u_int16 port, void *buf, u_int16 bytes);
	//DevSerialDiskOutput, //ioresult (*Output)(DEVICE *dev, u_int16 port, void *buf, u_int16 bytes);
	//FILESYSTEM *fs;
	//DIRECTORY *root; //Zero if the device cannot hold directories
	//u_int16  deviceInstance;
	//u_int16  deviceInfo[10]; // For filesystem use, size TBD	
};

void PrintBuffer(u_int16 *buf); //debug function in vo_fat.c


char* DevSerialDiskIdentify(register __i0 void *obj, char *buf, u_int16 bufsize){
	return "Serial Disk";
}

	
ioresult DevSerialDiskCreate (register __i0 DEVICE *dev, void *name, u_int16 extraInfo) {
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;	
	memcpy(dev, &devSerialDiskDefaults, sizeof(*dev));	
	dev->deviceInstance = __nextDeviceInstance++;	
	return dev->Ioctl(dev, IOCTL_RESTART, 0);
}



ioresult DevSerialDiskBlockRead(register __i0 DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data){
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;
	if (data == 0) {
		return SysError("Null Pointer");
	}
	if (!__F_CAN_SEEK_AND_READ(dev)) {
		return SysError("Seek");
	}
	while (blocks) {
		memset(data, 0, 256*blocks);
		// Do something to read 512 bytes (256 words) of data from sector "firstBlock"
		
		UartPutString("READ=");     //send the packet start string
		UartWrite(&firstBlock, 4);  //send the block number
		UartPutString("=read");		//send the packet end string
		UartRead(data, 512);        //read the data from the PC program

		blocks--;					
		data += (512/2);
		firstBlock++;
	}
	return S_OK;
}


ioresult DevSerialDiskBlockWrite(register __i0 DEVICE *dev, u_int32 firstBlock, u_int16 blocks, u_int16 *data){
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;
	u_int16 errorCount=0;
	
	if (!__F_CAN_SEEK_AND_WRITE(dev)) {
		return S_ERROR;
	}
	if (data == 0) {
		return SysError("Null Pointer");
	}

	while (blocks) {		
		// Do something to write 512 bytes (256 words) of data to sector "firstBlock"
		
		UartPutString("WRITE=");
		UartWrite(&firstBlock, 4);
		UartWrite(data, 512);
		UartPutString("=write");

		data += 256;
		firstBlock++;
		blocks--;		
	}	
	return S_OK;
}


ioresult DevSerialDiskIoctl(register __i0 DEVICE *dev, s_int16 request, char *argp) {
	devSerialDiskHwInfo* hw=(devSerialDiskHwInfo*)dev->hardwareInfo;
	u_int16 i, n, cmd;

	switch (request) {
		case IOCTL_START_FRAME:			
			break;
		
		case IOCTL_END_FRAME:
			break;

		case IOCTL_RESTART: 
			dev->flags = __MASK_PRESENT | __MASK_OPEN | __MASK_SEEKABLE | __MASK_READABLE | __MASK_WRITABLE;
			dev->fs = StartFileSystem (dev, "0"); // Find a filesystem, which understands this device			
			break;
					
	default:
		return S_ERROR;
		break;
	}			
	return S_OK;
}
		
Info: Line In and Line Out, VS1000 User interface, Overlay howto, Latest VSIDE, MCU Howto, Youtube
Panu-Kristian Poiksalo, VLSI Solution Oy

Post Reply