[MULTI] libfat con caché mejorada (V2)

Update: V2: corregidos algunos bugs y añadido soporte para analizar cuantos cluster se pueden cargar de forma consecutiva (valido solo para FAT 16/32)

Si sois usuarios de DevkitPro y os habeis visto en la necesidad de utilizar la libfat de "Chishm" alguna vez, tal vez os interese echar un ojo a esto, que aunque está pensado para optimizar la lectura/escritura en Wii mediante dispositivo USB, añade una serie de mejoras a la librería que deberian valer para todos los sistemas, salvo que el dispositivo que useis no permita la lectura de multiples sectores, en cuyo caso, deberiais limitar a 1 el parametro "sectorsPerCluster" en FAT_cache_constructor, pero eso ya seria trabajo vuestro y por supuesto, este hilo puede servir para vuestras aportaciones.

Pero antes, me gustaría dejar unas cuantes cosas bien claras:

- Yo no me he puesto en contacto con "Chishm", ni lo voy a hacer en el futuro (mi nivel de ingles me lo impide y tampoco voy a esperarme a que el autor original adopte o no adopte los cambios, porque estos son importantes para nosotros, pero lo mismo da problemas con un cartucho de GBA, por poner un ejemplo)

- No tengo contacto con los que mantienen Devkitpro, ni lo voy a tener, porque la barrera del idioma sigue siendo importante.

- Esta librería tiene los parches de Sven para dispostivos USB en Wii y ha sido modificada por rodries que ha arreglado algunos problemas y en parte ha sido la luz para orientarme en que sentido debía trabajar. Esta pensada para Wii, pero creo que es perfectamente valida para cualquier otra consola (el unico problema puede estar en que el dispositivo no esté preparado para lectura de multiples sectores o para soportar todos los que la librería le pida de golpe, pero eso es facil de arreglar)

Por tanto esta librería es muy posible que no llegue a los canales oficiales, o quizá llegue mas tarde. En todo caso, paso a describir lo que ha cambiado, segun lo he encontrado yo:

En primer lugar, los discos duros agrupan sectores en clusters por que es la forma mas rapida de leer sectores, agrupandolos. Tambien es comun el uso de caches para evitar leer repetidamente los clusters mas utilizado y que tiene especial importancia cuando se realizan pequeñas lecturas y escrituras.

Pero el creador de la librería, por alguna razón decidió dividir la caché en sectores y trabajar de esa forma, por lo que yo he tenido que cambiar esas forma de trabajar por otra que agrupe clusters enteros, o en el peor de los casos, tomar una porción de cluster como medida, que no sea desmasiado grande para suponer un desperdicio de memoria en sistemas pequeños como la GBA, pero tampoco tan pequeña para que se3 vuelva ineficaz.

Además, en vez de utilizar solo una caché, ahora se utilizan dos:

-partition->cache: se utiliza para la lecturas/escrituras de entradas FAT. Puede alojar clusters enteros, pero si el tamaño de cluster supera los 8 sectores, entonces se toma 8 como medida parcial. Desde el punto de vista de la librería, eso da igual, que no haya correspondecia con el tamaño del cluster del disco y evita que se superen los 32KB por entrada. En un sistema pequeño, fatInit(2,false) reservaría un maximo 8KB de uso para esta cache, mientras que fatInit(8,false), lo usual en Wii, reservaría un maximo de 32KB para ésta cache. Poner un numero mayor de paginas (sin ser exagerado), puede acelerar el acceso a las tablas FAT, evidentemente.

-partition->racache: se utiliza para las lecturas/escrituras de ficheros (es decir, los datos que manejaremos nosotros) y por defecto se asigna en fatInit() 2 paginas de 64 sectores, por lo que dejandolo así gastariamos 64KB. Sin embargo, podemos ajustar esto a nuestra conveniencia si usamos la funcion fatEnableReadAhead(). Por ejemplo, fatEnableReadAhead(PI_USBSTORAGE, 6, 64); es lo que suele usar rodries y corresponde con 6 paginas de 64 sectores, lo que equivale a gastar unos 192KB en la cache.

Teneis que tener claro una cosa: en un dispositivo USB en Wii, leer 64 sectores de una tacada, es mas rapido que leerlos de 8 en 8 hasta completar los 64, pero si el cluster tiene un tamaño de 8 sectores y el fichero está fragmentado, la estrategia de leer 64 sectores de una tacada, no es buena y redundará en perdida de eficacia. Por ello tengo previsto mas adelante, meter un sistema que mire el numero de entradas FAT disponibles de forma consecutiva y que aloje los cluster precisos para no penalizar.

Las caches están preparadas para trabajar en bloques de clusters(grupos, paginas... como mas os guste) pero trabajan al mismo tiempo a nivel de clusters.

Es decir: a la hora de elegir que clusters debo descartar de la cache por ser los mas antiguos en ser usados y necesitar cargar clusters nuevos, se va a tener en cuenta un contador de grupo que se refresca cada vez que se utiliza uno de los clusters que forma parte de ese bloque. Pero sin embargo, a la hora de buscar clusters, se hace de forma individual, al igual que cuando sea necesario escribir un cluster, se escribira ese cluster o los que estén consecutivos a el que tambien hayan sido modificados, de forma individual o en grupo, si se puede optimizar.

En la recarga del bloque de la cache, si se producen solapamientos de clusters, se marcarán como clusters vacios los nuevos clusters cargados coincidentes, para evitar paradojas.

Una cosa que he solucionado desde la versión de partida, es que solo estaba preparada para abrir un fichero como lectura o como escritura, pero no estaba preparada para leer un fichero como lectura/escritura y mantener la coherencia entre el dispositivo y la cache. Si abrias un fichero como fopen(name,"rb+") se podia armar una buena porque en las escrituras se volcaban los datos directamente, sin conservar lo que hubiera que conservar (primero se debe leer y luego, escribir para hacerlo correctamente)

Como leer y escribir un sector, es mas lento que escribirlo directamente, he tenido en cuenta que si se abre un fichero para solo escritura, si bien es necesario mantener los cambios en la caché, no es necesario leer desde el dispositivo. Por eso en estos casos, activo un flag en la cache "only_writable" que anula el refresco desde el dispositivo de la cache.

Para adaptar la librería al modo de trabajar en bloques, en las funciones de lectura/escritura de sectores, he dividido el sector pasado en dos parametros. Por regla general estas funciones pasan el sector base del cluster, o el sector base del cluster desplazado en una serie de clusters y por otro lado, el offset al sector.

Es por eso que estas funciones tienen un parametro sector0 y otro sector: con la combinacion de ambos, puedo agrupar correctamente los sectores por bloques, manteniendo al mismo tiempo la alineacion con respecto al sector de base de los clusters. Suena complejo, pero es lo que hace que los sectores de un cluster se lean de forma grupal sin solaparse y correctamente alineados.

Por otro lado, he corregido algunos otros problemas: por ejemplo, en algunas funciones que implican a ficheros es conveniente flushear toda la cache e incluso, invalidarla. Tambien es recomendable desmontar la particion antes de salir del programa, para asegurarse que todas las entradas fat modificadas, se escriban (de no hacerlo, se perderia informacion importante, mucho ojo a esto, porque si no cerramos un fichero que estemos escribiendo, pueden haber datos residuales que no se han actualizado).

Por ello si desmontas la particion, aunque la funcion falle por tener un fichero abierto, los datos seran flusheados al dispositivo, para evitar este tipo de errores.

Por otro lado, algunos programas requieren los bytes libres disponibles en el dispositivo. Esta operación puede tardar unos segundos, por lo que para evitar que cada vez que la llamemos, se quede "pillado", he añadido un flag que hace que solo refresque los datos, si necesita refrescarlos, devolviendo el espacio libre que se leyó previamente.

Por otro lado, he añadido un sistema de reintentos en caso de error en la lectura/escritura del dispositivo, que yo creo que vendrá muy bien y qu eno se porque coño no estaba implementado ya, pero bueno XD.

En fin, creo que esto resume mas o menos todo lo que he trabajado con ello: he hecho test abriendo hasta doscientos ficheros que se combinaban entre si y con operaciones de creacion/borrado de directorios, ficheros, etc y no he encontrado ninguna sola inconsistencia con el scandisk, asi que parece que al menos esto, es estable.

Como curiosidad, en Wii leyendo de dispositivo USB (USB 1.1 ) me arroja estos datos, para la escritura/lectura de un fichero de 8MB

Escritura creando fichero:

lectura/escritura: fopen(name,"wb+"); 323672 bytes/segundo

solo escritura: fopen(name,"wb"); 562804 bytes/segundo (como se nota el modo de cache only_writable :) )

Lectura de fichero: 759150 bytes/segundo

Todo eso utilizando :

fatInit(8, false);
fatEnableReadAhead(PI_USBSTORAGE, 6, 64); // antes deberiamos comprobar que la particion está montada,claro

Metodo de inicializacion en Wii

#include <fat.h>

int have_device=0;
....
   {
       DIR_ITER *dir;

    char fat[7] = "fat0:/";
   
   fat[3] = 48+PI_INTERNAL_SD;
   
   dir = diropen(fat);
    if (dir) {dirclose(dir); have_device|=1;fatEnableReadAhead(PI_INTERNAL_SD, 6, 64);}


   fat[3] = 48+PI_USBSTORAGE;
   
   dir = diropen(fat);
    if (dir) {dirclose(dir);  have_device|=2;fatEnableReadAhead(PI_USBSTORAGE, 6, 64);}
   

    }

if(havedevice & 1) fatSetDefaultInterface(PI_INTERNAL_SD); // asigna "fat:" a la SD
if(havedevice & 2) fatSetDefaultInterface(PI_USBSTORAGE); // asigna "fat:" al pendrive



Descarga: libfat vs Hermes (V2)
Deberias de platicar con Marcan que el si habla español y en otro hilo comento esto:

marcansoft escribió:Pff, al final me hareis hacer y sacar un SD ISO loader. Eso si, con una condición: os lo paso encriptado con AES-128-CBC.

Por cierto, acabo de confirmar que la Wii tiene EHCI (es decir, USB 2.0), ya con pruebas tangibles. ¿Alguien se anima a hacer un driver? Con tanto interés por el cargacopias, seguro que alguien lo hace, y nos vendría bien al resto de los usuarios de homebrew. Os doy info y todo: la base de los registros es 0x0d040000, y los registros de 8 y 16 bits están en direcciones inversas por el cambio de endian, al igual que en SD. A ver si por una vez es al revés, y en lugar de los piratas tirar de todo nuestro trabajo para el homebrew, añadir poco, y cargar copias, los de homebrew podemos aprovechar el trabajo de los piratas [+risas]


Saludos y Felicidades por el trabajo
Usuario-X escribió:Deberias de platicar con Marcan que el si habla español y en otro hilo comento esto:

marcansoft escribió:Pff, al final me hareis hacer y sacar un SD ISO loader. Eso si, con una condición: os lo paso encriptado con AES-128-CBC.

Por cierto, acabo de confirmar que la Wii tiene EHCI (es decir, USB 2.0), ya con pruebas tangibles. ¿Alguien se anima a hacer un driver? Con tanto interés por el cargacopias, seguro que alguien lo hace, y nos vendría bien al resto de los usuarios de homebrew. Os doy info y todo: la base de los registros es 0x0d040000, y los registros de 8 y 16 bits están en direcciones inversas por el cambio de endian, al igual que en SD. A ver si por una vez es al revés, y en lugar de los piratas tirar de todo nuestro trabajo para el homebrew, añadir poco, y cargar copias, los de homebrew podemos aprovechar el trabajo de los piratas [+risas]


Saludos y Felicidades por el trabajo


marcan es amigo mio y se que me comentó que tenian sospechas de por donde andaba la direccion, pero ya te adelanto yo que la SD tira de USB 2.0, porque la especificacion USB 1.1 tiene un tope de 12 Mbits, por segundo y yo tengo medido en test una lectura 6.5Mbytes y escritura por encima de 2Mbytes por segundo y eso no es 1.1 ni de coña
Hermes, eres grande.

Saludos.
Hermes, he llegado aqui desde el hilo de Scene de wii, a traves de tu aviso. Me da casi verguenza reconocer que no sabia que este foro estaba en activo, tal vez porque no esta enlazado desde wii. Con un poco de suerte durante esta semana tendre el tiempo que esta libreria se merece para echarle un buen ojo y empezar a trabajar sobre ella. Gracias por el trabajo (y por el post tan completo).
Como siempre, un gran trabajo. [oki]
Muchas gracias por tu trabajo [plas] [beer]
UPDATE: V2.

Se me habian colado un par de bugs, uno menor, que hacia que no se informara bien del tamaño de disco si cambiabamos de dispositivo/particion.

El otro era mas importante y tenia que ver con los ficheros de solo escritura, ya que no tenia en cuenta el caso de que otro fichero vaciara la cache y en escrituras parciales, se pudiera "estropear" la informacion a guardar. Creo que ya está solucionado, pues ahora el flag de solo escritura solo se puede activar si accedemos a nuevo cluster virgen.

Por otro lado, he añadido lo que comentaba de mirar cuantos clusters se podian cargar para evitar que el fraccionamiento de los ficheros, hiciera que perdieramos velocidad y la verdad es que se nota.

Por otro lado, ahora meter fatEnableReadAhead(PI_USBSTORAGE, 6, 256); o fatEnableReadAhead(PI_USBSTORAGE, 6, 512); puede que sea un desperdicio de memoria, pero si se nota que se incrementa la velocidad (aunque depende del grado de fraccionamiento), cuando antes se desperdiciaría mucho tiempo en caso de estar fragmentado. La diferencia, puede estar en torno a 30 KB, no es mucho, pero es lo que hay. Por otro lado, si vais a utilizar varios ficheros, seria mejor usar algo asi como: fatEnableReadAhead(PI_USBSTORAGE, 16, 256); El numero de paginas, es el que determina las veces que habra que recargar la cache, recordadlo
7 respuestas