Proyecto Fin de Carrera Trabajo Fin de Master IngenieríaMaster de Universitario Telecomunicación en Ingeniería Aeronáutica

FormatoCreación de Publicación Sistemas Operativos de la Escuela embebidos Técnica Superiorpersonalizados de Ingeniería basados en para SoC con FPGA integrada

Autor:Autor: F. Javier Ángel Payán Cea Somet Fons Tutor:Tutor: Juan MaríaJosé Murillo de los Ángeles Fuentes Martín Prats

Dep. TeoríaDep. de Ingenieríala Señal y Comunicaciones Electrónica EscuelaEscuela Técnica Técnica Superior Superior de Ingeniería de Ingeniería UniversidadUniversidad de Sevilla de Sevilla

Sevilla,Sevilla, 2013 2017

Trabajo Fin de Master Master Universitario en Ingeniería Aeronáutica

Creación Sistemas Operativos embebidos personalizados basados en Linux para SoC con FPGA integrada

Autor: Ángel Cea Fons

Tutor: María de los Ángeles Martín Prats Profesor Titular

Dep. Ingeniería Electrónica Escuela Técnica Superior de Ingeniería Universidad de Sevilla

Sevilla, 2017

Trabajo Fin de Master: Creación Sistemas Operativos embebidos personalizados basados en Linux para SoC con FPGA integrada

Autor: Ángel Cea Fons Tutor: María de los Ángeles Martín Prats

El tribunal nombrado para juzgar el trabajo arriba indicado, compuesto por los siguientes profesores:

Presidente:

Vocal/es:

Secretario:

acuerdan otorgarle la calificación de:

El Secretario del Tribunal

Fecha:

Resumen

n el mundo de la ingeniería la electrónica está cobrando cada vez una mayor importancia debido, E entre otros motivos, a la creciente automatización de procesos, el crecimiento en número y complejidad de los sistemas de aviónica embarcados en las aeronaves y la marcada tendencia hacia el All Electric Aircraft, que requiere la sustitución de sistemas mecánicos por sistemas electrónicos. Aunque suele pasar desapercibido, los sistemas embebidos tienen una importancia capital en el mundo de la aviación, controlando y supervisando procesos de una manera eficiente. El problema de estos sistemas, por lo general, subyace en su limitada potencia de procesamiento. Aquí es donde entra en juego lo que se va a tratar en el presente documento, el cual contendrá información acerca del desarrollo de una distribución de un sistema operativo (Linux) personalizada con , que permita satisfacer las necesidades pertinentes de forma eficiente, lo que nos permite no desaprovechar potencia de cálculo y además personalizar las herramientas y utilidades de las que se quiere dotar al sistema embebido.

I

Índice

Resumen I Comandos VII

1 Introducción1 1.1 Sistemas embebidos1 1.1.1 ZedBoard3 1.2 Linux embebido4 1.2.1 Código abierto (Open source)4 1.2.2 Licencias Open source 5 1.2.3 Los cuatro elementos de Linux embebido5 Toolchain5

2 Toolchain7 2.1 Tipos de Toolchain7 2.2 Elección de la librería de C7 2.3 Crosstool-NG8 2.3.1 Instalación de crosstool-NG8 2.3.2 Anatomía de una toolchain 12 2.3.3 El directorio sysroot, la librería y los archivos de cabecera 14 2.3.4 Librerías, componentes de la librería de 14

3 Bootloader 19 3.1 ¿Cuál es la función del bootloader? 19 3.2 La secuencia de arranque 19 3.2.1 Fase 1: código ROM 19 3.2.2 Fase 2: SPL 20 3.2.3 Fase 3: TPL 20 3.3 Del bootloader al kernel 21 3.4 Device trees 21 3.4.1 La propiedad reg 23 3.4.2 Phandles e interrupciones 24 3.4.3 Inclusión de archivos en los árboles de dispositivos 24 3.4.4 Elección de un bootloader 26

4 El kernel 29 4.1 ¿Qué es el kernel? 29

III IV Índice

4.2 Elección del kernel 29 4.3 Configuración del kernel 32 4.4 Módulos kernel 33 4.5 Compilando 33 4.5.1 Compilando la imagen del kernel 33 4.5.2 Compilando arboles de dispositivos 36 4.5.3 Compilando módulos kernel 36 4.5.4 Limpiando fuentes del kernel 36

5 El sistema de archivos raiz 39 5.1 Contenido del sistema de archivos raiz 39 5.2 Estructura del directorio 40 5.3 Permisos de acceso POSIX a archivos 40 5.4 Programas para el sistema de archivos raíz 41 5.4.1 El programa init 41 5.4.2 Shell 42 5.4.3 Utilidades 42 5.4.4 Busybox 43 5.4.5 Construyendo Busybox 43 5.4.6 Librerías para el sistema de archivos raiz 45 5.4.7 Nodos de dispositivos 45 5.4.8 El sistema de archivos proc y sys/ 45 5.4.9 Módulos kernel 47 5.5 Transfiriendo el sistema de archivos raíz al dispositivo de destino 47

6 Yocto Project 49 6.1 ¿Qué es Yocto Project? 49 6.2 Instalación de Yocto Project 49 6.3 Configuaración de Yocto Project 51 6.4 Capas 53 6.4.1 Creación de una nueva capa 54 6.5 Modulos kernel 60 6.5.1 Compilando modulos kernel con Yocto Project 61 6.5.2 Drivers 66 6.5.3 Creando un character device driver 70

7 Construyendo con Yocto Poject 81 7.1 Construyendo para la ZedBoard 81 7.1.1 Antes de intentar realizar la construcción 81 7.1.2 Construyendo 83 7.1.3 Creando una capa para la ZedBoard 87 7.1.4 Despliegue en la zedboard 90 7.1.5 Prueba del sistema con QEMU 92 7.2 Construyendo para la enclustra 93

8 Conclusiones 97

9 Futuras líneas de investigación 99 Índice V

Índice de Figuras 101 Índice de Tablas 103 Índice de Códigos 105 Bibliografía 109

Comandos

En esta lista se incluirán comandos utilizados en el documento para facilitar la lectura a personas que no estén familiarizadas con la terminal de Linux, pero este apartado no se dedicará a explicar con detalle y detenimiento ni el funcionamiento ni la sintaxis de los comandos, para ello buscar información en fuentes externas a este documento. sudo Colocado delante de otros comandos permite ejecutarlos como superusua- rio tar Comando que sirve pare realizar ta- reas de compresión y descompresión cd Realiza el cambio al directorio que se le indique a continuación (change directory) pwd > Muestra la ubicación del directorio actual (Print Working Directory) make Permite ejecutar archivo makefile en el directorio actual mv Mueve y renombra ficheros o direc- torios entre otras funciones rm Permite eliminar directorios y archi- vos dependiendo de las opciones que se le indiquen cp Copia directorios y archivos origen en el destino atendiendo a las opcio- nes que se le indiquen cat Lee un archivo y muestra su conteni- do tail Muestra las últimas líneas de un ar- chivo ls Muestra un listado de directorios y archivos dentro del directorio , y si no se le indica destino el listado lo hace del directorio actual lsmod Muestra un listado de los módulos kernel instalados actualmente en el sistema insmod Instala módulo kernel en el sistema

VII VIII Comandos

rmmod Desinstala módulo kernel del siste- ma df Muestra los sistemas de archivos montados, su espacio disponible y el espacio utilizado dmesg Muestra mensajes del kernel git clone Realiza una clonación del reposito- rio que se encuentre en la dirección indicada git checkout Cambia la rama a la que apunta el re- positorio a la indicada en el comando git branch Muestra la rama a la que apunta ac- tualmente el repositorio git branch -r Muestra las ramas disponibles mknod Crea nodo de dispositivo. Es necesa- rio tener permisos de superusuario mount Para montar directorios y archivos. Es necesario tener permisos de su- perusuario umount Para desmontar directorios y archi- vos. Es necesario tener permisos de superusuario sudo apt-get install Realiza la instalación de la aplica- ción indicada a continuación ./ Ejecuta la aplicación que se le indi- que source Ejecuta la aplicación que se le in- dique a continuación guardando los cambios realizados en elas variables de entorno tree Muestra estructura de directorios y ficheros en forma de árbol de la di- rección que se le indique | grep Toma como entrada la salida del co- mando anterior y muestra los resul- tados que contienen los caracteres indicados fdisk Permite formatear particiones y obte- ner información acerca de dispositi- vos del almacenamiento conectados al sistema find Comando que, como su propio nom- bre indica, sirve para realizar búsque- das de distinta índole dependiendo de las opciones que se añadan al co- mando 1 Introducción

n este capítulo se aclarará la definición de sistema embebido y se mostrarán los componentes E que habitualmente forman parte de los sistemas embebidos. Se introducirá a la ZedBoard como el sistema embebido que se utilizará para el desarrollo del presente proyecto y se hablará sobre lo que es Linux embebido y las partes que lo conforman.

1.1 Sistemas embebidos

Un sistema embebido o empotrado (integrado, incrustado) es un sistema de computación diseñado para realizar una o algunas pocas funciones dedicadas, frecuentemente en un sistema de computación en tiempo real. Al contrario de lo que ocurre con los ordenadores de propósito general (como por ejemplo una computadora personal o PC) que están diseñados para cubrir un amplio rango de necesidades, los sistemas embebidos se diseñan para cubrir necesidades específicas. En un sistema embebido la mayoría de los componentes se encuentran incluidos en la placa base (tarjeta de vídeo, audio, módem, etc.) y muchas veces los dispositivos resultantes no tienen el aspecto de lo que se suele asociar a una computadora. Algunos ejemplos de sistemas embebidos podrían ser dispositivos como un taxímetro, un sistema de control de acceso, la electrónica que controla una máquina expendedora o el sistema de control de una fotocopiadora entre otras múltiples aplicaciones. Como ya se ha nombrado anteriormente, estos sistemas suelen disponer de una potencia de cálculo limitada y pueden llevar a cabo generalmente también una variedad de tareas limitadas en comparación a un pc habitual. Debido a esto cobra mucho sentido desarrollar una distribución de un sistema operativo que esté optimizado en cuanto a consumo de recursos y solo incluya las funcionalidades que se vayan a utilizar, ahorrando así espacio de almacenamiento requerido y complejidad del sistema operativo. Por lo general, los Sistemas Embebidos se pueden programar directamente en el lenguaje ensam- blador del microcontrolador o microprocesador incorporado sobre el mismo, o también, utilizando los compiladores específicos que utilizan lenguajes como C o C++ y en algunos casos, cuando el tiempo de respuesta de la aplicación no es un factor crítico, también pueden usarse lenguajes interpretados como Java. Las arquitecturas más empleadas en sistemas embebidos contienen en general los siguientes componentes:

1. Microprocesador: Es el encargado de realizar las operaciones de cálculo principales del sistema. Ejecuta código para realizar una determinada tarea y dirige el funcionamiento de los demás elementos que le rodean, a modo de director de una orquesta. 2. Memoria principal: En ella se encuentra almacenado el código de los programas que el sistema puede ejecutar así como los datos. Su característica principal es que debe tener un

1 2 Capítulo 1. Introducción

acceso de lectura y escritura lo más rápido posible para que el microprocesador no pierda tiempo en tareas que no son meramente de cálculo. Al ser volátil el sistema requiere de un soporte donde se almacenen los datos incluso sin disponer de alimentación o energía. 3. Caché: Memoria más rápida que la principal en la que se almacenan los datos y el código accedido recientemente. Dado que el sistema realiza microtareas, muchas veces repetitivas, la caché hace ahorrar tiempo, ya que no hará falta ir a memoria principal si el dato o la instrucción ya se encuentra en la caché. Dado su alto precio tiene un tamaño muy inferior comparado la memoria principal. En el interior del chip del microprocesador se encuentra una pequeña caché (L1), pero normalmente se tiene una mayor en otro chip de la placa madre (L2). 4. Disco duro: En él la información no es volátil y además puede conseguir capacidades muy elevadas. A diferencia de la memoria que es de estado sólido éste solía ser magnético, lo que hacía inviable a veces su inclusión en sistemas embebidos, debido a su excesivo tamaño. Otro problema que presentan los dispositivos magnéticos, a la hora de integrarlos en sistemas embebidos, es que llevan partes mecánicas móviles, lo que los hace inviables para entornos donde estos estarán expuestos a ciertas condiciones de vibración. Estos problemas han sido subsanados en los últimos tiempos con la creación de tarjetas SD y micro SD (las cuales tienen un tamaño muy reducido) con capacidades cada vez mayores, disponiendo en muchos casos de más capacidad que discos duros convencionales de hace una década. 5. Disco flexible: Su función era la de almacenamiento, pero con discos con capacidades mucho más pequeñas y la ventaja de su portabilidad. Normalmente se encontraban en computadores personales estándar pero no así en una PC embebida. En nuestros días llevan varios años en total desuso en PC comunes. 6. BIOS-ROM: BIOS(Basic Input Output System, sistema básico de entrada y salida) es código que es necesario para inicializar la computadora y para poner en comunicación los distintos elementos de la placa madre. La ROM (Read Only Memory, memoria de sólo lectura no volátil) es un chip donde se encuentra el código BIOS. 7. CMOS-RAM: Es un chip de memoria de lectura y escritura alimentado con una pila donde se almacena el tipo y ubicación de los dispositivos conectados a la placa madre (disco duro, puertos de entrada y salida, etc.). Además contiene un reloj en permanente funcionamiento que ofrece al sistema la fecha y la hora. 8. Chipset: Chip que se encarga de controlar las interrupciones dirigidas al microprocesador, el acceso directo a memoria (DMA) y al bus ISA, además de ofrecer temporizadores, etc. Es frecuente encontrar la CMOS-RAM y el reloj de tiempo real en el interior del Chip Set. 9. Entradas al sistema: Pueden existir puertos para ratón, teclado, vídeo en formato digital, comunicaciones serie o paralelo, etc. 10. Salidas del sistema: Puertos de vídeo para monitor o televisión, pantallas de cristal líquido, altavoces, comunicaciones serie o paralelo, etc. 11. Ranuras de expansión para tarjetas de tareas específicas: Pueden no venir incorporadas en la placa madre, como pueden ser más puertos de comunicaciones, acceso a red de computadoras vía LAN (Local Area Network, red de área local) o vía red telefónica: básica, RDSI (Red Digital de Servicios Integrados), ADSL (Asynchronous Digital Subscriber Loop, Lazo Digital Asíncrono del Abonado), Cablemódem, etc. Un PC estándar suele tener muchas más ranuras de expansión que un sistema embebido. Las ranuras de expansión están asociadas a distintos tipos de bus: VESA, ISA, PCI, NLX (ISA + PCI), etc.

Finalmente se le va a dedicar un espacio en esta sección a nombrar las ventajas que tienen estos sistemas embebidos: 1.1 Sistemas embebidos 3

• Posibilidad de utilización de sistemas operativos potentes que ya realizan numerosas tareas: comunicaciones por redes de datos, soporte gráfico, concurrencia con lanzamiento de threads, etc. Estos sistemas operativos pueden ser los mismos que para PC compatibles (Linux, Windows, MS-DOS) con fuertes exigencias en hardware o bien ser una versión reducida de los mismos con características orientadas a los PC embebidos. • Al utilizar los Sistemas Embebidos, se pueden encontrar fácilmente herramientas de desarrollo de software potentes, así como numerosos programadores que las dominan, dada la extensión mundial de las aplicaciones para computadores compatibles. • Reducción en el precio de los componentes hardware y software debido a la gran cantidad de computadores en todo el mundo.

1.1.1 ZedBoard

La Zedboard (Zynq Evaluation Development Board) va a ser la placa que va a ser utilizada en este proyecto y para la cual se va a desarrollar el sistema operativo. La ZedBoard es una placa de desarrollo para el Xilinx Zynq-7000 Extensible Processing Plat- form (EPP). El Xilinx Zynq-7000 es un SoC que combina microprocesadores de doble núcleo ARM Cortex-A9, estructura de FPGA y periféricos claves en un solo dispositivo. El procesador y la estructura del FPGA se comunican con más de 10,000 interconexiones internas, ofreciendo un rendimiento entre el microprocesador y FPGA que es físicamente imposible de lograr con un procesador discreto y un FPGA implementado en una tarjeta de circuito impreso.

Figura 1.1 Diagrama del SoC Zynq-7000.

Una FPGA (field-programmable gate array) es un circuito integrado diseñado para ser configu- rado por un cliente, diseñador o desarrollador. Es decir, es una matriz donde se distribuyen bloques lógicos que pueden ser programados para que se conecten de distinta manera y se puedan realizar muy diversas tareas (se puede literalmente modificar las interconexiones entre puertas y bloques lógicos). Es decir, con esta placa se tiene la posibilidad de programar las tareas que realizará el procesador mediante software y la posibilidad de programar hardware para realizar tareas de una forma mucho más rápida y eficiente de lo que lo haría un procesador. Por ejemplo se podría dejar a la FPGA la 4 Capítulo 1. Introducción

tarea de filtrado de señales de entrada, ya que en lugar de que el filtrado lo realice el procesador mediante la potencia de cálculo, lo haría la FPGA de forma "física" tras haberse programado las conexiones físicas que esta utiliza. El SoC Zynq no es llamado un “FPGA” porque es el procesador el que está a cargo del sistema, en lugar de la estructura del FPGA. Es decir, el sistema de procesamiento arranca primero y controla la funcionalidad de la estructura del FPGA. Esto significa que los usuarios no tienen que estar profundamente familiarizados con técnicas de diseño de FPGA para ejecutar una aplicación en el subsistema del procesador del SoC Zynq. El SoC Zynq ofrece a los clientes la habilidad para crear sus diseños en C, C++ o SystemC usando el software de desarrollo de su elección y programar su diseño en el sistema de procesamiento del SoC Zynq. Si una parte de su diseño no se está ejecutando lo suficientemente rápido, los diseñadores pueden usar la herramienta Vivado High-Level Synthesis (HLS) de Xilinx o HANDEL-C de Mentor Graphics para traducir un algoritmo o parte de un algoritmo que desarrollaron a un nivel de C a VHDL y probar ese código ejecutándose en la sección de FPGA del SoC Zynq. Al descargar las funciones adecuadas del procesador a la estructura del FPGA y liberar el procesador para realizar las funciones que hace mejor, los clientes pueden alcanzar un incremento de 700 veces más rendimiento del sistema en comparación con los diseños basados en procesadores.

1.2 Linux embebido

Para realizar este proyecto se utilizará Linux, que será el sistema operativo que se instalará en la ZedBoard. ¿Por qué utilizar este SO en nuestro dispositivo target y no otro?. A continuación se mostrarán algunos argumentos en favor de Linux:

• Linux es capaz de lidiar con muchas funcionalidades, tanto necesarias en este proyecto como otras que no lo son (por lo menos en principio). Soporte para distintos tipos de conectividad (USB, Wi-Fi, Bluetooth etc), para gran variedad de dispositivos de almacenamiento, soporte para dispositivos multimedia y mucho más. • Linux es de código abierto, lo que implica que se dispone de la libertad de adquirir código fuente y modificarlo a nuestro antojo. Es decir, se pueden agregar nuevas funcionalidades que no estaban en el código fuente y/o eliminar otras que no sean necesarias en el proyecto para reducir los requisitos de procesamiento y memoria. • Linux tiene el soporte de una gran comunidad muy activa, lo que garantiza que va a estar actualizado, soportará las últimas tecnologías y se tendrá acceso a multitud de información que, entre otras cosas, ahorrará tiempo en la resolución de los problemas que vayan surgiendo en el desarrollo de este proyecto. • Las licencias de código abierto de Linux garantizan que se tiene acceso al código fuente. 1.2.1 Código abierto (Open source)

Se va a hablar de manera escueta sobre qué significa que la mayoría de los componentes de Linux embebido sean Open source y qué tipos de licencia se pueden encontrar en este tipo de código. Open source tiene varios significados, o más bien, el significado de open source tiene varias implicaciones. La primera implicación es el hecho de que el código Open source se puede adquirir gratuitamente y desplegarlo en el sistema que se requiera sin ningún coste, y esto es justo lo que garantizan las licencias de software de código abierto, pero la más importante es la segunda implicación. La segunda implicación del significado de Open source es que se tiene total libertad de adquirir el código fuente y modificarlo sin ningún tipo de restricción. No confundir código Open source con licencias de shareware que permiten copiar y ejecutar archivos binarios pero no permiten el acceso al código fuente o de otras licencias que permiten el 1.2 Linux embebido 5 uso del software en ciertas condiciones como por ejemplo que su uso sea personal y no comercial. Estos dos últimos ejemplos no son Open source.

1.2.2 Licencias Open source

Las licencias Open source se dividen básicamente en dos categorías: • GPL (General Public License) • BSD (Berkeley Software Distribution) La licencia BSD especifica que se puede modificar el código fuente y utilizarlo en cualquier sistema siempre que no se modifiquen los términos de la licencia. Es decir, cualquier usuario podrá tomar software bajo esta licencia libremente , sólo respetando la autoría del software, pero pudiendo decidir si liberar o no los cambios que se hayan realizado. Lo que el desarrollador logra es que su código sirva para cualquier propósito y, con código abierto o sin él, el siguiente desarrollador pueda elegir con libertad qué hacer con su propio trabajo. En cambio la licencia GPL también obliga a respetar la autoría, pero en este caso se debe liberar el código con la misma licencia. Esto quiere decir que el usuario que utilice software con esta licencia deberá publicar el resultado de su trabajo y además bajo la misma licencia.

1.2.3 Los cuatro elementos de Linux embebido

Cualquier proyecto de creación y desarrollo de un sistema operativo para ser instalado en un sistema embebido comienza con la obtención, personalización y el despliegue de estos 4 elementos: 1. Toolchain: Está compuesto del compilador y otras herramientas que se necesitan para crear el código que se instalará en el dispositivo final. 2. Bootloader: Este elemento es necesario para inicializar la placa y para cargar y "arrancar" el kernel de Linux. 3. Kernel: Es el elemento central del sistema, gestiona los recursos del sistema y hace de interfaz con el hardware. 4. Sistema de archivos raíz (root filesystem): Estos son los archivos que contienen información crítica sobre el sistema como pueden ser librerías y programas que arrancan una vez que el kernel ha completado su inicialización Los capítulos dedicados a cada elemento de Linux embebido por separado estarán basados en el libro Simmonds, C. (2015). Mastering Embedded Linux Programming. Packt Publishing Ltd, por lo que en él se puede encontrar información similar y ampliada. Es un buen libro para consulta de muchos de los temas tratados en el presente documento. A continuación se hará una ampliación breve sobre la toolchain, aunque se ahondará más en capítulos posteriores.

Toolchain Una toolchain es el conjunto de herramientas que compila código fuente en ejecutables que se pueden ejecutar en el dispositivo de destino e incluye un compilador, un enlazador (linker) y bibliotecas de tiempo de ejecución. Inicialmente, se necesita para construir los otros tres elementos de un sistema Linux incorporado: el bootloader, el kernel y el sistema de archivos raíz. Tiene que ser capaz de compilar código escrito en ensamblador, C y C ++ ya que estos son los lenguajes utilizados en los paquetes de código abierto base. Por lo general, las toolchain para Linux se basan en componentes del proyecto GNU (http://www.gnu.org). El proyecto GNU es un proyecto colaborativo de software libre con el objetivo de crear un sistema operativo completamente libre: el sistema GNU. Una toolchain estandar de GNU está compuesto de tres componentes principales: 6 Capítulo 1. Introducción

• Binutils: Un conjunto de utilidades binarias incluyendo el ensamblador, y el linker. Está disponible en http://www.gnu.org/software/binutils/. • GNU Compiler Collection (GCC): Estos son los compiladores para C y otros lenguajes que, dependiendo de la versión de GCC, incluyen C ++, Objective-C, Objective-C ++, Java, Fortran, Ada y Go. Todos ellos utilizan un back-end común que produce el código ensamblador del que se alimenta el ensamblador de GNU. Está disponible en http://gcc.gnu.org/. • Librerías de C: Una API estandarizada basada en la especificación POSIX, que es la interfaz principal de las aplicaciones con el kernel del sistema operativo. 2 Toolchain

n este capítulo se profundizará sobre la toolchain y su utilización. Se nombrarán los tipos de E toolchain, cuál será la mejor elección en este proyecto y se introducirá la utilización de una toolchain específica para mostrar la manera habitual de trabajar con estas herramientas y poder tener una idea un poco más profunda de esta parte de Linux embebido.

2.1 Tipos de Toolchain

Las toolchain se pueden dividir en dos tipos:

• Nativa: Este tipo de toolchain se ejecuta en el mismo tipo de sistema que el sistema para el que se generan los programas. Por poner un ejemplo sencillo, si vamos a utilizar la toolchain para crear programas que serán ejecutados en el mismo ordenador o un ordenador similar (misma arquitectura de procesador y similar arquitectura general). • Cruzada: Este tipo de toolchain se ejecuta en un sistema de distinto tipo del sistema donde se ejecutarán los programas que se creen con la toolchain.

En el caso de este proyecto tendremos que elegir una toolchain de tipo cruzada, ya que el sistema donde desarrollaremos el sistema operativo (nuestro sistema host) será un sistema con arquitectura x86 de 64 bits (un procesador intel más específicamente), mientras que el sistema donde se ejecutarán los programas que se creen con la toolchain (el target) será la ZedBoard que cuenta con arquitectura ARM. Se podría directamente hacer el desarrollo de los programas directamente sobre la Zedboard, con una toochain de tipo nativa, pero parece más adecuado, no solo por potencia y recursos sino por comodidad realizar el desarrollo en un ordenador convencional utilizando una toolchain de tipo cruzada.

2.2 Elección de la librería de C

Las librerías son archivos con rutinas que realizan tareas comunes (y a veces muy complejas), que ahorran infinidad de trabajo al poder ser llamadas desde otros programas y no teniendo así que ser programadas cada vez que se necesita de una funcionalidad en concreto. Las bibliotecas estándares, como puede ser la biblioteca estandar de C (también conocida como libc) es exacta- mente lo anteriormente comentado, solo que esta estandarizado por la Organización Internacional para la Estandarización (ISO) para que de esta manera pueda ser utilizado por gran cantidad de aplicaciones.

7 8 Capítulo 2. Toolchain

El lenguaje C es el que hace de interfaz entre la aplicación (que está dentro del espacio de usuario) y el kernel, por tanto será muy importante tener unas librerías de C que permitan la comunicación entre los programas generados y el kernel. Hay muchas opciones en cuanto a librerías de C se refiere, a continuación se muestran las principales opciones:

• glibc: Es la librería estándar de C de GNU. • eglibc: Las siglas significan embedded glibc. Agrega opciones de configuración y soporte para arquitecturas que no están cubiertas por gilbc. • ulibc: La "u" de las siglas en realidad es una "µ" haciendo referencia a que es una librería de C para microcontroladores. • libc: Es una nueva librería de C para sistemas embebidos.

Para este proyecto se ha elegido libc, ya que es la librería estándar y es quizás la opción más completa de todas las nombradas.

2.3 Crosstool-NG

Hay gran cantidad y variedad de toolchain en el mercado. Por ejemplo se pueden encontrar toolchain del proveedor del SoC que se haya adquirido, toolchain para Linux creados por terceros como Mentor Graphics, o un SDK generado por una herramienta integrada para desarrollo embebido como Yocto Project. Se tiene que evaluar si la una toolchain preconstruida satisface las necesidadesn del proyecto, y si nó se tendrá que construir una toolchain. Para mostrar la creación de la toolchain se trabajará con crosstool-NG ya que es una alternativa sencilla que encapsula el proceso en scripts. Es importante que se mantenga la toolchain una vez elegida o creada ya que el cambio inconsistente de compiladores y de librerías es una fuente de errores importante.

2.3.1 Instalación de crosstool-NG

Para instalar los paquetes necesarios para el correcto funcionamiento de crosstool-NG se tendrá que ejecutar el siguiente comando en la consola:

Código 2.1 Instalación de paquetes para crosstool-NG.

$ sudo apt-get install automake bison chrpath flex g++ git gperf gawk libexpat1-dev libncurses5-dev libsdl1.2-dev libtool python2.7-dev texinfo

Tras esto habrá que descargar la última versión de crosstool-NG visitando el enlace http://crosstool- ng.org/download/crosstool-ng. En el momento de escritura del presente documento la última ver- sión disponible es crosstool-ng-1.23.0. La instalación se realiza con los siguientes comandos, los cuales se pueden encontrar en la dirección http://crosstool-ng.github.io/docs/install/ .

Código 2.2 Instalación de crosstool-NG.

$ tar xf crosstool-ng-1.23.0.tar.bz2 $ cd crosstool-ng-1.23.0 $ ./configure --enable-local 2.3 Crosstool-NG 9

$ make $ make install

Como se puede observar en el código 2.2 la primera línea descomprime el archivo descargado, la segunda línea cambia de directorio al directorio que ha quedado después de descomprimir el archivo. Al hacer make puede dar un error, en este caso dio el error configure: error: missing required tool: help2man que se soluciona simplemente insertando el comando $ sudo apt-get install help2man. Luego el comando $ ./configure –enable-local permite instalar crosstool-NG en en directorio en el que se encuentra situado el sistema (crosstool-ng-1.23.0 ) y por lo tanto ejecutarlo también en este mismo directorio. Si ejecutamos el comando $ ./ct-ng se especificarán los distintos comandos que se pueden utilizar. Se puede ver lo anteriormente comentado en el código 2.3.

Código 2.3 Lista de comandos disponibles para crosstool-NG.

angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng This is crosstool-NG version crosstool-ng-1.23.0

Copyright (C) 2008 Yann E. MORIN This is ; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

See below for a list of available actions, listed by category:

Configuration actions: - Update current config using a menu based program nconfig - Update current config using a menu based program oldconfig - Update current config using a provided .config as base extractconfig - Extract to stdout the configuration items from a build.log file piped to stdin savedefconfig - Save current config as a mini-defconfig to ${ DEFCONFIG} defconfig - Update config from a mini-defconfig ${DEFCONFIG} (default: ${DEFCONFIG}=./defconfig) saveconfig - Save current config as a preconfigured target show-tuple - Print the tuple of the currently configured toolchain

Preconfigured toolchains (#: force number of // jobs): list-samples - prints the list of all samples (for scripting) show- - show a brief overview of (list with list- samples) - preconfigure crosstool-NG with (list with list-samples) build-all[.#] - Build *all* samples (list with list-samples) and install in ${CT_PREFIX} (set to ~/x-tools by default)

Build actions (#: force number of // jobs): 10 Capítulo 2. Toolchain

source - Download sources for currently configured toolchain build[.#] - Build the currently configured toolchain list-steps - List all build steps

Clean actions: clean - Remove generated files distclean - Remove generated files, configuration and build directories

Distribution actions: check-samples - Verify if samples need updates due to Kconfig changes update-samples - Regenerate sample configurations using the current Kconfig wiki-samples - Print a DokuWiki table of samples updatetools - Update the config tools

Environment variables (see /home/angel/Desktop/croostool-ng/crosstool-ng -1.23.0/docs/0 - Table of content.txt): STOP=step - Stop the build just after this step (list with list- steps) RESTART=step - Restart the build just before this step (list with list-steps) CT_PREFIX=dir - install samples in dir (see action "build-all", above ). V=0|1|2| - show only human-readable messages (default) 0 => do not show commands or human-readable message 1 => show only the commands being executed 2 => show both

Use action "menuconfig" to configure your toolchain Use action "build" to build your toolchain Use action "version" to see the version See "man 1 ct-ng" for some help as well

Si se quiere conocer una lista de ejemplos de toolchain preconfigurada se puede hacer con el comando $ ./ct-ng list-samples. Ya que la zedboard cuenta con un procesador de 2 nucleos ARM Cortex A9, del listado de ejemplos de toolchain preconfiguradas la más similar es la opción arm-cortexa9_neon-linux-gnueabihf. Se puede consultar la configuración de esta opción como se realiza en el código 2.4.

Código 2.4 Configuración de arm-cortexa9_neon-linux-gnueabihf.

angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng show- arm-cortexa9_neon-linux-gnueabihf IN config.gen/arch.in IN config.gen/kernel.in IN config.gen/cc.in IN config.gen/binutils.in IN config.gen/libc.in 2.3 Crosstool-NG 11

IN config.gen/debug.in [L.X] arm-cortexa9_neon-linux-gnueabihf OS : linux-4.10.8 Companion libs : gmp-6.1.2 mpfr-3.1.5 isl-0.18 mpc-1.0.3 expat-2.2.0 ncurses-6.0 binutils : binutils-2.28 C compilers : gcc | -6.3-2017.02 Languages : C,C++ C : glibc-2.25 (threads: nptl) Tools : gdb-7.12.1

Y para seleccionar esta configuración como la que se utilizará se ejecutará la siguiente linea de comando:

Código 2.5 Elección de configuración para el target.

angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng arm- cortexa9_neon-linux-gnueabihf

Se pueden realizar cambios de configuración con el comando del código configuration-target.

Código 2.6 Elección de configuración para el target.

angel@ubuntu:~/Desktop/croostool-ng/crosstool-ng-1.23.0$ ./ct-ng menuconfig

No se entrará en más detalles de mucho de los procesos ya que el objetivo de este documento se enfoca más en la creación de un sistema con Yocto Project y no con los elementos de Linux embebido por separado (Toolchain, bootloader, kernel y el sistema de archivos raíz). Aun así se trata de forma somera para que se tenga cierto conocimiento del funcionamiento del desarrollo de Linux embebido y para estar más familiarizado con las distintas herramientas de las que se disponen para desarrollar Linux embebido. Notar que la ejecución de $ ./ct-ng siempre se realiza desde el directorio angel@ubuntu: /Desktop/croostool- ng/crosstool-ng-1.23.0, es decir, la ejecución del comando siempre se realiza en el directorio donde se ha realizado la instalación de crosstool-NG. Se recomienda ejecutar $ ./ct-ng menuconfig y realizar los siguientes cambios de configuración.

• En Paths and misc options, desabilitar Render the toolchain read-only. • En Target options seleccionar en Target Architecture la arquitectura arm, y luego en Floating point seleccionar hardware (FPU). • Dentro de C-library escribir en extra config for –enable-obsolete-rpc.

La primera opción es necesaria si se quieren añadir librerías a la toolchain tras la instalación. La segunda selecciona la opción óptima de la implementación de punto flotante para un procesador con FPU (floating point unit). Tras esto se puede usar crosstool-NG para obtener, configurar y contruir componentes para nuestro sistema de destino haciendo uso del comando $ ./ct-ng build. Tras unas decenas de minutos se encontrará la toolchain en el directorio /home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/ 12 Capítulo 2. Toolchain

2.3.2 Anatomía de una toolchain

La toolchain se encuentra en el directorio /home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin y dentro de ella se encuentra el croscompilador arm-cortexa9_neon-linux-gnueabihf.gcc. Para hacer uso de él se tendrá que añadir al path como se muestra en el código 2.7.

Código 2.7 Adición del croscompilador al path.

PATH=~/x-tools/arm-cortexa9_neon-linux-gnueabihf/bin:$PATH

Ahora, por ejemplo, se puede tomar un programa simple llamado holamundo como el que sigue.

Código 2.8 Programa holamundo.

#include #include int main (int argc, char *argv[]) { printf ("Hola, mundo!\n"); return 0; }

Se puede croscompilar el programa de la siguiente manera.

Código 2.9 Croscompilación del programa holamundo.

arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundo

Y se puede comprobar que el archivo se ha croscompilado con éxito con el comando del código 2.10.

Código 2.10 Información sobre el ejecutable holamundo.

angel@ubuntu:~/Desktop$ file holamundo holamundo: ELF 32-bit LSB executable, ARM, EABI5 version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-armhf.so.3, for GNU/ Linux 3.2.0, not stripped

Si se precisa más información sobre el croscompilador, como puede ser tener conocimiento sobre la versión o sobre como se ha configurado, se puede adquirir información como se muestra en el código 2.11, donde también se muestra la respuesta a los comandos:

Código 2.11 Información sobre el croscompilador arm-cortexa9_neon-linux-gnueabihf-gcc.

angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc --version arm-cortexa9_neon-linux-gnueabihf-gcc (crosstool-NG crosstool-ng-1.23.0) 6.3.1 20170109 Copyright (C) 2016 Free Software Foundation, Inc. 2.3 Crosstool-NG 13

This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc -v Using built-in specs. COLLECT_GCC=arm-cortexa9_neon-linux-gnueabihf-gcc COLLECT_LTO_WRAPPER=/home/angel/x-tools/arm-cortexa9_neon-linux- gnueabihf/libexec/gcc/arm-cortexa9_neon-linux-gnueabihf/6.3.1/lto- wrapper Target: arm-cortexa9_neon-linux-gnueabihf Configured with: /home/angel/Desktop/crosstool-ng/crosstool-ng-1.23.0/. build/src/gcc-linaro-6.3-2017.02/configure --build=x86_64-build_pc- linux-gnu --host=x86_64-build_pc-linux-gnu --target=arm- cortexa9_neon-linux-gnueabihf --prefix=/home/angel/x-tools/arm- cortexa9_neon-linux-gnueabihf --with-sysroot=/home/angel/x-tools/arm- cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon-linux-gnueabihf/ sysroot --enable-languages=c,c++ --with-cpu=cortex-a9 --with-fpu= neon --with-float=hard --with-pkgversion='crosstool-NG crosstool-ng -1.23.0' --enable-__cxa_atexit --disable-libmudflap --disable- libgomp --disable-libssp --disable-libquadmath --disable-libquadmath- support --disable-libsanitizer --disable-libmpx --with-gmp=/home/ angel/Desktop/crosstool-ng/crosstool-ng-1.23.0/.build/arm- cortexa9_neon-linux-gnueabihf/buildtools --with-mpfr=/home/angel/ Desktop/crosstool-ng/crosstool-ng-1.23.0/.build/arm-cortexa9_neon- linux-gnueabihf/buildtools --with-mpc=/home/angel/Desktop/crosstool- ng/crosstool-ng-1.23.0/.build/arm-cortexa9_neon-linux-gnueabihf/ buildtools --with-isl=/home/angel/Desktop/crosstool-ng/crosstool-ng -1.23.0/.build/arm-cortexa9_neon-linux-gnueabihf/buildtools --enable- lto --with-host-libstdcxx='-static-libgcc -Wl,-Bstatic,-lstdc++,- Bdynamic -lm' --enable-threads=posix --enable-plugin --enable-gold -- with-libintl-prefix=/home/angel/Desktop/crosstool-ng/crosstool-ng -1.23.0/.build/arm-cortexa9_neon-linux-gnueabihf/buildtools -- disable-multilib --with-local-prefix=/home/angel/x-tools/arm- cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon-linux-gnueabihf/ sysroot --enable-long-long Thread model: posix gcc version 6.3.1 20170109 (crosstool-NG crosstool-ng-1.23.0)

Del código 2.11 la información más destacable es la siguiente:

• –with-sysroot=/home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon- linux-gnueabihf/sysroot : Esta línea informa de cual es el directorio sysroot por defecto. • –enable-languages=c,c++: Esta línea indica que tenemos disponible los lenguajes C y C++. • –with-cpu=cortex-a9 : Indica que el código se ha croscompilado para cpu cortex-a9. • –with-float=hard: Genera codigos de operación para la unidad de punto flotante y usa los registros de VFP para los parámetros. • –enable-threads=posix: habilita los hilos de ejecución POSIX. 14 Capítulo 2. Toolchain

Esta es la configuración por defecto del compilador, la cual se puede sobrescribir directamente en la línea de comandos, si se ve necesario. Si por ejemplo se quiere compilar para una CPU distinta, como por ejemplo la cortex A5, se haría con la línea de comando del código 2.12.

Código 2.12 Sobreescribir configuración para una ejecución a través de linea de comando.

angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc -mcpu= cortex-a5 holamundo.c -o holamundo

2.3.3 El directorio sysroot, la librería y los archivos de cabecera

El directorio sysroot es un directorio que contiene subdirectorios para las librerías, los archivos de cabecera y otros archivos de configuración. Como se ha visto, aparece cuando se llama a las opciones con las que se ha configurado el croscompilador mostrándose la línea –with-sysroot=/home/angel/x- tools/arm-cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon-linux-gnueabihf/sysroot , que indica donde se encuentra este directorio. En el directorio sysroot se encontrará lo siguiente:

• lib: Contiene los objetos compartidos para las librerias de C y el linker/loader dinámico llamado ld-linux. • usr/lib: los archivos de la librería estática para la librería estándar de C y otras librerías que pueden ser instaladas posteriormente. • usr/include: Este directorio contiene archivos de cabecera para las librerías. • usr/bin: Contiene programas de utilidad que se ejecutan en el destino como por ejemplo el comando ldd. • usr/share: Utilizada para la localización, contiene archivos de lectura que no dependen de la arquitectura, por lo tanto podría ser compartido por varias máquinas distintas, no siendo así con distintos S.O o distintas versiones de S.O. • sbin: proporciona la utilidad ldconfig que se encarga de controlar dónde Linux busca archivos en las librerías mientras esta ejecutando programas y por tanto optimizando la carga de los paths.

2.3.4 Librerías, componentes de la librería de C

En informática, una biblioteca (del inglés library) es un conjunto de implementaciones funcio- nales (como por ejemplo leer del teclado o mostrar en pantalla), codificadas en un lenguaje de programación, que ofrece una interfaz bien definida para la funcionalidad que se invoca. A diferencia de un programa ejecutable, las funcionalidades que implementan las librerías no esperan ser utilizadas de forma autónoma, es decir, no ejecutaremos estas funcionalidades de forma autónoma, sino que estas funcionalidades serán utilizadas por otros programas, de forma independiente y pudiendo ser simultánea. Es una forma de ahorrar tiempo y código al no tener que volver a programar de nuevo ciertas funcionalidades dentro de cada nuevo programa, sino que para utilizar esas funcionalidades se pueden realizar llamadas a esas funciones. Se utiliza en informática indistintamente el termino librería y biblioteca para referirse al término original en inglés library. Para que gran cantidad de programas puedan hacer uso de librerías existen librerías estándar, que simplemente son, como su propio nombre indica, librerías que se toman de forma estándar para utilizar en los programas. Esto hace que muchos programas puedan implementar las funcionalidades de la misma manera. 2.3 Crosstool-NG 15

La librería de C no solo se compone de un archivo, sino que se compone de cuatro partes principales que juntas implementan la API de funciones POSIX. Una API (Application Programming Interfaces) es una especificación formal sobre como un módulo software se comunica con otro. Es decir, es un conjunto de comandos, rutinas, protocolos, funciones y procedimientos que permiten que un programa utilice ciertas funcionalidades. Es un concepto parecido a la librería solo que es más focalizado si hablamos en general. Sería como la parte de la librería a la que accede un programa mientras usa la biblioteca. Una API puede ser implementada por distintas librerías y muchas veces la implementación interna de las funciones queda oculta al público. POSIX es el acrónimo de Portable Interface, y la X viene de UNIX como seña de identidad de la API. POSIX es una norma escrita por la IEEE. Dicha norma define una interfaz estándar del sistema operativo y el entorno, incluyendo un intérprete de comandos (o "shell"), y programas de utilidades comunes para apoyar la portabilidad de las aplicaciones a nivel de código fuente. Una vez aclarados estos conceptos se indica a continuación los cuatro elementos de la librería de C:

• libc: La parte principal de la librería de C que contiene funciones conocidas como open, close, read, write, printf, etc. • libm: Contiene funciones matemáticas como cos, exp y log. • libpthread: Lo forman todas las funciones hilos de ejecución (thread) de POSIX que comienzan con pthread_ • librt: Las extensiones para tiempo real de POSIX,incluyendo memoria compartida y I/O asíncrono.

Atendiendo a la forma en la que se enlazan las librerías para ser utilizadas por un programa se pueden diferenciar dos tipos de librería, las librerías estáticas y las dinámicas.

• Librerías estáticas: Se enlazan al compilar y quedan "dentro" del ejecutable final, ya que al compilarlo queda dentro del lenguaje máquina del ejecutable las funcionalidades a las que se han llamado a través de la librería. Es decir, para que sea de fácil entendimiento se puede decir que en lenguaje máquina del ejecutable se encuentra, tanto el código programado por el usuario, como el código de las librerías a las que se llama. Lo anteriormente descrito hace que el ejecutable ocupe más espacio aunque lo hace también muy robusto al no depender de archivos externos a él. En Windows tienen extensión .lib y en Linux tienen extensión .a. • Librerías dinámicas: Se enlazan al ejecutar, y por tanto el sistema operativo se debe hacer cargo de encontrarlas al ejecutar el programa, es decir, se enlazan dinámicamente en tiempo de ejecución. Esto hace que el ejecutable ocupe menos espacio en memoria porque en el ejecutable se encuentra en lenguaje máquina el código escrito por el usuario y las llamadas a las librerías, que ocupa menos código que las librerías en sí, pero tiene el problema de que, si no se han instalado bien las librerías, o en el lugar correcto pueden dar problemas. En Windows tienen la extensión .dll y se encuentran generalmente en la dirección c;\windows\system32 y en Linux tiene la extensión .so y se encuentra habitualmente en la dirección usr/local/lib o usr/lib.

Tras dar una breve explicación de los conceptos más relevantes sobre las librerías se mostrará lo que se debe tener en cuenta a la hora de utilizar el croscompilador. libc, al ser el principal de los nombrados cuando se nombró los cuatro componentes de la librería de C, está enlazado (linked) por defecto, sin tener el usuario que especificarlo a la hora de croscompilar, pero si queremos cualquier 16 Capítulo 2. Toolchain

otro de los componentes de la librería de C tendremos que indicarlo explícitamente. Para indicárserlo al croscompilador solo tendremos que introducir el parámetro -l seguido del nombre de la librería, omitiendo el término "lib". Es decir, para enlazar el programa que se quiera con libm, libthread o librt se hará respectivamente como se muestra a continuación en el código 2.13.

Código 2.13 Croscompilando y enlazando el ejecutable holamundo.

arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundoa -lm

arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundoa - lpthread

arm-cortexa9_neon-linux-gnueabihf-gcc holamundo.c -o holamundoa -lrt

Si se quiere enlazar con varias de las anteriores a la vez lo único que habrá que hacer es poner seguidos libm, libthread o librt, seguidos cada uno por un espacio siempre. Si por ejemplo se tiene un ejecutable que se ha enlazado con libm, libpthread y libc (este último porque esta enlazado siempre por defecto) y se quiere saber con qué librerías ha sido enlazado, se puede averiguar con el comando del código 2.14.

Código 2.14 Obteniendo información sobre librerías compartidas enlazadas con el ejecutable holamundo.

angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-readelf -a holamundo | grep "Shared library" 0x00000001 (NEEDED) Shared library: [libm.so.6] 0x00000001 (NEEDED) Shared library: [libpthread.so.0] 0x00000001 (NEEDED) Shared library: [libc.so.6]

Como se puede observar en el código 2.14, el programa holamundo ha sido enlazado con las tres librerías que se deseaban. Además, las librerías compartidas necesitan un enlazador en tiempo de ejecución (runtime linker), el cual se puede conocer con el comando de código 2.15.

Código 2.15 Obteniendo información sobre el runtime linker para el ejecutable holamundo.

angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-readelf -a prueba2 | grep "program interpreter" [Requesting program interpreter: /lib/ld-linux-armhf.so.3]

Se tendrá que tener cuidado de que en el target se encuentren tanto las librerías como el linker una vez desplegado el sistema en el target objetivo. Para realizar el enlace con las librerías de manera estática se realiza como se muestra a continua- ción en el código 2.16:

Código 2.16 Enlazando librerías de forma estática. 2.3 Crosstool-NG 17

arm-cortexa9_neon-linux-gnueabihf-gcc -static holamundo.c -o holamundo- estatico

Si se recoge información acerca del ejecutable holamundo-estatico se podrá comprobar que lo que ocupa el ejecutable ha incrementado dramáticamente con respecto al ejecutable holamundo, el cual se enlazó de forma dinámica. Esto ocurre debido a lo anteriormente comentado cuando se explicó la diferencia entre enlazar las librerías de forma estática y dinámica. Se dejará a elección del lector investigar más acerca de esta herramienta ya que el objetivo del presente documento es realizar una introducción de las toolchain, poner un ejemplo de ellas y mostrar funcionalidades de ejemplo para poder tener una idea global de lo que es Linux embebido, de qué partes está formado y qué papel juega cada elemento.

3 Bootloader

n este capítulo se hablará sobre los bootloaders y el proceso seguido en el arranque del sistema, E así como la introducción a la utilización de U-Boot, uno de los bootloaders más versátiles que se pueden encontrar. También se introducirá el concepto de árbol de dispositivos (device tree).

3.1 ¿Cuál es la función del bootloader?

En en Linux embebido el bootloader se encarga de dos tareas: la inicialización básica del sistema (también llamado con frecuencia el arranque) y la carga del kernel. Cuando el bootloader comienza a trabajar lo único que se encuentra operativo en el sistema es, típicamente, un núcleo de la cpu y algún dispositivo de memoria. Es decir, no se encuentran disponible la mayoría del hardware, ni siquiera está configurado el controlador de la RAM, por lo que el proceso de arranque será el encargado de conseguir, en varias fases, poner en funcionamiento cada vez más partes del sistema. La fase inicial de inicio o arranque finaliza cuando están funcionando las interfaces requeridas para cargar un kernel, es decir, está operativa la memoria principal (RAM) y además están operativos los dispositivos utilizados para acceder al kernel. La última fase del arranque del sistema es cargar el kernel en la RAM y crear un entorno de ejecución para él.

3.2 La secuencia de arranque

Con los años el proceso de arranque se ha ido haciendo cada vez más complejo, y actualmente se suele tratar de un proceso multietapa, que tiene diferencias dependiendo del SoC, pero que en general cuenta de las siguientes fases.

3.2.1 Fase 1: código ROM

En ausencia de memoria externa "confiable", el código que se ejecuta nada más que se intenta iniciar el sistema es el código ROM. Este código es programado directamente en el chip una vez que el chip se construye, y por tanto es software propietario y no se puede intercambiar por un equivalente de código abierto. En este caso la única memoria RAM a la que tiene acceso el código ROM es a la SRAM (Static Random Access Memory), ya que no tiene disponible los controladores de la DRAM (Dynamic Random Access Memory). El código ROM es capaz de cargar pequeñas porciones de códigos en una de varias ubicaciones preprogramadas en la SRAM. Cuando la SRAM de los SoCs no tiene la capacidad suficiente como para cargar un bootloader completo como U-boot se carga el SPL (Secondary Program Loader) que es un programa que se encarga de hacer el arranque intermedio. Haciendo una analogía con el

19 20 Capítulo 3. Bootloader

mundo de la aeronáutica, es como el sistema de arranque intermedio que se necesita para que el turbofan (el compresor más específicamente) gire a las revoluciones necesarias para poder terminar por el mismo el proceso de arranque. Al final de esta fase se encuentra cargado el SPL en la SRAM.

3.2.2 Fase 2: SPL

El SPL debe configurar y poner a punto para el funcionamiento a la memoria principal (DRAM) y otras partes esenciales del sistema, para cargar el TPL (Third Stage Program Loader) en la memoria principal. La funcionalidad del SPL se parece a la del código ROM , ya que lo que es capaz de hacer es de leer código desde distintas ubicaciones preprogramadas. Esas ubicaciones preprogramadas se basan en offsets preprogramados en ciertos dispositivos de almacenamiento o de nombres conocidos (preprogramados) como u-boot.bin. Esta fase del arranque todavía no permite ninguna interacción del usuario más allá de que probablemente se impriman por pantalla ciertos mensajes. A diferencia del código ROM el SPL puede ser de código abierto aunque no es extraño que contenga código de propiedad del fabricante. Al final de esta fase se encontrará cargado el TPL en la memoria principal del sistema.

3.2.3 Fase 3: TPL

Finalmente el sistema se encuentra por fin ejecutando un bootloader completo como U-boot. En esta ocasión ya si se permite interacción entre el usuario y el sistema en tareas como elegir el kernel que se quiere cargar y otras tareas. Al final de esta fase estará el kernel presente en la memoria principal esperando a ser iniciado. Habitualmente los bootloader de sistemas embebidos desaparecen de la memoria una vez iniciado el sistema. En la siguiente figura 3.1 se muestra de forma esquemática y de seguido, las distintas fases del arranque del sistema que se acaban de explicar.

Figura 3.1 Proceso de arranque del sistema. 3.3 Del bootloader al kernel 21

3.3 Del bootloader al kernel

Cuando el bootloader le pasa el control del sistema al kernel tiene que aportarle cierta información básica al kernel, como la que se presentará a continuación:

• En arquitecturas PowerPC y ARM se le muestra al kernel un número que identifica inequívo- camente el tipo de Soc. • Información básica acerca del hardware detectado hasta el momento, incluyendo al menos la capacidad y localización de la RAM física y la frecuencia de reloj de la CPU. • La línea de comando del kernel. La línea de comandos del kernel es una cadena ASCII simple que controla el comportamiento de Linux, configurando, por ejemplo, el dispositivo que contiene el sistema de archivos raíz. • Opcionalmente la localización y tamaño del binario de un device tree. • Opcionalmente también la localización y el tamaño del initial RAM disk, que es un sistema de archivos temporal usado por el núcleo Linux durante el inicio del sistema. Es usado típicamente para hacer los arreglos necesarios antes de que el sistema de archivos raíz pueda ser montado.

La forma en la que se le proporciona esta información al kernel depende de la arquitectura. Si se quiere obtener más información acerca de esto siempre es una buena idea consultar la documentación correspondiente. La manera en la que se le proporciona esta información al kernel actualmente es principalmente mediante lo llamado árbol de dispositivos (device tree).

3.4 Device trees

Un device tree es una manera flexible de definir los componentes hardware de un sistema. Normal- mente el device tree es cargado por el bootloader y pasado al kernel, pero es posible agrupar el device tree con la imagen del kernel para bootloader que no sean capaz de manejar estos de forma separada. El kernel de Linux contiene un gran número de archivos fuente de device tree en arch/$ARCH/boot/dts. Si se ha adquirido hardware de un tercero muy probablemente se disponga del archivo .dts por el paquete de soporte. El árbol de dispositivos representa al sistema como una colección de componentes unidos en una jerarquía en forma de árbol, con nodos raíz y nodos hijo. Este árbol comienza con un nodo raíz, representado por "/", el cual contiene nodos y nodos hijos que representan el hardware del sistema. Se va a representar un fragmento del device tree generado por Yocto Project para la ZedBoard en el código 3.1 y posteriormente se va a comentar.

Código 3.1 Fragmento del device tree generado por Yocto Project para la ZedBoard.

/dts-v1/;

/{ #address-cells = <0x1>; #size-cells = <0x1>; compatible = "xlnx,zynq-zed", "xlnx,zynq-7000"; model = "Zynq Zed Development Board"; 22 Capítulo 3. Bootloader

cpus { #address-cells = <0x1>; #size-cells = <0x0>;

cpu@0 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x0>; clocks = <0x1 0x3>; clock-latency = <0x3e8>; cpu0-supply = <0x2>; operating-points = <0xa2c2b 0xf4240 0x51616 0xf4240>; };

cpu@1 { compatible = "arm,cortex-a9"; device_type = "cpu"; reg = <0x1>; clocks = <0x1 0x3>; }; };

fpga-full { compatible = "fpga-region"; fpga-mgr = <0x3>; #address-cells = <0x1>; #size-cells = <0x1>; ranges; };

memory@0 { device_type = "memory"; reg = <0x0 0x20000000>; };

};

Se puede observar en el código 3.1 como se tiene un nodo raíz compuesto por un nodo llamado CPUs con dos nodos hijos (que son los dos núcleos que tiene la CPU de la ZedBoard), un nodo que contiene la descripción de la FPGA y otro nodo que describe la memoria RAM. En el árbol de dispositivos mostrado se puede observar nombres con una arroba seguido de una numeración, corchetes dentro de los cuales hay asignaciones de propiedades, etc. En resumen lo que se puede encontrar en un árbol de dispositivos es lo representado en la figura 3.2. Cuando se tiene un nombre_de_nodo@dirección, el cometido de la dirección es la de distinguir ese nodo de otros. Se puede observar en el código 3.1 que en los nodos de la CPU y de la FPGA se encuentra una propiedad llamada compatible que es la que utiliza el kernel de Linux para emparejar el hardware que tiene esta propiedad con sus correspondientes drivers. Se ha dicho con sus correspondientes drivers porque es común encontrarse con que la propiedad compatible tiene varios valores, esto significa que más de un driver pueden manejar esta pieza de hardware. 3.4 Device trees 23

Figura 3.2 Sintaxis básica de los arboles de dispositivos.

3.4.1 La propiedad reg

Los nodos de memoria y CPU tienen la propiedad reg. Esta propiedad nos indica posición inicial en registro de memoria y la longitud que ocupa en el registro. Ambos números se anotan como enteros de 32 bits llamado celdas (cells). Por tanto en el caso del código 3.1 el nodo de memoria señala que solo hay un banco de memoria la cual empieza en 0x0 y tiene 0x20000000 bytes de longitud, que si lo pasamos a sistema decimal son 536870912 bytes, es decir, 512 MiB (mebibyte). Esto que se acaba de comentar es en el caso de sistemas de 32-bits. En sistemas con direccionamiento de 64-bits se vuelve un poco más complejo, ya que se necesitan dos celdas. un ejemplo sería el del código 3.2.

Código 3.2 Ejemplo de fragmento de device tree de sistema de 64 bits.

/{ #address-cells = <2>; #size-cells = <2>; memory@80000000 { device_type = "memory"; reg = <0x00000000 0x80000000 0 0x80000000>; }; }

La información que contiene las celdas requeridas se encuentra en #address-cells y #size-cells, es decir, que para entender la propiedad reg se tendrá que buscar #address-cells y #size-cells. Si no 24 Capítulo 3. Bootloader

están estas dos propiedades definidas tendrán valor 1 por defecto. Los nodos del tipo CPU también tienen direcciones que indican el núcleo de la CPU del que se trata, es decir, el árbol de dispositivos de un procesador de 4 núcleos tendrá direcciones 0, 1, 2 y 3 respectivamente. En este caso como no se tiene profundidad, es decir solo se tienen direcciones de inicio y no se tiene longitud o rango de memoria se tiene en este caso #address-cells = <0x1> y #size-cells = <0x0> en lugar de #address-cells = <0x1> y #size-cells = <0x1> (como se puede observar en el código 3.1), lo que indica que se tiene celda de dirección pero no celda de tamaño.

3.4.2 Phandles e interrupciones

Con lo que se ha explicado hasta el momento se tiene una idea del árbol de dispositivos como una jerarquía de dispositivos hardware que se enlazan con drivers, pero hasta ahora no se ha dicho nada acerca de conexiones de unos dispositivos hardware con otros. Para expresar estas conexiones entre distintos componentes se tienen los phandles, que se pueden entender como punteros a otros nodos.

Código 3.3 Fragmento del device tree para mostrar phandles e interrupciones.

/dts-v1/; { intc: interrupt-controller@48200000 { compatible = "ti,am33xx-intc"; interrupt-controller; #interrupt-cells = <1>; reg = <0x48200000 0x1000>; }; serial@44e09000 { compatible = "ti,omap3-uart"; ti,hwmods = "uart1"; clock-frequency = <48000000>; reg = <0x44e09000 0x2000>; interrupt-parent = <&intc>; interrupts = <72>; }; };

Tomando como ejemplo el código 3.3 podemos observar la propiedad #interrupt-cells = <1> que lo que indica es el número de valores de 4 bytes que son necesarios para representar una línea de inte- rrupción, que en este caso es uno. El phandle se puede encontrar en el nodo serial@44e09000 como interrupt-parent = <&intc>, que indica un puntero al nodo con etiqueta intc. Por esto cuando en el nodo serial@44e09000 se define la propiedad interrupts = <72> solo aparece un número, porque en el nodo al que apunta serial@44e09000, que es el nodo intc: interrupt-controller@48200000 define que solo tendrá un valor de 32 bits.

3.4.3 Inclusión de archivos en los árboles de dispositivos

Los archivos de árboles de dispositivos pueden no presentarse de forma unitaria, sino que pueden estar divididos en varios archivos. Para esto se utilizan los archivos con extensión .dtsi, los cuales son los archivos de inclusión por llamarlos de alguna manera, mientras que los archivos con extensión .dts son los árboles de dispositivos finales. Normalmente los archivos .dtsi contienen definiciones a nivel de SoC (y a veces definiciones comunes a placas casi idénticas), mientras que los archivos .dts aportan la información a nivel de 3.4 Device trees 25 placa. La inclusión del archivo .dtsi se realiza simplemente superponiendo el archivo .dtsi al archivo .dts. En el caso de la ZedBoard, la información sobre el SoC zynq-7000 lo contendría el archivo .dtsi, y la información referente a la ZedBoard lo contendría el archivo .dts. La inclusión del archivo .dtsi dentro del archivo .dts se puede realizar de cualquiera de las 2 formas mostradas en el código 3.4, de la cual se recomienda la segunda de las opciones.

Código 3.4 Inclusión de archivo .dtsi en el archivo .dts.

/include/ "archivo.dtsi"

ó

#include "archivo.dtsi"

Como ejemplo se mostrará un fragmento de archivo .dtsi y la inclusión de este en un archivo .dts y como quedaría el código del .dtb final (si se descompilara para pasarlo a un archivo .dts).

Código 3.5 Fragmento de ejemplo de un .dtsi.

/{ compatible = "ti,am33xx"; [...] ocp { uart0: serial@44e09000 { compatible = "ti,omap3-uart"; reg = <0x44e09000 0x2000>; interrupts = <72>; status = "disabled"; }; }; };

Código 3.6 Fragmento de ejemplo de un .dts que incluye el .dtsi.

#include "am33xx.dtsi"

/{ compatible = "ti,am335x-bone", "ti,am33xx"; [...] ocp { uart0: serial@44e09000 { pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; }; }; 26 Capítulo 3. Bootloader

};

Compilando el archivo .dts del código 3.6 se obtiene el código 3.7 en el archivo .dtb. Notar que el archivo .dtb es un archivo en formato binario, por lo que el código que se ha representado es el texto equivalente al contenido de ese archivo .dtb

Código 3.7 Fragmento de ejemplo de un .dts que incluye el .dtsi.

/{ compatible = "ti,am335x-bone", "ti,am33xx"; [...] ocp { uart0: serial@44e09000 { compatible = "ti,omap3-uart"; reg = <0x44e09000 0x2000>; interrupts = <72>; pinctrl-names = "default"; pinctrl-0 = <&uart0_pins>; status = "okay"; }; }; };

El bootloader y el kernel necesitan de una representación binaria del árbol de dispositivos, por lo tanto el archivo .dts debe compilarse utilizando el compilador de árbol de dispositivos (DTC). El archivo resultante es el llamado binario del árbol de dispositivos como marca su extensión .dtb (device tree binary).

3.4.4 Elección de un bootloader

En la próxima tabla se presentan algunos de los bootloaders más utilizados:

Tabla 3.1 Principales bootloaders.

Nombre Arquitecturas soportadas Das U-Boot ARM, Blackfin, MIPS, PowerPC, SH ARM, Blackfin, MIPS, PowerPC GRUB 2 X86, X86_64 RedBoot ARM, MIPS, PowerPC, SH CFE Broadcom MIPS YAMON MIPS

Para el caso que ocupa se recomienda usar U-Boot ya que como se puede comprobar es la más versátil. Simplemente para introducir U-Boot se mostrará como se descarga el código, como se compila y el resultado de la compilación. Para su instalación copiamos el repositorio y realizamos un checkout a la versión más reciente como se muestra en el código 3.8.

Código 3.8 Obtención del código de U-Boot. 3.4 Device trees 27

angel@ubuntu:~/Desktop$ git clone git://git.denx.de/u-boot.git angel@ubuntu:~/Desktop$ cd u-boot angel@ubuntu:~/Desktop/u-boot$ git checkout v2016.09

Hay muchísimos archivos de configuración para placas típicas dentro del directorio configs/. Normalmente se sabe cuál usar nada más revisando el nombre del fichero de configuración, aunque se puede obtener más información en los archivos README de las placas situadas en el directorio board/. Si la información no queda clara, o se requiere más información, nunca viene mal una búsqueda por Internet o una consulta en algún foro. En el caso de querer compilar el U-Boot para la zedboard, ya que en el directorio configs/ se tiene un archivo de zynq_zed_defconfig el proceso es muy sencillo. Lo único que se requiere es informar a U-Boot del archivo de configuración que se va a utilizar y el croscompilador que se va a utilizar asignando este a la variable CROSS_COMPILE del make.

Código 3.9 Compilación de U-Boot para la zedboard.

angel@ubuntu:~/Desktop/u-boot$ PATH=~/x-tools/arm-cortexa9_neon-linux- gnueabihf/bin:$PATH

angel@ubuntu:~/Desktop/u-boot$ make zynq_zed_config

angel@ubuntu:~/Desktop/u-boot$ make CROSS_COMPILE=arm-cortexa9_neon- linux-gnueabihf-

Notar que se ha añadido al path el croscopilador generado por la toolchain mostrada en el capítulo 2 para que pueda ser utilizado en la compilación de U.-Boot. Tras el último comando del código 3.9 se ejecutará la compilación de U-Boot para nuestro target. Algunos de los archivos importantes generados por la compilación de U-Boot son los siguientes:

• u-boot: U-Boot en formato objeto ELF, adecuado para usar con un programa de depuración. • u-boot.map: La tabla de símbolos • u-boot.bin: U-Boot en formato binario sin procesar, adecuado para ejecutarse en el dispositivo. • u-boot.img: u-boot.bin con un encabezado para ser usado por la memoria de sólo lectura de arranque (boot ROM) para determinar cómo y donde cargar y ejecutar U-Boot. • u-boot.dtb: el device tree.

4 El kernel

n este capítulo se explicará qué es el kernel, que contiene este y como se puede conseguir E y compilar uno para introducirlo en el dispositivo de destino. También se introduciran el concepto de módulo kernel.

4.1 ¿Qué es el kernel?

El kernel es la parte del sistema operativo que se encarga de gestionar los recursos del sistema y de hacer de interfaz con el hardware. Es el que permite que el sistema funcione correctamente y de forma segura, ya que se encarga de proteger fragmentos de memoria para que no puedan ser corrompidos por acceder de forma errónea o intencionada, de manera que se pueda llegar a un estado degradado del funcionamiento de alguna funcionalidad o algún componente hardware (ya que también protege y gestiona el acceso al hardware). En resumen, el kernel tiene 3 tareas:

• Gestionar recursos • Hacer de interfaz con el hardware. • Proveer de una API que permita un nivel de abstracción útil para los programas del espacio de usuario.

En la figura 4.1 se representa de forma gráfica los distintos espacios en los que se pueden dividir un sistema. Las aplicaciones ejecutadas en el espacio de usuario tienen un bajo nivel de privilegio (en ejecución en CPU). Lo que realizan en mayor medida son llamadas a las bibliotecas. En concreto la biblioteca de C se puede decir que es la principal interfaz entre el espacio de usuario y el espacio kernel, ya que traduce las funciones de nivel de usuario (como las definidas por POSIX) a las llamadas al sistema kernel (kernel system calls).

4.2 Elección del kernel

En el caso de que se este creando un sistema operativo para un sistema embebido de manera "artesanal", es decir, construyendo todos los componentes del sistema operativo embebido por separado, se querrá tener un kernel que sea estable y tenga soporte a largo plazo. Se puede obtener el árbol de git estable de Linux con cualquiera de los comandos que se muestran en el código 4.1.

29 30 Capítulo 4. El kernel

Figura 4.1 Representación de espacio de usuario y espacio kernel.

Código 4.1 Descargando el repositorio con versiones estables del kernel de Linux.

$ git clone \ https://kernel.googlesource.com/pub/scm/linux/kernel/git/stable/linux- stable.git

$ git clone \ git://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git

$ git clone \ https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux-stable.git

Tras esto se puede hacer un git checkout a una versión particular de Linux. Por lo general, el kernel estable se mantiene solo hasta la próxima versión principal, es decir, de 8 a 12 semanas más tarde. Para atender a aquellos usuarios que desean actualizaciones durante un período de tiempo más largo y tener la seguridad de que se detectarán y corregirán los errores, algunos núcleos se etiquetan a largo plazo y se mantienen por dos o más años. Hay al menos un kernel a largo plazo cada año. Si está construyendo para un producto que tendrá que mantenerse durante este período de tiempo, el último kernel a largo plazo disponible podría ser una buena 4.2 Elección del kernel 31 opción. La versiones del kernel que se mantienen a largo plazo se pueden revisar en la dirección https://www.kernel.org/. Llegados a este punto se puede pensar que lo que queda es fácil, descargar el kernel que se quiera, configurarlo e introducirlo en el dispositivo objetivo, pero la realidad es que eso no siempre es posible. De hecho, Linux solo tiene soporte sólido para un pequeño subconjunto de la infinidad de dispositivos que pueden funcionar con Linux. También se podrá encontrar soporte para la placa objetivo o SoC de proyectos independientes de código abierto, como Yocto Project, por ejemplo. Pero muchas veces es casi obligatorio tener que consultar con el proveedor del SoC o placa para un kernel funcional. En el momento de escritura del documento una de las versiones de kernel mantenidas a largo plazo es la versión 4.9.61. Por tanto se apuntará a esta versión como se indica en el código 4.2.

Código 4.2 Cambiando a la versión del kernel que apunta ala rama 4.9.61.

angel@ubuntu:~/Desktop/linux-stable$ git checkout v4.9.61 Checking out files: 100% (32386/32386), done. Note: checking out 'v4.9.61'.

You are in 'detached HEAD' state. You can look around, make experimental changes and commit them, and you can discard any commits you make in this state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may do so (now or later) by using -b with the checkout command again. Example:

git checkout -b

HEAD is now at 5caae9d... Linux 4.9.61 angel@ubuntu:~/Desktop/linux-stable$ git branch * (HEAD detached at v4.9.61) master

Una vez actualizado a la rama deseada se puede echar un vistazo a lo que incluye este repositorio. Los principales directorios de interés son los siguientes:

• arch: Contiene archivos específicos de cada arquitectura contando con un directorio por cada arquitectura. • Documentation:Contiene ,como su propio nombre indica, documentación acerca del kernel. Siempre que se quiera tener cierta información acerca de algún aspecto de Linux se debe buscar en esta carpeta. • drivers: Contiene device drivers. Contiene un directorio por cada tipo de driver. • fs: Contiene código del filesystem. • include: Contiene archivos de cabecera del kernel, incluyendo también los que se necesitan para la construcción de la toolchain. • init: Contiene código de inicio del kernel. 32 Capítulo 4. El kernel

• kernel: Contiene funciones principales, como bloqueo, los temporizadores (timers), adminis- tración de energía, etc. • mm: Contiene la gestión de memoria. • net: Contiene protocolos de red. • scripts: Contiene muchos scripts utililes como el compilador de arboles de dispositivos (dtc). • tools: Contiene muchas herramientas utiles como por ejemplo la herramienta de contadores de rendimiento de Linux llamada .

4.3 Configuración del kernel

Uno de los puntos fuertes de Linux es el grado de personalización y configuración que tiene, ya que se puede configurar un kernel de manera que se oriente para el dispositivo más sencillo que uno pueda imaginar hasta el más complejo. El mecanismo de configuración se llama Kconfig, y el sistema de construcción con el que esta integrado se llama Kbuild. La documentación de ambós se puede consultar en Documentation/k- build. Estos dos mecanismos son utilizados también en otros proyectos, por ejemplo U-Boot y crosstool-NG, como ya se ha visto en capítulos anteriores. Las opciones de configuración se declaran en una jerarquía de archivos denominada Kconfig utilizando una sintaxis descrita en Documentation/kbuild/kconfig-language.txt. Es decir, se pueden definir las configuraciones mediante la modificación de archivos y después la lectura de estos y la producción de un archivo .config, que como indica el punto antes del nombre es un archivo oculto. Hay muchas maneras de leer los archivos Kconfig y generar el archivo .config, algunas de ellas muestran menús en pantalla que permiten hacer elecciones de una manera interactiva. Este es el caso de menuconfig, que quizas sea el más utilizado, pero también se podrían utilizar xconfig o gconfig. Para lanzar el menú de configuración se realiza como se indica en el código 4.3. Hay que tener en cuenta que se le debe especificar la arquitectura para la que se va a configurar, llamando a unos de los nombres de los directorios que contiene el directorio arch.

Código 4.3 Comando para ejecutar la interfaz de configuración del kernel.

angel@ubuntu:~/Desktop/linux-stable$ make ARCH=arm menuconfig

Configurar el kernel de cero puede ser un trabajo que no es razonable. Sería mas razonable empezar con alguna configuración parecida a la que se quiere, que ya esté construida buscando en arch/$ARCH/configs. Cada archivo contiene una configuración apropiada para un SoC o grupo de SoCs. En este caso, si se quiere construir un kernel para la ZedBoard el directorio en el que buscar será arch/arm/configs, ya que la arquitectura de la CPU de la ZedBoad es arm. La ZedBoard cuenta con un procesador dual core Cortex A9 que cuenta con arquitectura Armv7-A. Por tanto, se puede seleccionar el archivo multi_v7_defconfig que es una configuración realizada para una amplia variedad de SoCs que utilizan la arquitectura Armv7-A. Por lo tanto solo habrá que ejecutar el comando que se muestra en el código 4.4.

Código 4.4 Comando para ejecutar la creación del .config.

angel@ubuntu:~/Desktop/linux-stable$ make ARCH=arm multi_v7_defconfig 4.4 Módulos kernel 33

HOSTCC scripts/kconfig/conf.o HOSTLD scripts/kconfig/conf # # configuration written to .config #

Se puede obtener la versión del kernel que se ha creado con el comando que se muestra en el código 4.5.

Código 4.5 Comando para revisar que version del kernel se ha construido.

angel@ubuntu:~/Desktop/linux-stable$ make kernelversion 4.9.61

#

4.4 Módulos kernel

Se va a realizar una breve introducción acerca de los módulos kernel, los cuales se volverán a nombrar repetidas veces a lo largo del documento. Los módulos kernel (kernel modules en inglés) son piezas de código que se vinculan dinámi- camente con el kernel en tiempo de ejecución, extendiendo así la funcionalidad del kernel. Estas funcionalidades se cargan en tiempo de ejecución según las características requeridas y el hardware detectado (ya que se pueden conectar nuevos dispositivos una vez encendido el sistema). Si no fuera así, todos los controladores y funciones deberían estar vinculados estáticamente al kernel y cargados en el kernel de manera permanente, lo que podría hacer al kernel alcanzar un tamaño mucho mayor. Además los módulos kernel eliminan la necesidad de recompilar todo el kernel si se le quiere añadir a este una nueva funcionalidad. También se pueden añadir funcionalidades como módulos kernel para aliviar la carga de arranque y por tanto que el arranque se produzca más velozmente, dejando los drivers y funcionalidades que no sean esenciales para ser cargados más tarde.

4.5 Compilando

El sistema de construcción (en este caso también se le puede llamar compilación) del kernel Kbuild lo forman un conjunto de Makefiles que reunen la información que se presenta en el archivo .config, resuelven las dependencias y compilan todo lo que sea necesario para crear una imagen del kernel, que contiene todos los componentes enlazados de forma estática y posiblemente uno o más modulos kernel.

4.5.1 Compilando la imagen del kernel

Lo primero que hay que tener en cuenta es lo que cada bootloader requiere. En el caso de U-Boot, que es el bootloader que se ha utilizado en el capítulo anterior, requiere una imagen con nombre uImage, aunque las últimas versiones de U-Boot permiten cargar un archivo zImage usando el comando bootz. La mayoría de los demás bootloaders requieren imagen tipo zImage. Crear estos dos tipos de imagen es muy sencillo. Se comenzará con la imagen zImage, que se puede compilar como se muestra en el código 4.6. 34 Capítulo 4. El kernel

Código 4.6 Creación de zImage.

angel@ubuntu:~/Desktop/linux-stable$ PATH=~/x-tools/arm-cortexa9_neon- linux-gnueabihf/bin:$PATH angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- zImage

Notar que se tiene que tener agregado a la variable de entorno PATH el binario que permite utilizar el croscompilador creado con la toolchain. Con la asignación en el comando del código 4.6" -j 4" se le indica al sistema cuantos hilos de procesamiento se quieren dedicar a la tarea de compilación de la imagen, en este caso 4 hilos. Se puede comprobar la ubicación de zImage en la última línea que aparece tras ejecutar el comando de compilación, que es la que se muestra en el código 4.7.

Código 4.7 Ubicación de zImage.

Kernel: arch/arm/boot/zImage is ready

Para crear una imagen tipo uImage se puede tener un poco más de problemas. Existe un pequeño problema con la creación de un archivo uImage para la arquitectura ARM con soporte multipla- taforma. El soporte multiplataforma para ARM se introdujo en Linux 3.7 y permite que un solo binario de kernel se pueda ejecutar en múltiples plataformas. El objetivo de esto es poder tener solo una pequeña cantidad de núcleos para todos los dispositivos ARM. El kernel selecciona la plataforma correcta leyendo el número de máquina o el árbol de dispositivos que le pasa el gestor de arranque. El problema se debe a que la ubicación de la memoria física puede ser diferente para cada plataforma, por lo que la dirección de reubicación del kernel (normalmente 0x8000 bytes desde el inicio de la memoria RAM física) también puede ser diferente. La dirección de reubicación está codificada en el encabezado uImage por el comando mkimage cuando se genera el kernel, pero fallará si hay más de una dirección de reubicación para elegir. Por decirlo de otra manera, el formato uImage no es compatible con imágenes multiplataforma. Aún así se puede crear un binario uImage a partir de una compilación multiplataforma, siempre y cuando proporcione el LOADADDR del SoC particular en el que espera iniciar este kernel. Puede encontrar la dirección de carga mirando en mach- [SoC] /Makefile.boot y anotando el valor de zreladdr-y. En el caso de la ZedBoard, se tiene un SoC ZYNQ™-7000 SOC XC7Z020-CLG484-1, es decir, se tendrá que abrir el archivo arch/arm/mach-zynq/Makefile.boot pero este no existe, pero navegando un poco por la carpeta arch/arm/mach-zynq se puede observar un archivo llamado common.c, que dentro contiene unas líneas como las que se muestran en el código 4.8.

Código 4.8 Fragmento del archivo common.c.

static void __init zynq_memory_init(void) { if (!__pa(PAGE_OFFSET)) memblock_reserve(__pa(PAGE_OFFSET), 0x80000); }

Tal y como se muestra en el código 4.8, se especifica el offset que se debe dejar de memoria, por lo que el comando final que se debe ejecutar para la ZedBoard es el que se muestra en el código 4.9. 4.5 Compilando 35

Código 4.9 Creación de uImage.

angel@ubuntu:~/Desktop/linux-stable$ PATH=~/x-tools/arm-cortexa9_neon- linux-gnueabihf/bin:$PATH angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- LOADADDR=0x80000 uImage

Tras esto se habrá creado el archivo uImage. En la compilación del kernel se crean dos archivos entre otros, los cuales se llaman , que es el kernel como binario ELF y System. En la mayoría de casos, los bootloaders no pueden manejar codigos tipos ELF (con extensión .elf ) de manera directa, los archivos adecuados para los distintos bootloaders se despliegan en el directorio arch/$ARCH/boot y son los siguientes:

• Image: Es vmlinux convertido a binario puro. • zImage: Es una versión comprimida de Image, lo que implica que el bootloader tiene que llevar a cabo la descompresión. • uImage: Es zImage con un encabezado de U-Boot de 64 bits.

Cuando la compilación da errores es útil ver en linea de comandos justo lo que se esta ejecutando en cada momento, lo que se puede activar colocando, como se muestra en el código 4.10, la asignación V=1 en el comando para compilar la imagen.

Código 4.10 Compilación mostrandose cada línea de ejecución.

angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- LOADADDR=0x80000 V=1 uImage [...] make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE= arch/arm/ boot/Image make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE= arch/arm/ boot/zImage make -f ./scripts/Makefile.build obj=arch/arm/boot/compressed arch/arm/ boot/compressed/vmlinux make -f ./scripts/Makefile.build obj=arch/arm/boot MACHINE= arch/arm/ boot/uImage make -f ./scripts/Makefile.build obj=arch/arm/boot/compressed arch/arm/ boot/compressed/vmlinux /bin/bash ./scripts/mkuboot.sh -A arm -O linux -C none -T kernel -a 0 x80000 -e 0x80000 -n 'Linux-4.9.61' -d arch/arm/boot/zImage arch/arm /boot/uImage "mkimage" command not found - U-Boot images will not be built arch/arm/boot/Makefile:79: recipe for target 'arch/arm/boot/uImage' failed make[1]: *** [arch/arm/boot/uImage] Error 1 arch/arm/Makefile:329: recipe for target 'uImage' failed make: *** [uImage] Error 2 36 Capítulo 4. El kernel

4.5.2 Compilando arboles de dispositivos

Tras realizar la compilación del kernel el siguiente paso sería compilar el árbol o árboles de dispositivos (si se tiene una construcción multiplataforma). Para realizar la compilación de el árbol o árboles se tiene que ejecutar el comando que se muestra en el código 4.11.

Código 4.11 Compilación de arboles de dispositivos.

angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- dtbs

Tras realizar la compilación se puede comprobar que se han generado muchos arboles de disposi- tivos. Pensando en la ZedBoard se tendrá que intentar buscar el árbol de dispositivos específico para esta placa. Haciendo una búsqueda dentro de los archivos que se acaban de generar se puede encontrar de forma sencilla el archivo que se busca, como se muestra en el código 4.12.

Código 4.12 Búsqueda de arbol de dispositivos para introducir en la ZedBoard.

angel@ubuntu:~/Desktop/linux-stable$ ls arch/arm/boot/dts/ | grep zed zynq-zed.dtb zynq-zed.dts

4.5.3 Compilando módulos kernel

Si en la configuración se ha indicado crear alguna funcionalidad como un módulo kernel se pueden construir de manera separada de la forma que se indica en el código 4.13.

Código 4.13 Compilación de módulos kernel.

angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- modules

Los módulos kernel tienen extensión .ko (kernel object). Aunque estos están dispersos una vez creados se puede utilizar el modificador de make modules_install para instalarlos en el lugar correc- to. La ubicación predeterminada es /lib/modules en el sistema de desarrollo, que muy probablemente no es lo que se quiera, por ello, para instalarlo en el area del root filesystem se le proporciona un valor a la variable INSTALL_MOD_PATH. El comando final quedaría como el que se muestra en el código 4.14.

Código 4.14 Instalación de módulos kernel en el lugar deseado.

angel@ubuntu:~/Desktop/linux-stable$ make -j 4 ARCH=arm CROSS_COMPILE= arm-cortexa9_neon-linux-gnueabihf- INSTALL_MOD_PATH=$HOME/rootfs modules_install

4.5.4 Limpiando fuentes del kernel

Se tienen tres tipos de make para la limpieza de archivos fuente del kernel. 4.5 Compilando 37

• clean: Elimina los archivos objeto y la mayoría de los archivos intermedios. • mrproper: Elimina todos los archivos intermedios incluyendo el archivo .config. Devuelve el directorio al estado justo después de clonarlo. El nombre es un poco humorístico ya que Mr. Proper es un famoso producto de limpieza en varias partes del mundo. • distclean: Realiza la misma limpieza que mrproper pero además elimina archivos de copia de seguridad del editor, y otros archivos sobrantes.

5 El sistema de archivos raiz

n el presente capítulo se mostrará el cometido del sistema de archivos raíz dentro de Linux E embebido, así como su estructura, su contenido y una introducción al modo de proceder para iniciar la población de sus directorios, así como algunos conceptos nuevos como pueden ser los permisos de acceso a archivos.

5.1 Contenido del sistema de archivos raiz

Una vez que se inicie el sistema el kernel buscará un sistema de archivos raíz (root filesystem) y ejecutará el primer programa, que por defecto se nombra como init. Una vez en este punto el trabajo del kernel ha concluido y ahora dependerá del programa init empezar a procesar scripts, arrancar otros programas llamando a las funciones en la biblioteca de C, lo que se traduce en llamadas al sistema kernel. Para construir un sistema de archivos raíz útil este deberá contener al menos los siguientes elementos:

• init: Es el programa que inicia todo, generalmente ejecutando una serie de scripts. • shell: Es necesario para tener la capacidad de ejecutar líneas de comando, y lo que es más importante, para ejecutar los scripts de shell que son llamados por el programa init o cualquier otro programa. • daemons: Varios programas de servidor, llamados por init. • librerías: En general los programas están enlazados con librerías que se deben encontrar en el sistema de archivos raíz, a no ser que absolutamente todo esté enlazado de forma estática. • Archivos de configuración: La configuración para init y otras funcionalidades queda guarda- da en una serie de archivos de texto en código ASCII que se suelen encontrar en el directorio /etc. • Nodos de dispositivos: Los archivos especiales que dan acceso a varios controladores de dispositivos. • Directorios /proc y /sys: Dos pseudo sistemas de archivos que representan estructuras de datos del kernel como una jerarquía de directorios y de archivos. Muchos programas y funciones de biblioteca leen estos archivos. • Módulos kernel: Si se han configurado algunas funciones de kernel para que se construyan como módulos estos se encontrarán normalmente en la ubicación /lib/modules/.

39 40 Capítulo 5. El sistema de archivos raiz

Además de esto, en el sistema de archivos raíz se incluyen distintas aplicaciones del sistema (los distintos programas) que hacen que el sistema pueda realizar el trabajo que se requiere de él.

5.2 Estructura del directorio

Linux no se preocupa del diseño de los directorios y archivos más allá de la existencia del programa init, por lo que los distintos programas y archivos se pueden situar a priori donde se desee. Sin embargo, muchos programas esperan que algunos archivos se encuentren en ciertos lugares, además es de ayuda para los desarrolladores si los dispositivos usan un diseño de directorios similar. El diseño básico de un sistema Linux se define en el llamado Filesystem Hierarchy Standard (FHS). Este estándar cubre todas las implementaciones de sistemas operativos Linux. Generalmente los sistemas embebidos cuentan con los siguientes directorios:

• /bin: Aquí se encuentran programas esenciales para todos los usuarios. • /etc: Contiene archivos de configuración. • /dev: Contiene nodos de dispositivos y otros archivos especiales. • /lib: Se encuentran librerías compartidas. • /proc: Aquí se encuentra el sistema de archivos proc. • /sbin: Contiene programas esenciales para el administrador del sistema. • /sys: El sistema de archivos . • /usr: Como mínimo este directorio debería contener /usr/lib, /usr/bin y /usr/sbin, que con- tienen programas, librerías y utilidades del administrador adicionales. • /tmp: Directorio donde se archivan archivos temporales. • /var: Una jerarquía de directorios y archivos que pueden ser modificados en tiempo de ejecución, como por ejemplo mensajes de registro.

La principal diferencia entre /bin y /sbin es que /sbin no necesita ser incluido en la ruta de búsqueda para usuarios root. También hay que comentar que /usr puede estar en una partición separada del sistema de archivos raíz, por lo que no debe contener nada que sea necesario para iniciar el sistema. Una buena manera de empezar sería creando un directorio con la jerarquía que se ha mostrado, quedando el directorio similar al de la figura 5.1.

Código 5.1 Creando jerarquía de directorio rootfs.

angel@ubuntu:~/Desktop$ mkdir rootfs angel@ubuntu:~/Desktop$ cd rootfs/ angel@ubuntu:~/Desktop/rootfs$ mkdir bin dev etc home lib proc sbin sys tmp usr var angel@ubuntu:~/Desktop/rootfs$ mkdir usr/bin usr/lib usr/sbin var/log

5.3 Permisos de acceso POSIX a archivos

Todos los procesos que se están ejecutando en el sistema operativo pertenecen a un usuario y a uno o más grupos. El usuario queda identificado por un número de 32 bits llamado ID de usuario o UID. 5.4 Programas para el sistema de archivos raíz 41

Figura 5.1 Jerarquía de sistema de archivos raiz de ejemplo.

La información de asignaciones de ID a usuarios se encuentra guardada en el directorio /etc/passwd. Por otro lado los grupos quedan identificados con la GID, que queda registrado en /etc/group.El super user se llama así dado que, por defecto, tiene la capacidad de pasar la mayoría de revisiones de permisos, pudiendo modificar al antojo (solo con ciertas restricciones) el sistema. El sistema de seguridad en Linux se basa en restringir ciertas operaciones solo al super-usuario. Cada archivo y directorio tiene también un propietario. El nivel de acceso que tiene un proceso a un archivo o directorio está controlado por un conjunto de indicadores de permisos de acceso llamado el modo del archivo. Se compone de tres números en octal (3 bits cada número) de los cuales, el primero representa los permisos para el propietario del archivo, el segundo indica qué permisos se les da a los miembros del mismo grupo que el propietario del archivo y el tercer número representa los permisos que se le conceden a todos los demás usuarios. Se puede ver un ejemplo en la figura 5.2 de cómo quedaría la representación de los permisos de un archivo para que tenga permisos de lectura, escritura y ejecución para el propietario del archivo, lectura y ejecución para los miembros del mismo grupo que el propietario del archivo y simplemente permiso de lectura para todos los demás usuarios. Es de vital importancia, por motivos de seguridad y estabilidad, prestar atención a la propiedad y los permisos de los archivos que se colocarán en el dispositivo de destino, ya que por ejemplo en el caso de que el dispositivo reciba un ataque, si tenemos muchas funcionalidades disponibles para usuarios no root todas estas quedan fácilmente a disposición del atacante.

5.4 Programas para el sistema de archivos raíz

En esta sección se indicará de manera introductoria como poblar el sistema de archivos raíz con los programas necesarios, librerías, archivos de configuración y demás archivos para que el sistema pueda operar.

5.4.1 El programa init

Como se ha visto con anterioridad, el programa init es el primero que se ejecutará, y por ello tendrá PID 1. Se ejecuta como super-usuario, por tanto tiene acceso máximo a los recursos del sistema. En 42 Capítulo 5. El sistema de archivos raiz

Figura 5.2 Ejemplo de representación de permisos de archivo.

general ejecuta scripts de shell que inicia daemons: un daemon es un programa que se ejecuta en segundo plano, sin conexión a un terminal, se podría llamar también un programa de servidor. 5.4.2 Shell

Es necesario un intérprete de comandos (Shell) para ejecutar scripts y poder introducir líneas de comandos para poder interactuar con el sistema. Es probable que un shell interactivo no sea necesario en un sistema embebido, pero será útil para el desarrollo, depuración y mantenimiento del sistema. Hay varios intérpretes de comandos de uso común en sistemas embebidos. Se muestran a conti- nuación tres de ellos: • bash: Es el intérprete más común en Linux de escritorio. Es muy potente. • ash: Es una solución intermedia entre bash y hush. Busibox tiene una versión de ash exten- dida para que sea más compatible con bash. Aún así es mucho más pequeño que bash. • hush: Es un intérprete de comandos muy pequeño, que es de utilidad en sistemas donde la memoria se convierte en un serio problema. En el caso de que se utilice ash o hush probar si funcionan los comandos de bash y no dar por supuesto esto, ya que es un error común. 5.4.3 Utilidades

El intérprete de comandos no es más que una funcionalidad para lanzar otros programas y un script de shell es poco más que una lista de programas que ejecutar. Para que un intérprete de comandos sea útil necesita programas en los que se basa la línea de comandos de UNIX. Incluso en un sistema pequeño, hay aproximadamente unas 50 "utilidades", por tanto buscar el código fuente de esas utilidades y compilarlo de forma cruzada para el dispositivo de destino podría ser un poco tedioso, además de que el resultado de esto ocuparía varias decenas de megabytes, lo que, no hace demasiado tiempo, era una cantidad de bytes bastante importante, y más si se habla de sistemas embebidos. Para solucionar el problema que se acaba de mencionar nació Busybox. 5.4 Programas para el sistema de archivos raíz 43

5.4.4 Busybox

Busybox es un programa que combina muchas utilidades estándares de Unix en un solo ejecutable pequeño. Provee la mayoría de utilidades esenciales que están diseñadas para los sistemas UNIX, además de muchas utilidades de las que suelen disponer sistemas GNU/Linux. Busybox generalmente se utiliza en sistemas que funcionen desde disco flexible o sistemas embebidos. En su momento fue definido como "la navaja suiza de los sistemas de Linux embebido". Por ejemplo, para leer un archivo se puede lanzar seguido de la funcionalidad que se quiere utilizar, seguido de los argumentos que esa funcionalidad espera. En el caso de leer un archivo de texto el comando a ejecutar sería $ busybox cat archivo.txt. También se puede utilizar el comando busybox sin argumentos para tener una lista de las funcio- nalidades que incluye. La manera anterior de, por ejemplo, utilizar la funcionalidad cat no es la más adecuada. Es más inteligente crear un enlace simbólico entre /bin/cat y /bin/busybox, así cuando se ejecuta el comando cat lo que realmente se ejecuta es busybox, que revisa la cola del argumento pasado en argv[0], que será /bin/cat, extrae el nombre de la aplicación (cat) y exporta la función principal de la funcionalidad.

5.4.5 Construyendo Busybox

Busybox utiliza los Kconfig y Kbuild que se han explicado en capítulos anteriores. Se puede obtener Busybox clonando el repositorio y actualizando a la versión más reciente como se muestra en el código 5.2.

Código 5.2 Clonando Busybox y actualizando a la rama más reciente.

angel@ubuntu:~/Desktop$ git clone git://busybox.net/busybox.git Cloning into 'busybox'... remote: Counting objects: 102512, done. remote: Compressing objects: 100% (25969/25969), done. remote: Total 102512 (delta 82070), reused 94692 (delta 75919) Receiving objects: 100% (102512/102512), 19.88 MiB | 4.29 MiB/s, done. Resolving deltas: 100% (82070/82070), done. Checking connectivity... done. angel@ubuntu:~/Desktop$ cd busybox/ angel@ubuntu:~/Desktop/busybox$ git branch -r origin/0_60_stable origin/1_00_stable origin/1_00_stable_10817 origin/1_10_stable origin/1_11_stable origin/1_12_stable origin/1_13_stable origin/1_14_stable origin/1_15_stable origin/1_16_stable origin/1_17_stable origin/1_18_stable origin/1_19_stable origin/1_1_stable 44 Capítulo 5. El sistema de archivos raiz

origin/1_20_stable origin/1_21_stable origin/1_22_stable origin/1_23_stable origin/1_24_stable origin/1_25_stable origin/1_26_stable origin/1_27_stable origin/1_3_stable origin/1_4_stable origin/1_5_stable origin/1_6_stable origin/1_7_stable origin/1_8_stable origin/1_9_stable origin/HEAD -> origin/master origin/master angel@ubuntu:~/Desktop/busybox$ git checkout 1_27_stable Branch 1_27_stable set up to track remote branch 1_27_stable from origin . Switched to a new branch '1_27_stable' angel@ubuntu:~/Desktop/busybox$ git branch * 1_27_stable master

Tras esto se debe configurar Busybox, partiendo en este caso de la configuración por defecto, la cual activa muchas de las funciones de Busybox. Para llegar a este punto se tendrán que ejecutar los comandos $ make distclean y $ make defconfig. Mas tarde se podrá ejecutar $ make menuconfig para ajustar la configuración. En el menú de configuración es donde se indica donde se quiere instalar Busybox, accediendo a BusyBox settings y luego en la categoría BusyBox instalation prefix dentro de ...Installation Options. En el caso que ocupa por ejemplo, como el directorio tiene la ubicación /home/angel/Desktop/rootfs/, es este el valor del que se ha dotado al campo nombrado anteriormente dentro de las opciones de instalación. Tras esto podremos croscompilar de la manera habitual como se muestra en el código 5.3.

Código 5.3 Croscompilando BusyBox.

angel@ubuntu:~/Desktop/busybox$ PATH=~/x-tools/arm-cortexa9_neon-linux- gnueabihf/bin:$PATH angel@ubuntu:~/Desktop/busybox$ make -j 4 ARCH=arm CROSS_COMPILE=arm- cortexa9_neon-linux-gnueabihf-

El resultado de la operación realizada en el código 5.3 es BusyBox como ejecutable. Ya solo queda instalarlo, lo que se realiza con el comando $make install que realiza la copia del binario en el directorio que se ha configurado en opciones de instalación y crea los enlaces simbólicos pertinentes. 5.4 Programas para el sistema de archivos raíz 45

5.4.6 Librerías para el sistema de archivos raiz

Ya se habló un poco de las librerías en la sección 2.3.4. Con el conocimiento adquirido en esa sección ya se podrá entrever lo que se realizará a continuación. Los programas están, por norma general enlazados con librerías compartidas, por tanto para poder utilizar las funcionalidades que proporciona BusyBox serán necesarias también algunas al menos de estas librerías. Se pueden, o copiar todas las librerías de la toolchain, o ver las librerías y el runtime linker que hacen falta para ejecutar BusyBox. Sea como fuere viene bien recordar que se puede revisar donde se encuentra la ubicación sysroot donde la toolchain vuelca las librerías con el comando que se muestra en el código 5.4. Dentro de este directorio se encontrarán las librerías buscadas en los directorios lib/ y usr/lib.

Código 5.4 Ubicación del directorio sysroot de la toolchain.

angel@ubuntu:~/Desktop$ arm-cortexa9_neon-linux-gnueabihf-gcc -print- sysroot /home/angel/x-tools/arm-cortexa9_neon-linux-gnueabihf/arm-cortexa9_neon- linux-gnueabihf/sysroot

Se debe tener en cuenta que si no se tienen demasiados problemas con el almacenamiento es más sencillo y menos tedioso copiar todas las librerías, ya que muy probablemente no excederán los 40 MiB.

5.4.7 Nodos de dispositivos

La mayoría de dispositivos en Linux están representados por nodos de dispositivos, como ya se ha comentado, de acuerdo a la filosofía de Linux de tratar todo como un archivo. La ubicación general de estos dispositivos es en el directorio /dev, y los nodos se crean ejecutando el comando $ mknod , donde tipo se refiere a "c" para character devices y "b" para block devices. Se puede encontrar una lista de major y minor numbers standares en Documentation/devices.txt. En principio se necesitará crear un nodo de dispositivo para todos los dispositivos que se quieran acceder desde el sistema. Para BusyBox se necesitarán crear dos dispositivos en el directorio rootfs de destino y se pueden crear como se indica en el código 5.5.

Código 5.5 Creando los character devices null y console.

angel@ubuntu:~/Desktop/rootfs$ sudo mknod -m 666 dev/null c 1 3 [sudo] password for angel: angel@ubuntu:~/Desktop/rootfs$ sudo mknod -m 600 dev/console c 5 1

Es importante dar los permisos que se indican en el código 5.5, es decir, para el dispositivo null se permite la lectura y escritura para cualquier usuario, mientras que para el dispositivo console se permite lectura y escritura pero solo para el propietario del dispositivo, es decir, para el usuario root (super-usuario).

5.4.8 El sistema de archivos proc y sys/

Como ya se ha comentado con anterioridad proc y sysfs son dos pseudo archivos que representan el funcionamiento interno del kernel. Ambos representan datos del kernel en una jerarquía de 46 Capítulo 5. El sistema de archivos raiz

directorios y es una forma más para interactuar con los controladores de dispositivos y otros códigos del kernel. Estos dos pseudo archivos deben ser montados como se indica en el código 5.6, donde también se observa como una vez montado se han desplegado directorios y archivos en los correspondientes directorios.

Código 5.6 Montando proc y sysfs en los directorios ya creados proc/ y sysfs.

angel@ubuntu:~/Desktop/rootfs$ sudo mount -t sysfs sysfs sys angel@ubuntu:~/Desktop/rootfs$ sudo mount -t proc proc proc angel@ubuntu:~/Desktop/rootfs$ ls proc/ 1 15 1797 214 245 265 403 924 kmsg 10 151 18 2142 246 266 42 928 kpagecgroup 1000 1524 18163 2146 247 267 425 929 kpagecount 1017 1526 1817 215 248 268 43 930 kpageflags 10215 1538 1818 2153 249 269 44 932 loadavg 1032 1566 18183 216 25 27 45 935 locks 1045 1568 1826 217 250 2767 453 946 mdstat 1053 1571 1828 218 251 28 46 947 meminfo 109 1583 1829 219 252 2957 47 952 misc 11 1591 1830 22 253 2962 48 953 modules 111 1597 1840 220 25328 2965 49 956 mounts 112 16 1843 221 25362 2966 50 975 mpt 113 1601 1856 222 254 2969 51 976 mtrr 114 1608 1866 223 25414 297 52 acpi net 115 1614 1872 224 25451 2972 53 asound pagetypeinfo 1159 1629 1880 2243 25475 299 54 buddyinfo partitions 116 1632 1884 225 255 30 58 bus sched_debug 117 1639 1889 226 25547 31 59 schedstat 118 1649 19 227 25585 316 6 cmdline scsi 119 1651 1904 228 25589 318 60 consoles self 12 1656 1906 229 256 32 61 cpuinfo slabinfo 120 1657 1913 230 25634 320 64 crypto softirqs 121 1664 1916 231 25663 323 65 devices stat 122 1665 1966 232 25664 33 66 diskstats swaps 123 1673 2 233 25673 34 67 dma sys 1243 1689 20 234 257 341 68 driver sysrq-trigger 13 1696 201 235 258 346 682 execdomains sysvipc 130 1697 203 236 25827 347 7 fb thread-self 1307 1698 208 237 25879 36 7952 filesystems timer_list 1317 1701 209 238 259 3629 7953 fs timer_stats 14 1705 2097 239 25912 37 7954 interrupts tty 1400 1707 21 24 25940 372 7955 iomem uptime 1410 1708 210 240 26 374 8 ioports version 1413 1710 211 241 260 38 8081 irq version_signature 1430 1714 2115 2412 261 39 9 kallsyms vmallocinfo 1431 1732 212 242 262 398 906 kcore vmstat 1437 1737 213 243 263 4 909 keys zoneinfo 1439 1779 2135 244 264 40 922 key-users 5.5 Transfiriendo el sistema de archivos raíz al dispositivo de destino 47

angel@ubuntu:~/Desktop/rootfs$ ls sys/ block class devices fs kernel power bus dev hypervisor module

La acción realizada en el código 5.6 se debe realizar pero en el dispositivo de destino una vez iniciado, por tanto los comandos a ejecutar en el dispositivo de destino serán $ mount -t proc proc /proc y $ mount -t sysfs sysfs /sys. El pseudo archivo proc lleva en Linux desde el inicio de este, siendo su propósito original mostrar información en el espacio de usuario acerca de los procesos. Para este cometido se encuentran los directorios con números que se pueden observar en el código 5.6, siendo cada número un PID. Como se puede observar hay muchos otros directorios, los cuales también contendrán información acerca del nombre que queda asignado a cada directorio. El pseudo archivo sysfs exporta una jerarquía ordenada de archivos acerca de los dispositivos que existen y como están conectados entre sí.

5.4.9 Módulos kernel

Ya se ha hablado anteriormente de módulos kernel y se sabe que estos deben estar instalados en el root filesystem del dispositivo de destino. Así que solo queda recordar que esta acción se puede ejecutar como ya se vio en el código 4.14.

5.5 Transfiriendo el sistema de archivos raíz al dispositivo de destino

Una vez que ya tenemos el esqueleto del sistema de archivos raíz poblado con los elementos que se han comentado llega la hora de transferirlo al dispositivo de destino. Hay varias posibilidades para esto, de las cuales se van a indicar 3 de ellas:

• ramdisk: es una imagen del sistema de archivos el cual es cargado en la RAM por el bootloader. Estos son fáciles de crear y además no dependen de los drivers de almacenamiento masivo. Se puede utilizar en modo mantenimiento mientras necesite actualizarse el sistema de archivos raíz principal. Además se puede usar como sistema de archivos raíz principal en sistemas pequeños como lo son muchos sistemas embebidos. El contenido de la RAM es volátil por lo que se requerirá de otro tipo de almacenamiento para guardar datos permanentes, como los parámetros de configuración. • disk image: Una copia formateada del sistema de archivos raíz para cargarse en el almacena- miento masivo del dispositivo de destino. En el caso de la ZedBoard por ejemplo se utilizará formato EXT4 y se cargará en la tarjeta SD. Se carga en la memoria flash a través del gestor de arranque. • network filesystem: El directorio puede ser exportado a través de un servidor NFS y montarlo en el dispositivo de destino en el momento de arranque. Esto se hace a menudo durante la fase de desarrollo para evitar los ciclos de copia en el dispositivo del almacenamiento masivo e inserción de este en el dispositivo, ya que es un proceso un poco lento.

Con lo explicado hasta el momento sobre el sistema de archivos raíz es suficiente para la com- prensión de la estructura del mismo, su funcionamiento y su importancia en el sistema final. No se explicarán más detalles ya que no es el objetivo del presente documento mostrar el proceso a seguir para crear un Linux embebido creando cada parte del sistema por separado, ya que solo si se cuenta la creación del sistema de archivos raíz ya es tedioso. Por ello se mostrará en el siguiente capítulo la funcionalidad de Yocto Project, ya que permite la creación de Linux embebido personalizado de una manera más sencilla, o al menos más compacta, ya que permite crear de manera más o 48 Capítulo 5. El sistema de archivos raiz

menos automática todos los componentes que se han mencionado en estos capítulos modificando, básicamente, archivos de configuración e indicando al sistema las funcionalidades a incluir. Quizás uno de los pocos problemas de Yocto Project sea su elevada curva de aprendizaje, la cual se intentará suavizar lo máximo posible en este documento. 6 Yocto Project

ste capítulo tratará sobre Yocto Project que es el software utilizado en este proyecto para la E creación de Linux para la ZedBoard. Se mostrará su funcionamiento, como tener todos los elementos que este necesita y como personalizarlo con distintos elementos.

6.1 ¿Qué es Yocto Project?

Yocto Project es un proyecto de colaboración de código abierto que proporciona plantillas, herra- mientas y métodos para facilitar la creación de sistemas personalizados basados en Linux para productos embebidos, independientemente de la arquitectura del hardware. Fue fundada en 2010 como una colaboración entre muchos fabricantes de hardware, proveedores de sistemas operativos de código abierto y empresas de electrónica para poner algo de orden en el caos del desarrollo de Linux embebido. Como proyecto de código abierto, Yocto Project funciona con una estructura de gobierno jerárquica basada en la meritocracia y administrada por su arquitecto jefe, Richard Purdie, un becario de la Fundación Linux. Esto permite que el proyecto permanezca independiente de cualquiera de sus organizaciones miembro, que participan de diversas maneras y proporcionan recursos al proyecto. En cuanto al funcionamiento de Yocto Project de manera muy resumida se puede esquematizar como un programador de tareas llamado Bitbake que crea lo que quiera que hayamos configurado atendiendo a lo que llamaremos unas recetas. Por tanto la filosofía de Yocto Project se basa en crear un entorno Linux general, el cual para cambiar de máquina destino a priori solo hay que modificar en la configuración la máquina para la que crear el entorno, por lo tanto es altamente exportable a otros dispositivos de destino. Se aclarará esto a lo largo del documento.

6.2 Instalación de Yocto Project

Para trabajar con Yocto Project es necesario hacerlo en una de las siguientes distribuciones:

• Ubuntu • Fedora • openSUSE • CentOS •

En este proyecto se ha utilizado Ubuntu 16.04.2 LTS virtualizado en VMware Workstation 14 Player.

49 50 Capítulo 6. Yocto Project

Una vez instalado uno de estos sistemas Yocto Project funcionará si tenemos instalados los siguientes componentes

• Git 1.7.8 o superior • tar 1.24 o superior • Python 2.7.3 o superior excluyendo Python 3.x, que no esta soportado.

Para comprobar las versiones de los componentes anteriores se hizo a través de los siguientes scripts en bash:

Código 6.1 Comprobación de las versiones de los componentes. $ git --version git version 2.7.4

$ python -V Python 2.7.12

$ tar --version tar (GNU tar) 1.28 Copyright 2014 Free Software Foundation, Inc. License GPLv3+: GPL de GNU versión 3 o posterior Esto es software libre: usted es libre de cambiarlo y redistribuirlo. No hay NINGUNA GARANTÍA, hasta donde permite la ley.

Escrito por John Gilmore y Jay Fenlason.

En el Código 6.1 se han incluido tanto los comandos como las respuestas a ellos del sistema. Tras la comprobación se puede seguir realizando la instalación. Lo siguiente a realizar es la ins- talación de los paquetes que se especifican como necesarios para el correcto funcionamiento de Yocto Project en el manual de referencia del mismo. En el momento de escritura del presente documento esta información se encuentra en el apartado de System Requirements de la dirección http://www.yoctoproject.org/docs/current/ref-manual/ref-manual.html. Según este apartado y en el momento de la escritura de este documento los comandos que se deben introducir en la terminal de Ubuntu son los siguientes:

Código 6.2 Instalación de paquetes necesarios para poder construir el sistema.

$ sudo apt-get install gawk wget git-core diffstat unzip texinfo gcc- multilib \ build-essential chrpath socat cpio python python3 python3-pip python3- pexpect \ xz-utils debianutils iputils-ping

$ sudo apt-get install libsdl1.2-dev xterm

$ sudo apt-get install make xsltproc docbook-utils fop dblatex xmlto 6.3 Configuaración de Yocto Project 51

$ sudo apt-get install python-git

Lo que se debe hacer después es obtener Yocto Project en sí. Se puede obtener clonando el repositorio como se muestra a continuación. Se debe clonar el repositorio más reciente siempre que sea posible:

Código 6.3 Clonación del repositorio de Yocto Project.

$ git clone git://git.yoctoproject.org/poky

Tras realizar la acción anterior se recomienda hacer un $ git checkout a la rama más reciente esta- ble que esté disponible, la cual se puede encontrar en la dirección https://wiki.yoctoproject.org/wiki/Releases. Se realiza como se muestra a continuación:

Código 6.4 Actualización a la rama estable más reciente del proyecto.

$ git checkout -b pyro origin/pyro

6.3 Configuaración de Yocto Project

Además tendremos que instalar una capa que tenga soporte para la ZedBoard. Por tanto tendremos que instalar la capa de soporte de dispositivos y placas de Xilinx para Yocto Project. Tanto esta capa como muchas más se pueden encontrar en la dirección https://layers.openembedded.org/layerindex- /branch/master/layers/ Para abreviar se dejará a continuación el comando a introducir en el terminal una vez que se encuentre en el directorio /poky:

Código 6.5 Instalación de la capa de Xilinx para Yocto Project.

angel@ubuntu:~/poky$ git clone -b pyro https://github.com/Xilinx/meta- xilinx

A continuación se detallará como realizar la configuración de Yocto Project para un proyecto en este software. Para poder empezar se necesitará estar en el directorio /poky e iniciar el entorno de desarrollo como se muestra a continuación:

Código 6.6 Cambio al directorio poky e inicialización del entorno de desarrollo.

$ cd poky $ source oe-init-build-env nuevo_proyecto

El comando source es un comando que realiza la ejecución de un archivo y guarda los cambios realizados en las variables de entorno, que por facilitar la comprensión son las variables globales del sistema. Además tras haber utilizado utilizado el comando source se puede observar que Yocto Project ha creado una nueva carpeta llamada nuevo_proyecto y nos ha movido a ella. Esto es porque en la segunda línea del código 6.6 como último argumento se ha puesto este nombre. Si se hubiera dejado vacío este último argumento crearía por defecto una carpeta llamada build y nos movería 52 Capítulo 6. Yocto Project

a ella. Si revisamos la ubicación en la que se encuentra el sistema y que hay dentro de la nueva carpeta aparece algo similar a lo siguiente:

Código 6.7 Revisión de localización de nuevo_proyecto y su contenido.

angel@ubuntu:~/poky/nuevo_proyecto$ pwd /home/angel/poky/nuevo_proyecto

angel@ubuntu:~/poky/nuevo_proyecto$ ls conf

Se puede observar que se ha creado un directorio llamado conf en el que se encuentran los siguientes archivos:

• local.conf: Contiene especificaciones del dispositivo para el que se va a construir y sobre el entrono de desarrollo. • bblayers.conf: Contiene la lista de las capas que se van a utilizar. • templateconf.cfg: Contiene el nombre de un directorio que contiene varios archivos de configuración (.conf ). Por defecto apunta a meta-yocto/conf.

Lo siguiente es modificar el archivo local.conf. Lo que necesitamos es decirle a Yocto Project para qué máquina se va a construir. Para ello se debe buscar la máquina que esté descomentada y comentarla y tras esto introducir la línea mostrada en el código 6.8 en el archivo, ya para la ZedBoard no existe ningún comando comentado en el archivo que podamos descomentar para poder elegir este sistema.

Código 6.8 Configuración de la máquina.

MACHINE ?= "zedboard-zynq7"

El nombre de la máquina se puede encontrar en el archivo poky/meta-xilinx/conf/machine/zc702- zynq7.conf Tras realizar esto ya se le ha indicado a Yocto Project para qué máquina se va a realizar la construcción del entorno. Seguidamente tendremos que incluir en el archivo bblayers.conf que se encuentra dentro del directorio poky/nuevo_proyecto/conf las capas que sean necesarias para construir el entorno, por lo tanto, si por ejemplo se necesitan las siguientes capas

• meta • meta-skeleton • meta-xilinx • meta-poky • meta-yocto • meta-selftest • meta-yocto-bsp

el archivo bblayers.conf tendrá que quedar como sigue 6.4 Capas 53

Código 6.9 Configuración del listado de las capas.

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers. conf # changes incompatibly POKY_BBLAYERS_CONF_VERSION = "2"

BBPATH = "${TOPDIR}" BBFILES ?= ""

BBLAYERS ?= " \ /home/angel/poky/meta \ /home/angel/poky/meta-skeleton\ /home/angel/poky/meta-selftest \ /home/angel/poky/meta-poky \ /home/angel/poky/meta-yocto \ /home/angel/poky/meta-yocto-bsp \ /home/angel/poky/meta-xilinx \ "

No importa el orden en el que estén incluidos pero sí es importante que entre las dobles comillas y las capas no haya líneas en blanco, es decir, saltos de línea en blanco, porque si no, cuando se intente construir un entorno o una aplicación de este directorio intente ver las dependencias con las capas arrojará un error. Se puede comprobar qué capas se tienen incluida en el entorno de desarrollo introduciendo el comando $ -layers show-layers como sigue:

Código 6.10 Comprobación de las capas en el entorno de desarrollo de nuevo_proyecto.

angel@ubuntu:~/poky/nuevo_proyecto$ bitbake-layers show-layers layer path priority ======

meta /home/angel/poky/meta 5 meta-poky /home/angel/poky/meta-poky 5 meta-yocto-bsp /home/angel/poky/meta-yocto-bsp 5 meta-selftest /home/angel/poky/meta-selftest 5 meta-skeleton /home/angel/poky/meta-skeleton 1 meta-xilinx /home/angel/poky/meta-xilinx 5 meta-yocto-bsp /home/angel/poky/meta-yocto-bsp 5

6.4 Capas

Los metadatos (datos con información sobre otros datos) se estructuran en Yocto Project en capas, cada una con meta al principio de su nombre. Las capas núcleo de Yocto Project son:

• meta: Este es el núcleo de OpenEmbedded. • meta-yocto: Metadatos específicos de Yocto Project, incluida la distribución poky. 54 Capítulo 6. Yocto Project

• meta-yocto-bsp: Contiene los paquetes de soporte a las disintas placas y maquinas de Yocto Project.

Hay muchas más capas que son lanzadas por distintos distribuidores. Si se descargan nuevas capas solo se tendrá que revisar si son compatibles con nuestra versión de Yocto Project.

6.4.1 Creación de una nueva capa

Para crear una nueva capa solo habrá que moverse a la carpeta poky e introducir el siguiente comando.

Código 6.11 Creando una capa llamada meta-prueba.

$ scripts/yocto-layer create prueba

Una vez introducido este comando nos saldrá por pantalla para que elijamos las siguientes opciones:

Código 6.12 Opciones en la creación de una nueva capa.

Please enter the layer priority you'd like to use for the layer: [ default: 6] Would you like to have an example recipe created? (y/n) [default: n] Would you like to have an example bbappend file created? (y/n) [default: n] New layer created in meta-prueba. Don't forget to add it to your BBLAYERS (for details see metanova\README ).

Como pone en el código anterior, en la antepenúltima línea, se ha creado una capa llamada meta-prueba en el directorio poky. Lo último que nos dice el código anterior es que no se sebe olvidar meter esta capa en el archivo .bblayers.conf, por lo tanto agregamos esta nueva capa al final de nuestro archivo .bblayers.conf.

Código 6.13 Añadiendo nuestra nueva capa meta-zedboard a bblayers.conf.

# POKY_BBLAYERS_CONF_VERSION is increased each time build/conf/bblayers. conf # changes incompatibly POKY_BBLAYERS_CONF_VERSION = "2"

BBPATH = "${TOPDIR}" BBFILES ?= ""

BBLAYERS ?= " \ /home/angel/poky/meta \ /home/angel/poky/meta-skeleton\ /home/angel/poky/meta-selftest \ /home/angel/poky/meta-poky \ 6.4 Capas 55

/home/angel/poky/meta-yocto \ /home/angel/poky/meta-yocto-bsp \ /home/angel/poky/meta-xilinx \ /home/angel/poky/meta-prueba \ "

En las opciones del código 6.12 se eligió lo siguiente.

Código 6.14 Opciones elegidas en la creación de una nueva capa.

Please enter the layer priority you'd like to use for the layer: [ default: 6] Would you like to have an example recipe created? (y/n) [default: n] y Please enter the name you'd like to use for your example recipe: [ default: example] holamundo Would you like to have an example bbappend file created? (y/n) [default: n] y Please enter the name you'd like to use for your bbappend file: [default : example] prueba Please enter the version number you'd like to use for your bbappend file (this should match the recipe you're appending to): [default: 0.1]

New layer created in meta-prueba.

Don't forget to add it to your BBLAYERS (for details see meta-prueba/ README).

Siendo la entrada de una pulsación a Intro la elección de la opción por defecto. Se ha aceptado crear una receta ejemplo con nombre holamundo y un bbapend de ejemplo con nombre prueba para ver de qué forma se deben estructurar las capas y las recetas. En la figura 6.1 se puede observar que la capa contiene tres directorios, uno que contiene la configuración de la capa, otro que contiene la receta con los archivos necesarios (recipes-example) y un último directorio con el apéndice de las recetas (recipes-example-bbappend), que como su propio nombre indica son recetas que se añaden o modifican las del directorio de las recetas "normales"(recipes-example). Revisando el archivo de configuración layer.conf dentro del directorio conf encontramos el siguiente código.

Código 6.15 Contenido de archivo layer.conf.

# We have a conf and classes directory, add to BBPATH BBPATH .= ":${LAYERDIR}"

# We have recipes-* directories, add to BBFILES BBFILES += "${LAYERDIR}/recipes-*/*/*.bb \ ${LAYERDIR}/recipes-*/*/*.bbappend"

BBFILE_COLLECTIONS += "prueba" BBFILE_PATTERN_prueba = "^${LAYERDIR}/" 56 Capítulo 6. Yocto Project

Figura 6.1 Estructura de capa creada.

BBFILE_PRIORITY_prueba = "6"

En el código 6.15 se puede leer en las dos primeras líneas como realiza la agregación del directorio de la capa (eso es lo que significa LAYERDIR) al path. Las siguientes líneas agregan las recetas y los apéndices a los archivos de recetas. Los "*" significan cualquier carácter o combinación de caracteres, lo que quiere decir que se agregan todos los .bb y .bbappend contenidos en cualquier directorio de un solo nivel por debajo de los directorios llamados recipe-* (que significa recipe- cualquier cosa), es por esto que cuando se quieran crear recetas nuevas y archivos a los que llaman estas recetas se tiene que seguir una estructura como la de la figura 6.1, ya que los archivos de configuración que se generan por defecto suponen una estructura de este tipo. En las últimas líneas se agrega a la colección de capas la capa prueba, que es como hemos llamado a a la nueva capa creada y le da prioridad 6 a la capa. La prioridad 6 es la máxima y lo que permite la prioridad es cancelar recetas de otras capas con menor prioridad. El ejemplo de receta que ha creado Yocto Project es una receta holamundo_0.1.bb que realiza compilación cruzada (ya que compila para otra máquina distinta a la maquina en la que se está trabajando) de un programa (helloword.c) y la instala en el dispositivo de destino como se verá en el código de la receta. El código de la receta holamundo_0.1.bb es el siguiente:

Código 6.16 Contenido de la receta holamundo_0.1.bb.

# This file was derived from the 'Hello World!' example recipe in the # Yocto Project Development Manual. #

SUMMARY = "Simple helloworld application" SECTION = "examples" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835 ade698e0bcf8506ecda2f7b4f302" 6.4 Capas 57

SRC_URI = "file://helloworld.c"

S = "${WORKDIR}"

do_compile() { ${CC} helloworld.c -o helloworld }

do_install() { install -d ${D}${bindir} install -m 0755 helloworld ${D}${bindir} }

El código fuente necesario lo marca SRC_URI que en este caso indica que el archivo necesario es helloworld.c. Si hicieran falta más archivos para ejecutar la receta se podrían meter los archivos necesarios en el mismo directorio que helloworld.c llamandolos dentro de SRC_URI. Observando el código 6.16 la variable S indica donde se va a compilar el programa (en este caso en WORKDIR), do_compile realiza la compilación cruzada (${CC}) de helloword.c en un archivo objeto llamado helloword. Tras lo anterior se procede a la instalación del archivo objeto en el dispositivo de destino creando con la primera línea dentro del do_install el directorio usr/bin en los archivos raíz del dispositivo de destino (la ubicación de archivos raíz en el dispositivo de destino viene indicado por ${D}. Y tras crear el directorio se instala en él. Si se quisiera una receta que hiciera compilación cruzada de varios archivos e instalara los ejecutables en los archivos raíz del dispositivo de destino el archivo que contiene el código 6.16 quedaría de la siguiente manera:

Código 6.17 Receta para instalar distintos ejecutables en el dispositivo.

# This file was derived from the 'Hello World!' example recipe in the # Yocto Project Development Manual. #

SUMMARY = "Simple helloworld application" SECTION = "examples" LICENSE = "MIT" LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835 ade698e0bcf8506ecda2f7b4f302"

SRC_URI = "file://helloworld.c \ file://file2.c \ file://file3.c \ file://file4.c "

S = "${WORKDIR}"

do_compile() { ${CC} helloworld.c -o helloworld 58 Capítulo 6. Yocto Project

${CC} file2.c -o file2 ${CC} file3.c -o file3 ${CC} file4.c -o file4 }

do_install() { install -d ${D}${bindir} install -m 0755 helloworld ${D}${bindir} install -m 0755 file2 ${D}${bindir} install -m 0755 file3 ${D}${bindir} install -m 0755 file4 ${D}${bindir} }

En el caso que se quiera que el sistema genere solamente el resultado de la receta creada en la capa, se deberá ir a la carpeta de trabajo, por ejemplo, si es nuevo_proyecto, introduciendo el comando $ source oe-init-build-env nuevo_proyecto, y una vez allí utilizar el comando bitbake seguido por un espacio y por la receta que se quiera ejecutar, en este caso holamundo. Si se realiza la acción anterior Yocto Project emitirá el siguiente error:

Figura 6.2 Error al ejecutar la receta.

La figura 6.2 lo que indica es que no hay una receta válida en el .bbapend. En este caso como es un .bbapend de ejemplo no hace nada, así que se debe eliminar el directorio completo poky/meta- pruebba/recipes-example-bbapend para que no arroje errores el sistema. Una vez eliminado el di- rectorio y ejecutando de nuevo el comando, se realizará la compilación cruzada creando un directorio de trabajo para él en poky/zedboard/tmp/work/cortexa9hf-neon-poky-linux-gnueabi/holamundo/ y desplegará los paquetes en poky/zedboard/tmp/deploy/rpm/cortexa9hf_neon/holamundo-0.1- r0.cortexa9hf_neon.rpm como se puede comprobar a través de los comandos del código 6.18:

Código 6.18 Comprobación de la existencia de los paquetes generados.

angel@ubuntu:~/poky/zedboard$ ls tmp/work/cortexa9hf-neon-poky-linux- gnueabi | grep holamundo holamundo angel@ubuntu:~/poky/zedboard$ ls tmp/deploy/rpm/cortexa9hf\_neon | grep holamundo holamundo-0.1-r0.cortexa9hf_neon.rpm holamundo-dbg-0.1-r0.cortexa9hf_neon.rpm holamundo-dev-0.1-r0.cortexa9hf_neon.rpm

Aun ejecutando la receta todavía no es parte de la imagen (aunque estén desplegados los paquetes en poky/zedboard/tmp/deploy/rpm/cortexa9hf_neon/holamundo-0.1-r0.cortexa9hf_neon.rpm . Pa- 6.4 Capas 59 ra que este ejecutable sea construido e incluido en la imagen es necesario añadir el nombre de la receta que lo ha creado en el archivo conf/local.conf con el comando que sigue:

Código 6.19 Linea de código necesaria para instalación del paquete en la imagen.

IMAGE_INSTALL_append = " holamundo"

Es muy importante que no se olvide el espacio entre las dobles comillas y el nombre del primer paquete que introduzcamos en el la línea de código. El problema de realizar esta opción vía archivo conf/local.conf es que, como su propio nombre indica, es local y por tanto no es la mejor opción si se quiere crear una imagen que va a ser compartida con otros desarrolladores. Para ello es mejor la opción de guardar esos cambios en lo llamado una receta de imagen (images recipe). Se puede obtener la lista de imágenes disponibles mediante el siguiente comando una vez situados en el directorio /poky:

Código 6.20 Listado de recetas de imágenes disponibles para ejecutar.

$ ls meta*/recipes*/images/*.bb

Para crear una imagen que contenga instalados los paquetes (que contienen los programas) que se necesitan, lo que se debe hacer es crear dentro de la capa un directorio teniendo, en el caso que se está poniendo como ejemplo, la dirección meta-prueba/recipes-images/images y dentro de este directorio crear un archivo llamado, por ejemplo y para ser coherentes, zedboard-image.bb donde se especifique como va a ser construida la imagen. En el caso de que se necesite construir una imagen como la que se construye al ejecutar el comando $ bitbake core-image-minimal pero con el paquete o los paquetes que contienen los ejecutables que se necesiten instalados en la imagen, se procedería introduciendo el siguiente código en el archivo zedboard-image.bb dentro del directorio meta-prueba/recipes-images/images:

Código 6.21 Linea de código necesaria en zedboard-image.bb para instalación del paquete en la imagen.

require recipes-core/images/core-image-minimal.bb IMAGE_INSTALL += "holamundo segundo_paquete tercer_paquete"

En la receta anterior estamos indicando con require recipes-core/images/core-image-minimal.bb que se llame a la ejecución de la receta core-image-minimal y con IMAGE_INSTALL += "hola- mundo segundo_paquete tercer_paquete" que instale en esa imagen los paquetes generados por las recetas holamundo, segundo_paquete y tercer_paquete. Como se ha podido observar, para desplegar los paquetes que generan recetas siempre se llama a los nombres de las recetas que generan esos paquetes. No confundir esto con incluir en el IMAGE_INSTALL += nombres de programas que se quieran croscompilar u otros nombres, siempre se llama a nombres de recetas. Estos paquetes se deben haber generado ejecutando anteriormente bitbake . Para que se realice la construcción de una imagen tipo core-image-minimal y se instalen los paquetes generados por las recetas "holamundo segundo_paquete tercer_paquete" solo tendremos que ejecutar el comando bitbake zedboard-image, que es como se llama la receta que se ha creado para construir ese sistema. Notar además que generando la receta del código 6.21 y ejecutándola no 60 Capítulo 6. Yocto Project

hace falta que se haya ejecutado anteriormente las recetas "holamundo segundo_paquete tercer_- paquete" de la forma $ bitbake holamundo, $ bitbake segundo_paquete y $ bitbake zedboard- image para generar los paquetes que se requieren por la receta zedboard-image para la instalación, sino que aun sin haber generado los paquetes, como esta última receta los requiere ella misma llamará a esas recetas y las ejecutará para poder construir el sistema, así que es una forma de automatizar la creación del sistema disminuyendo los comandos a ejecutar.

6.5 Modulos kernel

Ya se hizo una breve introducción sobre los módulos kernel en la sección 4.4, explicando qué eran, por qué se utilizaban y cuando convenía su utilización. En esta sección se ampliará cierta información sobre estos y se explicará la manera general de construirlos y de obtener información acerca de ellos. Para recordar, un módulo kernel es simplemente una pieza de código que se vincula dinámicamente con el kernel en tiempo de ejecución, extendiendo así la funcionalidad del kernel. Los módulos kernel se almacenan en el directorio /lib/modules/$(uname -r)/kernel (el comando $ uname -r devuelve la versión actual del kernel) como se puee comprobar en el código 6.22.

Código 6.22 Localización de módulos kernel.

angel@ubuntu:~$ cd /lib/modules/$(uname -r)/kernel angel@ubuntu:/lib/modules/4.10.0-38-generic/kernel$ find . -name "*.ko" [...] ./crypto/khazad.ko ./crypto/sha3_generic.ko ./crypto/keywrap.ko ./crypto/chacha20poly1305.ko ./mm/hwpoison-inject.ko ./mm/z3fold.ko ./drivers/target/target_core_mod.ko ./drivers/target/target_core_user.ko ./drivers/target/target_core_pscsi.ko ./drivers/target/iscsi/iscsi_target_mod.ko ./drivers/target/iscsi/cxgbit/cxgbit.ko ./drivers/target/tcm_fc/tcm_fc.ko ./drivers/target/target_core_file.ko ./drivers/target/target_core_iblock.ko ./drivers/target/sbp/sbp_target.ko [...]

A continuación se muestran comandos para obtener información acerca de los módulos.

• Para mostrar los módulos del núcleo cargados actualmente: $ lsmod • Para mostrar información sobre un módulo: $ modinfo nombre_del_módulo • Para listar las opciones que se establecen para un módulo cargado: $ systool -v -m nombre_- del_módulo • Para mostrar la configuración completa de todos los módulos: $ modprobe -c | less 6.5 Modulos kernel 61

• Para mostrar la configuración de un módulo en particular: $ modprobe -c | grep nombre_- del_módulo • Listar las dependencias de un módulo (o alias), incluido el propio módulo: $ modprobe –show-depends nombre_del_módulo

Se va a presentar y comentar la información que se obtiene por ejemplo del comando lsmod.

Código 6.23 Ejecutando el comando lsmod.

angel@ubuntu:~$ lsmod Module Size Used by [...] snd_ac97_codec 131072 1 snd_ens1371 gameport 16384 1 snd_ens1371 aesni_intel 167936 0 ac97_bus 16384 1 snd_ac97_codec snd_pcm 102400 2 snd_ac97_codec,snd_ens1371 aes_x86_64 20480 1 aesni_intel crypto_simd 16384 1 aesni_intel snd_seq_midi 16384 0 snd_seq_midi_event 16384 1 snd_seq_midi snd_rawmidi 32768 2 snd_seq_midi,snd_ens1371 glue_helper 16384 1 aesni_intel cryptd 24576 3 crypto_simd,ghash_clmulni_intel,aesni_intel snd_seq 65536 2 snd_seq_midi_event,snd_seq_midi snd_seq_device 16384 3 snd_seq,snd_rawmidi,snd_seq_midi snd_timer 32768 2 snd_seq,snd_pcm vmw_balloon 20480 0 [...]

En el código 6.23 se pueden observar 4 columnas. La primera, llamada Module, hace referencia al nombre del módulo del cual se muestra información. La segunda, llamada Size, informa sobre el número de bytes que está ocupando en memoria el módulo. La tercera columna, llamada Used, informa sobre si hay algún otro módulo que esté usando este. Y la última columna, llamada by (en realidad podría también decirse que tiene tres columnas y la última es Used by), informa sobre el nombre de los módulos que están utilizando este.

6.5.1 Compilando modulos kernel con Yocto Project

En esta sección primero se explicará como construir módulos kernel de manera general, es decir, para el sistema host, y luego se extrapolará lo explicado a la construcción de módulos kernel para el sistema objetivo con Yocto Project. La compilación de los módulos kernel se puede hacer de forma sencilla con un archivo Makefile como el que se muestra en el código 6.24.

Código 6.24 Archivo Makefile que realiza la construcción de módulos kernel.

obj-m += hola-mod.o 62 Capítulo 6. Yocto Project

KVERSION = $(shell uname -r)

all: make -C /lib/modules/$(KVERSION)/build M=$(PWD) modules

clean: make -C /lib/modules/$(KVERSION)/build M=$(PWD) clean

#rm -fr *.o *.ko *.mod *.symvers *.order

La primera línea del código código 6.24 realiza la compilación de un archivo llamado hola-mod.c en el archivo hola-mod.o y deja este a disposición compilarse como hola-mod.ko cuando se realiza la acción de la línea 4 (contando solo las líneas escritas) de construir los módulos. Si hubiera más módulos que crear se introducirían las líneas que fueran necesarias, exactamente iguales a la primera, con los nombres de los módulos kernel que se quieran construir (que tienen que tener los mismos nombres que los archivos .c exceptuando, claro está, la extensión). El símbolo "+=" significa añadir a lo que ya haya asignado a esa variable lo que viene a continuación del símbolo. En la segunda línea se crea una variable y se le asigna un valor que surge de ejecutar el comando uname -r en la consola, es decir, que esta variable va a contener la versión del kernel de nuestro sistema host. En la tercera línea se ve all:, que es la acción por defecto del make si no se indica nada más, es decir, que cuando estando dentro del directorio donde se encuentra este Makefile se ejecute el comando make en la terminal sin indicar alguna otra cosa, la línea que sigue a all: será la que se ejecute. La cuarta línea, que como se acaba de comentar, será la que se ejecute por defecto, y indica que se ejecute el Makefile situado en el directorio /lib/modules/$(KVERSION)/build y compile módulos, como se indica al final de la línea, desplegándolos en el directorio actual, como indica el fragmento de línea M=$(PWD). La variable PWD (print working directory) almacena el directorio actual. En la quinta línea se indica la opción clean: que es la opción que se ejecuta cuando en la terminal se ejecuta el comando $ make clean y los dos puntos indican que lo que se ejecuta es lo que viene a continuación. La sexta línea es muy parecida a la tercera, solo que ahora lo que se ejecuta es el algoritmo al que llama clean del Makefile del directorio /lib/modules/$(KVERSION)/build, aplicándolo al directorio de trabajo actual. La última línea, la cual está comentada, es otra opción que se podría haber puesto en lugar de la línea anterior y que lo que ejecutaría es la eliminación de los archivos con extensión .o, .ko, .mod, .symvers, y .order del directorio actual. Ahora lo que se necesita es un archivo hola-mod.c que compilar para mostrar el funcionamiento del Makefile. Se ha creado para ello un programa muy sencillo para utilizarse como ejemplo. Es importante notar que en la capa de Yocto Project meta-skeleton se encuentran "esqueletos" de ejemplo de capas y recetas de ejemplo, por lo que es un buen lugar para buscar cómo construir capas y recetas de distintos tipos cuando no se tiene mucho conocimiento al respecto.

Código 6.25 Contenido del archivo hola-mod.c.

#include #include 6.5 Modulos kernel 63

static int hola_init(void){ printk(KERN_ALERT "Hola, mundo =)\n");

return 0; }

static void hola_exit(void){ printk(KERN_ALERT "Adios, mundo cruel =(\n"); }

module_init(hola_init); module_exit(hola_exit); MODULE_LICENSE("GPL");

En el programa mostrado en el código 6.25 se puede observar una gran similitud con un programa tipo holamundo habitual. Lo primero que se puede notar como diferencia es que se llama a librerías relacionadas con los módulos, que permiten entre otras cosas realizar la inicialización de los mismos y luego la salida de estos. Las funciones son estáticas, es decir, que solo son accesibles desde dentro del módulo y no son compartidas. Se puede observar que se utiliza la función printk, que lo que hace es mostrar información en el kernel. Por último se puede observar cómo se realiza la asignación de la función de inicio y la función de salida. Con este archivo ya se puede realizar la construcción de un módulo kernel, simplemente ejecutando el comando make en la terminal.

Código 6.26 Compilación del módulo kernel.

angel@ubuntu:~/Desktop/modulos-prueba$ make make -C /lib/modules/4.10.0-38-generic/build M=/home/angel/Desktop/ modulos-prueba modules make[1]: Entering directory '/usr/src/linux-headers-4.10.0-38-generic' CC [M] /home/angel/Desktop/modulos-prueba/hola-mod.o Building modules, stage 2. MODPOST 1 modules CC /home/angel/Desktop/modulos-prueba/hola-mod.mod.o LD [M] /home/angel/Desktop/modulos-prueba/hola-mod.ko make[1]: Leaving directory '/usr/src/linux-headers-4.10.0-38-generic' angel@ubuntu:~/Desktop/modulos-prueba$ ls hola-mod.c hola-mod.ko hola-mod.mod.c hola-mod.mod.o hola-mod.o Makefile modules.order Module.symvers

Se puede observar en el código 6.26 como se ha generado el archivo con extensión .ko, que es el archivo que ya permite ser instalado como un módulo kernel.

Código 6.27 Instalación , comprobación y desinstalación de módulo kernel. 64 Capítulo 6. Yocto Project

angel@ubuntu:~/Desktop/modulos-prueba$ sudo insmod hola-mod.ko angel@ubuntu:~/Desktop/modulos-prueba$ lsmod Module Size Used by hola_mod 16384 0 rfcomm 77824 0 vmw_vsock_vmci_transport 28672 2 bnep 20480 2 vsock 36864 3 vmw_vsock_vmci_transport uvcvideo 90112 0 videobuf2_vmalloc 16384 1 uvcvideo videobuf2_memops 16384 1 videobuf2_vmalloc [...] angel@ubuntu:~/Desktop/modulos-prueba$ sudo rmmod hola_mod

En el código 6.27 se puede observar como se ha realizado la instalación del módulo kernel, tras lo que se ha comprobado si se ha instalado de manera correcta, comprobando que sí, con el nombre hola_mod, y tras esto se ha desinstalado. Dado que en el archivo hola-mod.c que se compiló se indicó que tanto en la instalación como en la desinstalación del módulo kernel se mostrara un mensaje de alerta en el kernel, deberían aparecer estos mensajes, cosa que se puede comprobar ejecutando los comandos dmesg o sudo tail -f /var/log/syslog como se muestra en los códigos 6.28 y 6.29.

Código 6.28 Comprobación de alertas del kernel con dmesg.

angel@ubuntu:~$ dmesg [...] [ 310.501271] Hola, mundo =) [ 321.262518] Adios, mundo cruel =( [...]

Código 6.29 Comprobación de alertas del kernel con tail -f /var/log/syslog.

angel@ubuntu:~$ sudo tail -f /var/log/syslog [sudo] password for angel: [...] Nov 12 15:14:04 ubuntu kernel: [ 310.501271] Hola, mundo =) Nov 12 15:14:15 ubuntu kernel: [ 321.262518] Adios, mundo cruel =( [...]

Ya que se tienen nociones básicas sobre qué es un módulo kernel, cómo funciona y cómo se utiliza y se crea se pasará a indicar cómo se construyen módulos kernel en Yocto Project para el dispositivo de destino. La estructura de una capa ya es conocida, por lo que no se entrará en detalle, solo hay que tener en cuenta que es muy similar a lo explicado en la sección 6.4, solo que ahora, en la carpeta donde se sitúan los archivos a utilizar, además de hallarse los archivos que se deseen croscompilar, se encontrará un Makefile, similar al que se ha explicado varios párrafos atrás. La estructura de una capa para la creación de módulos kernel se puede ver en la figura 6.3. Como se puede comprobar en la figura 6.3 hay archivos con terminación "~". Estos son los archivos sin la terminación "~" antes del último guardado. Cuando se vuelvan a hacer modificaciones 6.5 Modulos kernel 65 y se guarde el archivo se sobrescribirá el archivo antes de la última modificación en el archivo con terminación "~". Se pueden eliminar si se quiere, si no se eliminan, Yocto Project simplemente los ignorará. También se puede comprobar en la figura 6.3 como se ha utilizado la palabra driver en los nombres tanto del archivo .c como en la receta. Esto es un adelanto a lo que se explicará seguidamente en el documento, ya que los módulos kernel se utilizan con mucha frecuencia para la instalación de drivers. Es decir, muchos drivers son instalados como módulos kernel.

Figura 6.3 Estructura capa de construcción de módulos kernel.

Los archivos con extensión .c son los archivos que se quieren compilar como módulos de kernel. Se echará un vistazo al contenido de los archivos Makefile y a la receta con extensión .bb.

Código 6.30 Contenido del Makefile.

obj-m := mydriver.o

SRC := $(shell pwd)

all: $(MAKE) -C $(KERNEL_SRC) M=$(SRC)

modules_install: $(MAKE) -C $(KERNEL_SRC) M=$(SRC) modules_install

clean: rm -f *.o *~ core .depend .*.cmd *.ko *.mod.c rm -f Module.markers Module.symvers modules.order rm -rf .tmp_versions Modules.symvers

Comenzando con el archivo Makefile que se presenta en el código 6.30, se tiene un archivo muy parecido al mostrado por ejemplo en el código 6.24, por lo tanto su entendimiento será inmediato. Notar que en la línea obj-m := mydriver.o se debe nombrar al archivo objeto exactamente igual 66 Capítulo 6. Yocto Project

que el archivo con extensión .c exceptuando la extensión. Si se tienen más archivos a los que se le quiere realizar la construcción de módulos kernel solo habrá que añadir líneas del tipo obj-m += segundo-archivo.o.

Código 6.31 Contenido de mydriver-mod.bb.

SUMMARY = "Example of how to build an external module" LICENSE = "GPLv2" LIC_FILES_CHKSUM = "file://COPYING;md5=12f884d2ae1ff87c09e5b7ccc2c4ca7e"

inherit module

SRC_URI = "file://Makefile \ file://mydriver.c \ file://COPYING \ "

S = "${WORKDIR}"

# The inherit of module.bbclass will automatically name module packages with # "kernel-module-" prefix as required by the oe-core build environment.

El contenido del archivo mydriver-mod.bb mostrado en el código 6.31 es una copia del contenido de la receta /poky/meta-skeleton/recipes-kernel/hello-mod/hello-mod_0.1.bb con la inclusión de la línea file://mydriver.c \ en lugar del file://hello.c \ del archivo original, dentro de SRC_URI =. Como ya se ha comentado antes, es una buena idea buscar en la capa meta-skeleton si hay alguna capa de ejemplo o receta de ejemplo que sirva, copiarla en una capa propia y realizarle las modificaciones pertinentes. Tras esto solo habrá que añadir la capa meta-mydriver al archivo bblayers.conf y ejecutar el comando $ bitbake mydriver-mod, o añadir a una receta de creación de imagen mydriver-mod como argumento de IMAGE_INSTALL += y ejecutar bitbake . Con esto, una vez iniciado el sistema en el dispositivo destino, si no se sabe donde se encuentra el módulo kernel se podrá ejecutar el comando $ find . -name "*mydriver.ko" para localizarlo (hacerlo en el directorio de máximo nivel posible para localizarlo). Se encontrará ya en el sistema y se tendrá que insertar manualmente, pero hay una forma con la que Yocto Project le indica al sistema que lo cargue directamente tras realizar el arranque. Si se quiere que un módulo se cargue directamente se tendrá que añadir la siguiente línea a la receta que crea la imagen personalizada KERNEL_MODULE_AUTOLOAD += "mydriver". El nombre que aparece en la asignación es el nombre del módulo kernel pero sin la extensión .ko. Como ya se adelantó unos párrafos atrás la creación de módulos kernel se utiliza en la mayor parte de los casos para instalar drivers.

6.5.2 Drivers

Un driver es un programa que tiene como finalidad permitir la interacción del sistema operativo con las partes hardware del sistema. Un solo driver puede controlar varios dispositivos del mismo tipo. 6.5 Modulos kernel 67

Los drivers son códigos que pueden echar abajo al sistema si no funcionan de forma correcta, así que se deben hacer lo más simples posibles. Es más, se pueden encontrar consejos sobre cómo escribir un driver, y uno de ellos puede ser que nunca escribas uno, sino que adquieras el código de un driver funcional parecido al que se desee fabricar y se modifique, ya que así se evitarán muchos problemas, incluso de problemas sobre los que no se tiene consciencia (no intentes reinventar la rueda, a no ser que sea totalmente necesario, construye sobre una base sólida que ya esté disponible). Se pueden diferenciar 3 tipos de controladores de dispositivos (device drivers):

• character: Son controladores con operaciones básicas I/O sin búfer y acceso síncrono (es decir, el proceso se queda bloqueado hasta que se reciba una respuesta). Transmite cadenas de bytes desde el kernel hasta el espacio de usuario (user space). • block: Orientado a interfaz con dispositivos de almacenamiento masivo de datos. Disponen de gran búfer diseñado para realizar tareas de lectura escritura en disco lo más rápido posible. • network: Similar al tipo block pero se usa para recibir y transmitir paquetes de red en lugar de bloques de disco.

Figura 6.4 Localización de los devices driver en los distintos espacios del sistema.

Esta sección estará centrada en los character device drivers. En Linux todo es tratado como archivo, así que los dispositivos hardware estarán en algún lugar representados como archivos. Estos archivos se encuentran en el direcorio /dev/. Haciendo un listado del contenido de este directorio se pueden observar los dispositivos y el tipo de dispositivo que es según el tipo de driver que lo controla, como se muestra en el código 6.32.

Código 6.32 Listado del directorio /dev/.

angel@ubuntu:/dev$ ls -l total 0 [...] crw------1 root root 10, 228 Nov 13 00:57 hpet 68 Capítulo 6. Yocto Project

drwxr-xr-x 2 root root 0 Nov 13 00:57 hugepages crw------1 root root 10, 183 Nov 13 00:57 hwrng lrwxrwxrwx 1 root root 25 Nov 13 00:57 initctl -> /run// initctl/fifo drwxr-xr-x 4 root root 280 Nov 13 00:57 input crw-r--r-- 1 root root 1, 11 Nov 13 00:57 kmsg drwxr-xr-x 2 root root 60 Nov 13 00:57 lightnvm lrwxrwxrwx 1 root root 28 Nov 13 00:57 log -> /run/systemd/ journal/dev-log brw-rw---- 1 root disk 7, 0 Nov 13 00:57 loop0 brw-rw---- 1 root disk 7, 1 Nov 13 00:57 loop1 brw-rw---- 1 root disk 7, 2 Nov 13 00:57 loop2 [...]

Como de puede observar en el código 6.32 la primera columna indica el tipo de archivo del que se trata. Si muestra "-" se trataría de un archivo habitual. Si muestra "d" indica que se trata de un directorio, si indica "l" se trata de un link, si se muestra "c" se trata de un character device controlado por un character device driver y si se indica "b" se trata de un block device. Se puede comprobar también en el código 6.32, como en el caso de los block devices y los character devices aparecen dos números antes de la fecha y la hora. Estos dos números son los llamados major number y minor number. El major number es un número que hace referencia a un device driver en concreto, enlazando los dispositivos con los device drivers que tienen ese mismo major number. Los minor number se utilizan para distinguir los distintos dispositivos individuales (ya sean físicos o lógicos) manejados por un mismo device driver, es decir, con el mismo major number.

Figura 6.5 Representación de major number y minor number.

Para ver un listado de los dispositivos configurados y cuyos módulos controladores están actual- mente cargados se puede ejecutar el comando mostrado en el código 6.33, donde se puede observar que solo se muestran los major numbers. Se puede notar como hay varios dispositivos controlados por el mismo driver, y por tanto tienen el mismo major number pero distinto minor number.

Código 6.33 Listado de dispositivos cuyos drivers estan cargados.

angel@ubuntu:/dev$ cat /proc/devices Character devices: 1 mem 4 /dev/vc/0 4 tty 6.5 Modulos kernel 69

4 ttyS 5 /dev/tty 5 /dev/console 5 /dev/ptmx 5 ttyprintk 6 lp 7 vcs 10 misc 13 input 14 sound/midi 14 sound/dmmidi 21 sg 29 fb 81 89 i2c 99 ppdev 108 ppp 116 alsa 128 ptm 136 pts 180 usb 189 usb_device 204 ttyMAX 216 rfcomm 226 drm 245 media 246 hidraw 247 aux 248 bsg 249 watchdog 250 rtc 251 dimmctl 252 ndctl 253 tpm 254 gpiochip

Block devices: 259 blkext 7 loop 8 sd 9 md 11 sr 65 sd 66 sd 67 sd 68 sd 69 sd 70 sd 71 sd 128 sd 70 Capítulo 6. Yocto Project

129 sd 130 sd 131 sd 132 sd 133 sd 134 sd 135 sd 253 device-mapper 254 mdp

6.5.3 Creando un character device driver

Como ya se ha comentado anteriormente, para crear un device driver, y más aún si no se tiene experiencia en este ámbito, lo más recomendable es conseguir el código fuente de un device driver funcional y modificarlo. Ya se sabe que en Linux se trata todo como un archivo, así que, incluso para interaccionar con los dispositivos hardware habrá que hacerlo a través de archivos. Estos archivos son los archivos espe- ciales anteriormente nombrados como character devices y block devices. Estos archivos especiales se pueden crear con el comando mknod como se muestra en el código 6.34.

Código 6.34 Creación de nodos de character devices y block devices.

angel@ubuntu:/dev$ sudo mknod /dev/nuevo_character_device c 38 0 [sudo] password for angel: angel@ubuntu:/dev$ ls -l |grep nuevo crw-r--r-- 1 root root 38, 0 Nov 13 06:11 nuevo_character_device

angel@ubuntu:/dev$ sudo mknod /dev/nuevo_block_device b 41 3 angel@ubuntu:/dev$ ls -l | grep nuevo_blo brw-r--r-- 1 root root 41, 3 Nov 13 06:15 nuevo_block_device

Como se puede observar en el código 6.34, al comando se le indica el tipo de dispositivo con una letra a la que le siguen dos números, el primero de ellos se trata del major number y el segundo es el minor number. Del control de cada nodo de dispositivo se encargará el driver que tenga asignado el mismo major number que el dispositivo. Para compilar drivers como módulos kernel se tendrá que proceder de la misma manera que en las secciones 6.5y 6.5.1, es decir, se tiene que disponer de un archivo con extensión .c, de un Makefile y en el caso de Yocto Project a esto se le debe añadir una receta. Ya se vio un ejemplo de archivo que contenía la programación de un módulo kernel en el código 6.25. Ahora el objetivo es comunicarse con el hardware, y como Linux trata a todo como un archivo habrá que realizar operaciones sobre archivos. Por tanto se necesitarán incluir librerías que permitan las operaciones en ficheros. Esta librería es la librería llamada fs.h, que se puede encontrar en el sistema en la dirección /usr/src/linux-headers-$(uname -r)/include/linux/fs.h, y también se puede encontrar su contenido en internet (como muchas otras librerías), ya que a veces es más sencillo y rápido una simple búsqueda en internet que una búsqueda por el sistema. En esta librería se encuentran muchas definiciones, de las cuales, para el siguiente ejemplo, que será un driver más o menos sencillo, se necesitarán las definiciones siguientes. Es necesario aclarar que para la comprensión de un driver se necesitan, al menos, conocimientos básicos de lenguaje C. 6.5 Modulos kernel 71

A este nivel ya es importante saber navegar por los ficheros de definiciones para encontrar las funcionalidades que se necesitan. En el caso de fs.h de las miles de líneas de código que contiene el archivo, las necesarias para el ejemplo son las siguientes y se puede encontrar buscando en internet character device kernel module y entrando en la dirección http://www.linuxtopia.org/online_- books/linux_kernel/linux_kernel_module_programming_2.6/x569.html.

Código 6.35 Código básico que se utilizarán de fs.h en el driver de ejemplo.

struct file_operations { struct module *owner; loff_t(*llseek) (struct file *, loff_t, int); ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t); ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); ssize_t(*aio_write) (struct kiocb *, const char __user *, size_t, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *); int (*open) (struct inode *, struct file *); int (*flush) (struct file *); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t(*readv) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*writev) (struct file *, const struct iovec *, unsigned long, loff_t *); ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *); ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area) (struct file *, unsigned long, unsigned long, unsigned long, unsigned long); };

#De los cuales se utilizará los siguientes

#ssize_t(*read) (struct file *, char __user *, size_t, loff_t *); #ssize_t(*write) (struct file *, const char __user *, size_t, loff_t *); #int (*open) (struct inode *, struct file *); #int (*release) (struct inode *, struct file *); 72 Capítulo 6. Yocto Project

#En el driver se definirá una variable del tipo de estructura anterior.

struct file_operations fops = { .read = device_read, .write = device_write, .open = device_open, .release = device_release };

En el driver se sustituirán los nombres device_read, device_write y los demás por el nombre que se quiera utilizar para llamar a las funciones en el archivo del driver. Hay que decir que solo se han utilizado esas pocas funciones porque para el primer driver ejemplo solo se querrán realizar operaciones de apertura de archivo, lectura y escritura y cierre de archivo. Si se quisiera realizar el mapeado de la memoria del archivo por ejemplo habría que definir dentro del .c del driver también la función int (*mmap) (struct file *, struct vm_area_struct *); y tenerla también definida en la variable de tipo estructura dándole el nombre que vayamos a utilizar dentro del device driver. Se pasará a continuación a mostrar el driver de ejemplo, llamado mydriver.c, que está comentado para mejorar su comprensión.

Código 6.36 Contenido de mydriver.c.

#include #include #include/* To access fyle operation functions*/ #include //#include //To register our char device in the new way

/*To later register our device we need a cdev object and some other variables*/ struct cdev *mcdev; //m stands 'my' int major_number; /*will store our major number- extracted from dev_t using macro - mknod /director/file c major minor*/ int ret; /*will be used to hold return values of functions; this is because the kernel stack is very small*/ /*so declaring variables all over the pass in our module functions eats up the stack very fast */ dev_t dev_num; //will hold major number that kernel gives us //name--> appears in /proc/devices*/

#define DEVICE_NAME "mydriver"

ssize_t mydriver_read (struct file *pfile, char __user *buffer, size_t length, loff_t *offset){ printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__); 6.5 Modulos kernel 73 return 0; }

ssize_t mydriver_write (struct file *pfile, const char __user *buffer, size_t length, loff_t *offset){ printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__); return length; }

int mydriver_open (struct inode *pinode, struct file *pfile){ printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__); return 0; }

int mydriver_close (struct inode *pinode, struct file *pfile){ printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__); return 0; }

/*Tell the kernel which functions to call when user operates on our device file*/ struct file_operations mydriver_fops = { .owner = THIS_MODULE, //prevent unloadind of this module when operations are in use .open = mydriver_open, //points to the method to call when opening the device .release = mydriver_close, //points to the method to call when closing the device .write = mydriver_write, //points to the method to call when writing to the device .read = mydriver_read //points to the method to call when reading from the device };

int mydriver_init(void){ 74 Capítulo 6. Yocto Project

printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__);

/*Register our device with the old system way*/ register_chrdev(244 /*major number*/, "mydriver" /*name of the driver*/, &mydriver_fops /*file operations*/);

/*Register our device with the new system way: a two step process //step(1) use dynamic allocation to assign our device // a major number-- alloc_chrdev_region(dev_t*, uint fminor, uint count, char* name)*/

// ret = alloc_chrdev_region(&dev_num,0,1,DEVICE_NAME); // // if(ret < 0) { /*at time kernel functions return negatives, there is an error*/ // printk(KERN_ALERT "mydriver: failed to allocate a major number");

// return ret; /*propagate error*/ // } // // major_number = MAJOR(dev_num); //extracts the major number and store in our variable (MACRO) // // printk(KERN_INFO "mydriver: major number is %d",major_number); // // printk(KERN_INFO "\tuse \"mknod /dev/%s c %d 0\" for device file", DEVICE_NAME,major_number); /*dmesg*/ // // /*step(2)*/ // // mcdev = cdev_alloc(); /*create our cdev structure, initialized our cdev*/ // mcdev->ops = &fops; /*struct file_operations*/ // mcdev->owner = THIS_MODULE; /*the use of -> is because of mcdev is a pointer // //now that we created cdev, we have to add it to the kernel // //int cdev_add(struct cdev* dev, dev_t num, unsigned int count)*/ // // ret = cdev_add(mcdev, dev_num, 1); // // if(ret < 0) { /*always check errors*/ // printk(KERN_ALERT "soliduscode: unable to add cdev to kernel"); // return ret; // // 6.5 Modulos kernel 75

// }

return 0; }

void mydriver_exit(void){

printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__);

unregister_chrdev(244, "mydriver");

// /*unregister everyting in reverse order // //(a) // cdev_del(mcdev); // // //(b)*/ // unregister_chrdev_region(dev_num, 1); // printk(KERN_ALERT "mydriver: unloaded module"); }

/*Inform the kernel where to start and stop with our module/driver*/ module_init(mydriver_init); module_exit(mydriver_exit);

El código 6.36 es un driver muy sencillo que implementa operaciones de abrir, leer, escribir y cerrar archivo (el archivo especial que tenga el mismo major number), y muestra un mensaje de alerta en el kernel indicando que se encuentra dentro de la función correspondiente. Se ha registrado el device driver de la manera antigua. Hay una nueva forma donde se le pide al kernel que devuelva un major number que esté libre, para más tarde registrar el driver con ese major number. Esta forma también se encuentra en el código pero comentada. Si se compila el código 6.36 en un módulo kernel, se inserta, se crea un dispositivo, por ejemplo de la forma $ sudo mknod /dev/mydevice c 244 0 y se le realiza un $ cat /dev/mydevice se obtendrá el resultado mostrado en el código 6.37 encontrando pues que la aplicación cat realiza la apertura de un archivo, su lectura y su cierre.

Código 6.37 Información mostrada por el kernel al realizar $ cat /dev/mydevice.

Nov 16 01:29:59 ubuntu kernel: [ 145.903138] Inside the mydriver_open function Nov 16 01:29:59 ubuntu kernel: [ 145.903147] Inside the mydriver_read function Nov 16 01:29:59 ubuntu kernel: [ 145.903156] Inside the mydriver_close function

Llegando un poco más lejos se podría crear un módulo kernel muy similar al mostrado en el código 6.36, solo cambiando un poco las funciones para leer y escribir, de forma que se pueda leer 76 Capítulo 6. Yocto Project

de usuario y mostrar información en espacio de usuario. También habría que crear un programa en .c para poder pedir información al usuario. Habría que añadir o modificar el código 6.36 con las líneas que se muestran en el código 6.38, y el programa de espacio usuario quedaría como se representa en el código 6.39.

Código 6.38 Modificaciones para que el módulo kernel lea y escriba desde espacio de usuario.

#include //copy_to_user; copy_from_user

[...]

ssize_t mydriver_read (struct file *pfile, char __user *buffer, size_t length, loff_t *offset){

printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__);

//take data from kernel space(device) to user space (processs) //copy_to_user (destination, source,sizeToTransfer)

ret = copy_to_user(buffer,data,length); return ret;

}

ssize_t mydriver_write (struct file *pfile, const char __user *buffer, size_t length, loff_t *offset){

printk(KERN_ALERT "Inside the %s function\n", __FUNCTION__);

//copy_from_user (dest, source, count)

ret = copy_from_user(data, buffer, length); return ret;

}

[...]

Código 6.39 Programa de usuario para comunicación con el character device a través del device driver.

#include #include #include 6.5 Modulos kernel 77

#define DEVICE "/dev/mydriver"

int main(){

int i, fd; char ch, w_buffer[100], r_buffer[100];

fd = open(DEVICE, O_RDWR); /*open for reading and writing*/

if (fd == -1){

printf("file %s either does not exist or has been locked by another proccess\n",DEVICE); exit(-1); }

printf("r = read from device\nw = write from device\nenter command: "); scanf ("%c", &ch);

switch (ch) {

case 'w': printf("enter data: "); scanf(" %[^\n]", w_buffer); write(fd, w_buffer, sizeof(w_buffer)); break;

case 'r':

read(fd, r_buffer, sizeof(r_buffer)); printf("mydriver: %s\n",r_buffer); break;

default:

printf("command not recognized\n"); break;

} close(fd);

return 0;

}

Con la información ya mostrada se podrían crear programas muy interesantes, como por ejemplo un programa que mapease cierta parte de memoria en una variable, de manera que el contenido de la variable sea un puntero a la memoria mapeada y por tanto, modificando el valor de la variable se modifique directamente la memoria. 78 Capítulo 6. Yocto Project

Esto es muy útil ya que, de forma general, para hacer esto se tendría que comunicar el programa situado en el espacio usuario con el driver, que se encuentra en el espacio kernel, y este último comunicarse con el hardware (archivo especial en el directorio /dev), y esto lo tendría que hacer cada vez que se quisiera modificar algo dentro del hardware al que se está accediendo, por lo que es un proceso bastante lento. Esto se puede realizar de una manera mucho más rápida y eficiente mapeando la memoria del hardware en una variable. De esta manera el programa de usuario no se tendría que comunicar con el driver, y el driver con el hardware, cada vez que se quiera acceder a modificar la memoria del hardware, sino que solo se comunicarían una vez, cuando el programa en el espacio usuario pidiese mapear la zona de memoria del hardware, ya que más tarde se tendría un puntero que apuntaría a la memoria del hardware permitiendo así modificarla directamente de una forma mucho más eficaz.

Ya que el Linux todo es tratado como archivo, se pondrá como ejemplo un programa que mapea la zona de memoria que ocupa un archivo con extensión .txt y que permite a través de este puntero leer el carácter que este en la posición que se le indique (siendo 0 la primera posición) o escribir en la posición que se le indique el carácter que se indique. La única diferencia con mapear la memoria que se encuentra en hardware es que no se necesita de un device driver que haga de mediador para poder hacer la llamada al mapeado de la zona. El programa ejemplo comentado se muestra en el código 6.40

Código 6.40 Programa ejemplo de mapeado de memoria.

#include #include #include

#include #include #include #include #include #include

#define DEVICE "test.txt"

int main(){

int i, position, fd, resul; void *map_addr; char ch, letter, w_buffer[100], r_buffer[100];

volatile char *mapped;

fd = open(DEVICE, O_RDWR); /*open for reading and writing*/

if (fd == -1){ 6.5 Modulos kernel 79 printf("file %s either does not exist or has been locked by another proccess\n",DEVICE); exit(-1); }

map_addr = mmap( NULL, getpagesize(), PROT_READ | PROT_WRITE, MAP_SHARED , fd, 0); if (map_addr == MAP_FAILED) { perror("Failed to mmap"); printf("device %s\n",DEVICE); return 1; } mapped = (char *)map_addr;

printf("r = read from device\nw = write to device\nenter command: "); scanf ("%c", &ch); switch (ch) { case 'w': printf("Enter the write position:"); scanf("%d",&position); printf("Enter the letter to write:"); //getchar(); scanf(" %c",&letter); mapped[position] = letter; break; case 'r': printf("Enter the read position:\n"); scanf("%d",&position); resul = mapped[position]; printf(" %c\n", resul); break; default: printf("command not recognized\n"); break; } 80 Capítulo 6. Yocto Project

close(fd);

return 0;

} 7 Construyendo con Yocto Poject

n este capítulo finalmente se mostrará una guía de la forma de trabajar para obtener una E construcción completa con Yocto Project, creando una capa personalizada para que se pueda exportar a otros sistemas, con todos los elementos necesarios tratados en los capítulos anteriores.

7.1 Construyendo para la ZedBoard

En esta sección se desarrollara un entorno Linux para la ZedBoard. Se mostrará cómo se han modificado los archivos de configuración, las capas que han sido necesarias descargar, y en general como trabajar con Yocto Project para conseguir un Linux personalizado para el target objetivo.

7.1.1 Antes de intentar realizar la construcción

Antes incluso de realizar lo que aparece en el manual de referencia de Yocto Project, el cual se puede encontrar en la dirección https://www.yoctoproject.org/docs/latest/ref-manual/ref-manual.html se recomienda descargar la lista de paquetes de los repositorios y la lista de actualizaciones con información sobre las versiones más recientes de los paquetes y sus respectivas dependencias, y tras esto obtener estos nuevos paquetes e instalarlos. Esto se realiza mediante los dos comandos mostrados en el código 7.1.

Código 7.1 Descargando e instalando paquetes más recientes de diversa índole.

$ sudo apt-get update

$ sudo apt-get -y upgrade

Tras realizar la acción anterior, se instalará, entre otras cosas, las ultimas versiones disponibles de librerías, lo que evitará ciertos problemas a la hora de construir el entorno Linux para nuestro target. Se recomienda instalar Vivado, que es el entorno de desarrollo de Xilinx. No se tienen evidencias claras de que la ausencia de Vivado en el sistema cause errores a la hora de la construcción del entorno para la zedboard con Yocto Project, pero es recomendable tenerlo instalado. Una vez instalado Yocto Project, para poder llevar a acabo la construcción del entorno Linux para la ZedBoard lo primero que se deberá hacer es descargar la capa de Xilinx para Yocto Project. Esta capa permite tener soporte de creación de entorno para la ZedBoard. Para esto hay que desplazarse hasta el directorio poky y realizar una clonación del repositorio como se realiza en el código 7.2.

Código 7.2 Descargando la capa de Xilinx para Yocto Project.

81 82 Capítulo 7. Construyendo con Yocto Poject

angel@ubuntu:~/poky$ git clone https://github.com/Xilinx/meta-xilinx.git

Se puede comprobar las ramas de las que dispone como muestra el código 7.3 (una vez dentro del directorio meta-xilinx).

Código 7.3 Revisando las ramas disponibles para meta-xilinx.

angel@ubuntu:~poky//meta-xilinx$ git branch -r origin/HEAD -> origin/master origin/daisy origin/danny origin/dev-uspea origin/dizzy origin/dora origin/dylan origin/fido origin/jethro origin/krogoth origin/master origin/morty origin/pyro origin/rel-v2016.1 origin/rel-v2016.2 origin/rel-v2016.3 origin/rel-v2016.4 origin/rel-v2017.1 origin/rel-v2017.2 origin/rel-v2017.3

Como se puede observar en el código 7.3 se dispone de muchas más ramas. Se recomienda que se revisen las ramas disponibles para todas las capas que se van a utilizar y se actualicen todas ellas a la rama estable más reciente que tengan en común. A la hora de construir entornos Linux con Yocto Project hay que tener cuidado con esto último y con muchos más detalles, los cuales algunos se comentarán más adelante, ya que es un proceso muy sensible y cualquier detalle, en principio insignificante, puede hacer que no se cree con éxito el entorno. Si se ejecuta el comando $ git checkout en el directorio poky se actualizará a la rama indicada todas las capas que viniesen por defecto con Yocto Project. Para revisar en que rama se encuentra una capa lo único que se tendrá que hacer es ejecutar el comando del código 7.4. En el caso de la creación del entorno para la ZedBoard se recomienda cambiar todas las capas a su rama pyro, ejecutando también $ git checkout pyro en el directorio poky.

Código 7.4 Revisando a que rama apunta meta-xilinx.

angel@ubuntu:~poky//meta-xilinx$ git branch daisy * pyro 7.1 Construyendo para la ZedBoard 83

Antes de continuar es importante realizar varios comentarios. El primero de ellos es que la construcción de un sistema con Yocto Project es un proceso altamente costoso tanto computacional- mente como en términos de consumo de memoria secundaria (es decir, de disco duro). Es decir, que en el caso de que Yocto Project se vaya a ejecutar en una máquina virtual destinar para esta máquina la mayor cantidad de núcleos que sea posible, ya que Yocto Project en su rama pyro es capaz de hacer uso de una gran cantidad de núcleos simultaneamente. También se aconseja destinar como mínimo 60GB de memoria de disco duro, y este mínimo se aconseja ya que el entorno de desarrollo Vivado, depende de la versión ocupa de 25GB a más de 50GB de espacio, y en cada construcción d sistema Yocto Project descarga unos 30GB de información, por este motivo si se quiere estar tranquilo con el almacenamiento se recomiendan unos 100GB de disco duro destinados para la máquina virtual. Finalmente, como ya se ha comentado, Yocto Project, sobre todo en la primera construcción del sistema (ya que, tras la primera construcción en la carpeta downloads/ dentro del directorio de construcción se guardan los archivos descargados y no se tienen que volver a descargar), tiene que descargar una gran cantidad de información, y por tanto se recomienda tener una conexión a internet decente. Para tener una idea del tiempo que toma en la construcción del sistema, con un host con 8 núcleos a 2.56GHz y una conexión a Internet de fibra óptica con 50Mbits/s de descarga la primera construcción del sistema toma aproximadamente un par de horas.

7.1.2 Construyendo

Como se mostró en el capítulo anterior se tendrá que indicar en el archivo poky//conf/local.conf, la máquina para la que se va a construir el entorno. En este caso se tendrá que comentar todas las máquinas y poner como única máquina sin comentar MACHINE ??= "zedboard-zynq7". En este archivo, muy probablemente se tendrá que añadir una línea, ya que es habitual que cuando se ejecute $ bitbake Yocto Project muestre un error similar al que se muestra en el código 7.5.

Código 7.5 Error debido a revisiones de conectividad.

ERROR: OE-core's config sanity checker detected a potential misconfiguration. Either fix the cause of this error or at your own risk disable the checker (see sanity.conf). Following is the list of potential problems / advisories:

Fetcher failure for URL: 'https://www.example.com/'. URL https://www. example.com/ doesn't work. Please ensure your network is configured correctly.

Este error se produce cuando Yocto Project revisa configuración de conexión e intenta obtener conexión con una URL determinada. Este error a veces sucede de forma inesperada y sin que se sepa con certeza por qué. Para solucionar este problema se necesita agregar el comando del código 7.6 en el archivo poky//conf/local.conf, por ejemplo al final del mismo.

Código 7.6 Solucionando el error representado en el código 7.5.

CONNECTIVITY_CHECK_URIS="" 84 Capítulo 7. Construyendo con Yocto Poject

Como se comentó en el capítulo anterior, en el archivo poky//conf/bblayers.conf hay que añadir las capas que se vayan a utilizar en la construcción del entorno. La pregunta ahora sería, ¿Como se puede saber que capas se van a utilizar?. Se mostrará un ejemplo real intentando construir un sistema para la ZedBoard.

Código 7.7 Error causado por la falta de alguna capa.

angel@ubuntu:~poky//zedboard$ bitbake zedboard-image Loading cache: 100% |############################################| Time: 0:00:00 Loaded 1326 entries from dependency cache. Parsing recipes: 100% |##########################################| Time: 0:00:00 Parsing of 856 .bb files complete (855 cached, 1 parsed). 1327 targets, 79 skipped, 0 masked, 0 errors. ERROR: Nothing PROVIDES 'zedboard-image'

Summary: There was 1 ERROR message shown, returning a non-zero exit code .

En el error del código 7.7 Yocto Project informa de que nada provee una receta en concreto. Es importante recordar que con el comando bitbake se ejecutan recetas, es decir se ejecutan archivos con extensión .bb. Un ejemplo de receta podría ser hello-mod_0.1.bb, para ejecutar esta receta el comando a ejecutar sería $ bitbake hello-mod, el "_0.1" se refiere a la versión y esto no se debe añadir cuando ejecutamos el comando, solo se debe ejecutar el nombre de la receta sin modificadores del nombre a causa de la versión. La forma de proceder cuando aparece un error de este tipo es buscar la ubicación de esta receta y ver si hemos agregado esta capa al archivo poky//conf/bblayers.conf. Se realiza como se muestra en el código.

Código 7.8 Buscando la ubicación de la receta que nos falta.

angel@ubuntu:~/poky$ find . -name "zedboard-image*.bb" ./meta-zedboard/recipes-images/images/zedboard-image-sato.bb ./meta-zedboard/recipes-images/images/zedboard-image.bb

El código 7.8 realiza una búsqueda (notar que la ubicación del sistema es poky, para buscar dentro de poky) dentro del directorio donde se encuentra del nombre zedboard-image.bb. El asterisco, que indica "cualquier cadena de caracteres", se introduce porque, como se ha comentado anteriormente, el nombre de la receta puede contener indicadores de la versión, y por tanto si no se agregara el asterisco es esa posición se buscaría el nombre literal y por tanto en el caso que tuviera número de versión no encontraría nada en el sistema. El .bb final de la búsqueda se agrega para que solo busque archivos con extensión .bb, es decir, recetas, que es lo que se quiere buscar, porque puede haber paquetes con ese nombre o cualquier otro tipo de archivo o directorio, por eso hay que especificar que se está buscando solo los archivos con extensión .bb. Se muestra en el código 7.9 la importancia de añadir el asterisco entre el nombre de la receta (sin el número de versión) y la extensión. 7.1 Construyendo para la ZedBoard 85

Código 7.9 Resultado de búsqueda con y sin asterisco.

angel@ubuntu:~/poky$ find . -name "hello-mod*.bb" ./meta-prueba/recipes-kernel/hello-mod/hello-mod_0.1.bb ./meta-skeleton/recipes-kernel/hello-mod/hello-mod_0.1.bb angel@ubuntu:~/poky$ find . -name "hello-mod.bb" angel@ubuntu:~/poky$

Como se puede observar en el código 7.9 si no se mantiene el asterisco el sistema no encuentra nada porque lo busca de manera literal. Una vez sabido esto se pueden buscar las aplicaciones (nombres de recetas) que se quieran antes de que el sistema arroje el problema típico del código 7.7. Por ejemplo, si se tiene una receta como la del código 7.10 se requerirá de las capas que contengan las recetas para proveer a la construcción del entorno con bash, el editor de textos nano y la herramienta de Debian para administrar paquetes APT. Para buscar si hay alguna receta relacionada con estas aplicaciones y encontrar (si es que las hay) su ubicación para añadir esas capas al archivo poky//conf/bblayers.conf se realiza la ejecución de comandos mostradas en el código 7.11.

Código 7.10 Receta sencilla de creación de imagen llamada zedboard-image.bb.

DESCRIPTION = "Imagen para zedboard con algunas funcionalidades" AUTHOR = "Angel Cea"

require recipes-core/images/core-image-minimal.bb IMAGE_INSTALL += " \ bash \ nano \ apt \ "

Como ya se comentó en el capitulo6, el código 7.10 realiza una construcción de una imagen tipo core-image-minimal (de ahí la línea require recipes-core/images/core-image-minimal.bb) con las funcionalidades comentadas en el párrafo anterior. En el caso de que se quisiera realizar una construcción de una imagen tipo core-image-sato, la cual dispone de una interfaz gráfica simple, añadiendo las mismas funcionalidades lo unico que se deberá cambiar es la línea require recipes-core/images/core-image-minimal.bb por require recipes-sato/images/core-image-sato.bb . Recordar que para realizar una búsqueda del tipo de imágenes disponibles se puede ejecutar el comando ls meta*/*/images/*.bb una vez situados en el directorio poky.

Código 7.11 Revisando existencia y ubicación de recetas requeridas.

angel@ubuntu:~/poky$ find . -name "bash*.bb" ./meta/recipes-extended/bash/bash_4.3.30.bb ./meta/recipes-support/bash-completion/bash-completion_2.5.bb angel@ubuntu:~/poky$ find . -name "nano*.bb" ./meta-/meta-oe/recipes-support/nano/nano_2.7.4.bb angel@ubuntu:~/poky$ find . -name "apt*.bb" ./meta/recipes-devtools/apt/apt-native_1.2.12.bb 86 Capítulo 7. Construyendo con Yocto Poject

./meta/recipes-devtools/apt/apt_1.2.12.bb

Del código 7.11 se extrae que se necesita añadir al archivo poky//conf/bblayers.conf las capas /meta y /meta-openembedded/meta-oe. Importante recordar que la meta-openembedded no tiene estructura de capa, por lo que si añadimos esta capa dará error. La capa meta-openembedded es una capa que contiene otras capas, las cuales si tienen estructura de capa, por tanto estas últimas serán las que se deban agregar en el archivo poky//conf/bblayers.conf. Otros errores típicos que indican falta de adición de las correspondientes capas son errores como el que se muestra en el código 7.12, que indica que se debe añadir la capa meta-python en el archivo poky//conf/bblayers.conf.

Código 7.12 Error a causa de la falta de adición de una capa por dependencia.

ERROR: Layer 'networking-layer' depends on layer 'meta-python', but this layer is not enabled in your configuration

Summary: There was 1 ERROR message shown, returning a non-zero exit code .

En muchos otros errores se mostrará la ubicación de un archivo que contiene el motivo de los errores, como es el caso del mostrado en el código 7.13.

Código 7.13 Error que muestra la ubicación del archivo que contiene el motivo del error.

ERROR: linux-xlnx-4.9-xilinx-v2017.1+gitAUTOINC+68e6869cfb-r0 do_uboot_mkimage: Function failed: do_uboot_mkimage (log file is located at /home/angel/poky/enclustra/tmp/work/zx3_pm3_zynq7-poky- linux-gnueabi/linux-xlnx/4.9-xilinx-v2017.1+gitAUTOINC+68e6869cfb-r0 /temp/log.do_uboot_mkimage.123723) ERROR: Logfile of failure stored in: /home/angel/poky/enclustra/tmp/work /zx3_pm3_zynq7-poky-linux-gnueabi/linux-xlnx/4.9-xilinx-v2017.1+ gitAUTOINC+68e6869cfb-r0/temp/log.do_uboot_mkimage.123723 Log data follows: | DEBUG: Executing shell function do_uboot_mkimage | arm-poky-linux-gnueabi-objcopy: 'vmlinux': No such file | WARNING: exit code 1 from a shell command. | ERROR: Function failed: do_uboot_mkimage (log file is located at /home /angel/poky/enclustra/tmp/work/zx3_pm3_zynq7-poky-linux-gnueabi/ linux-xlnx/4.9-xilinx-v2017.1+gitAUTOINC+68e6869cfb-r0/temp/log. do_uboot_mkimage.123723) ERROR: Task (/home/angel/poky/meta-xilinx/recipes-kernel/linux/linux- xlnx_2017.1.bb:do_uboot_mkimage) failed with exit code '1' NOTE: Tasks Summary: Attempted 2036 tasks of which 6 didn't need to be rerun and 1 failed.

Summary: 1 task failed: /home/angel/poky/meta-xilinx/recipes-kernel/linux/linux-xlnx_2017.1.bb: do_uboot_mkimage Summary: There was 1 WARNING message shown. 7.1 Construyendo para la ZedBoard 87

Summary: There was 1 ERROR message shown, returning a non-zero exit code .

En este caso nos está indicando que el archivo con el motivo del fallo se encuentra en la direc- ción /home/angel/poky/enclustra/tmp/work/zx3_pm3_zynq7-poky-linux-gnueabi/linux-xlnx/4.9- xilinx-v2017.1+gitAUTOINC+68e6869cfb-r0/temp/log.do_uboot_mkimage.123723. En el caso de que Yocto Project no de la suficiente información en la terminal siempre es una buena idea acudir al archivo que nos indica.

7.1.3 Creando una capa para la ZedBoard

Para realizar el trabajo de forma eficiente es muy recomendable crear una capa personalizada donde se vuelquen las recetas que se necesiten crear para la construcción del entorno Linux personalizado, con las funcionalidades que se deseen. Por ejemplo será habitual el interés por incluir en el sistema programas croscompilados con funcionalidades específicas, así como la instalación de módulos kernel que contengan drivers para poder manejar de manera exitosa distintos dispositivos hardware. En esta subsección se mostrará la estructura que debe tener esta capa y el contenido de la misma, para poder tener una capa que permita construir los ejecutables croscompilados que se quieran, los módulos kernel, y además que se presenten instalados finalmente en el target objetivo. La capa resultante para realizar estas acciones tiene la estructura de la figura 7.1. Como se puede observar en eta capa tenemos 3 tipos de recetas. La receta llamada programas.bb es una receta que realiza la croscompilación de los archivos que se le indican, los cuales se presentaran en el directorio meta-zedboard/recipes-core/programas/files, y es una receta con la estructura de la receta mostrada en el código 6.17. Hay otra receta contenida en el directorio meta-zedboard/recipes-kernel/modules-kernel, que es el directorio que se utilizará para la construcción de módulos kernel para el target donde se encuentra la receta mydriver-mod.bb que es la receta encargada de realizar esta acción y tiene una estructura como la mostrada en la sección 6.5.1. Aquí, al igual que en el párrafo anterior, los archivos a los que llame tanto el Makefile como la receta mydriver-mod.bb se encontrarán en la ubicación meta-zedboard/recipes-kernel/modules-kernel/files Por último se tiene un directorio dedicado en la creación de imágenes personalizadas llamado meta- zedboard/recipes-images. En este caso se tienen dos recetas, una para una imagen personalizada basada en core-image-minimal y otra basada en core-image-sato. También se puede observar una novedad en la estructura mostrada en la figura 7.1. Esta novedad son los archivos con extensión .sample, dentro de /meta-zedboard/conf, que son archivos exacta- mente iguales que los que se pueden encontrar dentro del directorio de configuración del directorio de construcción solo que los que están en esta capa de esta manera se pueden exportar antes de rea- lizar la ejecución $ source oe-init-build-env para que se sobrescriban sobre los que están en el directorio de ejecución, haciendo que con la capa se tengan todas las herra- mientas necesarias para construir un entorno Linux personalizado. Para realizar la exportación de los archivos de configuración basta con ejecutar el comando $ export TEMPLATECONF="meta- zedboard/conf" en el directorio poky justo antes de ejecutar el comando $ source oe-init-build-env .

Código 7.14 Imagen basada en core-image-sato.

DESCRIPTION = "Imagen para zedboard con algunas funcionalidades" AUTHOR = "Angel Cea" 88 Capítulo 7. Construyendo con Yocto Poject

Figura 7.1 Estructura de la capa meta-zedboard.

require recipes-sato/images/core-image-sato.bb

IMAGE_INSTALL += " \ bash \ nano \ gedit \ apt \ programas \ mydriver-mod \ "

Se puede observar en el código 7.14 como se realiza la receta para la construcción de una imagen basada en una imagen tipo core-image-sato, la cual cuenta con una interfaz gráfica, y además las recetas que introducimos en IMAGE_INSTALL += que lo que realiza es una llamada a las recetas con ese nombre (sin contar con el número de versión de las recetas), las ejecuta (creando paquetes como resultado de la ejecución de las recetas como se comentó en el capítulo dedicado a Yocto Project) y finalmente instalándolas en el dispositivo de destino. En este caso el fin último de este IMAGE_INSTALL += en concreto es la instalación en el dispositivo de los programas bash, nano, gedit, apt, los programas que tengamos en la capa que se quieran croscompilar a través de la receta 7.1 Construyendo para la ZedBoard 89 programas.bb y la instalación de los módulos kernel que se hayan indicado en el Makefile y en la receta mydriver-mod.bb. Recordar que a lo que se llama con el comando bitbake y en general cada vez que se llama a las recetas (como en el caso de la receta anterior para realizar la creación de la imagen) es al nombre de la receta sin el número de versión y sin la extensión .bb. Tras esto lo único que habría que hacer es, una vez en el directorio poky, exportar como se ha comentado los archivos de configuración de la capa (o copiar a mano el contenido de los archivos con extensión .sample en los archivos con el mismo nombre y sin esa extensión dentro del directorio de construcción), ejecutar el comando source oe-init-build-env , y realizar la ejecución del comando $ bitbake zedboard-image-sato. Puede que en la construcción se emita un error relacionado con la croscompilación como el que se muestra en el código 7.15. Para solucionarlo solo tendremos que añadir a la receta que realiza la croscompilación del archivo que da error, es decir, a la receta programas.bb, la línea que se muestra en el código 7.16.

Código 7.15 Error debido a croscompilación de helloworld.c.

angel@ubuntu:~/poky/zedboard$ bitbake zedboard-image Parsing recipes: 100% |##########################################| Time: 0:01:04 Parsing of 1973 .bb files complete (0 cached, 1973 parsed). 2728 targets , 213 skipped, 0 masked, 0 errors. NOTE: Resolving any missing task queue dependencies

Build Configuration: BB_VERSION = "1.34.0" BUILD_SYS = "x86_64-linux" NATIVELSBSTRING = "universal" TARGET_SYS = "arm-poky-linux-gnueabi" MACHINE = "zedboard-zynq7" DISTRO = "poky" DISTRO_VERSION = "2.3.2" TUNE_FEATURES = "arm armv7a vfp thumb neon callconvention-hard cortexa9" TARGET_FPU = "hard" meta meta-poky meta-yocto-bsp = "pyro:827eb5b232d54909377e2b18d39d34d6c1c21413" meta-xilinx = "pyro:18097af3120a394a8e6933b7abc85e73e508c7e3" meta-oe = "pyro:dfbdd28d206a74bf264c2f7ee0f7b3e5af587796" meta-zedboard2 = "pyro:827eb5b232d54909377e2b18d39d34d6c1c21413" meta-networking meta-python meta-gnome = "pyro:dfbdd28d206a74bf264c2f7ee0f7b3e5af587796"

Initialising tasks: 100% |#######################################| Time: 0:00:09 NOTE: Executing SetScene Tasks NOTE: Executing RunQueue Tasks ERROR: programas-1.0-r0 do_package_qa: QA Issue: No GNU_HASH in the elf binary: '/home/angel/poky/zedboard/tmp/work/cortexa9hf-neon-poky- 90 Capítulo 7. Construyendo con Yocto Poject

linux-gnueabi/programas/1.0-r0/packages-split/programas/usr/bin/ helloworld' [ldflags] ERROR: programas-1.0-r0 do_package_qa: QA run found fatal errors. Please consider fixing them. ERROR: programas-1.0-r0 do_package_qa: Function failed: do_package_qa ERROR: Logfile of failure stored in: /home/angel/poky/zedboard/tmp/work/ cortexa9hf-neon-poky-linux-gnueabi/programas/1.0-r0/temp/log. do_package_qa.94501 ERROR: Task (/home/angel/poky/meta-zedboard2/recipes-core/ejemplo/ programas.bb:do_package_qa) failed with exit code '1'

Código 7.16 Línea a añadir en programas.bb para solucionar problema de croscompilación.

[...] TARGET_CC_ARCH += "${LDFLAGS}"

7.1.4 Despliegue en la zedboard

En esta sección se describirá la manera en la que se despliegan los archivos necesarios en el dispositivo de destino, que en este caso es la ZedBoard. El objetivo es que la ZedBoard se inicie desde la tarjeta SD, por tanto el primer paso será formatear la tarjeta convenientemente (no olvidar colocar los jumpers en la posición que indica el datasheet para poder iniciar la ZedBoard desde la SD). Se inserta la SD en el sistema host, se puede comprobar si lo ha reconocido el sistema como se muestra en el código 7.17.

Código 7.17 Comprobación del reconocimiento del dispositivo desmontable.

angel@ubuntu:~$ sudo fdisk -l Disk /dev/sda: 100 GiB, 107374182400 bytes, 209715200 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes Disklabel type: dos Disk identifier: 0xa8ea2773

Device Boot Start End Sectors Size Id Type /dev/sda1 * 2048 202549247 202547200 96.6G 83 Linux /dev/sda2 202551294 209713151 7161858 3.4G 5 Extended /dev/sda5 202551296 209713151 7161856 3.4G 82 Linux swap / Solaris

Disk /dev/sdb: 7.3 GiB, 7780433920 bytes, 15196160 sectors Units: sectors of 1 * 512 = 512 bytes Sector size (logical/physical): 512 bytes / 512 bytes I/O size (minimum/optimal): 512 bytes / 512 bytes 7.1 Construyendo para la ZedBoard 91

Disklabel type: dos Disk identifier: 0x30a5a398

Device Boot Start End Sectors Size Id Type /dev/sdb1 2048 247807 245760 120M 6 FAT16 /dev/sdb2 247808 15196159 14948352 7.1G 83 Linux

Se puede comprobar en el código 7.17 que el sistema lo reconoce como un dispositivo ubicado en /dev/sdb, con dos particiones (/dev/sdb1 y /dev/sdb2). Para comprobar si estÁ montado en el sistema se utiliza el código 7.18 que en este caso indica que se encuentran montadas las particiones y además se indica donde se encuentran montadas.

Código 7.18 Comprobación de particiones montadas.

angel@ubuntu:~$ df Filesystem 1K-blocks Used Available Use% Mounted on 1689432 0 1689432 0% /dev tmpfs 342336 9948 332388 3% /run /dev/sda1 99552760 52649920 41822776 56% / tmpfs 1711664 212 1711452 1% /dev/shm tmpfs 5120 4 5116 1% /run/lock tmpfs 1711664 0 1711664 0% /sys/fs/cgroup tmpfs 342336 72 342264 1% /run/user/1000 /dev/sdb2 7225640 86244 6749304 2% /media/angel/root /dev/sdb1 122622 26212 96410 22% /media/angel/BOOT

En el presente proyecto se utilizó la aplicación GParted para dar formato a las particiones, aunque se podría hacer de una forma más "artesanal" con el comando fdisk. Antes de nada deberán estar desmontadas las particiones. Gparted permite desmontar las particiones, pero si se quiere hacer de forma manual se hace como se indica en el código 7.19.

Código 7.19 Desmontando las particiones.

angel@ubuntu:~$ sudo umount /media/angel/root /media/angel/BOOT

Tras esto se deben realizar dos particiones, la primera estará dedicada a los archivos que permiten realizar la inicialización de la ZedBoard, y la segunda partición estará dedicada a contener el root filesystem. La tarjeta (o el sistema de almacenamiento que se vaya a utilizar) debe quedar particionado como se muestra en la figura 7.2 .Para la partición con etiqueta BOOT no es necesario mucho almacenamiento, con unos 80MB es más que suficiente. Tras esto toca introducir los archivos necesarios. En la partición con etiqueta BOOT se deben presentar los siguientes archivos:

• boot.bin • uImage • core-image-minimal-zedboard-zynq7-20171010090447.rootfs.cpio.gz.u-boot • uImage-zynq-zed.dtb 92 Capítulo 7. Construyendo con Yocto Poject

Figura 7.2 Dispositivo de almacenamiento particionado de forma correcta.

• uEnv.txt • u-boot.img

Es necesario disponer en la partición con extensión EXT4 del root filesystem descomprimido, por lo que se ejecutará el comando similar al del código pero con la ubicación del origen y destino que correspondan.

Código 7.20 Descomprimiendo el root filesystem en la partición que corresponde.

angel@ubuntu:/media/angel/root$ sudo tar xvfz /home/angel/poky/zedboard/ tmp/deploy/images/zedboard-zynq7/-zedboard-zynq7.tar.gz

Tras esto ya está todo preparado para insertar la tarjeta sd en la ZedBoard y que todo funcione correctamente.

7.1.5 Prueba del sistema con QEMU

QEMU es un emulador de procesadores Open Source, que traduce el código binario de la arquitec- tura del sistema host a la arquitectura del sistema de destino. QEMU también tiene capacidad de virtualización dentro de varios sistemas operativos. Si se ha indagado un poco en el contenido de la capa meta-xilinx se habrá encontrado que soporta las máquinas mostradas en el código 7.21.

Código 7.21 Listado de máquinas soportadas por meta-xilinx.

angel@ubuntu:~/poky/meta-xilinx/conf/machine$ ls -l total 56 -rw-rw-r-- 1 angel angel 801 Oct 24 15:12 ep108-zynqmp.conf drwxrwxr-x 3 angel angel 4096 Oct 24 15:12 include -rw-rw-r-- 1 angel angel 644 Oct 24 15:12 kc705-microblazeel.conf -rw-rw-r-- 1 angel angel 647 Oct 24 15:12 microzed-zynq7.conf -rw-rw-r-- 1 angel angel 820 Oct 24 15:12 ml605-qemu-microblazeel.conf -rw-rw-r-- 1 angel angel 833 Oct 24 15:12 picozed-zynq7.conf -rw-rw-r-- 1 angel angel 765 Oct 24 15:12 qemu-zynq7.conf 7.2 Construyendo para la enclustra 93

-rw-rw-r-- 1 angel angel 767 Oct 24 15:12 s3adsp1800-qemu-microblazeeb. conf -rw-rw-r-- 1 angel angel 1028 Oct 24 15:12 zc702-zynq7.conf -rw-rw-r-- 1 angel angel 1056 Oct 24 15:12 zc706-zynq7.conf -rw-rw-r-- 1 angel angel 2480 Oct 24 15:12 zcu102-zynqmp.conf -rw-rw-r-- 1 angel angel 798 Oct 24 15:12 zedboard-zynq7.conf -rw-rw-r-- 1 angel angel 993 Oct 24 15:12 zybo-linux-bd-zynq7.conf -rw-rw-r-- 1 angel angel 724 Oct 24 15:12 zybo-zynq7.conf

Por tanto, la capa meta-xilinx soporta el QEMU de la arquitectura del procesador de la ZedBoard. Esta máquina es la "qemu-zynq7". Esto permitirá que se emule el sistema sin tener que utilizar la placa real, ahorrando tiempo en remover e insertar la SD, e introducir el contenido pertinente, por lo que es muy útil en muchos casos. Hay otros casos en los que no es tan útil o no es tan trivial la emulación del sistema real, y esto es debido a que el hardware conectado a la placa no sea el mismo o la configuración cambie de la de fábrica. Por ejemplo, si se ha programado la FPGA, esta no se podrá emular, ya que el hardware real configurado no está, y no se tiene una versión software emulada de ese software. En cualquier caso, para realizar la emulación con el QEMU se necesita construir el sistema para esta máquina y después arrancar el QEMU. Estas dos acciones de realizan con los comandos mostrados en el código 7.22.

Código 7.22 Construcción para máquina QEMU y ejecución de la emulación.

angel@ubuntu:~/poky$ source oe-init-build-env

angel@ubuntu:~/poky/$ MACHINE=qemu-zynq7 bitbake zedboard-image

angel@ubuntu:~/poky/$ runqemu qemu-zynq7

En el código 7.22 se puede observar que la sintaxis para la construcción del entorno para la emulación se realiza de la manera $ MACHINE= bitbake .

7.2 Construyendo para la enclustra

Se tratará brevemente como se construiría un entorno Linux para la enclustra. Será breve porque ya quedó casi todo explicado en la sección anterior dedicada a la Zedboard y por tanto esta sección estará enfocada en conseguir familiarización con el uso de capas descargadas y construcción para otros target en general. Como para el caso de la ZedBoard se recomienda ejecutar los comandos $ sudo apt-get update y $ sudo apt-get -y upgrade para tener instaladas las actualizaciones más recientes de librerías y otros paquetes. En internet se puede encontrar una página que indica la manera de construir para esta placa. Esta página se encuentra en la dirección https://github.com/enclustra/meta-enclustra/wiki/Yocto. Básicamente lo primero que hay que hacer es instalar Yocto como se indica en el manual de referencia de Yocto Project como ya se ha comentado varias veces. Después, dentro del directorio poky hacer un $ git checkout daisy que hará que la carpeta apunte a la rama daisy, que es la que se utilizará en este caso. También es necesario descargar las capas meta-xilinx y meta-xilinx-comunity. 94 Capítulo 7. Construyendo con Yocto Poject

La capa meta-xilinx-comunity se descarga para poder construir para la enclustra, ya que contiene la posibilidad de construcción para esta máquina, como se puede si se ejecuta el comando del código 7.23.

Código 7.23 Listado de máquinas disponibles en la capa meta-xilinx-comunity.

angel@ubuntu:~/poky/meta-xilinx-community/conf/machine$ ls -l total 28 drwxrwxr-x 8 angel angel 4096 Oct 25 01:32 boards drwxrwxr-x 4 angel angel 4096 Oct 25 01:31 include -rw-rw-r-- 1 angel angel 745 Oct 25 01:32 microzed-zynq7.conf -rw-rw-r-- 1 angel angel 1376 Oct 25 01:31 ml405-virtex4-ppc405.conf -rw-rw-r-- 1 angel angel 1237 Oct 25 01:31 ml507-virtex5-ppc440.conf -rw-rw-r-- 1 angel angel 414 Oct 25 01:32 ze7000-zynq7.conf -rw-rw-r-- 1 angel angel 488 Oct 25 01:32 zx3-pm3-zynq7.conf

La máquina a elegir sería pues la zx3-pm3-zynq7. Como se puede observar en el código 7.24, la capa rama estable más reciente para la capa meta-xilinx-comunity es la rama daisy, por lo que haremos un $ git checkout daisy tanto en la capa meta-xilinx como en la capa meta-xilinx-comunity. Esto de poner todo en la rama daisy es muy importante porque ya que de lo contrario no se creará el entorno con éxito. Comentar que para la zedboard se utilizó una máquina virtual con Ubuntu 16.04 LTS pero la rama daisy se realizó cuando no estaba todavía disponible esta versión de Ubuntu, así que para la creación de un entorno para la enclustra se tendrá que disponer de una máquina virtual con Ubuntu 14.04.

Código 7.24 Listado de ramas disponibles para la capa meta-xilinx-comunity.

angel@ubuntu:~/poky/meta-xilinx-community$ git branch -r origin/HEAD -> origin/master origin/aalonso/external-toolchain origin/aalonso/microblaze-support origin/aalonso/powerpc origin/aalonso/powerpc-support origin/daisy origin/danny origin/dora origin/edowson/xilinx-ml507-gcc-4.5 origin/edowson/xilinx-ml507-gcc-4.7 origin/edowson

También se requiere descargar la capa meta-enclustra ejecutando el comando del código 7.25 en el directorio poky. Esta última capa es la que contendrá la receta para construir la imagen para la enclustra.

Código 7.25 Descargando meta-enclustra.

git clone https://github.com/enclustra/meta-enclustra.git 7.2 Construyendo para la enclustra 95

Esta sección se centrará en mostrar esta capa, ya que en la sección anterior se explicó cómo trabajar con Yocto Project, los principales problemas etc. Conociendo esta capa se tendrá un ejemplo de cómo utilizar capas de terceros para la construcción de sistemas.

Figura 7.3 Estructura de la capa meta-enclustra.

Como se puede observar en la figura 7.3, ya es muy familiar esta estructura, ya que básicamente contiene los mismos elementos que se han ido comentando. Se encuentran también los ya nombrados archivos bblayers.conf.sample y local.conf.sample. Estos son archivos exactamente idénticos a los que se pueden encontrar en los directorios de construc- ción llamados igual pero sin la terminación .sample. Además tienen el mismo cometido solo que es- tán en una capa para poder ser exportados realizando la asignación export TEMPLATECONF="meta- enclustra/conf" y luego ejecutando el comando source oe-init-build-env (todo en el directorio poky). Las 2 acciones anteriores harán que los archivos de configuración con terminación .sample de la capa meta-enclustra sobrescriban los del directorio de construcción al que se acceda. Una vez comentado lo anterior solo incluye dos recetas que se utilizan para la creación de los archivos necesarios para el entorno Linux o solo para parte de ellos. Se comentará la receta zx3-image.bb ya que es la que permite la generación de todos los archivos necesarios.

Código 7.26 Contenido de la receta zx3-image.bb.

SUMMARY = "Developement Image for ZX3 Modules" DESCRIPTION = "Image with some basic developement and diagnostic tools" AUTHOR = "David Andrey " HOMEPAGE = "http://www.netmodule.com"

LICENSE = "MIT" inherit core-image

# Start with minimal linux image include recipes-graphics/images/core-image-minimal.bb

IMAGE_FEATURES += "ssh-server-openssh package-management"

IMAGE_INSTALL_append += " \ 96 Capítulo 7. Construyendo con Yocto Poject

bash \ openssh-sftp-server \ mtd-utils \ ethtool \ usbutils \ gdbserver \ iputils \ devmem2 \ nfs-utils \ tcpdump \ nano \ iperf \ i2c-tools \ "

IMAGE_LINGUAS = " "

export IMAGE_BASENAME = "zx3-image"

Comenzando a observar el código 7.26 nos encontramos con la declaración inherit core-image, que lo que permite es la llamada a la clase core-image.bbclass. Inherit permite utilizar clases dentro de las recetas. Las clases son archivos que permiten encapsulación lógica que puede ser utilizada en recetas, y en este caso la clase inherit core-image provee definiciones comunes paras las recetas tipo core-image-* que permite la instalación de IMAGE_FEATURES adicionales (declaración que se utiliza poco después en el archivo). Toda esta información y muchísima más se puede encontrar navegando un poco por el manual de referencia de Yocto Project. La segunda declaración se encuentra en el archivo es include recipes-graphics/images/core- image-minimal.bb que como bien dice el archivo es una imagen mínima sobre la que se va a basar la construcción y a la que se van a añadir posteriormente más funcionalidades. La tercera declaración es IMAGE_FEATURES += "ssh-server-openssh package-management" que realiza la adición de nuevas características de la imagen. Añade 2 de las disponibles en el archivo core-image.bbclass. Revisando este archivo se puede comprobar todas las características de imagen disponibles. Tras esto se realiza la declaración de los paquetes a instalar con IMAGE_INSTALL_append +=. Esta declaración funciona de forma similar a la ya vista anteriormente IMAGE_INSTALL +=, es decir, mira si en la carpeta poky//downloads se encuentra el paquete generado por la correspondiente receta, si es así lo instala, y si no ejecuta la receta para crear el paquete y luego lo instala en la construcción. 8 Conclusiones

En este capítulo se presentará de forma concisa las conclusiones obtenidas con la realización del proyecto.

• La primera conclusión que se puede sacar es que, aunque Yocto Project tiene una dura curva de aprendizaje, merece la pena su utilización ya que la creación de un entorno Linux para un sistema embebido se hace muy tedioso si construimos cada elemento de Linux embebido por separado (como hemos podido discernir en el presente documento), creciendo de forma exponencial la dificultad cuanto más complejo se quiera hacer el sistema. • La segunda conclusión es que la utilización de Yocto Project no solo es aconsejable por lo nombrado en el párrafo anterior, sino por la versatilidad que proporciona a la hora de poder exportar entornos a distintas máquinas, ya que como se ha visto en el documento es bastante sencillo en principio y se puede encapsular todo un entorno en una capa, la cual podrá ser enviada a cualquier persona para que construya el entorno desarrollado. • Otra conclusión que ha dado la experiencia a la hora de desarrollar el proyecto es que este tipo de desarrollos es muy sensible a una gran cantidad de elementos, muchos de los cuales escapan al entendimiento de personas que no estén muy familiarizadas con este tipo de desarrollos, por lo que hay que tener mucho cuidado ya que el más mínimo detalle puede hacer que no se ejecute la construcción de forma correcta. Por suerte, en la actualidad disponemos de Internet, que ofrece una ayuda inmensa, y más si no se es un experto en el tema, ya que no solo encontraremos infinidad de información y personas que ya han vivido muy probablemente el error que quizás haga que no se cree bien su entorno, sino que se dispone de muchos foros especializados donde hay mucha gente encantada de resolver problemas, por tanto es muy recomendable visitar este tipo de sitios cuando se pierda demasiado tiempo intentando resolver un problema. • Se puede concluir también que se ha conseguido mostrar cómo construir una capa en Yocto Project para crear un entorno Linux personalizado y poder incluir infinidad de programas, aplicaciones, módulos kernel, etc, cumpliendo así los requerimientos esenciales de muchos proyectos en los que se tenga que utilizar un sistema embebido, ya sea por restricciones de tamaño, peso y consumo energético (como puede ser un UAV) o por cualquier otro motivo.

97

9 Futuras líneas de investigación

En este capítulo se introducirán las formas de expandir el presente proyecto.

• Una línea futura de investigación es el diseño óptimo de drivers, utilizando el mapeado de la memoria comentado en el documento, incluyendo la capacidad de atender a interrupciones del hardware que se diseñen en la FPGA de manera eficiente y veloz. Tras esto sería muy interesante la comparación de la velocidad obtenida con otras soluciones comerciales como por ejemplo Xillybus. • La segunda línea futura de investigación sería utilizar la herramienta explicada en este documento (Yocto Project) para el desarrollo de un proyecto real.

99

Índice de Figuras

1.1 Diagrama del SoC Zynq-70003

3.1 Proceso de arranque del sistema 20 3.2 Sintaxis básica de los arboles de dispositivos 23

4.1 Representación de espacio de usuario y espacio kernel 30

5.1 Jerarquía de sistema de archivos raiz de ejemplo 41 5.2 Ejemplo de representación de permisos de archivo 42

6.1 Estructura de capa creada 56 6.2 Error al ejecutar la receta 58 6.3 Estructura capa de construcción de módulos kernel 65 6.4 Localización de los devices driver en los distintos espacios del sistema 67 6.5 Representación de major number y minor number 68

7.1 Estructura de la capa meta-zedboard 88 7.2 Dispositivo de almacenamiento particionado de forma correcta 92 7.3 Estructura de la capa meta-enclustra 95

101

Índice de Tablas

3.1 Principales bootloaders 26

103

Índice de Códigos

2.1 Instalación de paquetes para crosstool-NG8 2.2 Instalación de crosstool-NG8 2.3 Lista de comandos disponibles para crosstool-NG9 2.4 Configuración de arm-cortexa9_neon-linux-gnueabihf 10 2.5 Elección de configuración para el target 11 2.6 Elección de configuración para el target 11 2.7 Adición del croscompilador al path 12 2.8 Programa holamundo 12 2.9 Croscompilación del programa holamundo 12 2.10 Información sobre el ejecutable holamundo 12 2.11 Información sobre el croscompilador arm-cortexa9_neon-linux-gnueabihf-gcc 12 2.12 Sobreescribir configuración para una ejecución a través de linea de comando 14 2.13 Croscompilando y enlazando el ejecutable holamundo 16 2.14 Obteniendo información sobre librerías compartidas enlazadas con el ejecutable holamundo 16 2.15 Obteniendo información sobre el runtime linker para el ejecutable holamundo 16 2.16 Enlazando librerías de forma estática 16

3.1 Fragmento del device tree generado por Yocto Project para la ZedBoard 21 3.2 Ejemplo de fragmento de device tree de sistema de 64 bits 23 3.3 Fragmento del device tree para mostrar phandles e interrupciones 24 3.4 Inclusión de archivo .dtsi en el archivo .dts 25 3.5 Fragmento de ejemplo de un .dtsi 25 3.6 Fragmento de ejemplo de un .dts que incluye el .dtsi 25 3.7 Fragmento de ejemplo de un .dts que incluye el .dtsi 26 3.8 Obtención del código de U-Boot 26 3.9 Compilación de U-Boot para la zedboard 27

4.1 Descargando el repositorio con versiones estables del kernel de Linux 30 4.2 Cambiando a la versión del kernel que apunta ala rama 4.9.61 31 4.3 Comando para ejecutar la interfaz de configuración del kernel 32 4.4 Comando para ejecutar la creación del .config 32 4.5 Comando para revisar que version del kernel se ha construido 33 4.6 Creación de zImage 34 4.7 Ubicación de zImage 34 4.8 Fragmento del archivo common.c 34 4.9 Creación de uImage 35

105 106 Índice de Códigos

4.10 Compilación mostrandose cada línea de ejecución 35 4.11 Compilación de arboles de dispositivos 36 4.12 Búsqueda de arbol de dispositivos para introducir en la ZedBoard 36 4.13 Compilación de módulos kernel 36 4.14 Instalación de módulos kernel en el lugar deseado 36

5.1 Creando jerarquía de directorio rootfs 40 5.2 Clonando Busybox y actualizando a la rama más reciente 43 5.3 Croscompilando BusyBox 44 5.4 Ubicación del directorio sysroot de la toolchain 45 5.5 Creando los character devices null y console 45 5.6 Montando proc y sysfs en los directorios ya creados proc/ y sysfs 46

6.1 Comprobación de las versiones de los componentes 50 6.2 Instalación de paquetes necesarios para poder construir el sistema 50 6.3 Clonación del repositorio de Yocto Project 51 6.4 Actualización a la rama estable más reciente del proyecto 51 6.5 Instalación de la capa de Xilinx para Yocto Project 51 6.6 Cambio al directorio poky e inicialización del entorno de desarrollo 51 6.7 Revisión de localización de nuevo_proyecto y su contenido 52 6.8 Configuración de la máquina 52 6.9 Configuración del listado de las capas 53 6.10 Comprobación de las capas en el entorno de desarrollo de nuevo_proyecto 53 6.11 Creando una capa llamada meta-prueba 54 6.12 Opciones en la creación de una nueva capa 54 6.13 Añadiendo nuestra nueva capa meta-zedboard a bblayers.conf 54 6.14 Opciones elegidas en la creación de una nueva capa 55 6.15 Contenido de archivo layer.conf 55 6.16 Contenido de la receta holamundo_0.1.bb 56 6.17 Receta para instalar distintos ejecutables en el dispositivo 57 6.18 Comprobación de la existencia de los paquetes generados 58 6.19 Linea de código necesaria para instalación del paquete en la imagen 59 6.20 Listado de recetas de imágenes disponibles para ejecutar 59 6.21 Linea de código necesaria en zedboard-image.bb para instalación del paquete en la imagen 59 6.22 Localización de módulos kernel 60 6.23 Ejecutando el comando lsmod 61 6.24 Archivo Makefile que realiza la construcción de módulos kernel 61 6.25 Contenido del archivo hola-mod.c 62 6.26 Compilación del módulo kernel 63 6.27 Instalación , comprobación y desinstalación de módulo kernel 63 6.28 Comprobación de alertas del kernel con dmesg 64 6.29 Comprobación de alertas del kernel con tail -f /var/log/syslog 64 6.30 Contenido del Makefile 65 6.31 Contenido de mydriver-mod.bb 66 6.32 Listado del directorio /dev/ 67 6.33 Listado de dispositivos cuyos drivers estan cargados 68 6.34 Creación de nodos de character devices y block devices 70 6.35 Código básico que se utilizarán de fs.h en el driver de ejemplo 71 6.36 Contenido de mydriver.c 72 Índice de Códigos 107

6.37 Información mostrada por el kernel al realizar $ cat /dev/mydevice 75 6.38 Modificaciones para que el módulo kernel lea y escriba desde espacio de usuario 76 6.39 Programa de usuario para comunicación con el character device a través del device driver 76 6.40 Programa ejemplo de mapeado de memoria 78

7.1 Descargando e instalando paquetes más recientes de diversa índole 81 7.2 Descargando la capa de Xilinx para Yocto Project 81 7.3 Revisando las ramas disponibles para meta-xilinx 82 7.4 Revisando a que rama apunta meta-xilinx 82 7.5 Error debido a revisiones de conectividad 83 7.6 Solucionando el error representado en el código 7.5 83 7.7 Error causado por la falta de alguna capa 84 7.8 Buscando la ubicación de la receta que nos falta 84 7.9 Resultado de búsqueda con y sin asterisco 84 7.10 Receta sencilla de creación de imagen llamada zedboard-image.bb 85 7.11 Revisando existencia y ubicación de recetas requeridas 85 7.12 Error a causa de la falta de adición de una capa por dependencia 86 7.13 Error que muestra la ubicación del archivo que contiene el motivo del error 86 7.14 Imagen basada en core-image-sato 87 7.15 Error debido a croscompilación de helloworld.c 89 7.16 Línea a añadir en programas.bb para solucionar problema de croscompilación 90 7.17 Comprobación del reconocimiento del dispositivo desmontable 90 7.18 Comprobación de particiones montadas 91 7.19 Desmontando las particiones 91 7.20 Descomprimiendo el root filesystem en la partición que corresponde 92 7.21 Listado de máquinas soportadas por meta-xilinx 92 7.22 Construcción para máquina QEMU y ejecución de la emulación 93 7.23 Listado de máquinas disponibles en la capa meta-xilinx-comunity 94 7.24 Listado de ramas disponibles para la capa meta-xilinx-comunity 94 7.25 Descargando meta-enclustra 94 7.26 Contenido de la receta zx3-image.bb 95

Bibliografía

[1] Simmonds, Chris Mastering Embedded Linux Programming. Packt Publishing Ltd, 2015. [2] González, A. Embedded Linux Projects Using Yocto Project Cookbook. Packt Publishing Ltd, 2015. [3] Yocto Project Reference Manual. http://www.yoctoproject.org/docs/current/ref-manual/ref- manual.html [4] The Linux Documentation Project. http://www.tldp.org/ [5] The Linux Kernel Module Programming Guide. http://www.tldp.org/LDP/lkmpg/2.4/html/c43.htm [6] A Tutorial on the Device Tree (Zynq). http://xillybus.com/tutorials/device-tree-zynq-1 [7] ZedBoard Getting Started Guide Version 7.0. http://zedboard.org/sites/default/files/documentations/GS- AES-Z7EV-7Z020-G-V7-1.pdf [8] ZedBoard (Zynq™Evaluation and Development) Hardware User’s Guide. http://zedboard.org/sites/default/files/documentations/ZedBoard_HW_UG_v2_2.pdf [9] crosstool-NG. http://crosstool-ng.github.io/ [10] u-boot.git. http://git.denx.de/u-boot.git/ [11] The Linux Kernel Archives. https://www.kernel.org/ [12] BusyBox. https://git.busybox.net/busybox/ [13] meta-xilinx. https://github.com/Xilinx/meta-xilinx [14] meta-xilinx-community. http://git.yoctoproject.org/cgit/cgit.cgi/meta-xilinx-community [15] meta-enclustra. https://github.com/enclustra/meta-enclustra/wiki/Yocto [16] Proyecto GNU. https://www.gnu.org/ [17] GCC, the GNU Compiler Collection. http://gcc.gnu.org/ [18] OpenEmbedded Layer Index. https://layers.openembedded.org/layerindex/branch/master/layers/ [19] Linux From Scratch. http://www.linuxfromscratch.org/lfs/ [20] QEMU. https://www.qemu.org/ [21] Mars ZX3. https://www.enclustra.com/en/products/system-on-chip-modules/mars-zx3/

109