
特に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)が含まれています。
- BAR0は、サイズが1キロバイトのMSIが使用されない場合のレジスタと割り込みをサポートするMMIOエリアです
- MSIサポートが有効な場合、BAR1はMSI-Xに使用されます。
- 共有メモリオブジェクトにアクセスするためのBAR2。
このメカニズムは、元のレポート「Nahanni-KVMの共有メモリインターフェイス」 (後にivshmemとして知られるようになった)でCam Macdonnelによって紹介され、次の点を提唱しました。
- ゼロコピーデータアクセス
- 割り込みメカニズム
- ゲスト/ゲストおよびホスト/ゲストの相互作用
全体的なパフォーマンスを分析しました。
現在、公式には、誰もivshmemサポートを実施していませんが、Red Hatの従業員はivshmem開発に大きな貢献をしています。
目的
ivshmemは、多くのデバイスクラスをシミュレートおよびデバッグするための基盤として機能します。
この記事では、割り込みのソースでもある汎用入出力(GPIO)仮想PCIボードと、sysfsメカニズムを介したアクセスと制御を備えた対応するドライバーについて検討します。
前提条件:
- Qemu 2.5.1.1ソースコード(下位バージョンの使用は推奨されません)
- ソースコードlinux-kernel 4.1
開発とテストには、仮想ボードqemu versatilepb(システムARM)が使用されました。
必要に応じて:
- アームクロスツールチェーン
- nairobi-embedded-ゲスト側のivshmem PCIデバイステストソース
凡例:
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つの方法で説明されます。
- 標準:プライマリアドレスでレジスタエントリを完全に読み取る
- 設定とクリーニング(メーカー推奨):対応する接点を出力として設定およびクリアするには、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_chipとirq_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ドライバー。ただし、その使用はそれほど明確ではありません。割り込み処理をよりエレガントにすることができ、レジスタオフセット値はドライバ構造に最適に保存されます。
それでも、このドライバーを基礎として、以下のトピックを明らかにして説明できます。
- デバイスツリーサブシステムとの統合および割り込みソースとしての使用
- 汎用GPIOドライバーを使用して、mmio GPIOドライバーの開発を簡素化する
- ADC上のGPIOなどの非定型デバイスに基づく実装
- gpioに基づく特別なドライバー-ボタン、ダイオード、電源、リセット
ジャストはの最近の変化の失う光景はできませんgpiolib - のsysfs GPIOは廃止されました。GPIOと通信するための新しい標準になるパス上のgpiolibの新しいioctlベースのインターフェース。しかし、若いバージョンは長期間使用され、さらに、現時点ではカーネルから古いインターフェイスを削除する人はいません。たとえば、カーネルバージョン2.6.34で正常に動作するデバイスがまだあります。材料リスト:
- http://nairobi-embedded.org/category/device-drivers.html [Suga Mugabi]
- http://lxr.free-electrons.com/source
- プロフェッショナルLinuxカーネルアーキテクチャ[Wolfgang Mauerer]
- LDD3 [ジョナサン・コーベット、アレッサンドロ・ルビニ、グレッグ・クローア・ハートマン]
追加の読み物に推奨される資料:
- http://derekmolloy.ie/writing-a-linux-kernel-module-part-1-introduction/(3つの部分すべて)
- https://developer.ridgerun.com/wiki/index.php?title=Gpio-int-test.c
- http://www.assert.cc/2015/01/03/selects-exceptional-conditions.html
ソースコード、Makefile、およびREADME:https :
//github.com/maquefel/virtual_gpio_basic