gpioドライバーにgpio-genericおよびirq_chip_genericを使用する

この記事は前の記事の論理的な続きであり、 前の資料を読んでから読むことをお勧めします。 現在のメモは、後続の資料を理解するために必要であり、gpioサブシステム全体をさらに理解し、独自のgpioドライバーの開発に役立ちます。 この記事の規定は、仮想gpioドライバーだけでなく、mmio gpioドライバー全体にも既に適用されています。 前の部分で言及した「最適化」について説明します。







目的



より興味深く有用なものに進む前に、クリーンアップする必要があります。 前述のように、コードの量を減らすことができます。 このために、2つのサブシステム(ドライバー)gpio-generic(4.7から始まるgpio-mmio)とirq_chip_genericを使用します。







Linuxカーネルの原則によれば、わかりやすくするためと、サンプルコードが重複しないようにするために、パフォーマンスを犠牲にすることをお勧めします。 そして、Linuxカーネルで既存の実装を使用することは、間違いなくこの目的に役立ちます。







実装



pci_ids



小さな余談。

最初に、bgpioに切り替えると、8、16、および32の入力を持つデバイスをサポートする仮想ドライバーの小規模な成功実験が行われました。 最終的なコードはここにあります 。 この結果は、わずか7行で、渡されたパラメーターsub-vendor-idおよびsub-device-idをpciデバイスに追加するためのivshmemの変更のおかげで達成されました(ブランチに含まれるパッチはバージョンqemu 2.5.1.1を対象としています)。







gpio-mmioを使用する



カーネルでは、このドライバーはCONFIG_GPIO_GENERICに依存しています

さまざまなMMIO gpio用に作成されたシンプルなドライバーから始めましょう。その後、一部のドライバーでアクティブに使用されました。 2010年にAnton Vorontsovによって導入されました( https://lkml.org/lkml/2010/8/25/303 )。 彼のパッチでは、ドライバーの次の機能を発表しました。









原則として、このドライバーにより、状態の設定や接触の方向の選択などの機能を実装する必要がなくなります。







使用するには、サイズとデータレジスタを転送するだけで十分です。他のすべてはオプションであり、gpioコントローラーの特定の実装に依存します。初期化関数はパラメーターとして受け取ります。









つまり、次の状況が考えられます。









ルート案内にも同じことが言えます。 出力としての、または入力としてのジョブのレジスター。 または、接点を出力または入力状態に切り替える機能の欠如。







上記のアクティビティのいずれかをシミュレートできます。 私たちの目的のために、行動番号3とステータスを出力として設定し、連絡先のデフォルト状態を入力として設定すれば十分です。







bgpio_init







#if IS_ENABLED(CONFIG_GPIO_GENERIC) int bgpio_init(struct gpio_chip *gc, struct device *dev, unsigned long sz, void __iomem *dat, void __iomem *set, void __iomem *clr, void __iomem *dirout, void __iomem *dirin, unsigned long flags); #define BGPIOF_BIG_ENDIAN BIT(0) #define BGPIOF_UNREADABLE_REG_SET BIT(1) /* reg_set is unreadable */ #define BGPIOF_UNREADABLE_REG_DIR BIT(2) /* reg_dir is unreadable */ #define BGPIOF_BIG_ENDIAN_BYTE_ORDER BIT(3) #define BGPIOF_READ_OUTPUT_REG_SET BIT(4) /* reg_set stores output value */ #define BGPIOF_NO_OUTPUT BIT(5) /* only input */ #endif
      
      





bgpio_initは入力数をサイズとして受け入れないことに注意する必要がありますが、パラメーターは8の倍数、つまりngpio / 8です。







  err = bgpio_init(&vg->chip, dev, BITS_TO_BYTES(VIRTUAL_GPIO_NR_GPIOS), data, NULL, NULL, dir, NULL, 0);
      
      





irq_chip_genericを使用する



カーネルでは、このドライバーはGENERIC_IRQ_CHIPに依存していますが、これはmenuconfigまたはoldconfigでこのパラメーターを有効にする方法がないため、欠点です。

次に、少し複雑な部分を検討します。 irq_chip_genericはカーネルバージョンv3.0-rc1で導入され、gpio-mmioと同様の機能を実行します。つまり、irq_chipの多くの場合に標準実装を提供します。







レジスタの読み取り/書き込み用の標準関数は32ビットです。これが、ドライバーの8/16ビットバージョンを放棄することにした理由の1つでしたが、サブシステムirq_chip_genericに読み取り/書き込み用の関数を提供したり、標準のものを指定したりすることができます (たとえば、ioread8、iowrite8) )







irq_chip_genericとgpiochip_irqchip_add、gpiochip_set_chained_irqchip関数の併用



irq_chip_genericおよび初期化下のメモリは、irq_alloc_generic_chipを使用して発生します。 この関数は、単純なパラメーターに加えて、irq_baseを必要とします。これは一般的に、gpiochip_irqchip_addを呼び出す前に不明であり、これはstruct irq_chipを必要とします。 ただし、irq_alloc_generic_chipはメモリの割り当てと一部のパラメーターの初期化のみを行うため、irq_baseパラメーターとして0を渡し、gpiochip_irqchip_add関数を呼び出した後に実際の値を割り当てることができます。







  gc = irq_alloc_generic_chip(VIRTUAL_GPIO_DEV_NAME, 1, 0, vg->data_base_addr, handle_edge_irq);
      
      





使用するには、マスキング/アンマスキング、割り込み確認、レジスタの標準機能を指定するだけで十分です。 割り込みの種類を設定する関数は、割り込みハンドラーと同様に変更されません。







  ct->chip.irq_ack = irq_gc_ack_set_bit; ct->chip.irq_mask = irq_gc_mask_clr_bit; ct->chip.irq_unmask = irq_gc_mask_set_bit; ct->chip.irq_set_type = virtual_gpio_irq_type;
      
      





確認およびマスキング/マスキング解除のためのレジスタシフト:







  ct->regs.ack = VIRTUAL_GPIO_INT_EOI; ct->regs.mask = VIRTUAL_GPIO_INT_EN;
      
      





引き続き、virtual_gpio_irq_type関数で、割り込みタイプレジスタを管理します。







したがって、gpiochip_irqchip_addおよびgpiochip_set_chained_irqchipの初期化では、irq_chip_generic-> chip_types [0]に割り当てられたirq_chipのインスタンスを渡します(irq_chip_genericには、同じサブセットirqに関連付けられた複数のタイプのirq_chipがあります)。







その後、結果のirq_baseを使用して、irq_chip_genericのセットアップを完了します。







  irq_setup_generic_chip(gc, 0, 0, 0, 0); gc->irq_cnt = VIRTUAL_GPIO_NR_GPIOS; gc->irq_base = vg->chip.irq_base; u32 msk = IRQ_MSK(32); u32 i; for (i = gc->irq_base; msk; msk >>= 1, i++) { if (!(msk & 0x01)) continue; struct irq_data *d = irq_get_irq_data(i); d->mask = 1 << (i - gc->irq_base); irq_set_chip_data(i, gc); }
      
      





irq番号の再初期化を避けるために、mskパラメーターとして意図的に0を渡します。







irq_chip_genericは非常に深刻なirqコントローラ、主にプラットフォームコントローラに使用されるため、割り込み処理を高速化するためにirqレジスタのマスクを事前に計算し、動作中のマスクの計算に時間を浪費しません。 0をパラメーターとしてirq_setup_generic_chip関数に渡すため、マスクを自分で初期化します。







irq_set_chip_dataに関連するもう1つの問題が残っています( irq_data.chip_data = (void*)struct irq_chip_generic;



)。 事実は、gpiolibもvoid * chip_dataに依存しており、構造gpio_chipへのポインターがあるべきだと考えている( http://lxr.free-electrons.com/source/drivers/gpio/gpiolib.c#L1138 )。 この問題は、標準のgpiochip_irq_reqresおよびgpiochip_irq_relresではなく、irq_request_resourcesおよびirq_release_resources関数の独自の実装を提供することで解決されます。







この例のソースコード。







代替アプローチ



厳密に言えば、上記の操作は明らかではなく、それらを理解するのは難しいかもしれません。 gpiochip_irqchip_addを放棄してみましょう。







まず最初に、irq_baseを自分でリクエストします。







  irq_base = irq_alloc_descs(-1, 0, vg->chip.ngpio, 0);
      
      





前の記事から思い出すように、このケースは線形です(irq_domain_add_simple関数はほぼ同じことを行いますが、廃止されました)。







  vg->irq_domain = irq_domain_add_linear(0, vg->chip.ngpio, &irq_domain_simple_ops, vg); irq_domain_associate_many(vg->irq_domain, irq_base, 0, vg->chip.ngpio);
      
      





次の手順はほとんど変更されず、gpio_chipをirq_chipにリンクすることで構成されます。







  vg->chip.irqdomain = vg->irq_domain; gpiochip_set_chained_irqchip(&vg->chip, &ct->chip, pdev->irq, NULL);
      
      





唯一のポイントは、「エッジ」ファイルが対応する連絡先管理ディレクトリ( gpiolib-sysfs )で利用できることです。to_irq変換関数を指定する必要があります。







  vg->chip.to_irq = virtual_gpio_to_irq;
      
      





おわりに



既存のサブシステムを使用したおかげで、コードはわずかに削減されました。 理解すると、これらのサブシステムの使用が非常に一般的であるため、ドライバーも簡単になり、落とし穴のない標準ドライバーであることがすぐにわかります。 さらに、このような構造では、デバイスツリーメカニズムを使用する方がはるかに簡単になります。







CONFIG_GPIO_GENERICGENERIC_IRQ_CHIPへの依存は条件付き欠陥と見なされるべきですが、最初のパラメーターはLinuxヤード構成に簡単に含まれます。2番目のパラメーターはおそらく既に含まれています。







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







  1. 高レベルIRQフローハンドラー
  2. LinuxカーネルIRQドメイン
  3. エッジトリガーVsレベルトリガー割り込み


» ソースコード、Makefile、およびREADME








All Articles