FAT basic routines

Introduction

There is a set of routines that help to read from a FAT volume. Each function deals only with one aspect of accessing a FAT volume. It has grown over the time and it has undergone many changes. At the moment these are read-only routines, that is there is no support to create new files or to expand existing files.

The functions are written to support FAT-16 and FAT-32 volumes at the same time. At the moment we only support one mounted volume. This will most likely change in order to support multiple volumes.

Note that I tried to impose some register conventions. E.g. the temp register as well as register r0 and r1 are never preserved. The Carry bit should always be cleared on success and set on error. If required the error code is returned in the register named char

Later I have also started to never preserve registers r4, r5, r6, r7 as they are often used as a temporary 32-bit integer. All other registers must always be preserved.

Sometimes we will also use the EQ flag to return a condition in case we just need to know if something happend or not and there is no possibility for an error. I.e. when we follow linked cluster we either have a new cluster (NE) or we have reached then end of the cluster list (EQ)

Open Directory

It is not very surprising that you first need to be able to read the directory of a FAT volume. OpenDir initialises all fields in directory data structure. As input the function requires a pointer to a directory data cluster with the P_Cluster offset set to the first cluster of the directory. In fact directories are just files on a FAT volume and the disk space allocated to a directory is managed exactly the same way as for normal files. The only exception is the root directory of a FAT-12/16 volume. This is a fixed junk of space allocated in the partition and is also the reason why there is only a limited number of files you can maintain in the root directory. FAT-32 is very clever, the only thing that is stored in the volume control block is the start cluster of the root directory, this allows to expand the root directory of a FAT-32 volume as long as there is enough space.

You must call OpenDir before you can sequentially read a directory. Setting the start cluster to zero instructs the function to open the root directory of the volume, this works for FAT-16 and FAT-32 volumes the same way.

;--------------------------------------------------------------------------
;
;	Open Directory
;
;	Input:
;		Y	pointer to datastrcucture with P_Cluster set to the first cluster
;			of the sub-directory or 0 to read the root directory
;
;	Registers:
;		r0, temp
;
;	Completioncode:
;		CS
;		CC
;		

Read Directory

Once a directory has been opened successfully the control block is initialised in a way that we can now call ReadDir to sequentially read each directory entry. Each call of ReadDir will update the control block to point to the next entry. If no more entries are found then the carry will be set. Note that directory entries have one of three possible status. Initially all directory entries are marked as free. When you create a file then the directory entry is active and when you later delete the file the directory entry is marked as deleted. Directory entries are never freed. ReadDir will just skip deleted entries and will stop at either the end of the directory or at the first entry still marked free.

;--------------------------------------------------------------------------
;
;	Read Directory Entry and returns a pointer either to the next active
;	or free directory entry if one exists. If no free directory entry
;	exists the pointer is set to zero. To read a directory first call
;	OpenDir with the P_Cluster set to the first cluster of the directory
;	file (or 0 in case you want to read the root directory). OpenDir and
;	ReadDir automatically handle the special case of FAT16 root directory.
;	
;	Input:
;		Y				Pointer to the data structure for directory
;		P_LongFileN		must be set to a buffer that will hold the
;						long filename
;	Output:
;		P_Entry			copy of the primary directory entry
;
;	Completioncode:
;		CC	Active Entry found
;		CS	Free or no entry found 
;
;	Registers:
;		r0, temp
;

Match File Name

One option after calling ReadDir is to match the file name of the directory entry against a given name.

;--------------------------------------------------------------------------
;
;	Match filename
;		Y			Pointer to datastructure setup to read a directory
;					entry (e.g. after calling ReadDir)
;		NameBuffer	null terminate filename
;
;	Completioncode:
;		CC		entry matches filename
;		CS		entry does not match
;
;	Registers:
;		temp
;
;
;	Alternate entry:
;
;	This entry will copy back the found real filename to the buffer which
;	pointer is stored at P_DirName of the datastructure. The name is not
;	zero terminated, so in case you need a zero terminated string you must
;	clear the buffer in advance. This feature is normally used by the 
;	Name2DirEntry function to update inline the given path with the real
;	filenames. This is because filenames are case insensitive but sometimes
;	we want to give feedback to the user with the as it is stored in the
;	directory with the correct case. One example is the "pwd" print working
;	directory command.
;

Copy Name Partially

When finding a file based on the path we need to step through all elements of the file path. Copy Name will copy a filename or directoryname from the string pointed to by the X register to the global buffer called NameBuffer.

;--------------------------------------------------------------------------
;
;	CopyName
;
;	Copy file/directory name from buffer at X to NameBuffer. The buffer at
;	X is assumed to contain a combined path to a file or a directory with
;	file/directory name separators. CopyName will copy up to the next 
;	separator, null or carriage return, whichever occurs first. This
;	function is supposed to be called successively to scan and process
;	a path.
;
;	Input:
;		X			Pointer to string
;
;	Output:
;		X			Pointer after terminating character
;		NameBuffer	Name Element, null terminated string
;		Char		Terminating Character
;
;	Conditions:
;		CS			Result has length 0
;		CC			Result has length >0
;
;	Registers
;		temp
;

Translate a Name to a Directory Entry

Name2DirEntry combines all of the above routines to one single function. It accepts as input the pointer to a directory control block and a pointer to a filename. It will first copy a partial name and try to find it in the directory described by the directory control block. It also handles all occurencees of .. and . in the input string. When a partial name matches the name of a directory name it will then open this directory and repeat the above steps. It will iterate until the last partial name has matched the directory entries. If a match has been found the Carry bit is cleared and the control block will be filled with the necessary data to identify the corresponding directory entry. Else the Carry bit will be set.

;--------------------------------------------------------------------------
;
;	Name2DirEntry
;
;	This function takes a pointer to a path name. It walks through the 
;	path and checks if the name exists in the current directory.
;	-	If the name is not found it will return an error. 
;	-	If the name found is a normal filename and the name is not the
;		last name in the path it will return an error. Else it will 
;		return success
;	-	If the name found is a directory it will take the next name and 
;		search for it in the new directory.
;	-	If the end of the path has been reached it will return success
;
;	Input:
;		Y		Pointer to data structure for directory IO
;		X		Pointer to zero terminated path
;
;	Output:
;		Directory structure updated 
;		Y+P_StartCluster	start cluster of file/directory
;		Y+P_DirPointer		points to the directory entry
;		Char				the terminating character
;
;	Conditions:
;		CS		file not found
;		CC		file found
;
;	Registers
;		temp
;

Translate Cluster to Sector

In many cases we need to translate a cluster to a sector. For example when we want opened a file, or when we have reached the end of a cluster when reading a file only now the cluster-id for the next block. In case of a recently opened file this is the first cluster and when we have reached the end of a cluster we will first only be able to find the linked cluster-id using LinkedCluster but to retrieve the real data of the file we need to translate this cluster-id to the physical sector number on the SD-Card of the first sector/block within this cluster.

Build Fragment List

For block IO files we typically want to be able to translate a logical block number to a physical block number. To speed this process the function BuildFragList can create a linked list of fragment descriptors. This will later allow to translate a logical block number within a file to the physical block number on the SD-Card without having to consult the FAT table. Hence this translation will always be very fast. It takes less than 100 cycles per fragment. So even in a highly fragmented file with 10 fragments it will take only about 50usec to translate a logical block to a physical SD-Card block number.

Before you call this routine you need to allocate a file control block and copy all necessary information from the found directory entry to the file control block.

The result will be an update file control block with a queue of file control blocks added to it.

;--------------------------------------------------------------------------
;
;	Create a fragment list of a file. The fragmentlist consists of
;	tuples of fragmentsize and fragmentstart.  Each tuple is a 32-bit
;	integer. This is to support direct block IO to disk images without
;	the need to read additional sectors from the device.
;
;	size	the size of this fragments in sectors
;	start	the number of the first sector covered by this fragment
;
;	When we now need to read or write a specific sector of a file we need
;	to find the corresponding entry. For this we compare the sector we 
;	want with the size of the fragment. If it is lower then we found the
;	fragment. Else we subtract the size from the sector which can be looked
;	at as the offset into the next fragment. If the calculated value is
;	less then the size of the next fragment we are done. Else we continue
;	until the number of sector is negative (if a file is 4 sectors long
;	the sectors we can read are numbered 0,1,2,3. When we subtract the
;	legnths of all fragements from the sector to read the result is -1)
;
;	When we found a fragment we just need to add the (remaining) sector
;	offset to the start sector number of this fragment. The results is
;	then the absolute sector on the device.
;
;	Input:
;		Y	Pointer to file control block
;
;	Output:
;		Fragmentlist created
;
;	Registers:
;		r4, r5, r6, r7, temp
;
;	Completioncode:
;		CS		The file has more fragments than we can store in memory
;		CC		Created complete fragment list
;
;
;	2019-01-03	Use linked fragment list. That is we first allocate a
;				fragment descriptor buffer and link it to the previous
;				one (or in case if it is the first we link it to the
;				fragment descriptor queue head of the IO block). 
;				We support as many fragments as we have free memory.
;
;
BuildFragList:

Translate Logical Block Number to Physical Block

This routine is called for block IO files to translate the logical block number, i.e. the block number within the file, to a physical block number on the SD-Card. This routine will scan the fragment queue list and find the corresponding entry. Then it will use the entry to translate the logical block number to a block number within the partition and then will add the partition offset. This will allow to immediately call the SD_CARD_READ or SD_CARD_WRITE function afterwards to retrieve or store the corresponding block on the SD-Card.

;--------------------------------------------------------------------------
;
;	Translate logical block to physical sector using the fragment list
;	of the file entry
;
;	2019-07-26	Input changed from P_Sector to P_Cluster so we can keep the
;				logical block number in P_Cluster. As this is only used for
;				block IO files P_Cluster is not used as we have a fragment
;				list to access the sectors of the file.
;
;	Input:
;		Y			Pointer to data structure for file IO
;		Y+P_Cluster	Logical Block Number
;
;	Output;
;		Y+P_Sector	Physical Sector Number
;
;	Condition:
;		CC			Translation was successfull
;		CS			Translation failed
;
;	Registers:
;		temp
;
;	2019-01-03	Use linked fragment list
;	2019-07-24	Input is now expected in P_Cluster
;	2019-07-27	Save and restore registers
;
Logical2Physical:

Get next linked Cluster

The FAT on a MS-DOS volume is just a collection of linked clusters. Therefore if you reached the end of a cluster within a file you need to find the next cluster. The function LinkedCluster finds the next cluster using the FAT.

;--------------------------------------------------------------------------
;
;	Subroutine for LinkedCluster
;		Converts Cluster to sector offset in FAT adds fat1start and
;		checks if the sector is already in pfatbuffer. Else it reads
;		the sector to pfatbuffer. Then it calculates the offset into
;		the sector and reads the linked cluster and checks if the end
;		of the linked list has been reached.
;	Input:
;		Y		datastructure of file
;
;	Output:
;		P_Cluster updated with linked cluster
;
;	Completioncode:
;		EQ		end of file reached no more clusters
;		NE		new cluster stored int P_Cluster
;
;	Registers:
;		r0, r4, r5, r6, r7, temp
;
LinkedCluster:

Create a Path using the Current Working Directory and a File Name

We always keep track of the current working directory so in case operations that use a filename with a relative path need to be able to combine the current directory with the filename. Also in Case the filename is absolute we need to ignore the current path. This function will handle all the possible cases and translate the two entities “current working directory” and a given filename to a new working directory.

;--------------------------------------------------------------------------
;
;	2019-01-06	Added CreatePath
;
;	Combine the current working path with a new path/file name to form
;	a fully qualified path. It is assumed that both string are valid and
;	existing paths or files respectively. E.g. have been checked using
;	Name2DirEntry, i.e. both, the working path and the new path/file
;	name must exist.
;	The function will scan the new path/file for the delimiter. If the
;	substring corresponds to ".." then it will remove the top element
;	from the working path. If it is a filename it will be added to the
;	working path. 
;	2019-07-18	Paths preceeded with DELIM are now handled as absolute paths
;				Use pointer and not location Path
;
;	Input:
;		X			Pointer to the new path/file zero terminated string
;		Z			Pointer to the current working path
;
;	Output:
;					Working path updated with 
;

ReadFileOpen

You can open a file for read using the ReadFileOpen routine. This routine expects a pointer to the current directory entry in memory and allocates a file-control block and a sector buffer and initialises the fields in the file control block to sequentially read bytes from the file.

;--------------------------------------------------------------------------
;
;	Open File for Read access
;
;	Create a file control block with IO buffer, initialise the file
;	control block and read first sector/block
;
;	
;	Input
;
;	Z		Pointer to directory entry
;
;	Output
;
;	CS		Error
;	char	Error Code
;
;	CC		Success
;	Y		Pointer to file control block
;
;	fcb_sector
;	fcb_cluster
;	fcb_address
;	fcb_secinclst
;	fcb_position
;	fcb_fileszie
;	fcb_byteinsec
;
;	Registers
;
;	temp, r24, r25
;

ReadFileByte

Once you have opened a file using ReadFileOpen you can sequentially retrieve the bytes from the file. ReadFileByte just returns the next byte. In case of error or when it has reached the end of file it will set the carry bit

;--------------------------------------------------------------------------
;
;	Read One Byte from open file
;
;	Get next byte from file. 
;
;	-	Return 0x1a (^Z for end-of-file) if already all bytes read
;	-	Update file pointer
;	-	read next sector in cluster if last byte of sector already read
;	-	link to next cluster if all bytes in cluster already read
;
;	Input
;	
;	Y		file control block
;
;	Output
;
;	CS		Error
;	char	Error Code, most important 0x1a for end-of-file
;
;	CC		Error
;	char	Byte
;
;	Registers
;	
;	temp, r4, r5, r6, r7
;	

ReadFileClose

As you may expect there is also a file close routine. This routine must be called to free all allocated buffers.

;--------------------------------------------------------------------------
;
;	Close File
;
;	Input
;	
;	Y		file control block
;

The three ReadFile routines are mainly used to read text-files from the FAT volume. These routines have been written to read the INI file for the RLV12 emulator but are kept as general as possible so you can read any file using these three routines.