Mload: la llave del starlet
Para consultas y debates sobre el contenido de este artículo, visita el Hilo oficial.
Contenido |
Introducción
- Un custom IOS (o cIOS para abreviar) no es mas que una modificación o añadido que se le hace a los IOS originales para conseguir nuevas prestaciones y por ejemplo, poder emular la unidad de DVD desde un disco duro para cargar un backup, pero también puede servir para abrir una puerta nueva al Homebrew y con esa idea Hermes diseñó mload.
- mload es un módulo (un programa) como cualquier otro de los que componen el IOS, orientado como una herramienta que suministrará una forma de cargar otros módulos externos desde el PPC, hacia el Starlet y así permitir que las aplicaciones pudieran proporcionar de forma personalizada, sus propios módulos, sin necesidad de tener que instalar un nuevo cIOS cada vez que se lleve a cabo una modificación de cualquier tipo. De esta forma, se facilita el desarrollo y se evitan colisiones e incompatibilidades indeseadas porque el cIOS está preparado para trabajar de forma diferente.
- Trabajar en el Starlet no es sencillo, puesto que por un lado, hay que convivir con el resto de módulos que componen el IOS, respetando sus direcciones de memoria, etc, pero también hay una serie de protecciones de memoria y permisos que hacen que por ejemplo, si desde un módulo trato de escribir en determinadas áreas, se produzca un cuelgue (por una excepción). Además, nosotros no sabemos casi nada y tenemos que avanzar a tientas probando cosas.
- Así pues, cuando Hermes desarrolló mload lo primero que tuvo que buscar fué una zona de memoria que estuviera libre y que proporcionara un espacio lo suficientemente amplio para que pudiéramos cargar nuestros módulos: la zona elegida fue la comprendida entre 0x13700000 y 0x1377ffff. Es decir, 512KB para nuestros propósitos, que es un espacio aceptable y que parece que no da problemas. También tuvo que investigar el uso de las syscalls y aprender a ejecutar hilos y controlarlos, así como la utilización de los timers, etc (cosa de la que también hablaré mas adelante)
- Descarga de fuentes de mload: http://mods.elotrolado.net/~hermes/wii/cios_mload_source_install_3.5.rar
Características generales de mload
- mload se compone de tres secciones de memoria. El código ejecutable se carga en la dirección 0x138c0000, el espacio de variables, la pila etc, a partir 0x138c8000 y el área que nos interesa, en 0x13700000, ajustando los permisos como lectura y escritura, simplemente (lo podéis ver en la carpeta de scripts en el fichero link.ld de los fuentes del módulo).
- Cuando compilamos el programa, obtenemos un módulo con unas dimensiones un tanto respetables (unos 643 KB!), pero éste módulo elf no está preparado para que el IOS lo pueda asimilar tal cual y debe pasar por una herramienta llamada stripios (de nuevo os remito a los fuentes) que Hermes modificó para que pudiera comprimir una de las secciones que le indiquemos, a un tamaño de 1 solo byte (por tratarse de un espacio vacío en realidad) utilizando uno de los trucos del formato elf .
De esa forma, la sección 0x13700000 reduce todo su tamaño (en el makefile podéis ver la línea "@$(STRIPIOS) $< $@ strip 0x13700000" que se ocupa de ello) y el módulo a incluir en el IOS es de solo 7KB como resultado.
- Desde el PPC (también desde el Starlet obviamente), podemos acceder a una serie de funciones para manipular la memoria o ejecutar módulos desde el dispositivo "dev/mload" (que luego trataremos con más detalle), principalmente, en el área de trabajo entre 0x13700000 y 0x1377ffff, claro está.
- En la última versión de mload se suministrá también una serie de funciones nuevas, cuyo propósito principal es ganar permisos a la hora de realizar ciertas tareas, mediante Interrupciones por Software (SWI) desde el Starlet y de esa forma poder realizar casi cualquier cosa (por ejemplo, utilizar estas herramientas para cambiar el vector original de interrupciones y procesarlo desde el módulo EHCI o copiar zonas de memoria críticas, con las interrupciones deshabilitadas y todos los permisos).
- mload responde al servicio "svc 0xcc", permitiendo registrar otros servicios desde 0x00 a 0xff (exceptuando 0xcc obviamente) y tratando de forma interna el servicio "svc 0xab", en concreto la función os_puts(""); para obtener logs reportados por otros módulos (obviamente, se puede registrar otra función para tratar 0xab y redirigir los mensajes a otro dispositivo)
Otro detalle interesante, es que mload es cargado usando la máxima prioridad admitida (0x79) con el fin de permitir que podamos crear hilos con el abanico más amplio de posibilidades (un hilo no puede crear otro hilo que tenga mayor prioridad que el).
mload identifica el IOS base (entre 36, 37,38 y 60) y hace los parches necesarios de forma directa e interna, aunque por el momento, solo ha podido cargarse con éxito con base 36 y 38. También se ocupa de pre-inicializar el driver EHCI.
Preparación del cIOS: parches y módulos
- El instalador actual, realiza dos series de parches: uno destinado al modulo que se suele denominar "ES" de forma genérica para identificarlo (aunque en realidad contiene más cosas, como un cargador de elf con distintas secciones con el kernel, etc). Y otro que va destinado al modulo "DIP". En cIOS 202 se capan los parches destinados a facilitar la conexión de dip_plugin (código en formato binario que es cargado en la dirección 0x1377E000 que hace de interfaz entre el módulo DIP y el módulo EHCI para la emulación de DVD).
- Los parches de la parte ES se han transferido al módulo mload y se han reemplazado en el instalador por dos nuevos parches que actúan sobre la tabla de saltos de la syscall os_software_IRQ (función que se usa para habilitar una interrupción ligada a un dispositivo), en concreto para os_software_IRQ(4) (para poder habilitar las interrupciones EHCI, saltando una comprobación) y para os_software_IRQ(9) (interrupción fuera de uso en el Starlet y que aquí se utiliza para saltar al crt0.s de mload y ganar permisos de sistema, con los que poder llevar a cabo los parches necesarios desde mload). A efectos prácticos, una sola llamada a os_software_IRQ(9) durante la ejecución de mload, es lo que hace que mload se vuelva especial y tengamos un gran poder en nuestras manos.
- El módulo EHCI, se compila para ser cargado en la dirección 0x13700000 hasta la 0x1373ffff (¡ocupa la mitad del espacio disponible!) pero también hay que decir que en realidad se compone de tres módulos en uno (EHCI+USB Storage+WBFS) y se ha sido generoso proporcionándole espacio para la pila y asignación de memoria.
Tanto el módulo EHCI (ehcmodule.elf) como dip_plugin, no son cargados por el cIOS, si no que son módulos externos que se alojan en memoria por las aplicaciones (por ejemplo, uLoader) y cada cual puede repartir a su gusto la memoria como le parezca (cargando otro tipo de módulos o modificando sus direcciones de linkado)
Caja de Herramientas de dev/mload
- Para trabajar desde el PPC con mload, en examples/ppc/libmload podeis encontrar un conjunto de herramientas con las que realizar diferentes tareas.
En mload.h podéis ver el catálogo de las funciones disponibles. Todas las funciones, llaman a mload_init() de forma interna y retornan un valor negativo en caso de error. El único cuidado especial que debemos de tener, es de llamar a mload_close(); antes de cargar un nuevo IOS si vamos a seguir utilizando los servicios de dev/mload.
- Aquí se listan las principales funciones:
IOS
int mload_get_IOS_base(); |
ret: devuelve el IOS utilizado como base del cIOS (FFS, ES, IOSP), actualmente el 38. Esto es importante conocerlo porque por ejemplo, si vamos a cambiar el vector de interrupciones, realizar parches de algún tipo o incluso llamar funciones del sistema desde fuera, las localizaciones son diferentes en los distintos IOS. |
Carga de módulos
int mload_module(void *addr, int len); |
Función pensada para cargar módulos pequeños desde la memoria del PPC de forma directa. Recomiendo utilizar mejor mload_elf().
addr: dirección del módulo .elf en memoria, alineada a 32 len: longitud del módulo elf ret: devuelve negativo si hubo error o la id del hilo creado en el Starlet. |
int mload_elf(void *my_elf, data_elf *data_elf); |
Función utilizada para copiar en la memoria del Starlet las diferentes secciones de un módulo en formato elf.
my_elf: dirección del módulo .elf en memoria, alineada a 32 data_elf: estructura que es inicializada por mload elf situando los valores de inicio, prioridad, stack, etc ret: 0->OK <0 -> Error |
int mload_run_thread(void *starlet_addr, void *starlet_top_stack, int stack_size, int priority); |
Función utilizada para ejecutar un hilo en el starlet (lo normal es utilizarla en conjunción con los datos devueltos por mload_elf() en data_elf)
starlet_addr: dirección de inicio del hilo del programa starlet_top_stack: dirección superior de la pila (la pila es descendente). stack_size: tamaño total de la pila. priority: prioridad de 0x1 a 0x79 (maxima) ret: devuelve negativo si hubo error o la id del hilo creado en el Starlet. |
Leer/Escribir memoria
int mload_seek(int offset, int mode); |
Posicionar el puntero en memoria para las operaciones de lectura/escritura
offset: dirección de memoria absoluta/relativa en el Starlet mode: SEEK_SET normalmente. ret: <0 error |
int mload_read(void* buf, u32 size); int mload_write(const void * buf, u32 size); |
lectura/escritura desde la posición del puntero en el Starlet
buf: buffer donde se leerá o desde el que se escribirá. Se recomienda alinear a 32 size: bytes a leer o escribir ret: <0 error. Numero de bytes leidos/escritos. |
int mload_memset(void *starlet_addr, int set, int len); |
función especial que equivale a memset() para el Starlet |
Especiales
void * mload_get_ehci_data(); |
Devuelve la dirección de la estructura EHCI. Se utiliza en ehcmodule.elf. |
int mload_get_log(); |
Función que situa el puntero de lectura/escritura en la dirección de inicio del área reservada para logs mediante la función os_puts(). Normalmente, tiene una extensión de 4KB y se compone de una cadena de caracteres terminada en '\0'.
Debería ir seguida de un mload_read() y tiene su utilidad para volcar su contenido a la SD, por ejemplo. ret: si falla retorna <0. Devuelve el tamaño máximo del buffer de log. |
int mload_getw(const void * addr, u32 *dat); int mload_geth(const void * addr, u16 *dat); int mload_getb(const void * addr, u8 *dat); int mload_setw(const void * addr, u32 dat); int mload_seth(const void * addr, u16 dat); int mload_setb(const void * addr, u8 dat);
|
Funciones especiales para leer/escribir registros o zonas de memoria sin cachear accesibles en modo usuario desde el Starlet. |
Syscalls
- Bien, ya conocemos las funciones de mload que nos permiten leer/escribir/cargar y ejecutar en memoria, código o modulos desde el PPC, pero de poco nos sirve eso si no conocemos una serie de funciones que nos proporciona el sistema para construir dichos módulos y poder comunicarnos con ellos. Así que vendría bien darle un repaso a las principales syscalls que podemos necesitar para trabajar con nuestros módulos.
Antes de nada, hay que señalar que no podemos utilizar ciertas funciones de la librería estándar como malloc(), free() o printf().
Memoria
int os_heap_create(void* ptr, int size); |
Función que se utiliza para crear un montículo de memoria que utilizaremos en nuestro módulo de forma privada. Dicho de otra forma, es como si indicáramos en un programa una zona de memoria que luego se asignará con malloc() (que ya he mencionado que no se debe utilizar)
ptr: inicio del area de memoria utilizada como montículo (alineado a 32, probablemente) size: tamaño en bytes del montículo ret= <0 error. Id del Heap |
void* os_heap_alloc(int heap, unsigned int size); |
El equivalente al malloc().
heap: id del heap a utilizar. 0 parece ser el montículo global size: tamaño en bytes asignar. ret= dirección de la memoria asignada o NULL |
void os_heap_free(int heap, void* ptr); |
El equivalente a free().
heap -> id del heap. ptr ->dirección a liberar |
Caché
- Como sabéis, los procesadores implementan memoria caché para acelerar el acceso de memoria, dado que la memoria RAM es más lenta, tanto para el código en ejecución (icache) como para datos (dcache). Las syscalls siguientes se utilizan para trabajar con la caché de datos.
void os_sync_before_read(void* ptr, int size); |
Función a utilizar antes de las lecturas del procesador cuando se presume que han habido operaciones DMA (fuera de la caché) en el bloque de memoria especificado. Tengo una duda, sobre ésta función, que no he comprobado : no se si simplemente, invalida la caché o si además procede a refrescarla (si es el primer caso, las líneas de caché se irían refrescando a medida que fuéramos leyendo desde la RAM, por lo que si invalidamos la caché y al cabo de un segundo, por ejemplo, cambiáramos un dato vía DMA y después procediéramos a leer dicho dato, tendríamos el dato mas actual, porque la caché se refrescaría en el momento de leer, mientras que en el segundo caso, tendríamos el dato antiguo, puesto que la caché se habría refrescado cuando llamamos a os_sync_before_read() y la lectura se haría dentro de la cache (salvo que por alguna razón externa, el procesador hubiera descartado esos datos) y no serían visibles los cambios fuera de caché, salvo que llamáramos de nuevo a os_sync_before_read(). Solo se que el ARM tiene una instrucción específica para invalidar caché, pero aquí hay otras operaciones un tanto raras que sugieren que puede haber una invalidación y refresco de la caché.
ptr: dirección de memoria. Ojo con esto porque las lineas de caché trabajan con 32 bytes, por lo que si se solapan datos (es decir, que el puntero no esté alineado a 32), se puede producir resultados imprevistos. size: tamaño en bytes. Ojo con esto por que el tamaño es redondeado para ser divisible entre 32 y si se solapan datos (es decir, que el redondeo exceda del tamaño) se pueden producir resultados imprevistos |
void os_sync_after_write(void* ptr, int size); |
Función a utilizar después de las escrituras cuando se presume que se va a utilizar el bloque de memoria especificado en operaciones DMA (fuera de la caché), para que la caché se escriba en la memoria RAM.
ptr: dirección de memoria. Ojo con esto porque las lineas de caché trabajan con 32 bytes, por lo que si se solapan datos (es decir, que el puntero no esté alineado a 32), se puede producir resultados imprevistos. size: tamaño en bytes. Ojo con esto por que el tamaño es redondeado para ser divisible entre 32 y si se solapan datos (es decir, que el redondeo exceda del tamaño) se pueden producir resultados imprevistos |
Colas
- Las colas (queue) son el mecanismo que nos permite sincronizar la ejecución de los distintos hilos, de forma que podemos registrar una cola para un dispositivo por ejemplo y ésta queda a la espera de que se le envíe un mensaje, que puede ser la dirección de una estructura de datos o simplemente una señal de otro tipo.
int os_message_queue_create(void* ptr, unsigned int id); |
Función para crear una cola.
ptr: puntero donde se recibirán los mensajes de la cola. id: Numero de entradas que soportará la cola. Cada entrada ocupa 4 bytes en la memoria señalada por ptr. Hay que tener mucho cuidado de no envioar mas mensajes de los que la cola pueda soportar (especialmente peligroso cuando trabajemos con timers). Por cierto, la etiqueta 'id' debería ser un 'max_entries' o algo similar. ret: <0 si error. queuehandle asociado a la cola. |
int os_message_queue_receive(int queue, unsigned int* message, unsigned int flags); |
Función que aguarda a que una cola reciba un mensaje, interrumpiendo la ejecución del hilo.
queue: queuehandle (devuelto por os_message_queue_create()) message: Si es un dispositivo, lo normal es que sea la dirección de una estructura ipcmessage (ver syscalls.h) flags: siempre a 0 ret: <0 error. Es posible que devuelva el numero de elementos en cola, pero no lo he comprobado. |
int os_message_queue_send(int queue, unsigned int message, int flags); int os_message_queue_send_now(int queue, unsigned int message, int flags); |
Función para enviar un mensaje a una cola. Probablemente el send_now provoque la activación del hilo saltándose la prioridad de las colas.
queue: queuehandle (devuelto por os_message_queue_create()) message: dirección o mensaje a enviar a la cola. flags: a 0 ret: <0 error |
void os_message_queue_ack(void* message, int result); |
Función que devuelve una respuesta del dispositivo mediante IPC. Realmente, no veo que relación tiene esto con la colas por que
es más bien una función de respuesta de dispositivo, que otra cosa ... message: Dirección de la estructura ipcmessage que se recibió mediante os_message_queue_receive() result: Resultado de la operación IPC solicitada (IOS_OPEN, IOS_CLOSE, ...) |
Dispositivos
int os_device_register(const char* devicename, int queuehandle); |
Función que asocia un nombre de dispositivo a una cola, para operaciones IPC.
devicename: Nombre del dispositivo: Ej "/dev/mload" queuehandle: queuehandle (devuelto por os_message_queue_create()) |
int os_open(char* device, int mode); |
Equivalente a IOS_Open en PPC.
device: nombre del dispositivo a abrir. mode: flags de modo (O_CREAT, O_TRUNC, O_RDONLY, O_WRONLY, O_RDWR ... en include/sys/fcntl.h) ret: <0 error, fd del dispositivo. |
int os_close(int fd); |
Cierra el dispositivo.
fd: fd del dispositivo ret: <0 error |
int os_seek(int fd, int offset, int mode); |
Posiciona el puntero del dispositivo.
fd: fd del dispositivo offset: posición dentro del dispositivo mode: SEEK_SET, SEEK_END, SEEK_CUR... ret: <0 error. Posición actual absoluta dentro del dispositivo |
int os_read(int fd, void *d, int len); int os_write(int fd, void *s, int len); |
Funciones para lectura/escritura en dispositivo
fd: fd del dispositivo s: d: direcciones fuente/destino para escritura/lectura len: longitud en bytes. ret: <0 Error. Numero de bytes escritos/leídos. |
int os_ioctlv(int fd, int request, int bytes_in, int bytes_out, ioctlv *vector); |
Función de control I/O mediante vectores. Conviene utilizar las funciones de caché apropiadas en las transferencias de datos.
fd: fd del dispositivo request: indice función ioctlv bytes_in: Numero de elementos de entrada (la etiqueta está mal, puesto que no son bytes) bytes_out: Numero de elementos de salida (la etiqueta está mal, puesto que no son bytes) vector: Dirección del array de vectores (tipo ioctlv en syscalls.h). in+out ret: <0 error. Resultado depende de la función seleccionada. |
int os_ioctl(int fd, int request, void *in, int bytes_in, void *out, int bytes_out); |
Función de control I/O. Conviene utilizar las funciones de caché apropiadas en las transferencias de datos.
fd: fd del dispositivo request: indice función ioctl in: dirección buffer de entrada bytes_in: longitud buffer de entrada out: dirección buffer de salida bytes_out: longitud buffer de salida ret: <0 error. Resultado depende de la función seleccionada. |
Timers
- Los timers se utilizan para enviar un mensaje a la cola cada cierto tiempo. Internamente, son controlados por una alarma que genera una interrupción cada cierto tiempo, por lo que el si el tiempo del timer es muy bajo, seguramente tenga cierta distorsión (aparte de que la activación de las colas dependerá de la prioridad de los hilos, etc). La resolución de los timers se mide en microsegundos. Hay que tener especial cuidado de que el tiempo de repetición no sea demasiado corto y acabe por rebasar la cola asociada.
int os_create_timer(int time_us, int repeat_time_us, int message_queue, int message); |
Función para crear un timer.
time_us: tiempo en microsegundos de la primera cuenta repeat_time: tiempo en microsegundos de las siguientes cuentas. Si solo se requiere una cuenta, se puede fijar un tiempo muy alto aquí y proceder a parar el timer cuando se reciba el mensaje message_queue: queuehandle de la cola que recibirá el mensaje message: mensaje que recibirá la cola al cumplirse el tiempo (podría ser la dirección de una estructura, por ejemplo) ret: <0 error al crear el timer. id del timer handle asociado |
int os_stop_timer(int timer_id); |
Para la cuenta de un timer. Se puede restablecer con os_restart_timer().
timer_id: Timer handle ret: <0 error |
int os_destroy_timer(int time_id); |
Elimina un timer. Es posible que sea necesario pararlo primero con os_stop_timer().
timer_id: Timer handle ret: <0 error |
int os_restart_timer(int timer_id, int time_us); |
Reinicia un timer que fue parado con os_stop_timer(). La cuenta es repetitiva, por lo que hay que tener cuidado que el tiempo no sea demasiado corto como para que se produzca un desborde en la cola asociada al timer.
timer_id: Timer handle time_us: tiempo en microsegundos de las sucesivas repeticiones ret: <0 error |
int os_timer_now(int time_id); |
Probablemente fuerce a un timer a enviar el mensaje y reiniciar su cuenta al ser llamada
timer_id: Timer handle ret: <0 error |
Interrupciones
int os_register_event_handler(int device, int queue, int message); |
Registra una cola a un dispositivo que recibirá eventos mediante interrupciones. Solo es posible registrar si no hay otro evento registrado.
device: IRQ del dispositivo en cuestión 4->EHCI, 5->OHC0, etc (ver libcios/include/starlet.h) queue: quehandle de la cola asociada message: mensaje que se envía a la cola ret: <0 si error |
int os_unregister_event_handler(int device); |
Elimina los eventos por interrupción para el dispositivo especificado.
device: IRQ del dispositivo en cuestión 4->EHCI, 5->OHC0, etc (ver libcios/include/starlet.h) ret: <0 si error |
int os_software_IRQ(int dev); |
Función que habilita el uso de interrupciones en el dispositivo. Tiene una doble función: por un lado, elimina una posible interrupción pendiente para dicho dispositivo y por otro, ajusta la máscara de interrupciones (Hollywood) para que estas puedan producirse. Sin embargo, ésta syscall solo admite fijar desde IRQ4 (EHCI) en adelante y existe una comprobación (probablemente ligada a la ID del proceso) que hace que no en todos los casos se pueda fijarla interupción desde aquí (de ahí que uno de los parches de mload se dedique a habilitar os_software_IRQ(4).
Sin embargo, es posible acceder a los registros HW_ARMIRQFLAG y HW_ARMIRQMASK de forma directa mediante los servicios SWI de mload (como veremos mas adelante) dev: IRQ del dispositivo en cuestión a partir de 4->EHCI, 5->OHC0, etc (ver libcios/include/starlet.h) ret: <0 error |
Hilos
- NOTA: el thread ID=0 indica hilo actual en algunas funciones.
int os_thread_create( unsigned int (*entry)(void* arg), void* arg, void* stack, unsigned int stacksize, unsigned int priority, int autostart); |
Función que crea un hilo de programa en el starlet. El hilo se encuentra parado de inicio y requiere el uso de os_thread_continue() para ejecutarse.
entry: función de inicio que recibirá el foco del hilo. arg: argumento que se le pasará a la función de inicio stack: dirección de memoria como tope de pila. Por ejemplo, si dedicamos u32 my_stack[STACK_SIZE] como pila del hilo, aquí habria que pasar &my_stack[STACK_SIZE] como dirección stacksize: Tamaño en bytes de la pila priority: prioridad entre 0x1 y 0x79. La prioridad no puede ser mayor que la del hilo donde se invoca ésta función (o no funcionará) autostart: a 0. No parece funcionar como autostart y puede que no tenga uso o que sirva para indicar otra cosa. ret: <0 Error. ID del hilo. |
int os_thread_continue(int id); |
Continua con la ejecución del hilo (después de crearse o de una parada)
id: ID del hilo ret: <0 error |
int os_thread_stop(int id); |
Para la ejecución de un hilo.
id: ID del hilo ret: <0 error |
int os_get_thread_id(void); |
Devuelve la ID del hilo actual.
ret: ID del hilo. |
void os_thread_set_priority(int id, unsigned int priority); |
Fija la prioridad actual del hilo.
id: ID del hilo priority: prioridad entre 0x1 y 0x79. La función fallará si la prioridad es mayor que la de la creación del hilo. |
int os_thread_get_priority(void); |
Devuelve la prioridad del hilo actual.
ret: <0 error. Prioridad actual |
Log
void os_puts(char *str); |
Función que envía una cadena de texto al buffer de log |
Códigos Fuentes
- Antes de mirar algunos ejemplo de código, hay que explicar un poco la organización de los fuentes de mload, para que sepais donde buscar información:
apps:
- cios_installer: instalador de los cIOS 202/222/223
cios_mload:
- cios_installer: fuentes del instalador de cIOS
- ehcmodule: fuentes del módulo EHCI
- examples: fjemplos para PPC y Starlet. Librería mload para PPC
- ppc:
- example1: ejemplo simple que muestra la actividad de un módulo
- example2: ejemplo que muestra la utilización del módulo FAT/FAT32
- libmload: librería utilizada en el apartado "Caja de Herramientas de mload"
- starlet:
- example1: módulo que muestra el uso de timers, colas y multihilo (para resetear una cuenta)
- libfat: módulo que soporta FAT/FAT32 con memorias SD
- stripios: réplica de la utilidad stripios
- ppc:
- libcios: librerías base
- include:
- starlet.h: información sobre registros (Hollywood) IRQ, GPIO, TIMER (sacado de wiibrew.org)
- swi_mload.h: librería de servicios SWI de mload (Avanzado)
- syscalls.h: librería de syscalls
- types.h: tipos de datos usados en el Starlet
- source:
- swi_mload.c: librería de servicios SWI de mload (Avanzado)
- swi_mload.s: código ensamblador para llamar SWI
- syscalls.s: código ensamblador para llamar a las syscalls
- include:
- mload: fuentes del módulo mload
- stripios: utilidad que convierte los elfs normales a los que precisa el IOS
- tinyehci: fuentes del driver EHCI /USB Storage
- wii_wbfs: fuentes para trabajar con particiones WBFS
Organización de un módulo
- Los módulos en el Starlet, se compilan en una dirección fija y además es necesario convertir el elf resultante mediante la utilidad stripios. Voy a explicar aquí como están estructurados los fuentes partiendo del módulo example1:
example1:
- bin: aquí se alojará el módulo resultante
- scripts: aquí se encuentra nostart.specs y link.ld. Este último contiene el mapa de direcciones del módulo
- source:
- crt0.S: aquí se fija la prioridad inicial del módulo, la pila, etc. Y otras rutinas en ensamblador.
- main.c: el cuerpo del módulo propiamente dicho
- Makefile
Creando un hilo y un timer que lo active
- En primer lugar, debemos declarar un espacio que utilizaremos para la pila:
#define THREAD_STACK 1024 u8 thread_stack[THREAD_STACK] __attribute__ ((aligned (32)));
- Luego, debemos declarar una función que recibirá el foco del hilo:
int thread_start(void *arg) { /* creamos una cola que usaremos en el hilo para aguardar un evento que lo active observar que en este caso, estamos utilizando como heaphandle 0, para asignar 32 bytes y una cola de 8 elementos (de 4 bytes) */ int thread_queuehandle = os_message_queue_create( os_heap_alloc(0, 0x20), 8); /* vamos a definir un evento (en este caso un timer) que enviará un mensaje que activará el hilo cada 10 segundos */ os_create_timer(1000*1000*10, 1000*1000*10, thread_queuehandle, 0x555); while(1) { u32 message; /* aguarda a que la cola reciba un mensaje */ os_message_queue_receive(thread_queuehandle, (void*)&message, 0); if(message==0x555) { // mensaje del timer recibido: hacer aquí lo que corresponda } } return 0; }
- Ahora tocaría crear el hilo desde el main():
int my_thread_id=os_thread_create( (void *) thread_start, NULL, &thread_stack[THREAD_STACK], THREAD_STACK, 0x48, 0); if(my_thread_id>=0) os_thread_continue(my_thread_id);