Logiciels Libres et Systèmes Embarqués


5.7. Pilote du contrôleur d'interruptions

Nous allons voir dans cette section comment écrire le pilote du contrôleur d'interruptions externes de Xilinx. Ce pilote marche uniquement si le contrôleur est sur le bus OPB et qu'il possède les registres optionnels IVR, SIE et CIE. Il faut donc faire très attention à leur présence lors de la conception du système avec EDK (dont le schéma de connexion est donné à la figure 5.2).

Figure 5.2. Connections du contrôleur d'interruptions

Connections du contrôleur d'interruptions

Dans un premier temps, il faut modifier arch/ppc/kernel/Makefile de la façon suivante :

  obj-$(CONFIG_40x)		+= ppc4xx_setup.o
- obj-$(CONFIG_4xx)		+= ocp.o ppc4xx_pic.o todc_time.o
+ obj-$(CONFIG_4xx)		+= todc_time.o
+ ifeq ($(CONFIG_VIRTEX),y)
+ obj-$(CONFIG_40x)		+= virtex_pic.o
+ else
+ obj-$(CONFIG_4xx)		+= ocp.o ppc4xx_pic.o
+ endif

Le fichier arch/ppc/kernel/virtex_pic.c contient les macros et les variables globales nécessaires à ce pilote :

#include "linux/irq.h"
#include "asm/io.h"
#include "linux/init.h"

#define ISR ((volatile u32 *)(intc))   /* Interrupt Status Register   */
#define IPR ((volatile u32 *)(intc+1)) /* Interrupt Pending Register  */
#define IER ((volatile u32 *)(intc+2)) /* Interrupt Enable Register   */
#define IAR ((volatile u32 *)(intc+3)) /* Interrupt Ack. Register     */
#define SIE ((volatile u32 *)(intc+4)) /* Set Interrupt Enable bits   */
#define CIE ((volatile u32 *)(intc+5)) /* Clear Interrupt Enable bits */
#define IVR ((volatile u32 *)(intc+6)) /* Interrupt Vector Register   */
#define MER ((volatile u32 *)(intc+7)) /* Master Enable Register      */

static u32 *intc = 0;
struct hw_interrupt_type *ppc4xx_pic;

Les numéros d'IRQ sont des entiers dans le noyau Linux. Cependant, en ce qui concerne le contrôleurs d'interruptions externes, il s'agit de vecteurs de bits. Pour les convertir, il suffit de prendre les bits significatifs, c'est-à-dire les cinq premiers puisqu'il n'y a que 32 vecteurs d'interruptions, puis de créer un masque avec le bit significatif au bon endroit, grâce à un décalage. Les fonctions de ce pilotes sont alors :

#define IRQ_TO_MASK(irq) (1 << ((irq) & 0x1F))

static void virtex_xintc_enable(unsigned int irq)
{
    out_be32(SIE, IRQ_TO_MASK(irq));
}

static void virtex_xintc_disable(unsigned int irq)
{
    out_be32(CIE, IRQ_TO_MASK(irq));
}

static void virtex_xintc_disable_and_ack(unsigned int irq)
{
    out_be32(CIE, IRQ_TO_MASK(irq));
    out_be32(IAR, IRQ_TO_MASK(irq));
}

int virtex_xintc_get_irq(struct pt_regs *regs)
{
    return in_be32(IVR);
}

Les fonctions out_be32 et in_be32 permettent d'écrire ou de lire trente-deux bits de manières synchrones dans l'ordre "Big Endian".

Contrairement à ce qu'on pourrait penser, le registre IVR contient le numéro de l'interruption activée de plus haute priorité (plus petit numéro) et pas le masque.

Il faut ensuite remplir la structure hw_interrupt_type contenant les fonctions du pilote de contrôleurs d'interruptions externes :

static struct hw_interrupt_type virtex_pic = {
    "VIRTEX Xilinx Interrupt Controller",
    NULL,
    NULL,
    virtex_xintc_enable,
    virtex_xintc_disable,
    virtex_xintc_disable_and_ack,
    NULL,
    NULL
};

Comme nous l'avons vu à la section 5.6.3, le pilote du contrôleur d'interruptions externes est initialisé dans la fonction Init_IRQ, il faut donc écrire cette fonction d'initialisation :

void __init ppc4xx_pic_init(void)
{
    intc = ioremap(CONFIG_VIRTEX_XINTC_ADDR, 32);

    out_be32(MER, 0x00000000); /* Disable IRQ output signal          */
    out_be32(IER, 0x00000000); /* Disable all interrupt sources      */
    out_be32(IAR, 0xFFFFFFFF); /* Acknowledge all sources            */
    out_be32(MER, 0x00000003); /* Master & hardware Interrupt enable */

    ppc4xx_pic = &virtex_intc;
    ppc_md.get_irq = virtex_xintc_get_irq;
}

Le pilote du noyau Linux gérant les interruptions doit savoir combien de lignes d'IRQ sont disponibles sur le processeur. Il faut alors modifier le fichier include/asm-ppc/irq.h de la manière suivante :

  #define  NR_IRQS (NR_AIC_IRQS + NR_BOARD_IRQS)
+ #elif defined (CONFIG_VIRTEX)
+ #define NR_IRQS 32
  #elif !defined (CONFIG_403)

Il ne reste alors plus qu'à modifier le fichier arch/ppc/config.in pour rajouter cette entrée dans le menu de configuration :

  if [ "$CONFIG_40x" = "y" ]; then
      choice 'Machine Type'               \
           "Oak           CONFIG_OAK      \
           Virtex         CONFIG_VIRTEX   \
           Walnut         CONFIG_WALNUT"  Walnut
  fi

+ if [ "$CONFIG_VIRTEX" = "y" ]; then
+   hex "  Virtex interrupt controller address" \
+       CONFIG_VIRTEX_XINTC_ADDR 0x80000000
+ fi