[Linuxドライバー] PCIeドライバー入門

pcieバスはPCIバスと下位互換性があり、本文中のPCIはデフォルトでpcieと同じです。

pcieトポロジ

ここに画像の説明を挿入
pcieトポロジは、主にバス、ブリッジ、およびデバイスカードで構成され、ブリッジは親バスと子バスを接続してツリー構造を形成します。橋は主に次の3つのカテゴリに分類されます。

  • ホスト/ PCIブリッジ:CPUとPCIルートバスを接続するために使用されます。PCでは、メモリコントローラーも通常ホスト/ PCIブリッジデバイスチップに統合されています。ホスト/ PCIブリッジは通常「ノースブリッジチップセット」と呼ばれます。 。
  • PCI / ISAブリッジ:古いISAバスを接続するために使用されます。PCI / ISAブリッジは、「サウスブリッジチップセット」とも呼ばれます。
  • PCI-to-PCIブリッジ:PCIプライマリバスとセカンダリバスを接続するために使用されます。
    ここに画像の説明を挿入
    PCIバス番号は、深さ優先(DFS)トラバーサル順序を採用しています
    ここに画像の説明を挿入

pcieアドレス空間

PCIeのは、すなわち、3つのアドレススペースで構成されていますPCI配置空间PCI/IO空间PCI内存地址空间

PCI配置空间 pcieデバイスカードのコンフィギュレーションレジスタとして理解できます。このコンフィギュレーションレジスタには特定の仕様があります。PCIプロトコルのサイズは256バイトで、PCIプロトコルは4Kに拡張できます。最初の64バイトはデバイス内で固化されており、読み取りまたは書き込みができず、他の部分は読み取りおよび書き込みが可能です。構成レジスタにアクセスする方法は、pciカード構成に0xCF8〜0xCFFのアドレスを使用するノースブリッジを使用することです。その中で、アクセスするPCIカードのIDを0xCF8〜0xCFCに書き込みます。IDは、PICバス番号+デバイス番号+機能番号+レジスタ番号で構成されます。具体的な形式は下図のとおりです。前の手順でカードとレジスタを選択した後、0xCFC〜0xCFFの読み取りと書き込みを行うと、ノースブリッジは構成トランザクションを送信してレジスタの構成を完了します。
ここに画像の説明を挿入

PCI内存地址空间 これは、デバイスの設計時に決定されるpciデバイスのデータレジスタとして理解できます。このスペースはPCI配置空间最初の64k情報に記録されます。オペレーティングシステムは、このアドレスにアクセスするために、このアドレスをメモリにマップするだけで済みます。

PCI/IO空间 これは、pcieデバイスのアドレス指定可能なスペースとして理解できます。32ビットのPCIバスのアドレス指定可能なスペースは4GBであるためPCI内存地址空间、4GBを超えることはできません。

pcie構成スペース

以下に示すように、PCIe構成レジスタのフォーマットを詳細に確認します。
ここに画像の説明を挿入

ここに画像の説明を挿入

  1. デバイスIDとVerdorID。製造元によって割り当てられた読み取り専用。
  2. リビジョンID。PCIデバイスのバージョン番号を記録します。読み取り専用
  3. クラスコード。PCIデバイスの分類を読み取り専用で記録します。これは、基本クラスコード(デバイスをグラフィックカード、ネットワークカード、PCIブリッジなどに分割)、サブクラスコード(細分割)、およびインターフェイス(プログラミングインターフェイスの定義)の3つのフィールドに分けられます。このレジスタは、システムソフトウェアが現在のPCIデバイス分類を識別するために使用できます。タイプ別にドライバを作成することは、この区別に依存します。
  4. ヘッダータイプ。現在の構成スペースタイプを読み取り専用で示します。
  5. キャッシュラインサイズ。HOSTプロセッサが使用するキャッシュラインの長さは、システムソフトウェアによって設定されます。(PCIeには意味がありません
  6. サブシステムIDとサブシステムベンダーID。デバイスIDとベンダーIDを区別できない場合
  7. 拡張ROMベースアドレス。ROMプログラムのベースアドレスを記録します。一部のPCIデバイスは、プロセッサがオペレーティングシステムを実行する前に、基本的な初期化設定を完了する必要があります。PCIデバイスはROMプログラムを提供し、プロセッサは初期化プロセス中にこのROMプログラムを実行して、これらのPCIデバイスを初期化します。
  8. 機能ポインタPCIデバイスでは、レジスタはオプションであり、PCI-XおよびPCIeデバイスでサポートされている必要があります。
  9. 割り込みライン。システムソフトウェアがPCIデバイスを構成するときに書き込まれ、現在のPCIデバイスで使用される割り込みベクトル番号を記録します。8259A割り込みコントローラーが適用できない場合、このレジスタは意味がありません。
  10. 割り込みピン。そのピンを割り込みとして使用します。PCIには4つの割り込みピンがあります。
  11. ベースアドレスレジスタ0〜5。PCIデバイスが使用するアドレス空間のベースアドレスを保存し、デバイスのアドレスをPCIバスドメインに保存します。

実際、構成レジスタには次の3つのタイプがあります。

  • PCIエージェントが使用する構成スペース
  • PCIブリッジが使用する構成スペース
  • Cardbusブリッジ
    使用する構成スペースは、エージェントのレジスタです。以下のブリッジを見てみましょう
    ここに画像の説明を挿入
    。PCIブリッジには2セットのBARレジスタがあります。PCIブリッジ自体にプライベートレジスタがない場合、BARレジスタにはできません。使用され(透過ブリッジ)、0に初期化されます。

PCI Agentの構成スペースと比較して、PCIBridgeの構成スペースにはより多くのバス番号レジスタがあります

  1. 従属バス番号レジスタは、現在のPCIサブツリーで最大番号のPCIバス番号を格納します
  2. セカンダリバス番号は、現在のPCIブリッジのセカンダリバスで使用されているバス番号を格納します。これは、サブツリーで番号が最も小さいバス番号でもあります。
  3. プライマリバス番号は、PCIブリッジの上流にあるPCIバス番号を格納します

LinuxカーネルAPI

pciデバイスの構成レジスタの読み取りと書き込み

pci_read_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value);
pci_write_config_byte/word/dword(struct pci_dev *pdev, int offset, int *value);
  • 1
  • 2

構成レジスタリストでベースアドレスの開始アドレスを取得します。ここで、barはアドレスで、合計6つあります。

pci_resource_start(dev, bar) /*Bar值的范围为0-5*/
  • 1

構成レジスタリストでベースアドレスの終了アドレスを取得します

pci_resource_end(dev, bar) /*Bar值的范围为0-5*/
  • 1

構成レジスタリストでベースアドレスのステータスを取得します。ステータスはまだたくさんあります。詳細については、ioport.hを参照してください。

pci_resource_flags(dev, bar) /*Bar值的范围为0-5*/
  • 1

構成レジスタリストのベースアドレスアドレスの長さを取得します。これも次の値に等しくなります。pci_resource_end((dev), (bar))-pci_resource_start((dev), (bar)) + 1)

pci_resource_len(dev,bar) /*Bar值的范围为0-5*/
  • 1

リソースの申請またはリリース

int pci_request_regions(struct pci_dev *pdev, const char *res_name);
void pci_release_regions(struct pci_dev *pdev);
  • 1
  • 2

デバイスを有効または無効にします

int pci_enable_device(struct pci_dev *pdev);
int pci_disable_device(struct pci_dev *pdev);
  • 1
  • 2

マスターデバイスとして設定

void pci_set_master(struct pci_dev *pdev);
  • 1

デバイスを探す

struct pci_dev *pci_find_slot (unsigned int bus,unsigned int devfn);
  • 1

電源状態を設定する

int pci_set_power_state(struct pci_dev *pdev, pci_power_t state);
  • 1

検索機能

int pci_find_capability(struct pci_dev *pdev, int cap);
  • 1

有効無効無効書き込み

int pci_set_mwi(struct pci_dev *pdev);
void pci_clear_mwi(struct pci_dev *pdev);
  • 1
  • 2

こんにちは世界

pciデバイスのフレームワークはバスのフレームワークに似ており、分離することもできません。おおよそ次の手順です。

1.ドライバー登録:

  • ハードウェアがpicをサポートしているかどうかを確認します

2.適応を推進する:

  • pciデバイスをオンにします
  • 構成レジスタ
  • マップされたアドレスを読み取る
  • キャラクターデバイス、ネットワークデバイス、USBなどを初期化します。

3.pciデバイスの読み取りと書き込み

pci_dev構造は基本的に構成レジスタの内容に対応します

struct pci_dev {
    struct list_head global_list;
    struct list_head bus_list;
    struct pci_bus  *bus;
    struct pci_bus  *subordinate;
    void        *sysdata;
    struct proc_dir_entry *procent;
    unsigned int    devfn;
    unsigned short  vendor;
    unsigned short  device;
    unsigned short  subsystem_vendor;
    unsigned short  subsystem_device;
    unsigned int    class;
    u8      hdr_type;
    u8      rom_base_reg;
    struct pci_driver *driver;
    void        *driver_data;
    u64     dma_mask;
    u32             current_state;
    unsigned short vendor_compatible[DEVICE_COUNT_COMPATIBLE];
    unsigned short device_compatible[DEVICE_COUNT_COMPATIBLE];
    unsigned int    irq;
    struct resource resource[DEVICE_COUNT_RESOURCE];
    struct resource dma_resource[DEVICE_COUNT_DMA];
    struct resource irq_resource[DEVICE_COUNT_IRQ];
    char        name[80];
    char        slot_name[8];
    int     active;
    int     ro;
    unsigned short  regs;
    int (*prepare)(struct pci_dev *dev);
    int (*activate)(struct pci_dev *dev);
    int (*deactivate)(struct pci_dev *dev);
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

全体的なプレゼンテーション

#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");


/* 对特定PCI设备进行描述的数据结构 */
struct hello_card {
    unsigned int magic;
    /* 使用链表保存所有同类的PCI设备 */
    struct hello_card *next;
}

static int hello_ioctl(struct inode *inode, struct file *file,
      unsigned int cmd, unsigned long arg)
{
    switch(cmd) {
        case hello_RDATA:
            /* 从I/O端口读取4字节的数据 */
            val = inl(card->iobae + 0x10);
/* 将读取的数据传输到用户空间 */
    }
    return 0;
}

static int hello_open(struct inode *inode, struct file *file)
{
    /* 申请中断,注册中断处理程序 */
    request_irq(card->irq, &hello_interrupt, SA_SHIRQ,
        card_names[pci_id->driver_data], card)) {
    /* 检查读写模式 */
    if(file->f_mode & FMODE_READ) {
        /* ... */
    }
    if(file->f_mode & FMODE_WRITE) {
       /* ... */
    }
     
    /* 申请对设备的控制权 */
    down(&card->open_sem);
    while(card->open_mode & file->f_mode) {
        if (file->f_flags & O_NONBLOCK) {
            /* NONBLOCK模式,返回-EBUSY */
            up(&card->open_sem);
            return -EBUSY;
        } else {
            /* 等待调度,获得控制权 */
            card->open_mode |= f_mode & (FMODE_READ | FMODE_WRITE);
            up(&card->open_sem);
            /* 设备打开计数增1 */
            MOD_INC_USE_COUNT;
            /* ... */
        }
    }
}

static void hello_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
    struct hello_card *card = (struct hello_card *)dev_id;
    u32 status;
    spin_lock(&card->lock);
    /* 识别中断 */
    status = inl(card->iobase + GLOB_STA);
    if(!(status & INT_MASK)) 
    {
        spin_unlock(&card->lock);
        return;  /* not for us */
    }
    /* 告诉设备已经收到中断 */
    outl(status & INT_MASK, card->iobase + GLOB_STA);
    spin_unlock(&card->lock);
     
    /* 其它进一步的处理,如更新DMA缓冲区指针等 */
}

static int hello_release(struct inode *inode, struct file *file)
{
    /* 释放对设备的控制权 */
    card->open_mode &= (FMODE_READ | FMODE_WRITE);
     
    /* 唤醒其它等待获取控制权的进程 */
    wake_up(&card->open_wait);
    up(&card->open_sem);
     
    /* 释放中断 */
    free_irq(card->irq, card);
     
    /* 设备打开计数增1 */
    MOD_DEC_USE_COUNT;
}

static struct file_operations hello_fops = {
    owner:      THIS_MODULE,
    read:       hello_read,
    write:      hello_write,
    ioctl:      hello_ioctl,
    open:       hello_open,
    release:    hello_release,
};


static int __init hello_probe(struct pci_dev *pci_dev, const struct pci_device_id *pci_id)
{
    struct hello_card *card;
    /* 启动PCI设备 */
    if (pci_enable_device(pci_dev))
        return -EIO;
    /* 设备DMA标识 */
    if (pci_set_dma_mask(pci_dev, hello_DMA_MASK)) {
        return -ENODEV;
    }
    /* 在内核空间中动态申请内存 */
    if ((card = kmalloc(sizeof(struct hello_card), GFP_KERNEL)) == NULL) {
        printk(KERN_ERR "pci_hello: out of memory\n");
        return -ENOMEM;
    }
    memset(card, 0, sizeof(*card));
    /* 读取PCI配置信息 */
    card->iobase = pci_resource_start(pci_dev, 1);
    card->pci_dev = pci_dev;
    card->pci_id = pci_id->device;
    card->irq = pci_dev->irq;
    card->next = devs;
    card->magic = hello_CARD_MAGIC;
    /* 设置成总线主DMA模式 */
    pci_set_master(pci_dev);
    /* 申请I/O资源 */
    request_region(card->iobase, 64, card_names[pci_id->driver_data]);
    return 0;
}

static struct pci_device_id hello_pci_tbl [] __initdata = {
    {PCI_VENDOR_ID_hello, PCI_DEVICE_ID_hello,
     PCI_ANY_ID, PCI_ANY_ID, 0, 0, hello},
    {0,}
};

static struct pci_driver hello_pci_driver = {
    name:       hello_MODULE_NAME,
    id_table:   hello_pci_tbl,
    probe:      hello_probe,
    remove:     hello_remove,
};

static int __init hello_init_module (void)
{
    //检查系统是否支持PCI总线
    if (!pci_present())
        return -ENODEV;

    if (!pci_register_driver(&hello_pci_driver)) {
        pci_unregister_driver(&hello_pci_driver);
        return -ENODEV;
    }
    return 0;
}

static void __exit hello_cleanup_module (void)
{
    pci_unregister_driver(&hello_pci_driver);
}

module_init(hello_init_module);
module_exit(hello_cleanup_module)

おすすめ

転載: blog.csdn.net/star871016/article/details/112990144