///////////////////////////////////////////////////////////////////////////////
//                      FAT32 / FAT16 driver (solo lectura)
//                             J. Arias (2015/07)
///////////////////////////////////////////////////////////////////////////////
//
// nota: para optimizar los accesos a memoria se utilizan punteros (u32 *) o (u16 *)
// para leer datos de los sectores. ¡Pero en ARM estos punteros deben estar
// correctamente alineados ((u32 *) a múltiplo de 4, (u16 *) a dirección par)! Esto no
// siempre ocurre, y en esos casos los accesos se dividen en partes más pequeñas. Esto
// también hace que el código sea únicamente válido para CPUs Little-Endian.

// tipos de enteros de tamaño de bit explícito:
// u8, u16, u32: sin signo. s8, s16, s32: con signo
//#include "mtypes.h"

// prototipo de la función de lectura de sector
//int rdsector( u32 LBA, u8 *buf);

////////////////////////////// Variables Globales /////////////////////////////
// Para ARM-thumb es muy conveniente que las variables globales estén agrupadas
// en estructuras pues GCC es un rato tonto optimizando las tablas de constantes.
// En cualquier caso, esto no hace daño y además parece profesional :)
struct {
	u32 storage_begin;	// primer cluster del área de datos
	u32 fat1_begin;		// primer sector de la FAT1
	u32 root_cluster;	// primer cluster del dierectorio raiz (FAT32)
	u32 root_sector;	// primer sector del directorio root (FAT16)
	u32 last_cluster;	// último cluster buscado en la FAT

	u32 filelen;		// Tamaño de fichero en bytes
	u32 filecluster;	// Cluster actual del fichero

	u8 l2spc;			// log2(sectores por cluster)
	u8 fat_type;		// FAT32 o FAT16
	u8 root_nsector;	// nº de sectores en dir raiz (FAT16)

	u8 filesector;		// sector dentro del cluster
} FATvar;

u8 __attribute__ ((aligned(4))) FATbuf[512];			// buffer

///////////////////////////////// constantes //////////////////////////////////
enum {
	OK,
	BAD_MBR,
	BAD_FAT,
	BAD_SPC,
	HARD_FAULT,
};

enum {
	FAT16,
	FAT32,
};

///////////////////////////////////////////////////////////////////////////////
//
// Extrae información de la tabla de particiones y el Volume_ID (superbloque)
// retorna 0 si todo bien
//

int initFAT()
{
	u32 *pi,poff,i,j;
	u16 *ps;
	pi=(u32 *)FATbuf;
	ps=(u16 *)FATbuf;

	poff=0;
	if (rdsector(0,FATbuf)) return HARD_FAULT;		// Tabla de particiones?
	if (ps[510/2]!=0xAA55) return BAD_MBR;
	if ((ps[0x52/2]==0x4146 && ps[0x54/2]==0x3354) ||
	    (ps[0x36/2]==0x4146 && ps[0x38/2]==0x3154)) {	// Si FAT, no hay tabla de particiones
#ifdef SDDEBUG
		_printf("Without partition table\n");
#endif
	} else {
#ifdef SDDEBUG
		_printf("Partition 1\n");
#endif
		poff=ps[454/2]+(ps[456/2]<<16);					// offset hasta partición 1 (no alineado)
		if (rdsector(poff,FATbuf)) return HARD_FAULT;	// superbloque
	}
	// Tipo de FAT
	if (ps[0x52/2]==0x4146 && ps[0x54/2]==0x3354) FATvar.fat_type=FAT32;
    else {
		if (ps[0x36/2]==0x4146 && ps[0x38/2]==0x3154 && FATbuf[0x3A]=='6') FATvar.fat_type=FAT16;
		else return BAD_FAT;
	}
	// Sectores por cluster (debe ser 2^n)
	for(j=0,i=1;j<8;j++,i<<=1) if (FATbuf[0x0d]==i) break;
	if (j==8) return BAD_SPC;
	FATvar.l2spc=j;
	// offsets a las distintas áreas del sistema de ficheros
	FATvar.fat1_begin=poff+ps[0x0e/2];	// FATbuf[14-15]: nº de sectores reservados
	if (FATvar.fat_type==FAT16) {
		i=ps[22/2];				// FATbuf[22-23]: sectores/fat
		FATvar.root_sector=FATvar.fat1_begin+i*FATbuf[16];	// FATbuf[16]: nº de FATs
		i=FATbuf[17]+(FATbuf[18]<<8);	// nº entradas en directorio raíz
		FATvar.root_nsector=i>>4;
		FATvar.storage_begin=FATvar.root_sector+FATvar.root_nsector;
		FATvar.root_cluster=0; // caso especial
	} else {	// FAT32
		// FATbuf[0x24-0x27]: sectores por FAT
		// FATbuf[0x10]: nº de FATs
		FATvar.storage_begin=FATvar.fat1_begin+FATbuf[0x10]*pi[0x24/4];
		FATvar.root_cluster=pi[0x2c/4];
	}
	FATvar.last_cluster=-1;
	return 0;
}

///////////////////////////////////////////////////////////////////////////////
//
// Devuelve el siguiente cluster de una cadena
//  nota: FATbuf se usa como una caché que almacena
//    128 clusters de 32 bits 
//  o 256 clusters de 16 bits
//

u32 next_cluster32(u32 cluster)
{
	u32 *pi;
	
	if ((cluster^FATvar.last_cluster)&0xffffff80) {
		// leemos nuevo trozo de FAT
		if (rdsector(FATvar.fat1_begin+(cluster>>7),FATbuf)) return 0;
		FATvar.last_cluster=cluster;
	}

	pi=(u32 *)FATbuf;
	return pi[cluster&127];
}

u32 next_cluster16(u32 cluster)
{
	u16 *ps;
	
	if ((cluster^FATvar.last_cluster)&0xffffff00) {
		// leemos nuevo trozo de FAT
		if (rdsector(FATvar.fat1_begin+(cluster>>8),FATbuf)) return 0;
		FATvar.last_cluster=cluster;
	}

	ps=(u16 *)FATbuf;
	return ps[cluster&255];
}

///////////////////////////////////////////////////////////////////////////////
//
// Lectura de un sector de un fichero
// Primero hay que dar valores a las variables globales: filecluster, filesector
// y filelen (estructura FATvar). Después se lee un sector (512 bytes) por cada
// llamada y se actualizan estas variables con los valores del siguiente sector.
//
// retorna: nº de bytes válidos (512 salvo al final del fichero)
//

u32 rdfile(u8 *buf)
{
	u32 nc,ns,i;

	// Siguiente sector
	nc=FATvar.filecluster;
	ns=FATvar.filesector+1;
	i=1<<FATvar.l2spc;	// i = sectores por cluster
	if (FATvar.fat_type==FAT16) {
		if (nc) { // lectura normal
			if (ns>=i) {ns=0; nc=next_cluster16(nc);}
		} else {  // lectura del directorio raíz
			if (ns>=FATvar.root_nsector) return 0;
		} 
	} else { // FAT32
		if (ns>=i) {ns=0; nc=next_cluster32(nc);}
	}
	// final de fichero?
	if(FATvar.filecluster>=((FATvar.fat_type==FAT16)?0xfff0:0x0ffffff0)) return 0;
	// marcamos FATbuf como no válido para FAT si lo sobrescribimos
	if (buf==FATbuf) FATvar.last_cluster=-1; 
	// cluster=0 es el directorio en FAT16 (caso especial)
	i=( (FATvar.filecluster)?
		FATvar.storage_begin+((FATvar.filecluster-2)<<FATvar.l2spc) : 
		FATvar.root_sector
	  ) + FATvar.filesector;
	if (rdsector(i,buf)) return 0;
	// actualizamos datos del próximo sector a leer
	FATvar.filesector=ns;
	FATvar.filecluster=nc;
	// actualizamos tamaño de fichero y retornamos nº de bytes válidos
	i=(FATvar.filelen>512)?512:FATvar.filelen;
	FATvar.filelen-=i;
	return i;
}

///////////////////////////////////////////////////////////////////////////////
//
// Escaneo de directorio: busca un fichero dentro del directorio apuntado por 
// 'cluster'. La búsqueda es recursiva si hay '/' o '\' en el nombre.
//  Si encontrado:
//	  - 'FATvar.filecluster' y 'FATvar.filelen' se escriben con los datos del 
//      fichero en el directorio.
//    - 'FATvar.filesector' se pone en cero.
//    - Retorna 'FATvar.filecluster'.
//  Si no encontrado: retorna 0
//

u32 scandir(u8 *fname, u32 cluster)
{
	u32 i,j,*pi;
	u16 *ps;
	u8 *p,fn[11];

	// primero preparamos un nombre compatible con DOS <8.3>
	p=fname;
restart:
	for (i=0;i<11;i++) {
		j=*p++;
		if (j=='/' || j=='\\') { // subdirectorio: buscamos recursivamente
			fn[i]=0; cluster=scandir(fn,cluster);
			if (!cluster) return 0;
			goto restart;
		}
		if (!j) while(i<11) fn[i++]=' '; // final de cadena: rellenamos con espacios
		if (j>='a' && j<='z') j-=32;	// en mayúsculas
		if (j!='.') fn[i]=j;
		else {
			while(i<8) fn[i++]=' ';		// punto: rellenamos con espacios hasta fn[7]
			i--;
		}
	}
	
	// Ahora leemos el directorio y buscamos el nombre
	FATvar.filesector=0;
	FATvar.filecluster=cluster;
	FATvar.filelen=-1;
	for(;;) {
		i=rdfile(FATbuf); if(!i) return 0;
		p=FATbuf;
		do {
			if (!p[0]) return 0;	// final del directorio
			for (i=0;i<11;i++) if (p[i]!=fn[i]) break;
			if (i==11) {		// encontrado
				pi=(u32 *)p;
				ps=(u16 *)p;
				FATvar.filelen=pi[28/4];
				FATvar.filesector=0;
				i=ps[26/2];		// cluster inicial (16-bits)
				if (FATvar.fat_type!=FAT16) i+=ps[20/2]<<16; // FAT32: bits MSB
				FATvar.filecluster=i;
				return i;
			}
			p=&p[32];
		} while (p<&FATbuf[512]);
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////

///////////////////// Emulación PC //////////////////////
/*
#define _LARGEFILE64_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>

char *devn;

int rdsector( unsigned int LBA, unsigned char *buf)
{
	static int fatfd=0;
	long long ll;
	if (!fatfd) {
		fatfd=open(devn,O_RDONLY);
		if (fatfd<0) {perror("open"); exit(1);}
	}
	ll=LBA; ll*=512;
	lseek64(fatfd,ll,SEEK_SET);

	if (read(fatfd,buf,512)==512) return 0; else return -1;
}

void dumphex(unsigned char *buf, unsigned int len)
{
	int i,j;
	for (i=0;i<len;i+=16) {
		printf("%03X  ",i);
		for (j=0;j<16;j++) {printf("%02X ",buf[i+j]); if (j==7) printf(" ");}
		for (j=0;j<16;j++) {
			printf("%c",isprint(buf[i+j])?buf[i+j]:'.');
			if (j==7) printf(" ");
		}
		printf("\n");
	}
}

/////////////////////////////////////////////////////////////


main(int argc, char **argv)
{
	int i;
	u8 buf[512];

	devn=argv[1];

	if (i=initFAT()) {printf("Error: %d\n",i); exit(0);}

	fprintf(stderr,"FAT%d\n",(FATvar.fat_type==FAT16)?16:32);
	fprintf(stderr,"Sectores/cluster=%d\n",1<<FATvar.l2spc);
	fprintf(stderr,"storage_begin   =%d\n",FATvar.storage_begin);
	fprintf(stderr,"fat1_begin      =%d\n",FATvar.fat1_begin);
	if (FATvar.fat_type==FAT32) fprintf(stderr,"root_cluster    =%d\n",FATvar.root_cluster);
	if (FATvar.fat_type==FAT16) {
		fprintf(stderr,"root_sector     =%d\n",FATvar.root_sector);
		fprintf(stderr,"root_nsector    =%d\n",FATvar.root_nsector);
	}

	fprintf(stderr,"----------------------------\n");

	i=scandir(argv[2],FATvar.root_cluster);
	if (!i) {fprintf(stderr,"Error: %s no encontrado\n",argv[2]); exit(0);}
	fprintf(stderr,"fichero: %d bytes\n",FATvar.filelen);
	do {
		i=rdfile(buf);
		fwrite(buf,1,i,stdout);
	} while (i==512);

}
*/
