Embedded weblog

Aller au contenu | Aller au menu | Aller à la recherche

vendredi, juin 25 2010

cours de kernel Linux en ligne

L'annonce (sur linkedIn) de ces quelques articles de début de cours sur la programmation kernel Linux me fait rappeler que des slides de formation peuvent aussi être trouvés sur le web : chez FreeElectron, et bien entendu, chez Linagora !

Bonne lecture !  :)

jeudi, octobre 30 2008

portage Linux 2.6.25 et 2.6.27 sur carte Embest 2440, via J-Tag

Suite de nos aventures avec la carte chinoise faisant figurer le S3C2440. Ce fut long et laborieux (surtout à essayer de trouver du temps pour les tests), mais ça boote ! Et avec des logs consoles en ASCII, aussi (la précision a son importance), et un driver de carte réseau qui marche... Il n'y a peut-être pas encore tout de fonctionnel, mais en tout cas on a une console et des programmes qui tournent à la fin, c'est déjà beaucoup quand on sait d'où l'on part à la base (au début, il n'y avait rien...). Notamment une traversée des sites tous chinois, coréens et quelques uns japonais, expliquant plus ou moins comment faire marcher le bestiaux, avec une traduction google très approximative (voire franchement comique), et en réalité, il s'agit plus de la bidouille que de la vraies explications sur les problèmes rencontrés. En ayant eu marre de ramer dans ces sites-là (qu'il faut tout de même remercier, mais c'est dingue, n'y-a-til donc personne parlant Anglais ou Français et pouvant faire le même boulot ?), je me suis finalement attelé tout seul comme un grand au code noyau de A à Z, depuis l'init (problèmes de boot, il faut trouver la bonne config, puis de décompression, trouver la bonne adresse) jusqu'à celui des drivers, en passant par la console qui crachait n'importe quoi. J'ai donc tout d'abord essayé de porter un uClinux, mais ça ne marche pas ; je suis donc reparti avec le même code du 2.6.25 avec MMU, et j'ai vérifié que ce que j'ai écrit dans la suite de ce billet marche bien pour un 2.6.27 (j'ai bidouillé tellement de choses dans le kernel que même les logs font peur à voir  :)   ).

Tout d'abord, la compilation : on ne trouve aucun .config correct sur le Net, voici donc configuration kernel pour carte Embest2440. Comme vous le verrez, j'avais décidé de partir sur un uClinux, mais manifestement l'initialisation de la mémoire se vautre lamentablement (outre un bug impliqué par le numéro de machine écrasé, cf plus tard). Donc retour à l'utilisation normale de la MMU (en réalité, sur ARM, il n'est pas possible de se passer du coproc' P15, d'après une thèse lyonnaise de normal sup' que j'ai trouvée : la MMU est donc initialisée à l'identité, ie  @phys = @log ).

Première étape : compilation du kernel Linux. Avec le .config fourni, le kernel devrait faire à peine plus d'1,3Mo. Les flags de debug sont activés, le kernel avec symboles (environ 50Mo) se trouve à la racine ./S3C2440/linux-2.6.25/vmlinux.

make ARCH=arm xconfig
make ARCH=arm CROSS_COMPILE=arm-none-linux-gnueabi- uImage

Notez bien la ligne de commande :

root=/dev/nfs nfsroot=192.168.1.1:/home/gblanc/S3C2440/root_default ip=192.168.1.2:192.168.1.1::255.255.255.0:Linagora_board::none ro init=/linuxrc console=ttySAC0,115200

Ici on boote en NFS en utilisant l'IP 192.168.1.2, le serveur étant à l'IP 192.168.1.1, pour le reste, configuration standard. Avec ceci, vous risquez de voir apparaître beaucoup de garbage sur le minicom, même en réglant bien convenablement les options (qui sont celles par défaut, en fait) : c'est un problème de clock pour la carte SMDK2440 (censée être compatible avec la carte Embest, je commence à penser qu'il vaut peut-être mieux créer une nouvelle archi par copie avec un nouveau numéro... D'ailleurs un "find . -name '*sbc*'" sur le kernel fourni par Embest montre qu'ils ont patché dans tous les sens), il faut la passer en 12Mhz (c'est fait convenablement pour quasiment tous les IDs de machines similaires, mais pas pour la SMDK, or notre carte Embest est en 12Mhz). Dans le fichier arch/arm/mach-s3c2440/mach-smdk2440.c, replacer comme suit :

static void __init smdk2440_map_io(void)
{
        s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
//      s3c24xx_init_clocks(16934400);
        s3c24xx_init_clocks(12000000);
        s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
}

Ca devrait aller mieux tout à coup (on remarque aussi que s3c24xx_init_clocks appelé avec "0" met l'horloge à la valeur par défaut, qui est... 12*1000*1000). Reste le problème de la carte réseau, la CS8900. Un driver est fourni avec la carte, mais pour le noyau 2.6.13 : depuis, tellement d'eau a coulé sous les ponts qu'il n'est pas aisé de faire un portage. Et une fois fait, le nombre de bugs et autres Oops à débugger est assez scandaleux (je dirais même que l'architecture du chargement des drivers réseau a franchement changé, il faut réécrire l'init, remettre la section privée de la structure device au bon endroit, etc). En réalité, il y a un driver générique pour les cartes Cirrus qui a été écrit, depuis : CS89x0. Sauf qu'il n'est pas activé, et pire, pas activable par défaut. Il faut donc patcher le Kconfig qui va bien, et mettre les bonnes adresses de lecture/écriture de registres (sur ports I/O) et initialiser la MMU pour qu'elle pointe au bon endroit, histoire de ne pas Oopser (@phys sur 0x19000000, c'est fichtrement plus lisible dans u-boot que dans Linux pour retrouver cette adresse ! Evidemment, rien dans les doc, sinon regarder dans./include/asm-arm/arch-s3c2410/sbc2440v3-map.h du kernel 2.6.13 fourni par Embest !). Donc tout d'abord, dans drivers/net/Kconfig, aller à la section "Config CS89x0", et rajouter "|| ARCH_S3C2410" (ne faisons pas dans le détail ! Le but est de bypasser la dépendance sur NET_PCI et les cartes associées). Puis dans le code du driver, il va falloir taper sur une adresse logique qui va bien ; celle du code de la carte QT2410 (en 0xE0000300 -- il faut ajouter "300" aux adresses pour taper dans le réseau, ne me demandez pas d'où ça sort, cékomsa) est bien, mais j'ai finalement opté pour celle du vieux driver CS8900 de Embest, à l'adresse 0xD0000000, ce qui marche sur 2.6.25, mais pas sur 2.6.27, je suis donc retourné à la 0xE0000000. Dans arch/arm/mach-s3c2440/mach-smdk2440.c (puisque l'on décide d'adapter la SMDK2440), ajouter dans le tableau smdk2440_iodesc :

static struct map_desc smdk2440_iodesc[] __initdata = {
        /* ISA IO Space map (memory space selected by A24) */

        {
                .virtual        = (u32)S3C24XX_VA_ISA_WORD,
                .pfn            = __phys_to_pfn(S3C2410_CS2),
                .length         = 0x10000,
                .type           = MT_DEVICE,
        }, {
                .virtual        = (u32)S3C24XX_VA_ISA_WORD + 0x10000,
                .pfn            = __phys_to_pfn(S3C2410_CS2 + (1<<24)),
                .length         = SZ_4M,
                .type           = MT_DEVICE,
        }, {
                .virtual        = (u32)S3C24XX_VA_ISA_BYTE,
                .pfn            = __phys_to_pfn(S3C2410_CS2),
                .length         = 0x10000,
                .type           = MT_DEVICE,
        }, {
                .virtual        = (u32)S3C24XX_VA_ISA_BYTE + 0x10000,
                .pfn            = __phys_to_pfn(S3C2410_CS2 + (1<<24)),
                .length         = SZ_4M,
                .type           = MT_DEVICE,
        },
        { 0xE0000000, __phys_to_pfn(S3C2410_CS3+0x01000000), SZ_1M, MT_DEVICE }
};

Ainsi, l'adresse 0x18000000 + 0x01000000 (soit 0x19000000) sera mappée sur 0xE0000000 (avec une taille de SZ_1M, ce qui me semble un peu grand, mais c'est ce qui est "recommandé", alors...). Petit intermède à propos de ce mapping : dans la 2.6.27, on a la macro VMALLOC_END dans arch/arm/mach-s3c2410/include/mach/vmalloc.h (fichier qui n'existe pour 2.6.25) qui vaut 0xE0000000. Or, dans la mise en place des zones de I/O map, on vérifie que l'on n'écrase pas une zone allouée au mallocage : fonction  de /arch/arm/mm/mmu.c, et message "BUG: mapping for 0x19000000 at 0xd0000000 overlaps vmalloc space" si l'on met 0xD0000000 à la place de 0xE0000000.

Dans le même fichier, il faut "enregistrer" le driver réseau comme faisant partie de la configuration matérielle à activer :

static struct platform_device *smdk2440_devices[] __initdata = {
        &s3c_device_usb,
        &s3c_device_lcd,
        &s3c_device_wdt,
        &s3c_device_i2c,
        &s3c_device_iis,
        &s3c_device_cs89x0,
};

Cette structure est à déclarer dans arch/arm/plat-s3c24xx/devs.c (personnellement, j'ai inséré ce code avant la partie propre au 2440 marquée par "#ifdef CONFIG_CPU_S3C2440" autour de la ligne 500) :

/* CS8900 */

static struct resource s3c_cs89x0_resource[] = {
        [0] = {
                .start  = 0x19000000,
                .end    = 0x19000000 + 16,
                .flags  = IORESOURCE_MEM,
        },
        [1] = {
                .start  = IRQ_EINT9,
                .end    = IRQ_EINT9,
                .flags  = IORESOURCE_IRQ,
        },
};

struct platform_device s3c_device_cs89x0 = {
        .name           = "cirrus-cs89x0",
        .num_resources  = ARRAY_SIZE(s3c_cs89x0_resource),
        .resource       = s3c_cs89x0_resource,
};

EXPORT_SYMBOL(s3c_device_cs89x0);

On y trouve l'IRQ qui va bien, l'@ phys à attaquer pour les registres du device aussi (Si vous obtenez en sortie console quelque chose comme "cs89x0: request_region(0xd0000300, 0x10) failed", c'est que votre mapping s'est mal passé, et le driver ne marchera évidemment pas). Pour que l'utilisation du symbole s3c_device_cs89x0 se passe bien, il faut dans include/asm/plat-s3c24xx/devs.h () rajouter une ligne "extern struct platform_device s3c_device_cs89x0;". A présent dans drivers/net/cs89x0.c (le code du driver réseau), ajouter :

#elif defined(CONFIG_ARCH_PNX010X)
// blablabla
#elif defined(CONFIG_ARCH_S3C2410)
#include <asm/arch/irqs.h>  // copie de asm-arm/arch-s3c2410/irqs.h dans 2.6.25 ; attention, dans 2.6.27 se sera <mach/irqs.h> qui pointe vers arch/arm/mach-s3c2410/include/mach/irqs.h ; il faut trouver où est IRQ_EINT9
#include <linux/irq.h>
static unsigned int netcard_portlist [] __initdata = { 0xD0000300, 0 };
static unsigned int cs8900_irq_map[] = { IRQ_EINT9, 0, 0, 0 };
#define NO_EPROM
#else
static unsigned int netcard_portlist[] __used __initdata =
   { 0x300, 0x320, 0x340, 0x360, 0x200, 0x220, 0x240, 0x260, 0x280, 0x2a0, 0x2c0, 0x2e0, 0};
static unsigned int cs8900_irq_map[] = {10,11,12,5};
#endif

On indique donc que le port sera sur l'adresse virtuelle 0xD0000300 (c'est la base, ensuite les additions pour trouver les bons registres de lecture/écriture/contrôle sont toujours les mêmes). On indique aussi l'IRQ (ce sera la 53). Et qu'il n'y a pas d'EEPROM ; sinon il se passera des choses étranges -- de fait, il faudra entrer l'adresse mac à la main (oui, c'est très moche) :

printk(" IRQ %d", dev->irq);

dev->dev_addr[0] = 0x00;
dev->dev_addr[1] = 0x00;
dev->dev_addr[2] = 0xc0;
dev->dev_addr[3] = 0xff;
dev->dev_addr[4] = 0xee;
dev->dev_addr[5] = 0x08;
set_mac_address(dev, dev->dev_addr);

A mettre dans le driver, dans la fonction cs89x0_probe1  (qui fait l'initialisation du device), vers la ligne 930 sur le 2.6.25 (ou 831 pour un 2.6.27), après la section macroïsé par "#ifndef MONO_IRQ_MAP" et avant celle de "#if ALLOW_DMA". A présent, ça arrêtera de Oopser. On peut aussi juste au dessus (aux environs de la ligne 910, 805 sur 2.6.27) mettre en place la bonne méthode d'enregistrement de l'IRQ dans la structure device :

                if (lp->chip_type == CS8900) {
#if defined(CONFIG_MACH_IXDP2351) || defined(CONFIG_ARCH_IXDP2X01) || defined(CONFIG_ARCH_PNX010X) || defined(CONFIG_ARCH_S3C2410)
                        i = cs8900_irq_map[0];
#else

Mais à l'exécution, ça va merder sur l'IRQ, tout de même :

irq 53: nobody cared (try booting with the "irqpoll" option)
[<c0027d68>] (dump_stack+0x0/0x14) from [<c00602a4>] (__report_bad_irq+0x38/0x88)
[<c006026c>] (__report_bad_irq+0x0/0x88) from [<c00605a4>] (note_interrupt+0x2b0/0x308)
 r4:00000000
[<c00602f4>] (note_interrupt+0x0/0x308) from [<c0061380>] (handle_edge_irq+0x11c/0x1a4)
[<c0061264>] (handle_edge_irq+0x0/0x1a4) from [<c0032cd4>] (s3c_irq_demux_extint8+0x98/0xa8)
 r8:00000000 r7:00000001 r6:00000038 r5:c02c0ee8 r4:00000000
[<c0032c3c>] (s3c_irq_demux_extint8+0x0/0xa8) from [<c0023044>] (asm_do_IRQ+0x44/0x60)
[<c0023000>] (asm_do_IRQ+0x0/0x60) from [<c00235f8>] (__irq_svc+0x38/0xb0)
Exception stack(0xc0345e0c to 0xc0345e54)
5e00:                            00000000 fb000000 00000001 00000000 c02c1a80
5e20: 40000013 00000035 c0d223e0 c016811c c0ccb000 c0ccb000 c0345e70 c0345e30
5e40: c0345e54 c006074c c005fda0 a0000013 ffffffff
 r6:00000020 r5:f4000000 r4:ffffffff
[<c005fc00>] (setup_irq+0x0/0x230) from [<c005fed4>] (request_irq+0xa4/0xd0)
 r7:00000000 r6:00000000 r5:00000035 r4:c0d223e0
[<c005fe30>] (request_irq+0x0/0xd0) from [<c0167af8>] (net_open+0xe4/0x4b8)
[<c0167a14>] (net_open+0x0/0x4b8) from [<c01d7abc>] (dev_open+0x84/0xdc)
 r6:00001002 r5:c0ccb02c r4:c0ccb000
[<c01d7a38>] (dev_open+0x0/0xdc) from [<c01d684c>] (dev_change_flags+0xb0/0x178)
 r5:00001003 r4:c0ccb000
[<c01d679c>] (dev_change_flags+0x0/0x178) from [<c001d0a4>] (ip_auto_config+0x168/0xcb8)
 r7:00001002 r6:00000001 r5:c0ccb000 r4:c02f9ecc
[<c001cf3c>] (ip_auto_config+0x0/0xcb8) from [<c000890c>] (kernel_init+0xb8/0x278)
[<c0008854>] (kernel_init+0x0/0x278) from [<c0040524>] (do_exit+0x0/0x620)
handlers:
[<c016811c>] (net_interrupt+0x0/0x3cc)
Disabling IRQ #53

Oui, ça fait peur. Le résultat, c'est que l'on émet bien des paquets sur l'interface réseau, mais à la réception de la réponse, l'IRQ a été désactivé faute d'avoir trouvé un handler correct, et donc le driver n'est pas notifié de l'arrivée des nouveaux paquets : c'est un dialogue de sourds :

eth0: Attempting TP
eth0: 10Base-T (RJ-45) has no cable
eth0: using half-duplex 10Base-T (RJ-45)
cs89x0: net_open() succeeded
IP-Config: Complete:
     device=eth0, addr=192.168.1.2, mask=255.255.255.0, gw=255.255.255.255,
     host=Linagora_board, domain=, nis-domain=(none),
     bootserver=192.168.1.1, rootserver=192.168.1.1, rootpath=
Looking up port of RPC 100003/2 on 192.168.1.1
eth0: sent 42 byte packet of type 806
NETDEV WATCHDOG: eth0: transmit timed out
eth0: transmit timed out, IRQ conflict ??
eth0: sent 42 byte packet of type 806
cs89x0: Tx buffer not free!
NETDEV WATCHDOG: eth0: transmit timed out
eth0: transmit timed out, IRQ conflict ??
eth0: sent 42 byte packet of type 806

Il va donc falloir aussi corriger cela. Après avoir bien lutté, il s'avère que c'est la "déclaration" de l'IRQ qui manque, avant son enregistrement comme correspondant au bon handler (via request_irq). Donc dans le fichier ./drivers/net/cs89x0.c, dans la procédure net_open(), on ajoute :

#if !defined(CONFIG_MACH_IXDP2351) && !defined(CONFIG_ARCH_IXDP2X01) && !defined(CONFIG_ARCH_PNX010X) && !defined(CONFIG_ARCH_S3C2410)
                if (((1 << dev->irq) & lp->irq_map) == 0) {
                        printk(KERN_ERR "%s: IRQ %d is not in our map of allowable IRQs, which is %x ",
                               dev->name, dev->irq, lp->irq_map);
                        ret = -EAGAIN;
                        goto bad_out;
                }
#endif

                set_irq_type(dev->irq, IRQ_TYPE_EDGE_RISING);   // IRQT_RISING dans les vieux kernels
/* FIXME: Cirrus' release had this: */
                writereg(dev, PP_BusCTL, readreg(dev, PP_BusCTL)|ENABLE_IRQ );
/* And 2.3.47 had this: */
#if 0
                writereg(dev, PP_BusCTL, ENABLE_IRQ | MEMORY_ON);
#endif
                write_irq(dev, lp->chip_type, dev->irq);
                ret = request_irq(dev->irq, &net_interrupt, 0, dev->name, dev);

Et voilà le travail !  :)  Si vous obtenez ceci (juste avant "Looking up port of RPC 100003/2 on 192.168.1.1"), c'est que vous vous êtes planté quelque part :

eth0: EEPROM is configured for unavailable media
IP-Config: Failed to open eth0
IP-Config: No network devices available.

Par exemple, sur la 2.6.27, ce n'est pas votre faute : la macro NO_EPROM n'existe plus, résultat, c'est la cata, plus rien ne marche. C'est très bête (euphémisme). Après le commentaire "/* First check to see if an EEPROM is attached. */" (ligne ~730), il faut donc insérer :

#ifdef NO_EPROM
        if (1) {
                printk(KERN_NOTICE "cs89x0: No EEPROM ");
                lp->adapter_cnf = A_CNF_10B_T | A_CNF_MEDIA_10B_T;
                lp->auto_neg_cnf = EE_AUTO_NEG_ENABLE | IMM_BIT;
        } else
#endif

Et tout à coup, ça va beaucoup mieux :

eth0: 10Base-T (RJ-45) has no cable
eth0: using half-duplex 10Base-T (RJ-45)
IP-Config: Complete:
     device=eth0, addr=192.168.1.2, mask=255.255.255.0, gw=255.255.255.255,
     host=Linagora_board, domain=, nis-domain=(none),
     bootserver=192.168.1.1, rootserver=192.168.1.1, rootpath=
Looking up port of RPC 100003/2 on 192.168.1.1

Il faut bien entendu avoir un serveur NFS correctement configuré, on peut utiliser aussi wireshark pour observer les trames passer (attention au firewall et au contrôle d'accès par wilcard -- ici il n'y en a aucun, c'est la fête, heureusement qu'on est en réseau local entre ami, mais ce sont les joies de l'embarqué, *il faut* ne pas squasher root, et avoir un bon dossier /dev avec devices console, null, zero et random au minimum, en root) :

# cat /etc/exports
/home/gblanc/S3C2440/Linux/rootarm/     (rw,no_root_squash,async,anonuid=0,anongid=0)
/home/gblanc/S3C2440/root_default       (rw,no_root_squash,async,anonuid=0,anongid=0)

Je vous ai fait une archive avec les fichiers modifiés aux bons endroits pour une 2.6.27.4 (pour la 2.6.25, il faudrait trop nettoyer de choses :)   ). Le voici : linux-2.6.27.4-emb2440.tar.gz.

Seconde étape : bien brancher son câble J-TAG Amontec-à-30€, ne pas oublier de monter l'usbfs et d'exécuter openOCD en root (ou de bidouiller son udev), sinon ça démarre mal. Bonne nouvelle : on peut faire ce que l'on veut avec le câble, j'ai même trouvé des gens sur le net qui ont fait marcher le Flash programmer, mais je ne m'en suis pas servi personnellement. En revanche, la limitation pour les breakpoints/watchpoints (qui ne marchent pas super bien, en fait) est de seulement deux, donc dès que l'on veut faire du stepping sous gdb, la limite tombe à un : comment passer son temps à faire des del et des b*0x... pour changer au fur-et-à-mesure de points d'arrêts hardware, dommage. En ce qui concerne le gdb, celui de codesourcery marche convenablement, je n'ai pas compris pourquoi celui de buildroot était buggué (dans l'accès aux registres, ça retourne des erreurs ou fait n'importe quoi), j'ai seulement trouvé que le bug avait été repéré il y a quelques années par... un développeur de codesourcery sur gdb. Bref, démarrons notre OpenOCD avec ce fichier de conf :

interface ft2232
jtag_speed 0
jtag_ntrst_delay 100
jtag_nsrst_delay 100

ft2232_vid_pid 0x0403 0xcff8
ft2232_layout "jtagkey"
ft2232_device_desc "Amontec JTAGkey"

jtag_device 4 0x1 0xf 0xe

#daemon_startup attach
target arm920t little 0 arm920t

reset_config trst_and_srst combined

working_area 0 0x33F00000 0x4000 nobackup

#run_and_halt_time 0 500

#nand device <nand_controller> [controller options]
nand device s3c2440 0

#script
init
reset
halt
#nand probe 0
#arm7_9 sw_bkpts enable
arm7_9 force_hw_bkpts enable

On remarque une partie "script" avec "init", "reset" et "halt" ; "reset" sert à redémarrer la carte automatiquement quand on lance OpenOCD (ça évite d'appuyer sur le bouton), si l'on veut se brancher à chaud sans redémarrer la carte, il suffit de le commenter (ça marche très bien) ; "halt" met le CPU sur "pause", idem on peut s'en passer si l'on ne veut pas avoir à entrer "continue" sous gdb (on retrouvera plus tard cet état de la carte avec la commande "mon poll"). Puis on lance arm-none-linux-gnueabi-gdb :

target remote localhost:3333
load ./S3C2440/linux-2.6.25/arch/arm/boot/compressed/vmlinux 0x33D50000
set $r0=0
set $r1=362
set $r2=0
set $sp=0x33D50000
set $pc=0x33D50000
set $lr=0x33D50000

Le Linux compressé (vmlinux) peut être charger à l'adresse 0x33D50000, c'est calculé à la louche (sur une base d'un kernel ne dépassant pas les 2Mo et quelques) pour ne pas être en problème de réécrasement de mémoire lorsque le kernel va se décompresser à l'adresse 0x30008000, et pour éviter d'écraser aussi le u-boot en mémoire à l'adresse 0x33F80000, sait-on jamais, il pourrait resservir. Attention : ne pas charger en 0x0, ça boote mais crashe au bout d'un court moment : la mémoire mappée en 0x0 est bien de la RAM, mais c'est normalement le SteppingStone (copie des 4 premiers Kb de la flash NAND en RAM pour booter dessus, faute d'avoir de la flash NOR, cf paragraphes 6 et 5 de la documentation du S3C2440), de telle sorte qu'au bout d'un certain temps toute la mémoire après 0x1000 est remise à 0. Donc avec un uClinux, il ne faut pas utiliser load tel quel, mais bien préciser une adresse de chargement (je me suis bien fait avoir, surtout que ça avait l'air de marcher... jusqu'à ce que ça parte n'importe où en mémoire, et que ça reboote cycliquement tout seul).

Il ne faut pas oublier de mettre les trois premiers registres dans un état que normalement le boot loader devrait gérer, et qui seront recupérés plus tard au démarrage du noyau : r0 doit être à 0 ; r1 doit contenir l'ID de la machine (on récupère ça dans arch/arm/tools/mach-types, SMDK2440 est 1008, s3c2440 est 362, a9m2440 est 698, et QT2410 est 1108, etc) ; r2 doit pointer sur la ligne de commande, le mettre à 0 revient à utiliser celle compilée en dur par l'option CMDLINE. Il n'y a plus qu'à mettre sp, pc et lr (normalement seul sp suffit, mais bon...) pour pointer sur l'adresse mémoire de chargement du noyau compressé, lancer évidemment un minicom à côté, et entrer "c" comme "continue" dans gdb : c'est parti ! Attention : parfois l'initialisation de sp/pc/lr échoue silencieusement (et parfois bruyamment : sp peut s'être transformé en r13), si l'on retombe sur le boot loader après avoir entré "c", il faut faire un ctrl-C et réaffecter les six registres ci-dessus ; ça méritera un coup de debugging du débugger un de ces quatre... (apparemment ça arrive surtout quand on appelle gdb avec un argument -- cf en dessous)

Pour débugger votre noyau, il faut lancer gdb comme suit :

arm-none-linux-gnueabi-gdb ./S3C2440/linux-2.6.25/vmlinux

Il ne faut pas utiliser add-symbol file : c'est malheureusement buggué, les symboles sont décalés au bout d'un certain temps (la commande "file" semble en revanche saine). Attention : ça utilisera les bonnes adresses après chargement de la MMU. On peut s'en rendre compte avec

arm-none-linux-gnueabi-objdump -D vmlinux | less

Tout est calé sur 0xC0008000. Pour débugger Linux avant décompression, on peut en revanche faire appel à :

add-symbol-file ./S3C2440/linux-2.6.25-uc/arch/arm/boot/compressed/vmlinux 0x33D50000

Ca chargera les symboles (ils ne sont pas bien nombreux) avec comme référence notre adresse de chargement du dessus (attention aux doublons, notamment en terme d'initialisation d'UART !). On peut ensuite mettre des break sur les symboles, faire des print, et des disassemble ; pour arrêter le kernel on peut toujours faire Ctrl-C n'importe quand, bien entendu. On peut aussi passer directement des commandes à OpenOCD :

(gdb) mon poll
target state: halted
target halted in ARM state due to debug request, current mode: Supervisor
cpsr: 0x40000053 pc: 0x33d50000
MMU: disabled, D-Cache: enabled, I-Cache: enabled
(gdb) mon reg
(0) r0 (/32): 0x00000000 (dirty: 1, valid: 1)
(1) r1 (/32): 0x0000016a (dirty: 1, valid: 1)
(2) r2 (/32): 0x00000000 (dirty: 1, valid: 1)
//etc//

"mon help" permet de toutes les obtenir. On peut ainsi modifier directement les registres ("mon reg r1 0x0", par exemple), et gérer le coprocesseur P15 (mon arm920t cp15), cependant mes tentatives pour désactiver la MMU et rebooter manuellement sans avoir à réinitialiser la carte (et donc passer par un rechargement du noyau compressé, ce qui prend un peu de temps) se sont soldés par des échecs (la carte est en carafe, impossible de la mettre en état halt). Attention aussi : le "step" de gdb en assembleur n'est parfois pas terrible (il n'exécute pas une instruction), auquel cas il vaut mieux faire appel au step de OpenOCD via "mon step" ; à ce moment-là, seul "mon reg" donne un état correct des registres, "info registers" semble connaître des problèmes de cache/synchronisation lorsqu'on n'utilise pas les routines de gdb, de telles sortes que les valeurs communiquées sont obsolètes.

Sur le noyau 2.6.27, ttySAC0 ne donne rien comme affichage (après le message de décompression du kernel, plus rien ne s'affiche sur le minicom), en fait la console n'est pas initialisée. C'est un bon moyen de voir comment on peut débugger avec notre J-TAG. Après le chargement en mémoire, et avant de lancer le kernel, on récupère l'adresse virtuelle de printk :

> grep " printk$" System.map
c003deac T printk

On met alors un breakpoint dessus dans gdb :

(gdb) b *0xc003deac
Breakpoint 1 at 0xc003deac
(gdb)

Je n'utilise pas la commande file pour charger les fichiers ici : on pourrait le faire, mais en l'occurrence un bug m'en empêche, gdb segfaulte ! (il faudra vraiment débugger le débugger à un moment :)  ) Il faut dire qu'on le pousse là dans ses derniers retranchements. Il n'y a plus qu'à lancer le kernel avec "c". Lorsqu'on arrive sur printk, voici ce que l'on peut faire :

(gdb) c
Continuing.

Breakpoint 1, 0xc003deac in ?? ()
(gdb) printf "%s",$r0
Linux version 2.6.27.4linagora (gblanc@deepblue) (gcc version 4.2.0 20070413 (prerelease) (CodeSourcery Sourcery G++ Lite 2007q1-10)) #4 PREEMPT Wed Oct 29 19:33:38 CET 2008
(gdb) c
Continuing.

Breakpoint 1, 0xc003deac in ?? ()
(gdb) printf "%s",$r0
CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx
(gdb) c
Continuing.

Breakpoint 1, 0xc003deac in ?? ()
(gdb) printf "%s",$r0
Machine: %s
(gdb) printf "%s",$r1
SMDK2440(gdb) c
Continuing.

Ceci s'affiche avant même l'initialisation de la console ! (cf la fonction register_console dans printk.c, pour ça) On peut ainsi voir où l'on en est : en l'occurrence, on trouve que les printk sautent ce qui devrait normalement s'afficher,

Console: colour dummy device 80x30
selected clock c02c9bf0 (pclk) quot 26, calc 117187
console [ttySAC0] enabled

Le "selected clock" et le "enabled" sont absents. Voilà qui est gênant. Un breakpoint sur la fonction strcmp utilisée dans register_console pour trouver la bonne console à activer montre que "ttySAC" n'est jamais testé ! On cherche donc dans notre ancien kernel 2.6.25 comment cela marchait avant, ie où était inscrit en dur la châine de caractère "ttySAC" : on trouve une macro dans./drivers/serial/s3c2410.c  et quelque chose dans ./drivers/serial/Kconfig : regardons dans 2.6.27, la macro n'existe plus, mais on a quelque chose dans le Kconfig. On fait un xconfig et on active : SERIAL_SAMSUNG et SERIAL_SAMSUNG_CONSOLE, ainsi que SERIAL_S3C2440, tous marqués comme "(NEW)". Tadam, ça marche !  :)

Uncompressing Linux..............................................................................................
done, booting the kernel.
Linux version 2.6.27.4linagora (gblanc@deepblue) (gcc version 4.2.0 20070413 (prerelease) (CodeSourcery Sourcery G
++ Lite 2007q1-10)) #14 PREEMPT Thu Oct 30 15:14:11 CET 2008
CPU: ARM920T [41129200] revision 0 (ARMv4T), cr=c0007177
Machine: SMDK2440
Warning: bad configuration page, trying to continue
Memory policy: ECC disabled, Data cache writeback
CPU S3C2440A (id 0x32440001)
S3C244X: core 405.000 MHz, memory 101.250 MHz, peripheral 50.625 MHz
S3C24XX Clocks, (c) 2004 Simtec Electronics
CLOCK: Slow mode (1.500 MHz), fast, MPLL on, UPLL on
CPU0: D VIVT write-back cache
CPU0: I cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
CPU0: D cache: 16384 bytes, associativity 64, 32 byte lines, 8 sets
Built 1 zonelists in Zone order, mobility grouping off.  Total pages: 4064
Kernel command line: root=/dev/nfs nfsroot=192.168.1.1:/home/gblanc/S3C2440/Linux/rootarm ip=192.168.1.2:192.168.1
.1::255.255.255.0:Linagora_board::none rw init=/bin/sh console=ttySAC0,115200
irq: clearing pending ext status 00000200
irq: clearing subpending status 00000002
PID hash table entries: 64 (order: 6, 256 bytes)
timer tcon=00500000, tcnt a4ca, tcfg 00000200,00000000, usec 00001e57
Console: colour dummy device 80x30
console [ttySAC0] enabled
Dentry cache hash table entries: 2048 (order: 1, 8192 bytes)
Inode-cache hash table entries: 1024 (order: 0, 4096 bytes)
Memory: 16MB = 16MB total
Memory: 13100KB available (2688K code, 277K data, 112K init)
Calibrating delay loop... 201.93 BogoMIPS (lpj=504832)
Mount-cache hash table entries: 512
CPU: Testing write buffer coherency: ok
net_namespace: 288 bytes
NET: Registered protocol family 16
S3C2410 Power Management, (c) 2004 Simtec Electronics
S3C2440: Initialising architecture
S3C2440: IRQ Support
S3C24XX DMA Driver, (c) 2003-2004,2006 Simtec Electronics
DMA channel 0 at c1800000, irq 33
DMA channel 1 at c1800040, irq 34
DMA channel 2 at c1800080, irq 35
DMA channel 3 at c18000c0, irq 36
S3C244X: Clock Support, DVS off
usbcore: registered new interface driver usbfs
usbcore: registered new interface driver hub
usbcore: registered new device driver usb
NET: Registered protocol family 2
IP route cache hash table entries: 1024 (order: 0, 4096 bytes)
TCP established hash table entries: 512 (order: 0, 4096 bytes)
TCP bind hash table entries: 512 (order: -1, 2048 bytes)
TCP: Hash tables configured (established 512 bind 512)
TCP reno registered
NET: Registered protocol family 1
NetWinder Floating Point Emulator V0.97 (double precision)
JFFS2 version 2.2. (NAND) �© 2001-2006 Red Hat, Inc.
msgmni has been set to 25
io scheduler noop registered
io scheduler anticipatory registered (default)
io scheduler deadline registered
io scheduler cfq registered
Console: switching to colour frame buffer device 30x40
fb0: s3c2410fb frame buffer device
Serial: 8250/16550 driver4 ports, IRQ sharing enabled
s3c2440-uart.0: s3c2410_serial0 at MMIO 0x50000000 (irq = 70) is a S3C2440
s3c2440-uart.1: s3c2410_serial1 at MMIO 0x50004000 (irq = 73) is a S3C2440
s3c2440-uart.2: s3c2410_serial2 at MMIO 0x50008000 (irq = 76) is a S3C2440
brd: module loaded
loop: module loaded
nbd: registered device at major 43
cs89x0:cs89x0_probe(0x0)
cs89x0.c: v2.4.3-pre1 Russell Nelson <nelson@crynwr.com>, Andrew Morton <andrewm@uow.edu.au>
eth0: cs8900 rev K found at 0xe0000300
cs89x0: No EEPROM
cs89x0 media RJ-45, IRQ 53eth0: Setting MAC address to c0:ff:ee:08:00:00.
, programmed I/O, MAC c0:ff:ee:08:00:00
cs89x0_probe1() successful
cs89x0:cs89x0_probe(0x0)
cs89x0: request_region(0xe0000300, 0x10) failed
cs89x0: no cs8900 or cs8920 detected.  Be sure to disable PnP with SETUP
Uniform Multi-Platform E-IDE driver
S3C24XX NAND Driver, (c) 2004 Simtec Electronics
s3c2440-nand s3c2440-nand: Tacls=3, 29ns Twrph0=7 69ns, Twrph1=3 29ns
NAND device: Manufacturer ID: 0xec, Chip ID: 0x76 (Samsung NAND 64MiB 3,3V 8-bit)
Scanning device for bad blocks
Creating 8 MTD partitions on "NAND 64MiB 3,3V 8-bit":
0x00000000-0x00004000 : "Boot Agent"
0x00000000-0x00200000 : "S3C2410 flash partition 1"
0x00400000-0x00800000 : "S3C2410 flash partition 2"
0x00800000-0x00a00000 : "S3C2410 flash partition 3"
0x00a00000-0x00e00000 : "S3C2410 flash partition 4"
0x00e00000-0x01800000 : "S3C2410 flash partition 5"
0x01800000-0x03000000 : "S3C2410 flash partition 6"
0x03000000-0x04000000 : "S3C2410 flash partition 7"
aoe: AoE v47 initialised.
usbmon: debugfs is not available
s3c2410-ohci s3c2410-ohci: S3C24XX OHCI
s3c2410-ohci s3c2410-ohci: new USB bus registered, assigned bus number 1
s3c2410-ohci s3c2410-ohci: irq 42, io mem 0x49000000
usb usb1: configuration #1 chosen from 1 choice
hub 1-0:1.0: USB hub found
hub 1-0:1.0: 2 ports detected
mice: PS/2 mouse device common for all mice
S3C24XX RTC, (c) 2004,2006 Simtec Electronics
i2c /dev entries driver
s3c2440-i2c s3c2440-i2c: slave address 0x10
s3c2440-i2c s3c2440-i2c: bus frequency set to 98 KHz
s3c2440-i2c s3c2440-i2c: i2c-0: S3C I2C adapter
S3C2410 Watchdog Timer, (c) 2004 Simtec Electronics
s3c2410-wdt s3c2410-wdt: watchdog inactive, reset disabled, irq enabled
Registered led device: led4
Registered led device: led5
Registered led device: led6
Registered led device: led7
Advanced Linux Sound Architecture Driver Version 1.0.17.
ALSA device list:
  No soundcards found.
TCP cubic registered
RPC: Registered udp transport module.
RPC: Registered tcp transport module.
drivers/rtc/hctosys.c: unable to open rtc device (rtc0)
eth0: using half-duplex 10Base-T (RJ-45)
IP-Config: Complete:
     device=eth0, addr=192.168.1.2, mask=255.255.255.0, gw=255.255.255.255,
     host=Linagora_board, domain=, nis-domain=(none),
     bootserver=192.168.1.1, rootserver=192.168.1.1, rootpath=
Looking up port of RPC 100003/2 on 192.168.1.1
Looking up port of RPC 100005/1 on 192.168.1.1
VFS: Mounted root (nfs filesystem).
Freeing init memory: 112K
sh-3.00#
sh-3.00#
sh-3.00# ls
bin  etc   lib      lost+found  opt   root  tmp  var
dev  home  linuxrc  mnt         proc  sbin  usr
sh-3.00# bin
bin/  bind
sh-3.00# bin/
addgroup   chmod      ed         hostname   mv         sleep      vi
adduser    chown      egrep      kill       netstat    stty       zcat
arch       cp         false      ln         pidof      su         zcmp
ash        date       fgrep      login      ping       sync       zdiff
bash       dd         g++        ls         ps         tar        zegrep
bashbug    delgroup   gcc        mkdir      pwd        touch      zfgrep
busybox    deluser    getopt     mknod      rm         true       zforce
c++        df         grep       mktemp     rmdir      umount     zgrep
cat        dir        gunzip     more       run-parts  uname      zless
cc         dmesg      gzexe      mount      sed        usleep     zmore
chgrp      echo       gzip       mt         sh         vdir       znew
sh-3.00# bin/echo toto
toto
sh-3.00# ps
Error, do this: mount -t proc none /proc
sh-3.00# mount -t proc none /proc
sh-3.00# ps
  PID TTY          TIME CMD
    1 ?        00:00:01 sh
    2 ?        00:00:00 kthreadd
    3 ?        00:00:00 ksoftirqd/0
    4 ?        00:00:00 watchdog/0
    5 ?        00:00:00 events/0
    6 ?        00:00:00 khelper
   64 ?        00:00:00 kblockd/0
   69 ?        00:00:00 ksuspend_usbd
   75 ?        00:00:00 khubd
   78 ?        00:00:00 kseriod
   85 ?        00:00:00 kmmcd
  108 ?        00:00:00 pdflush
  109 ?        00:00:00 pdflush
  110 ?        00:00:00 kswapd0
  111 ?        00:00:00 aio/0
  112 ?        00:00:00 nfsiod
  796 ?        00:00:00 mtdblockd
  852 ?        00:00:00 kpsmoused
  878 ?        00:00:00 rpciod/0
  891 ?        00:00:00 ps
sh-3.00#

Ceci est l'ensemble des logs obtenus avec un noyau 2.6.27. On remarque que certaines choses ne marchent pas encore, comme la carte son (à moins de se planter dans le mapping mémoire, ce qui fait sortir de la carte un son extrêmement strident de ce qui s'avère être un buzzer, mais ça n'a rien à voir avec la carte son), ou la RTC (qui sur un 2.6.27, fait ramer le démarrage, alors que ça se passe sans problème sur le 2.6.25) ; le LCD n'a pas encore été testé. Ici, j'utilise un autre config.emb2440-2.6.27eabi avec l'EABI d'activé : ça marche ! :) Il faudra aussi penser à virer le mapping de la flash en dur (berk), et mettre quelque chose de propre avec mtdparts, par exemple (en réalité, on s'en moque, le but maintenant va être de pouvoir écrire sur la flash où l'on veut quand on veut depuis le réseau et non le J-TAG tout lent ou le u-boot tout imprédictible). A voir dans un prochain épisode, certainement (si j'ai le temps...).

mercredi, octobre 29 2008

Linux Device Driver en ligne

Je ne le savais point jusqu'à hier, c'est ici, et c'est sous GNU/FDL. C'est la seconde édition de Alessandro Rubini et Jonathan Corbet, juin 2001, ça date un peu, mais ça évite toujours de transporter son propre exemplaire (et pour faire des recherches, c'est plus rapide aussi :)  ).