Linux用QEMU ivshmem割り込みコントローラーを備えた仮想GPIOドライバー

中断の性質



特にARM組み込みシステムの世界では、 GPIOの役割を過小評価することは困難です。 GPIOは、すべての初心者向けガイドで非常に人気のある素材であることに加えて、多くの周辺機器を制御したり、貴重な中断の原因として機能したり、SOCで世界と通信する唯一の方法であったりします。



私自身のささやかな経験に基づいて、割り込みはLinuxコミュニティで最も奉献されたトピックとはほど遠いと言えます。 その機能とハードウェアへの強力なアタッチメントのため、中断に関するすべてのトレーニング資料には、実際の簡単に再現可能な例がありません。 この事実は、特に組み込みLinuxの分野では、割り込みとGPIOがしばしば分離できないという理解を妨げます。 多くの人々は、GPIOは非常に単純で退屈なものであると信じ始めます(これは、sysfsサブシステムのおかげでそうなった)。





LDD3(ヌルドライバー)の例でも、ペアのデバイス関数への明示的な呼び出しによって割り込みが発行されます。 USFCAコース( http://cs.usfca.edu/~cruse/cs686s08/ )にも例がありますが、他の誰かの割り込みを使用し、x86アーキテクチャと密接に関連しており、非常に古くなっています。



提案されたソリューションは、これらの問題を解決できます。 ユーザー空間の観点から、そして多くの点で内部実装において、ドライバーは、汎用入出力ポートへの割り込みを提供するほとんどの「実際の」ドライバーと区別できません。 現在、ドライバーはトレーリングエッジまたはトレーリングエッジをサポートしており、他のデバイスの割り込みのソースとして使用できます。



ivshmem-VM間共有メモリ





さまざまなゲストプラットフォームとの複数のQEMUプロセスによる共有メモリ(POSIX共有メモリAPIメカニズムを介してホストプラットフォームに割り当てられた)を共有するために設計されています。 すべてのゲストプラットフォームが共有メモリ領域にアクセスできるように、ivshmemは、PCI BARとしてメモリアクセスを提供することにより、PCIデバイスをモデル化します。







仮想マシンの観点から見ると、ivshmem PCIデバイスには3つの基本アドレスレジスタ(BAR)が含まれています。





このメカニズムは、元のレポート「Nahanni-KVMの共有メモリインターフェイス」 (後にivshmemとして知られるようになった)でCam Macdonnelによって紹介され、次の点を提唱しました。





全体的なパフォーマンスを分析しました。



現在、公式には、誰もivshmemサポートを実施していませんが、Red Hatの従業員はivshmem開発に大きな貢献をしています。



目的





ivshmemは、多くのデバイスクラスをシミュレートおよびデバッグするための基盤として機能します。

この記事では、割り込みのソースでもある汎用入出力(GPIO)仮想PCIボードと、sysfsメカニズムを介したアクセスと制御を備えた対応するドライバーについて検討します。



前提条件:





開発とテストには、仮想ボードqemu versatilepb(システムARM)が使用されました。



必要に応じて:





凡例:



g >>-ゲストシステムで実行されるコマンドまたは出力。

h >>メインのもの。



例と元のコード





最初に、元のコード( https://github.com/henning-schild/ivshmem-guest-code )に基づいた元のコードと、変更後のSiro Mugabiのデモを行います

h>> qemu: += -device ivshmem,shm=ivshmem,size=1 g>> # insmod ne_ivshmem_ldd_basic.ko ivshmem 0000:00:0d.0: data_mmio iomap base = 0xc8c00000 ivshmem 0000:00:0d.0: data_mmio_start = 0x60000000 data_mmio_len = 1048576 ivshmem 0000:00:0d.0: regs iomap base = 0xc88ee400, irq = 27 ivshmem 0000:00:0d.0: regs_addr_start = 0x50002400 regs_len = 256 g>> # ./ne_ivshmem_shm_guest_usr -w "TEST STRING" h>> $ xxd -l 16 /dev/shm/ivshmem 0000000: 5445535420535452 494e 4700 0000 0000 TEST STRING.....
      
      







原則として、これはすでにこの形式でGPIOをエミュレートするのに十分です。 また、多くの場合、エントリの単純な状態または出口へのエントリで十分な場合にそうしました。sysfsと割り込みの使用は、I / Oメモリへの小さなアドオンを示唆しています。



実装





/ dev / ivshmem0およびne_ivshmem_shm_guest_usr.cは不要になりました。ユーザー空間のゲストマシンからのデバイスのすべての作業は、 sysfsインターフェースを使用して行われます。



デバイスをメモリにマークする前に、ほとんどのgpioドライバーで使用されているスキームを単純に複製することに注意してください。



まず、すべてのgpio I / Oは、通常8、16、32入力のポートに分割されます。 各ポートには、入力/出力切り替えがサポートされている場合( GPIO_OUTPUT )、少なくとも入力ステータスレジスタ( GPIO_DATA )、方向レジスタがあります。 さらに(デバイス自体にサポートがある場合)、割り込みステータスレジスタ、立ち上がりエッジ(立ち下がり)および立ち下がりエッジ(立ち下がり)およびレベル(高および低)の割り込みレジスタ。 メイン割り込みコントローラーによって提供されるハードウェア割り込みは、通常、ポート全体で1つであり、すべてのポート入力間で共有されます。



コメント付きの既存の実装の例





シタラam335x





ビーグルボーンボードとしてよく知られています



開発者: Texas Instruments

ドキュメント: AM335x Sitara Processorsテクニカルリファレンスマニュアル (4865ページ)

対応するgpioドライバーは次の とおりです。linux/ drivers / gpio / gpio-omap.c

対応するヘッダー: linux / include / linux / platform_data / gpio-omap.h

入力/出力の数: 128(4 gpioポート-各32接点)



am335x Sitara gpioレジスタテーブル-ポートA
登録名 オフセット ドライバーの名前 解説
GPIO_IRQSTATUS_0 0x02C OMAP4_GPIO_IRQSTATUS_0 特定の入力の割り込みステータス
GPIO_IRQSTATUS_1 0x030 OMAP4_GPIO_IRQSTATUS_1 特定の入力の割り込みステータス
GPIO_IRQSTATUS_SET_0 0x034 OMAP4_GPIO_IRQSTATUS_SET_0 セットポイント割り込みを有効にします
GPIO_IRQSTATUS_SET_1 0x038 OMAP4_GPIO_IRQSTATUS_SET_1 セットポイント割り込みを有効にします
GPIO_IRQSTATUS_CLR_0 0x03C OMAP4_GPIO_IRQSTATUS_CLR_0 特定の入力の割り込みをオフにします
GPIO_IRQSTATUS_CLR_1 0x040 OMAP4_GPIO_IRQSTATUS_CLR_1 特定の入力の割り込みをオフにします
GPIO_OE 0x134 OMAP4_GPIO_OE モニター状態のイン/アウト
GPIO_DATAIN 0x138 OMAP4_GPIO_DATAIN I / Oステータス
GPIO_DATAOUT 0x13C OMAP4_GPIO_DATAOUT 出力のステータス設定(低/高)
GPIO_LEVELDETECT0 0x140 OMAP4_GPIO_LEVELDETECT0 低信号入力の割り込みを有効/無効にする
GPIO_LEVELDETECT1 0x144 OMAP4_GPIO_LEVELDETECT1 高信号入力の割り込みのオン/オフ
GPIO_RISINGDETECT 0x148 OMAP4_GPIO_RISINGDETECT 立ち上がりエッジ入力の割り込みのオン/オフ
GPIO_FALLINGDETECT 0x14C OMAP4_GPIO_FALLINGDETECT トレーリングエッジエントリの割り込みのオン/オフ
GPIO_CLEARDATAOUT 0x190 OMAP4_GPIO_CLEARDATAOUT 対応する入力をローに切り替えます。
GPIO_SETDATAOUT 0x194 OMAP4_GPIO_SETDATAOUT 対応する入力を高に切り替えます。




注:GPIO_IRQSTATUS_NはIRQ ACKにも使用されます。 バウンスと食事の管理は、この記事の範囲外です。



GPIO_IRQOUT_レジスタに加えて、GPIO_CLEARDATAOUTおよびGPIO_SETDATAOUTレジスタ、GPIO_IRQSTATUS_Nに加えてGPIO_IRQSTATUS_SET_NおよびGPIO_IRQSTATUS_CLR_Nが存在することは、出力ステータスを記録する2つの方法で説明されます。



ep9301





開発者: Cirrus Logic

ドキュメント: EP9301ユーザーガイド (523ページ)

対応するgpioドライバーは次の とおりです。linux/ drivers / gpio / gpio-ep93xx.c

対応するヘッダー: linux / arch / arm / mach-ep93xx / include / mach / gpio-ep93xx.h

入力/出力の数: 56(7 gpioポート-各8接点)



ep9301 gpioレジスタテーブル-ポートA
登録名 オフセット ドライバーの名前 説明
パドル 0x00 EP93XX_GPIO_REG(0x0) I / Oステータスレジスタ読み取り可能
PADDR 0x10 EP93XX_GPIO_REG(0x10) モニター状態のイン/アウト
GPIOAIntEn 0x9C int_en_register_offset [0] セットポイント割り込みを有効にします
GPIOAIntType1 0x90 int_type1_register_offset [0] 中断レベル/エッジのタイプを設定します
GPIOAIntType2 0x94 int_type2_register_offset [0] 割り込みの種類に応じて、高/上昇または低/下降を設定します
GPIOAEOI 0x98 eoi_register_offset [0] 処理された割り込みの通知に登録する
IntStsA 0xA0 EP93XX_GPIO_A_INT_STATUS 割り込みステータスを登録する




注:

これらのうち、8、8、1、2、3、2、4の入力/出力に7つのポートが使用可能で、1番目、2番目、5番目のポートにのみ割り込みレジスタがあります。

テーブルではポートAのみ​​が考慮されます。

ep9301の機能の1つは、両方の割り込みのタイプがハードウェアレベルでサポートされておらず、割り込みがトリガーされた時点でドライバーが切り替わることです。 もう1つの興味深い機能-ポートFでは、各連絡先に独自の割り込みがあります。



Bt848





最後の例:gpioを使用したPCIボードBt848。



開発者: Intel

ドキュメント: Bt848 / 848A / 849A (68ページ)

対応するgpioドライバー: linux / drivers / gpio / gpio-bt8xx.c

対応するヘッダー: linux / drivers / media / pci / bt8xx / bt848.h

I / Oの数: 24



BT848はビデオキャプチャカードです。



BT848 GPIOレジスタテーブル
登録名 オフセット ドライバーの名前 説明
BT848_GPIO_OUT_EN 0x118 BT848_GPIO_OUT_EN I / Oステータスレジスタの読み取りおよび書き込み可能
BT848_GPIO_DATA 0x200 BT848_GPIO_DATA モニター状態のイン/アウト




割り込みサポートなし。 ステータスと入力/出力設定の2つのレジスタのみ。



デバイスをメモリにマークする





最初に、データおよび状態管理用のスペースを割り当てましょう。



デバイスに8つの汎用入力/出力があるとします。

登録名 オフセット ドライバーの名前 説明
データ 0x00 VIRTUAL_GPIO_DATA I / Oステータスレジスタの読み取りおよび書き込み可能
アウトプット 0x01 VIRTUAL_GPIO_OUT_EN モニター状態のイン/アウト




Gpioインターフェースのクイックリファレンス



 struct gpio_chip { /*   gpio */ const char *label; /*     */ int (*direction_input)(struct gpio_chip *chip, unsigned offset); /*   */ int (*get)(struct gpio_chip *chip, unsigned offset); /*     */ int (*direction_output)(struct gpio_chip *chip, unsigned offset, int value); /*   */ void (*set)(struct gpio_chip *chip, unsigned offset, int value); /*      ,       -1 */ int base; /*   */ u16 ngpio; };
      
      







ドキュメント:

https://www.kernel.org/doc/Documentation/gpio/sysfs.txt



ソースコードリンク:

linux-kernel 4.1



スイッチ出力ステータス





ファイル/ sys / class / gpio / gpioN / directionを提供するdirection_output関数のint値パラメーターに注意する必要があります。このパラメーターは、 値が渡される「in」/「out」だけでなく「high」/「low」も取りますパラメーターとして( この単純な事実は、何らかの理由で、初心者のマニュアルではめったに言及されていません )。

 g>> /sys/class/gpio # echo low > gpio0/direction g>> /sys/class/gpio # cat gpio0/value 0 g>> /sys/class/gpio # echo high > gpio0/direction g>> /sys/class/gpio # cat gpio0/value 1
      
      







動的なintベース割り当てとレガシーARCH_NR_GPIOS





歴史的に、カーネル内のGPIOの数はARCH_NR_GPIOSパラメーターによって制限されていました。デフォルトは256で、その後512( バージョン3.18 )に増加しました。



その意味は非常に単純です。カーネルでは、パラメーター値よりもGPIOを多くすることはできません。計画された量がデフォルト値よりも大きかった場合、対応するプラットフォームヘッダーファイルで再定義されました。



この動作の理由は、GPIO記述テーブルを静的として定義し、各ポートの最大オフセットを制限することでした:

 static struct gpio_desc gpio_desc[ARCH_NR_GPIOS];
      
      







GPIOポートとそのオフセットは、特定のSOCのハードウェアを記述するファイルにハードコードされていました。次に例を示します。

EP93XX_GPIO_BANK
/source/arch/arm/mach-ep93xx/gpio.c

 #define EP93XX_GPIO_BANK(name, dr, ddr, base_gpio) \ { \ .chip = { \ .label = name, \ .direction_input = ep93xx_gpio_direction_input, \ .direction_output = ep93xx_gpio_direction_output,\ .get = ep93xx_gpio_get, \ .set = ep93xx_gpio_set, \ .dbg_show = ep93xx_gpio_dbg_show, \ .base = base_gpio, \ .ngpio = 8, \ }, \ .data_reg = EP93XX_GPIO_REG(dr), \ .data_dir_reg = EP93XX_GPIO_REG(ddr), \ } static struct ep93xx_gpio_chip ep93xx_gpio_banks[] = { EP93XX_GPIO_BANK("A", 0x00, 0x10, 0), EP93XX_GPIO_BANK("B", 0x04, 0x14, 8), EP93XX_GPIO_BANK("C", 0x08, 0x18, 40), EP93XX_GPIO_BANK("D", 0x0c, 0x1c, 24), EP93XX_GPIO_BANK("E", 0x20, 0x24, 32), EP93XX_GPIO_BANK("F", 0x30, 0x34, 16), EP93XX_GPIO_BANK("G", 0x38, 0x3c, 48), EP93XX_GPIO_BANK("H", 0x40, 0x44, 56), };
      
      







バージョン3.19からgpiochip_add()関数で割り当てられた各GPIOポート静的配列が動的配列に置き換えられました。



ただし、 ARCH_NR_GPIOSは(バージョン4.7の時点で)まだここにあり、動的ベース割り当てのオフセットを見つけるために使用されます。

 /* dynamic allocation of GPIOs, eg on a hotplugged device */ static int gpiochip_find_base(int ngpio);
      
      







gpio_chip構造体のベースパラメータは-1として定義でき、オフセットは端から始まる最初の空き範囲として定義されます。つまり、ポートに8つのコンタクトがある場合、ポートはARCH_NR_GPIOSパラメータが256( ARCH_NR_GPIOS -ngpio)最初にシステムに登録されます。



ドライバーの次の機能を定義します





対応する連絡先を入力として設定します。

static int virtual_gpio_direction_input(struct gpio_chip * gpio、unsigned nr)
 static int virtual_gpio_direction_input(struct gpio_chip *gpio, unsigned nr) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 outen, data; spin_lock_irqsave(&vg->lock, flags); data = vgread(VIRTUAL_GPIO_DATA); data &= ~(1 << nr); vgwrite(data, VIRTUAL_GPIO_DATA); outen = vgread(VIRTUAL_GPIO_OUT_EN); outen &= ~(1 << nr); vgwrite(outen, VIRTUAL_GPIO_OUT_EN); spin_unlock_irqrestore(&vg->lock, flags); return 0; }
      
      







連絡先の現在の状態の読み取り:

static int virtual_gpio_get(struct gpio_chip * gpio、unsigned nr)
 static int virtual_gpio_get(struct gpio_chip *gpio, unsigned nr) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 data; spin_lock_irqsave(&vg->lock, flags); data= vgread(VIRTUAL_GPIO_DATA); spin_unlock_irqrestore(&vg->lock, flags); return !!(data & (1 << nr)); }
      
      







対応する連絡先を出力として設定します。

static int virtual_gpio_direction_output(struct gpio_chip * gpio、unsigned nr、int val)
 static int virtual_gpio_direction_output(struct gpio_chip *gpio, unsigned nr, int val) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 outen, data; spin_lock_irqsave(&vg->lock, flags); outen = vgread(VIRTUAL_GPIO_OUT_EN); outen |= (1 << nr); vgwrite(outen, VIRTUAL_GPIO_OUT_EN); data = vgread(VIRTUAL_GPIO_DATA); if (val) data |= (1 << nr); else data &= ~(1 << nr); vgwrite(data, VIRTUAL_GPIO_DATA); spin_unlock_irqrestore(&vg->lock, flags); return 0; }
      
      







出力ステータスを設定します。

static void virtual_gpio_set(struct gpio_chip * gpio、unsigned nr、int val)
 static void virtual_gpio_set(struct gpio_chip *gpio, unsigned nr, int val) { struct virtual_gpio *vg = to_virtual_gpio(gpio); unsigned long flags; u8 data; spin_lock_irqsave(&vg->lock, flags); data = vgread(VIRTUAL_GPIO_DATA); if (val) data |= (1 << nr); else data &= ~(1 << nr); vgwrite(data, VIRTUAL_GPIO_DATA); spin_unlock_irqrestore(&vg->lock, flags); }
      
      







ドライバーをgpio_chipデバイスとして登録する機能:

static void virtual_gpio_setup(struct virtual_gpio * gpio)
 static void virtual_gpio_setup(struct virtual_gpio *gpio) { struct gpio_chip *chip = &gpio->chip; chip->label = dev_name(&gpio->pdev->dev); chip->owner = THIS_MODULE; chip->direction_input = virtual_gpio_direction_input; chip->get = virtual_gpio_get; chip->direction_output = virtual_gpio_direction_output; chip->set = virtual_gpio_set; chip->dbg_show = NULL; chip->base = modparam_gpiobase; chip->ngpio = VIRTUAL_GPIO_NR_GPIOS; chip->can_sleep = 0; // gpio never sleeps! }
      
      







vgreadおよびvgwriteは、iowrite8およびioread8関数の単なるラッパーです。

 #define vgwrite(dat, adr) iowrite8((dat), vg->data_base_addr+(adr)) #define vgread(adr) ioread8(vg->data_base_addr+(adr))
      
      







モジュールの動的ロード中にパラメーターとしてgpiobase値を渡す





注: バージョン4.2以降、これはGPIOポートを登録する推奨方法です。

 static int modparam_gpiobase = -1; /* dynamic */ module_param_named(gpiobase, modparam_gpiobase, int, 0444); MODULE_PARM_DESC(gpiobase, "The GPIO base number. -1 means dynamic, which is the default.");
      
      







モジュールのロードとテスト



 h>> $ rm /dev/shm/ivshmem h>> Adding parameters to qemu launch command line += -device ivshmem,shm=ivshmem,size=1 g>> # ls /sys/class/gpio/ export unexport g>> # insmod virtual_gpio_basic.ko PCI: enabling device 0000:00:0d.0 (0100 -> 0102) ivshmem_gpio 0000:00:0d.0: data_mmio iomap base = 0xc8a00000 ivshmem_gpio 0000:00:0d.0: data_mmio_start = 0x60000000 data_mmio_len = 1048576 ivshmem_gpio 0000:00:0d.0: regs iomap base = 0xc88e6400, irq = 27 ivshmem_gpio 0000:00:0d.0: regs_addr_start = 0x50002400 regs_len = 256 g>> # ls /sys/class/gpio/ export gpiochip248 unexport g>> # cat /sys/class/gpio/gpiochip248/label 0000:00:0d.0 g>> # cat /sys/class/gpio/gpiochip248/base 248 g>> # cat /sys/class/gpio/gpiochip248/ngpio 8 g>> # rmmod virtual_gpio_basic Unregister virtual_gpio device. g>> # insmod virtual_gpio_basic.ko gpiobase=0 g>> # ls /sys/class/gpio/ export gpiochip0 unexport g>> # echo 0 > /sys/class/gpio/export g>> # echo high > /sys/class/gpio/gpio0/direction
      
      







簡単なチェック:

 h>> $ xxd -b -l 2 -c 2 /dev/shm/ivshmem 0000000: 00000001 00000001 ..
      
      







データオン、OUTPUTENオン。



割り込みを追加する





割り込みレジスタマークアップと基本的な割り込み処理





注:仮想ドライバーでは、EDGEDETECT_RISEおよびEDGEDETECT_FALLのみが考慮されます。



注:2.5.0またはqemu-linaroより古いqemuバージョンのみを使用してください。 ivshmem割り込みサポートは2.5.0で壊れているか、2.5.0の下の一部のバージョンでは機能しません。 2.5.0を使用している場合は、2.5.0のパッチ( http://lists.gnu.org/archive/html/qemu-stable/2015-12/msg00034.html )を使用する必要があります。



次のレジスタを追加します。

登録名 オフセット ドライバーの名前 説明
INTERRUPT_EN 0x01 VIRTUAL_GPIO_INT_EN セットポイント割り込みを有効にします
INTERRUPT_ST 0x02 VIRTUAL_GPIO_INT_ST 割り込みステータスレジスタ
INTERRUPT_EOI 0x03 VIRTUAL_GPIO_INT_EOI 処理された割り込みの通知に登録する
EDGEDETECT_RISE 0x04 VIRTUAL_GPIO_RISING 立ち上がりエッジ入力の割り込みのオン/オフ
EDGEDETECT_FALL 0x05 VIRTUAL_GPIO_FALLING トレーリングエッジエントリの割り込みのオン/オフ
LEVELDETECT_HIGH NC 接続されていません
LEVELDETECT_LOW NC 接続されていません




次の関数は、pciバスからの割り込みの処理を担当します。現時点では、その役割は、処理された割り込みについて通知するだけです。

静的irqreturn_t virtual_gpio_interrupt(int irq、void *データ)
  static irqreturn_t virtual_gpio_interrupt(int irq, void *data) { u32 status; struct virtual_gpio *vg = (struct virtual_gpio *)data; status = readl(vg->regs_base_addr + IntrStatus); if (!status || (status == 0xFFFFFFFF)) return IRQ_NONE; printk(KERN_INFO "VGPIO: interrupt (status = 0x%04x)\n", status); return IRQ_HANDLED; }
      
      







この段階では、外部デーモンが必要です。これは標準のqemu配信に含まれています-ivshmem-server。 UNIXソケットへの-chardevパスがqemu起動行に追加され、eventfdメカニズムを使用して、実行中のqemu、ivshmem-serverおよびivshmem-clientインスタンス間でメッセージが交換されます。

 h>> $ ivshmem-server -v -F -p ivshmem.pid -l 1M #  qemu    h>> $ += -chardev socket,path=/tmp/ivshmem_socket,id=ivshmemid -device ivshmem,chardev=ivshmemid,size=1,msi=off g>> # echo 8 > /proc/sys/kernel/printk g>> # insmod virtual_gpio_basic.ko h>> $ ivshmem-client #   qemu ivshmem    ivshmem-server     id cmd> int 0 0 # :       cmd> help #   : g>> VGPIO: interrupt (status = 0x0001)
      
      







irq_chipとchained_interruptの概念





詳細は説明しません。このトピックは、 irq_chip 、カーネルドキュメント、および「Professional Linux Kernel Architecture」という本を紹介する最初のパッチで十分に開示されました(これは古くなっていますが、irq_chipも新しいものではありません)。



現時点では、私たちにとっての主な事実は、親の割り込みコントローラーからカスケードされた割り込みを提供するGPIOポートが、現代のLinuxの時代では一般的な慣行であるということです。



これが、GPIOドライバーの割り込み部分がirq_chipを使用する理由です。 つまり、このようなドライバーは、 gpio_chipirq_chipの 2つのサブシステムを同時に使用します。



irqサブシステムの概要を見ると、次の図がわかります。





高レベル割り込みサービスルーチン(ISR) -デバイスドライバーで必要なすべての割り込みサービス作業を実行します。 たとえば、読み取り可能な新しいデータを示すために割り込みが使用される場合、ISRの操作はデータを適切な場所にコピーすることです。



割り込みフロー処理 -このサブシステムは、信号レベル(レベル)またはエッジ(エッジ)によるトリガーなど、割り込み処理の実装機能を担当します。



回線で潜在的な変更が発生したと判断されると、エッジトリガーが発生します。 レベルごとのトリガー(レベルトリガー)は、ポテンシャルの特定の値として定義されますが、ポテンシャルの変化は役割を果たしません。



カーネルの観点からは、各割り込みの開始後にマスクする必要があるため、レベルによるトリガーはより複雑なケースです。



チップレベルのハードウェアカプセル化 - ハードウェアでの作業の実装の機能をカプセル化するために使用されます。 このサブシステムは、割り込みコントローラーの一種の「デバイスドライバー」と見なすことができます。




ご覧のとおり、適切なインフラストラクチャを提供する場合、カーネルは割り込みチェーンの処理と、タイプ(フロントとレベル)の実装の違いを制御します。



IRQドメイン





irq:add irq_domain変換インフラストラクチャパッチに登場したIRQドメインサブシステムにより、コントローラーのローカルの割り込み番号をカーネルの割り込み番号から分離し、割り込み番号の共通配列を提供できるようになりました。 公式ドキュメントを引用して: 「今日はIRQ番号であり、単なる数字です。



この更新の前は、ハードウェア番号はカーネル番号に1:1としてマップされ、カスケードはサポートされていませんでした。 ハードウェア番号とは、コントローラーのローカル割り込み番号を意味します。この場合、これはローカルGPIO番号と一致します。



IRQドメインには、次の表示タイプがあります。





割り込みベクトルは十分に小さく、「マップなし」マップにはまったく関心がないため、マップは線形です。実際、数値は1対1のオフセットにマッピングされます。古いアプローチとの違いは、irq番号を割り当ててオフセットを計算することです割り当てられた範囲の連続性を確保しながら、コア。



struct irq_data構造へのポインターは、irq_chipインターフェースの各関数に渡されます。irq_data-> irqはLinuxカーネルの割り込み番号、 irq_data-> hwirqはドライバーのローカル割り込み番号です。 struct virtual_gpio構造へのポインター struct irq_dataに渡されます。



irq_chipとgpio_chipのバインド





カーネルの下位バージョンにガイドされている場合、 irq_domain_add_simple関数を使用して番号を表示する必要がありますが、gpioパッチのバージョン3.15以降:gpiolibパッチIRQチップヘルパーを追加するため、IRQドメインインターフェイスを直接使用する必要はありません。



したがって、IRQドメインインターフェイスを直接使用して、ローカル番号をグローバルにマッピングするためのインフラストラクチャを提供する( .map() ops) 代わりにgpiochip_irqchip_addおよびgpiochip_set_chained_irqchip関数 (GPIOLIB_IRQCHIP Kconfigパラメーターに依存)を使用します。



優れた使用例と使いやすさは、 gpio-pl061ドライバーです。



irq_chipを既存のgpio_chipに バインドします。

 gpiochip_irqchip_add(&vg->chip, &virtual_gpio_irq_chip, 0, handle_edge_irq, IRQ_TYPE_NONE);
      
      







handle_edge_irqは、エッジ割り込みチェーンを制御する組み込みストリームハンドラの1つです。



注:エッジ割り込みが最も一般的です。 レベルの中断との主な違いは、チェーン管理にあります。レベルの中断は、受信後すぐにカーネルでマスクされます。

 gpiochip_set_chained_irqchip(&vg->chip, &virtual_gpio_irq_chip, pdev->irq, NULL);
      
      







gpiochip_set_chained_irqchip関数を呼び出すことにより、irq_chipがPCIバスからの割り込みを使用し、割り込みがpdev-> irqからカスケードされることをカーネルに通知します。



VIRTUAL_GPIO_INT_STの状態に応じて割り込みを生成するようにハンドラーを変更します。

 pending = vgread(VIRTUAL_GPIO_INT_ST); /* check if irq is really raised */ if(pending) { for_each_set_bit(i, &pending, VIRTUAL_GPIO_NR_GPIOS) generic_handle_irq(irq_find_mapping(vg->chip.irqdomain, i)); }
      
      







irq_find_mapping-ローカル入力番号をグローバル割り込み番号に変換するための補助関数。



すべてをまとめる





まず、ドライバーのirq_chipインターフェースは次のように見えることに注意してください。

 static struct irq_chip virtual_gpio_irq_chip = { .name = "GPIO", .irq_ack = virtual_gpio_irq_ack, .irq_mask = virtual_gpio_irq_mask, .irq_unmask = virtual_gpio_irq_unmask, .irq_set_type = virtual_gpio_irq_type, };
      
      







ack()関数は、常にコントローラーのハードウェア仕様と密接に関連しています。たとえば、一部のデバイスでは、後続のリクエストを処理する前に、割り込みリクエスト処理の確認が必要です。

static void virtual_gpio_irq_ack(struct irq_data * d)
 static void virtual_gpio_irq_ack(struct irq_data *d) { unsigned long flags; u8 nr = d->hwirq; u8 mask = 1 << nr; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); spin_lock_irqsave(&vg->lock, flags); vgwrite(mask, VIRTUAL_GPIO_INT_EOI); spin_unlock_irqrestore(&vg->lock, flags); }
      
      







私たちの場合、プログラムvg_get_setはeoiレジスタのかなり粗雑なエミュレーションを使用します。割り込みステータスフラグを設定した後、eoiレジスタはループ内で常にポーリングされます。割り込み通知入力ビットがドライバによって設定されると、eoiレジスタがリセットされ、入力の割り込みステータスビットがクリアされます。



対応する値をレジスタINTERRUPT_ENに書き込むことにより、マスクとマスク解除が行われます。



割り込みマスキング:

static void virtual_gpio_irq_mask(struct irq_data * d)
 static void virtual_gpio_irq_mask(struct irq_data *d) { u8 mask; unsigned long flags; u8 nr = d->hwirq; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); spin_lock_irqsave(&vg->lock, flags); mask = vgread(VIRTUAL_GPIO_INT_EN); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_INT_EN); spin_unlock_irqrestore(&vg->lock, flags); }
      
      







割り込みのマスク解除:

static void virtual_gpio_irq_unmask(struct irq_data * d)
 static void virtual_gpio_irq_unmask(struct irq_data *d) { u8 mask; unsigned long flags; u8 nr = d->hwirq; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); spin_lock_irqsave(&vg->lock, flags); mask = vgread(VIRTUAL_GPIO_INT_EN); mask |= (1 << nr); vgwrite(mask, VIRTUAL_GPIO_INT_EN); spin_unlock_irqrestore(&vg->lock, flags); }
      
      







irq_typeは:現在カーネルに以下のタイプを定義-あなたがトリガの種類を指定することができ

IRQ_TYPE_NONE -タイプが指定されていない

IRQ_TYPE_EDGE_RISINGを-前縁に

エッジ末尾- IRQ_TYPE_EDGE_FALLING

IRQ_TYPE_EDGE_BOTHを-前縁及び後縁に

高レベルで- IRQ_TYPE_LEVEL_HIGHを

Lowレベル- IRQ_TYPE_LEVEL_LOW

static int virtual_gpio_irq_type(struct irq_data * d、unsigned intタイプ)
 static int virtual_gpio_irq_type(struct irq_data *d, unsigned int type) { unsigned long flags; struct gpio_chip *gc = irq_data_get_irq_chip_data(d); struct virtual_gpio *vg = to_virtual_gpio(gc); u8 mask; u8 nr = d->hwirq; spin_lock_irqsave(&vg->lock, flags); switch (type) { case IRQ_TYPE_EDGE_RISING: mask = vgread(VIRTUAL_GPIO_RISING); mask |= (1 << nr); vgwrite(mask, VIRTUAL_GPIO_RISING); mask = vgread(VIRTUAL_GPIO_FALLING); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_FALLING); break; case IRQ_TYPE_EDGE_FALLING: mask = vgread(VIRTUAL_GPIO_FALLING); mask |= (1 << nr); vgwrite(mask, VIRTUAL_GPIO_FALLING); mask = vgread(VIRTUAL_GPIO_RISING); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_RISING); break; default: retval = -EINVAL; goto end; } /* enable interrupt */ mask = vgread(VIRTUAL_GPIO_INT_EN); mask &= ~(1 << nr); vgwrite(mask, VIRTUAL_GPIO_INT_EN); end: spin_unlock_irqrestore(&vg->lock, flags); return retval; }
      
      





テストと結果





割り込みに関する情報のユーザー空間への転送をテストするには、特別に作成されたユーティリティvg_guest_clientを使用します。gpio_sysfsのドキュメントによると、「selectを使用してイベントを追跡する場合は、ファイル(記述子)記述子をexceptfdsに設定します」



関連コード:

  FD_ZERO(&efds); maxfd = 0; for(i = 0; i < gpio_size; i++) { FD_SET(gpios[i].fd, &efds); maxfd = (maxfd < gpios[i].fd) ? gpios[i].fd : maxfd; } ready = pselect(maxfd + 1, NULL, NULL, &efds, NULL, NULL); if(ready > 0) for(i = 0; i < gpio_size; i++) if(FD_ISSET(gpios[i].fd, &efds)) { read(gpios[i].fd, &value, 1); /*    lseek  http://lxr.free-electrons.com/source/fs/kernfs/file.c?v=4.1#L769 */ if(lseek(gpios[i].fd, 0, SEEK_SET) == -1) perror("lseek"); printf("gpio number=%d interrupt caught\n", gpios[i].number); }
      
      







sysfsを使用して作業用の入力を準備します。

 g>> # echo 504 > /sys/class/gpio/export g>> # echo 505 > /sys/class/gpio/export g>> # echo 506 > /sys/class/gpio/export g>> # echo rising > /sys/class/gpio/gpio504/edge g>> # echo rising > /sys/class/gpio/gpio505/edge g>> # echo rising > /sys/class/gpio/gpio506/edge
      
      







注:大部分のデバイスのgpioは、デフォルトで入力として初期化されます。

 #      gpiochip   g>> # ./vg_guest_client 504 gpio_chip: base: 504 ngpio: 8 Added gpio 504 to watchlist. Added gpio 505 to watchlist. Added gpio 506 to watchlist. Entering loop with 3 gpios. h>> $ ./vg_get_set -p 1 -i 0 g>> gpio number=504 interrupt caught
      
      







割り込みハンドラーからpselect通知への呼び出しのチェーン:

 static irqreturn_t virtual_gpio_interrupt (int irq, void *data) int generic_handle_irq(unsigned int irq); ... static irqreturn_t gpio_sysfs_irq(int irq, void *priv); static inline void sysfs_notify_dirent(struct kernfs_node *kn); void kernfs_notify(struct kernfs_node *kn); static void kernfs_notify_workfn(struct work_struct *work);
      
      







おわりに





この記事は、一般的な紹介なしでは想像するのが難しい、または不可能な資料の基礎として私に暗示されました。 Qemuとivshmemの組み合わせは、この目的のための優れた理解しやすい基盤を提供しました。この特定のバンドルを選択する理由は、健全なドキュメントと使用の透明性の可用性です。



gpio sysfs自体の動作は、sysfsサポートが実装されたデバイスでは変わりません。GPIOを使用するための指示は、このインターフェイスを開発するときに意図した別の同様のデバイスに正常に適用できます。すべての違いは、特定のデバイスドライバーレベルで終わります。



ドライバー自体は、その無条件の教育的価値にもかかわらず、現代のコアのコンテキストでは理想からはほど遠いです。このような単純なドライバーの場合は、使用する必要がありますmmio gpioドライバーの同様の反復コードを回避するように設計された汎用gpioドライバー。ただし、その使用はそれほど明確ではありません。割り込み処理をよりエレガントにすることができ、レジスタオフセット値はドライバ構造に最適に保存されます。



それでも、このドライバーを基礎として、以下のトピックを明らかにして説明できます。





ジャストはの最近の変化の失う光景はできませんgpiolib - のsysfs GPIOは廃止されました。GPIOと通信するための新しい標準になるパス上のgpiolibの新しいioctlベースのインターフェースしかし、若いバージョンは長期間使用され、さらに、現時点ではカーネルから古いインターフェイスを削除する人はいません。たとえば、カーネルバージョン2.6.34で正常に動作するデバイスがまだあります。材料リスト:





  1. http://nairobi-embedded.org/category/device-drivers.html [Suga Mugabi]
  2. http://lxr.free-electrons.com/source
  3. プロフェッショナルLinuxカーネルアーキテクチャ[Wolfgang Mauerer]
  4. LDD3 [ジョナサン・コーベット、アレッサンドロ・ルビニ、グレッグ・クローア・ハートマン]




追加の読み物に推奨される資料:

  1. http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/(3つの部分すべて)
  2. https://developer.ridgerun.com/wiki/index.php?title=Gpio-int-test.c
  3. http://www.assert.cc/2015/01/03/selects-exceptional-conditions.html




ソースコード、Makefile、およびREADME:https :

//github.com/maquefel/virtual_gpio_basic




All Articles