2016-08-12 5 views
1

Ich möchte einen Open-Source-Core-Treiber für die Steuerung von Schrittmotoren in Linux schreiben. In diesem Fall speziell für 3D-Drucker.ARM A7 Linux-Roh-Interrupt-Handling möglich?

Die Grundidee ist, dass der Treiber Pins an einem IO-Port reserviert und dann diese Pins auf einmal manipuliert. Er empfängt einen Puffer voll mit "toggle this, toggle that" -Werten und gibt diese dann mit einem Hardware-Timer an den Port aus.

Jetzt ist die Frage: Gibt es eine Möglichkeit, einen bestimmten Hardware-Interrupt so schnell wie möglich zu behandeln?

Der betreffende Chip ist ein Allwinner H3, und ich verwende die TMR1-Ressource des besagten Chips (IRQ 51). Ich benutze kann es ganz gut, und es funktioniert als Interrupt auch:

static irqreturn_t stepCore_timer_interrupt(int irq, void *dev_id) 
{ 
     writel(TMR1_IRQ_PEND, TMR_IRQ_ST_VREG); 
     icnt++; 

     porta_state = readl(PA_VDAT); 
     porta_state &= porta_mask; 

     if(icnt & 0x00000001) 
     { 
      porta_state |= 0x00000001; 
     } 

     writel(porta_state, PA_VDAT); 

     return IRQ_HANDLED; 
} 

static struct irqaction stepCore_timer_irq = { 
     .name = "stepCore_timer", 
     .flags = IRQF_DISABLED | IRQF_NOBALANCING , IRQF_PERCPU, 
     .handler = stepCore_timer_interrupt, 
     .dev_id = NULL, 
}; 

static void stepCore_timer_interrupt_setup(void) 
{ 
    int ret; 
    u32 val; 

    writel(24000000, TMR1_INTV_VALUE_VREG); 
    writel((TMR1_MODE_CONTINUOUS | TMR1_CLK_PRES_1 | TMR1_CLK_SRC_OSC24M), TMR1_CTRL_VREG); 

    ret = setup_irq(SUNXI_IRQ_TIMER1, &stepCore_timer_irq); 
    if (ret) 
      printk("%s: ERROR: failed to install irq %d\n", __func__, SUNXI_IRQ_TIMER1); 
    else 
      printk("%s: irq %d installed\n", __func__, SUNXI_IRQ_TIMER1); 

    ret = irq_set_affinity_hint(SUNXI_IRQ_TIMER1, cpumask_of(3)); 
    if (ret) 
      printk("%s: ERROR: failed to set irq affinity for irq %d\n", __func__, SUNXI_IRQ_TIMER1); 
    else 
      printk("%s: set irq affinity for irq %d\n", __func__, SUNXI_IRQ_TIMER1); 
    /* Enable timer0 interrupt */ 
    val = readl(TMR_IRQ_EN_VREG); 
    writel(val | TMR1_IRQ_EN, TMR_IRQ_EN_VREG); 

} 

TMR1 sonst ungenutzt ist (in der Tat hatte ich es selbst hinzufügen) und so arbeitet weit. Es gibt jedoch eine gewisse Latenz bei der Handhabung der ziemlich einfachen IRQ-Routine. Da ich einen Code erzeugen möchte, der für einen 3D-Drucker verwendbar ist, mag ich einen "stabileren" Timer-Interrupt.

Meine Frage ist also: Gibt es eine Möglichkeit, eine sehr kurze IRQ-Routine in Linux zu haben, die die höchstmögliche Priorität hat? Oder kümmert es überhaupt nicht um den Linux-Scheduler und "macht es einfach"? Im Grunde ein roher IRQ-Handler, ignorierend, was Linux denkt, dass es sein sollte?

Der Kern, auf dem es läuft, ist sowieso genau dieser Aufgabe gewidmet. Der Handler wird so kurz wie möglich sein: hole ein u32 von einem Array, schreibe das an den Port, fertig.

Vorzugsweise möchte ich etwas haben, das einfach den Rest von Linux alle zusammen ignoriert. Ja, ich weiß, dass das nicht der richtige Weg ist. Aber das ist für einen speziellen Fall gedacht, daher habe ich keine Bedenken, die regulären Kernel-Quellen an diese Bedürfnisse anzupassen.

Oh, das erinnert mich, der Kernel ist 3.4.112 mit den passenden preempt-rt Patches.

Jede Hilfe wird sehr geschätzt.

Grüße,

Chris

+0

Haben Sie sich den RTAI-Kernel angesehen? Thet Laufwerke wie Linux-Cnc und sollte leicht mit Ihren Timing-Anforderungen fertig werden. – tofro

+0

Ich habe Referenzen auf den RTAI-Kernel gefunden, ja. Aber das ziemlich traurige Problem ist, dass das Zeug für Allwinner-Chips nur teilweise im Mainstream-Kernel ist. Th H3 ist dort nicht existent. Als solcher muss ich mich durch den alten Kernel 3.4.39, den ich bis 3.4.112 bekam, und dann bis zur Vorgängerversion davon packen. – ChrisK

+0

Auch brauche ich keine anspruchsvolle RT-Funktionalität. Alles was ich will (wenn das überhaupt möglich ist) ist, dass ein IRQ im GIC auf dem Bare Metal behandelt wird. In diesem Fall IRQ 51 (für TMR1), mit wenig bis gar keiner Interaktion. – ChrisK

Antwort

0

Hier ist eine allgemeine Lösung für dieses Problem. Sie können ein Kernelmodul schreiben, das die bestehende Routine für die Interruptbehandlung überschreibt und durch Ihre eigene Routine ersetzt wird, wo Sie Ihr irq von Interesse behandeln und alle irq an die bestehende Kernelinterruptbehandlungsroutine umleiten können. Es ist möglich für x86 arch, wo Sie Low-Level-CPU-Anweisungen erhalten, um die vorhandene Adresse der Interrupt-Beschreibungsroutine (lidt) zu erhalten. Ich glaube, dass es auch für ARM möglich sein sollte. Nun, Linux verfügt über CPU-Isolationstechnik isolcpus durch diese Technik können Sie eine CPU aus der Scheduler-Domäne nehmen, d. H. Es wird keine Task auf dieser bestimmten CPU geplant, bis Sie eine Task angeben, die auf dieser bestimmten CPU ausgeführt werden soll (Taskset verwenden). Nachdem Sie eine CPU aus der Scheduler-Domäne genommen haben, können Sie die Technik des affinen Interrupts für diese isolierte CPU nutzen, Sie können es über /proc/irq/IRQ_NUMBER/smp_affinity tun. Jetzt wird Ihr gesamter Interrupt von dieser isolierten CPU verarbeitet und zu 100% für diesen Interrupt reserviert. Und mit Ihrer eigenen IRQ-Routine haben Sie die volle Kontrolle über die Interrupt-Handhabung.

Hoffentlich hilft das!