Livt packages

Livt.FileSystems

Version 0.0.2

Bounded, read-only FAT32 file streaming and partition discovery for Livt FPGA applications.

Published files (15)

src/Fat32Geometry.lvt

namespace Livt.FileSystems

/**
 * Validated FAT32 layout, independent of USB and board clocks. Borrows a complete
 * boot sector during Parse. All derived addresses remain inside the selected
 * partition. Only 512-byte sectors and power-of-two clusters through 32 KiB are
 * supported. Honors the active FAT when mirroring is disabled.
 */
component Fat32Geometry
{
	// Published layout; usable only after Parse returns true.
	public fatStart: uint
	public dataStart: uint
	public clusterCount: uint
	public rootCluster: uint
	public clusterShift: int
	public sectorsPerCluster: uint

	// Exclusive upper bound for data-cluster validation.
	clusterLimit: uint

	/**
	 * Parses a retained valid boot sector using serialized scheduled reads.
	 * start is its absolute LBA; length is the partition size in 512-byte blocks.
	 * The caller must first bound that range within the device and uint addresses.
	 * True publishes usable geometry. False may leave partial fields; do not use them.
	 */
	public fn Parse(sector: ISectorBuffer, start: uint, length: uint) bool
	{
		if (sector.Read16(510) != 0xAA55 || sector.Read16(11) != 512
			|| sector.Read16(17) != 0 || sector.Read16(19) != 0
			|| sector.Read16(22) != 0 || sector.Read16(42) != 0) {
			return false
		}

		sectorsPerCluster = sector.ReadByte(13) as uint
		var reserved = sector.Read16(14)
		var copies = sector.ReadByte(16) as uint
		var total = sector.Read32(32)
		var fatSize = sector.Read32(36)
		var flags = sector.Read16(40)
		rootCluster = sector.Read32(44)

		if (sectorsPerCluster == 0 || sectorsPerCluster > 64
			|| (sectorsPerCluster & (sectorsPerCluster - 1)) != 0
			|| reserved == 0 || copies < 1 || copies > 2 || fatSize == 0
			|| total > length || reserved >= total) {
			return false
		}

		var fatLimit = total - reserved

		if (copies == 2) {
			fatLimit = fatLimit >> 1
		}

		if (fatSize > fatLimit) {
			return false
		}

		var fatSectors = fatSize

		if (copies == 2) {
			fatSectors = fatSize << 1
		}

		var overhead = reserved + fatSectors

		if (overhead >= total) {
			return false
		}

		clusterShift = 0
		var span = sectorsPerCluster

		while (span > 1) {
			span = span >> 1
			clusterShift++
		}

		var dataSectors = total - overhead
		clusterCount = dataSectors >> clusterShift

		if (clusterCount < 65525 || clusterCount > 0x0FFFFFEE) {
			return false
		}

		var lastFatEntry = clusterCount + 1
		var lastFatSector = lastFatEntry >> 7
		clusterLimit = clusterCount + 2

		if (lastFatSector >= fatSize || !IsCluster(rootCluster)) {
			return false
		}

		var active: uint = 0

		if ((flags & 128) != 0) {
			active = flags & 15
		}

		if (active >= copies) {
			return false
		}

		fatStart = start + reserved

		if (active == 1) {
			fatStart = fatStart + fatSize
		}

		dataStart = start + overhead
		return true
	}

	/**
	 * Reports whether a value names a data cluster after a successful Parse.
	 */
	public fn IsCluster(cluster: uint) bool
	{
		if (cluster < 2) {
			return false
		}

		return cluster < clusterLimit
	}

	/**
	 * Requires a successful Parse and IsCluster(cluster); returns the first data LBA.
	 */
	public fn ClusterLba(cluster: uint) uint
	{
		var index = cluster - 2
		var offset = index << clusterShift
		return dataStart + offset
	}
}