2015年9月7日 星期一

Linux中斷控制器的抽象封裝



BCM2835內部的中斷控制器:


BCM2835內部的中斷控制器的架構:  主要包含GPU本身中斷(64個中斷)及21個基本中斷, 其中在 21個基本中斷,除了ARM IRQ外也同時包含了部份GPU的中斷在內 , 例如IRQ 79是GPU的 IRQ 53 (此為I2C中斷觸發),IRQ 80是GPU的 IRQ 54 (此為SPI中斷觸發)。而BCM2835 其全部54根GPIO 都是有產生中斷能力的, 由GPIO controller 產生中斷進入到中斷控制器的IRQ是配置在IRQ 49~52。



GPU  Interrupt Pending Bit (0~63)

ARM Basic Interrupt Pending Bit (0~20)



關於BCM2835 GPIO控制器

 共有54根GPIO分成2個bank , 一個管0~31, 另一個管32~53。由GPIO所產生中斷訊號共有3條, gpio_int[0],gpio_int[1], gpio_int[3], 分別是中斷控制器IRQ49, IRQ50及IRQ52,gpio_intp[0]是表示GPIO 0~31有產生中斷,而gpio_intp[1]是表示GPIO為32~53有中斷產生,而gpio_intp[3]是表示任一根GPIO中斷產生,不論是bank 0或bank1。





GPIO Event Detect Status Register


GPIO若要要產生中斷能力, 則必須設定 GPRENn GPFENn GPHENn, GPLENn, 4個暫存器,
分別用來設定欲中斷觸發類型, Rising Edge Enable, .Falling Edge, Enable,High Level Enable, Low Level Enable。若某根GPIO要Rising 及Falling Edge 都要觸發中斷, 則須別在GPRENn 及GPFENn 對應的bit設1.
每當有中斷產生時, 其對應pending register 的bit都會為1, 用以標示該中斷已產生。每當ISR 跑完後,都必須將其bit 清除, 否則該中斷將無法再繼續產生。而通常清除的方法,就是在對應的bit 上"寫 1" 。在BCM2835的GPIO中斷其等同於pending register的意義是 EDS Register (Event Detect Status ) 用以記錄GPIO 的中斷觸發事件 (不論是Edge 或 Level 都記錄)。


Linux中斷通用層的資料結構

Linux 必須有能力處理不同Interrupt controller 硬體間的差異,如此一來才具備可攜性。Linux底下主要透過三個資料結構完成:

  1. struct irq_chip: 中斷硬體控制器,實作中斷控制晶片的行為,例如 mask IRQ, unmask IRQ
  2. struct irqaction: 指向中斷發生時動作行為, 即描述ISR
  3. struct irq_desc: 中斷描述表 Interrupt Descriptor Table (IRQ Line描述)

Linux中斷通用層的資料結構之關聯


建立GPIO Interrupt Descriptor Table

用一個for loop 產生多張Interrupt Descriptor (struct irq_dest), 每個Descriptor均有不同GPIO IRQ Line編號,但都指向同一個Interrupt controller Chip。

static void bcm2708_gpio_irq_init(struct bcm2708_gpio *ucb)
{
  unsigned irq;
  ucb->gc.to_irq = bcm2708_gpio_to_irq;

 //for loop 針對每一個 IRQ Line 建立 irq_desc 
  for (irq = GPIO_IRQ_START; irq < (GPIO_IRQ_START + GPIO_IRQS); irq++) 
 { 
  irq_set_chip_data(irq, ucb);  //set chip specific data for an irq (170+i)
  irq_set_chip (irq, &bcm2708_irqchip); //set the irq chip for an irq
  set_irq_flags (irq, IRQF_VALID);
  }

  setup_irq (IRQ_GPIO3, &bcm2708_gpio_irq);  // 稍後說明
}



註冊GPIO IRQ 使用 request_irq() API

每一個IRQ Line都有了irq descriptor 之後, 就可以開始註冊每一個GPIO ISR , 可直接透過來完成。

 request_irq(unsigned int irq, irq_handler_t handler, unsigned long flags, const char *name, void *dev)


request_irq的第一個參數irq, 即是向系統的IDT(Interrupt Descriptor Table), (IDT為struct irq_desc的集合) 找到對應的irq_desc, 然後登記ISR,即 irq_desc->action=handler。例如,
request_irq(193, .....); 從IDT尋找179 是屬於GPIO interrupt chip controller, 所以一旦request_irq成功, 則IRQ 193 是掛在名為GPIO irq_chip, 而非ARMCTRL irq_chip。

誰來執行我們的GPIO IRQ Handler

在一個程式代碼中, 最後一行呼叫   setup_irq (IRQ_GPIO3, &bcm2708_gpio_irq); 其目的是向Interrupt controller Chip(ARMCTRL) 註冊IRQ 52 及其ISR bcm2708_gpio_irq 。
bcm2708_gpio_irq為一個 struct irqaction 結構, 其中 .handler指向一個 interrupt handler "bcm2708_gpio_interrupt" ,由此handler 來執行我的IRQ Handler。

bcm2708_gpio_interrupt 函式作法就是掃描 GPIO EDS Register , 若bit 為1, 則執行 generic_handle_irq (gpio_to_irq(gpio));  目的是透過 irq 去搜尋其 irq_desc, 然後再由irq_desc->action 呼叫對應註冊的ISR。struct irqaction為一個串列, 所以會不斷呼叫下去,直到有一個ISR 傳回IRQ_HANDLED。當IRQ為Shard IRQ 時,則發生此情況,若此次非由該裝置產生,則傳回IRQ_NONE, 但一定要有一個ISR傳IRQ_HANDLED,否則將會導致kernel pacnic.



static struct irqaction bcm2708_gpio_irq = {
  .name = "BCM2708 GPIO catchall handler",
  .flags = IRQF_DISABLED | IRQF_TIMER | IRQF_IRQPOLL,
  .handler = bcm2708_gpio_interrupt,
};

static irqreturn_t bcm2708_gpio_interrupt (int irq, void *dev_id)
{
  unsigned long edsr;
  unsigned bank;
  int i;
  unsigned gpio;
  for (bank = 0; bank <= 1; bank++) { //scan pending register (Event Detect Status)
  edsr = readl(__io_address(GPIO_BASE) + GPIOEDS(bank));
  for_each_set_bit(i, &edsr, 32) {     // i1bit位置處
  gpio = i + bank * 32;
  generic_handle_irq (gpio_to_irq(gpio)); //call GPIO ISR
      // clear pending regisgter
  writel(1<<i,__io_address(GPIO_BASE) + GPIOEDS(bank));
  }
  }
  return IRQ_HANDLED;
}


定義一個GPIO 中斷的irq chip

static struct irq_chip bcm2708_irqchip = {
 .name = "GPIO",
 .irq_enable = bcm2708_gpio_irq_unmask,  
.irq_disable = bcm2708_gpio_irq_mask,  
.irq_unmask = bcm2708_gpio_irq_unmask, 
.irq_mask = bcm2708_gpio_irq_mask,
 .irq_set_type = bcm2708_gpio_irq_set_type
};


說明: 宣告一個 struct irq_chip, 名稱為"GPIO", 並設定其相關底層的控制設定 ,如底下GPIO mask的程式, 即在對應的pin上. 對GPRENn GPFENn GPHENn, GPLENnBCM2835 設0。(ARM Peripherals Manual GPIO p.89)

static void bcm2708_gpio_irq_mask(struct irq_data *d)
{
  unsigned irq = d->irq;
  struct bcm2708_gpio *gpio = irq_get_chip_data(irq);
  unsigned gn = irq_to_gpio(irq);
  unsigned gb = gn / 32;
  unsigned long rising  = readl(gpio->base + GPIOREN(gb));
  unsigned long falling = readl(gpio->base + GPIOFEN(gb));
  unsigned long high    = readl(gpio->base + GPIOHEN(gb));
  unsigned long low     = readl(gpio->base + GPIOLEN(gb));
  gn = gn % 32;
  writel(rising  & ~(1 << gn), gpio->base + GPIOREN(gb));
  writel(falling & ~(1 << gn), gpio->base + GPIOFEN(gb));
  writel(high    & ~(1 << gn), gpio->base + GPIOHEN(gb));
  writel(low     & ~(1 << gn), gpio->base + GPIOLEN(gb));
}


DEMO


實作出來的結果, GPIO 控制器所產生的中斷, 是註冊在中斷控制器(ARMCTRL)的IRQ 號碼52 (gpio_int[3]), IRQ Handler 的名稱為 "BCM2708 GPIO catchall handler", 此IRQ Handler 再來scan GPIO controller 是那一根gpio pin# 產生中斷? 接著再呼叫該gpio pin#所註冊的ISR,此ISR即為
request_irq(193, irq_handler ,"my_button_int",..) 所註冊的ISR, 此中斷193號碼是掛在GPIO controller 的中斷。GPIO pin# IRQ 號碼為 170+GPIO Pin number。 另外, 因為所有GPIO 產生的中斷都是串接在中斷控制器(ARMCTRL)的, 故IRQ 52 的CPU中斷次數為個別GPIO 中斷次數的加總。

cat /proc/interrupts 輸出內容






沒有留言 :

張貼留言