00 Linux PCIe sürücü modeli — pci_driver yapısı
Linux PCIe sürücüsü, struct pci_driver üzerine inşa edilir. Kernel, id_table ile eşleşen bir cihaz bulduğunda probe() çağırır; cihaz kaldırıldığında remove() çağırır.
#include <linux/module.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#define MY_VENDOR_ID 0x1234
#define MY_DEVICE_ID 0xABCD
/* Cihaz başına özel durum yapısı */
struct mydev {
struct pci_dev *pdev;
void __iomem *bar0; /* BAR0 MMIO pointer */
int irq;
/* rx/tx ring, DMA buffer'lar... */
};
/* id_table: hangi cihazları iddia ettiğimiz */
static const struct pci_device_id mydrv_ids[] = {
{ PCI_DEVICE(MY_VENDOR_ID, MY_DEVICE_ID) },
{ PCI_DEVICE(MY_VENDOR_ID, 0xABCE) }, /* başka bir varyant */
{ 0, } /* sentinel */
};
MODULE_DEVICE_TABLE(pci, mydrv_ids);
static int mydrv_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct mydev *dev;
int ret;
dev_info(&pdev->dev, "probe: %04x:%04x\n",
pdev->vendor, pdev->device);
/* Private veri tahsis et ve pdev'e bağla */
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev) return -ENOMEM;
dev->pdev = pdev;
pci_set_drvdata(pdev, dev);
/* ... kaynak etkinleştirme, BAR, IRQ, DMA ... */
return 0;
}
static void mydrv_remove(struct pci_dev *pdev)
{
struct mydev *dev = pci_get_drvdata(pdev);
dev_info(&pdev->dev, "remove\n");
/* devm_ kaynakları otomatik serbest bırakılır */
(void)dev;
}
static struct pci_driver mydrv_driver = {
.name = "mydrv",
.id_table = mydrv_ids,
.probe = mydrv_probe,
.remove = mydrv_remove,
};
module_pci_driver(mydrv_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Emirhan Pehlevan");
MODULE_DESCRIPTION("Minimal PCIe sürücü örneği");
Bu bölümde
- pci_driver: name, id_table, probe, remove — minimum gereksinim
- id_table: PCI_DEVICE(vendor, device) makrosu; MODULE_DEVICE_TABLE ile sysfs/modprobe entegrasyonu
- probe(): devm_kzalloc ile private veri; pci_set_drvdata ile pdev'e bağla
- module_pci_driver(): module_init/exit çiftini otomatik oluşturur
01 pci_enable_device ve kaynak yönetimi
probe() içinde cihazı etkinleştirmek, bus mastering açmak ve kaynakları talep etmek gerekir. Managed versiyonlar (pcim_, devm_) cleanup'ı otomatikleştirir.
static int mydrv_probe(struct pci_dev *pdev,
const struct pci_device_id *id)
{
struct mydev *dev;
int ret;
dev = devm_kzalloc(&pdev->dev, sizeof(*dev), GFP_KERNEL);
if (!dev) return -ENOMEM;
dev->pdev = pdev;
pci_set_drvdata(pdev, dev);
/* 1. Managed PCIe etkinleştirme — pcim_ sürümü tercih edilir */
ret = pcim_enable_device(pdev);
if (ret) {
dev_err(&pdev->dev, "pcim_enable_device: %d\n", ret);
return ret;
}
/* 2. Bus mastering: cihazın DMA başlatabilmesi için zorunlu */
pci_set_master(pdev);
/* 3. BAR'ları talep et (request_region / request_mem_region) */
ret = pcim_iomap_regions(pdev,
BIT(0) | BIT(2), /* BAR0 ve BAR2 */
"mydrv");
if (ret) {
dev_err(&pdev->dev, "iomap_regions: %d\n", ret);
return ret;
}
/* 4. MMIO pointer al */
dev->bar0 = pcim_iomap_table(pdev)[0]; /* BAR0 */
dev_info(&pdev->dev, "BAR0: %p (size %llu)\n",
dev->bar0,
(unsigned long long)pci_resource_len(pdev, 0));
return 0;
}
/* Manuel (non-pcim_) yol — kıyaslama için */
static int mydrv_probe_manual(struct pci_dev *pdev,
const struct pci_device_id *id)
{
int ret;
ret = pci_enable_device(pdev);
if (ret) return ret;
pci_set_master(pdev);
ret = pci_request_regions(pdev, "mydrv");
if (ret) goto err_disable;
void __iomem *bar0 = pci_iomap(pdev, 0, 0);
if (!bar0) { ret = -ENOMEM; goto err_release; }
return 0;
err_release:
pci_release_regions(pdev);
err_disable:
pci_disable_device(pdev);
return ret;
}
pci_resource_ yardımcıları
| Fonksiyon | Açıklama |
|---|---|
| pci_resource_start(pdev, bar) | BAR fiziksel başlangıç adresi |
| pci_resource_end(pdev, bar) | BAR fiziksel bitiş adresi |
| pci_resource_len(pdev, bar) | BAR boyutu (bayt) |
| pci_resource_flags(pdev, bar) | IORESOURCE_MEM / IORESOURCE_IO / IORESOURCE_PREFETCH |
Bu bölümde
- pcim_enable_device(): managed; probe başarısız olursa veya remove çağrılırsa otomatik disable
- pci_set_master(): BusMaster bit'ini set eder; DMA işlemleri için zorunlu
- pcim_iomap_regions(): BAR request + iomap tek adımda; pcim_iomap_table() ile pointer al
- devm_ / pcim_ tercih et — manual cleanup kodunda hata riski azalır
02 BAR mapping — pci_iomap, ioremap, readl/writel
BAR (Base Address Register), cihazın MMIO bölgesini tanımlar. Bu bölgeye CPU erişimi için virtual adres eşlemesi (ioremap) ve doğru accessor fonksiyonları kullanılmalıdır.
#include <linux/pci.h>
#include <linux/io.h>
/* Register offset'leri — cihaz datasheet'ten */
#define REG_CTRL 0x00
#define REG_STATUS 0x04
#define REG_DMA_ADDR_LO 0x10
#define REG_DMA_ADDR_HI 0x14
#define REG_DMA_LEN 0x18
#define REG_DMA_CMD 0x1C
#define CTRL_ENABLE BIT(0)
#define CTRL_RESET BIT(1)
#define STATUS_READY BIT(0)
#define STATUS_ERROR BIT(1)
struct mydev {
struct pci_dev *pdev;
void __iomem *bar0;
};
/* --- Register okuma/yazma yardımcıları --- */
static inline u32 mydev_read32(struct mydev *dev, u32 offset)
{
return readl(dev->bar0 + offset);
}
static inline void mydev_write32(struct mydev *dev, u32 offset, u32 val)
{
writel(val, dev->bar0 + offset);
}
/* --- Cihaz başlatma --- */
static int mydev_hw_init(struct mydev *dev)
{
u32 status;
int retries = 100;
/* Reset */
mydev_write32(dev, REG_CTRL, CTRL_RESET);
udelay(10);
/* Reset bitini temizle, enable et */
mydev_write32(dev, REG_CTRL, CTRL_ENABLE);
/* Hazır olmasını bekle */
do {
status = mydev_read32(dev, REG_STATUS);
if (status & STATUS_READY) break;
if (status & STATUS_ERROR) {
dev_err(&dev->pdev->dev, "HW error: status=0x%08x\n", status);
return -EIO;
}
udelay(10);
} while (--retries);
if (!retries) {
dev_err(&dev->pdev->dev, "HW init timeout\n");
return -ETIMEDOUT;
}
dev_info(&dev->pdev->dev, "HW init OK, status=0x%08x\n", status);
return 0;
}
/* --- 64-bit DMA adresi yaz --- */
static void mydev_set_dma_addr(struct mydev *dev, dma_addr_t addr)
{
mydev_write32(dev, REG_DMA_ADDR_LO, (u32)(addr & 0xFFFFFFFF));
mydev_write32(dev, REG_DMA_ADDR_HI, (u32)(addr >> 32));
}
Bu bölümde
- pci_iomap(pdev, bar, maxlen): BAR'ı virtual adrese eşle; 0=tam boyut
- readl/writel: 32-bit MMIO erişim; implicit memory barrier içerir
- void __iomem *: MMIO pointer türü; derleyici yanlışlıkla önbelleğe almaz
- BIT(n): bit mask makrosu; register flag tanımları için standart
03 Interrupt yönetimi — INTx, MSI, MSI-X
PCIe üç interrupt mekanizması destekler: eski INTx (PCI uyumlu), MSI (Message Signaled Interrupt) ve MSI-X (genişletilmiş). Modern sürücüler MSI-X tercih eder.
INTx, MSI ve MSI-X karşılaştırması
| Tür | Vektör Sayısı | Paylaşım | Avantaj |
|---|---|---|---|
| INTx (legacy) | 1 (INTA-INTD) | Birden fazla cihaz paylaşır | Eski sistem uyumluluğu |
| MSI | 1–32 | Hayır | Paylaşım yok, daha az gecikme |
| MSI-X | 1–2048 | Hayır | En fazla vektör, NUMA'ya yönlendirilebilir |
#define MYDRV_MAX_VECTORS 4
struct mydev {
struct pci_dev *pdev;
void __iomem *bar0;
int num_vectors;
};
/* MSI-X handler */
static irqreturn_t mydrv_msix_handler(int irq, void *data)
{
struct mydev *dev = data;
u32 status = mydev_read32(dev, REG_STATUS);
if (!(status & STATUS_READY))
return IRQ_NONE;
/* Interrupt kaynağını temizle */
mydev_write32(dev, REG_STATUS, status);
/* İş yapma: tasklet veya NAPI schedule */
/* tasklet_schedule(&dev->rx_tasklet); */
return IRQ_HANDLED;
}
static int mydrv_setup_irq(struct mydev *dev)
{
struct pci_dev *pdev = dev->pdev;
int i, ret;
/* MSI-X talep et; başarısız olursa MSI dene */
dev->num_vectors = pci_alloc_irq_vectors(pdev,
1, /* minimum */
MYDRV_MAX_VECTORS, /* istenen maksimum */
PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_LEGACY);
if (dev->num_vectors < 0) {
dev_err(&pdev->dev, "irq_vectors: %d\n", dev->num_vectors);
return dev->num_vectors;
}
dev_info(&pdev->dev, "irq vectors: %d (%s)\n",
dev->num_vectors,
pdev->msix_enabled ? "MSI-X" :
pdev->msi_enabled ? "MSI" : "INTx");
/* Her vektör için handler kaydet */
for (i = 0; i < dev->num_vectors; i++) {
int irq = pci_irq_vector(pdev, i);
ret = devm_request_irq(&pdev->dev, irq,
mydrv_msix_handler,
0, /* flags */
"mydrv", dev);
if (ret) {
dev_err(&pdev->dev, "request_irq[%d]: %d\n", i, ret);
/* devm_ önceki request'leri otomatik temizler */
pci_free_irq_vectors(pdev);
return ret;
}
}
return 0;
}
static void mydrv_free_irq(struct mydev *dev)
{
/* devm_request_irq kullandıysak remove'da otomatik serbest bırakılır */
/* Ama vektörleri açıkça serbest bırak */
pci_free_irq_vectors(dev->pdev);
}
Bu bölümde
- pci_alloc_irq_vectors(): MSI-X → MSI → legacy sırasıyla dener; flag kombinasyonu ile
- pci_irq_vector(pdev, i): i. vektörün IRQ numarasını döner
- MSI-X: her vektör farklı CPU'ya yönlendirilebilir; RX/TX kuyrukları için idealdir
- devm_request_irq(): remove veya probe hatasında otomatik free_irq
04 DMA işlemleri — coherent ve streaming
PCIe cihazı, DMA ile sistem belleğine doğrudan erişir. Linux DMA API, cihaza görünür fiziksel adresleri yönetir ve cache tutarlılığını sağlar.
#include <linux/dma-mapping.h>
#define DMA_BUF_SIZE (4 * 1024) /* 4 KB descriptor ring */
struct mydev {
struct pci_dev *pdev;
void __iomem *bar0;
/* Coherent DMA buffer */
void *dma_buf; /* sanal adres */
dma_addr_t dma_handle; /* cihazın göreceği fiziksel adres */
};
static int mydrv_dma_init(struct mydev *dev)
{
struct pci_dev *pdev = dev->pdev;
int ret;
/* 1. DMA mask ayarı: 64-bit DMA adresleme */
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
if (ret) {
dev_warn(&pdev->dev, "64-bit DMA yok, 32-bit deneniyor\n");
ret = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
if (ret) {
dev_err(&pdev->dev, "DMA mask ayarlanamadı\n");
return ret;
}
}
/* 2. Coherent DMA buffer — CPU ve cihaz her zaman tutarlı görür */
dev->dma_buf = dma_alloc_coherent(&pdev->dev,
DMA_BUF_SIZE,
&dev->dma_handle,
GFP_KERNEL);
if (!dev->dma_buf) {
dev_err(&pdev->dev, "dma_alloc_coherent başarısız\n");
return -ENOMEM;
}
dev_info(&pdev->dev,
"DMA buf: virt=%p, phys=0x%llx, size=%u\n",
dev->dma_buf,
(unsigned long long)dev->dma_handle,
DMA_BUF_SIZE);
/* Cihaza DMA adresini yaz */
mydev_set_dma_addr(dev, dev->dma_handle);
return 0;
}
static void mydrv_dma_cleanup(struct mydev *dev)
{
if (dev->dma_buf) {
dma_free_coherent(&dev->pdev->dev,
DMA_BUF_SIZE,
dev->dma_buf,
dev->dma_handle);
dev->dma_buf = NULL;
dev->dma_handle = 0;
}
}
/* --- Streaming DMA (tek paket için) --- */
static int mydrv_dma_send(struct mydev *dev, void *data, size_t len)
{
dma_addr_t dma_addr;
/* Buffer'ı DMA için eşle */
dma_addr = dma_map_single(&dev->pdev->dev, data, len,
DMA_TO_DEVICE);
if (dma_mapping_error(&dev->pdev->dev, dma_addr)) {
dev_err(&dev->pdev->dev, "dma_map_single hata\n");
return -ENOMEM;
}
/* Cihaza DMA adresi ve uzunluğunu ver */
mydev_write32(dev, REG_DMA_ADDR_LO, (u32)(dma_addr & 0xFFFFFFFF));
mydev_write32(dev, REG_DMA_ADDR_HI, (u32)(dma_addr >> 32));
mydev_write32(dev, REG_DMA_LEN, (u32)len);
mydev_write32(dev, REG_DMA_CMD, 0x01); /* START */
/* İşlem tamamlandıktan sonra unmap (genellikle completion IRQ'da) */
dma_unmap_single(&dev->pdev->dev, dma_addr, len, DMA_TO_DEVICE);
return 0;
}
Bu bölümde
- dma_set_mask_and_coherent(): DMA adres aralığını belirt; 64-bit önce dene
- dma_alloc_coherent(): CPU+cihaz tutarlı bellek; cache flush gerekmez
- dma_map_single(): streaming DMA; DMA_TO_DEVICE / FROM_DEVICE / BIDIRECTIONAL
- dma_mapping_error(): harita hatası mutlaka kontrol edilmeli; IOMMU veya bellek doluysa başarısız
05 MMIO register erişimi — barrier ve relaxed accessor
MMIO register'larına erişimde memory ordering kritik öneme sahiptir. readl/writel implicit barrier içerirken readl_relaxed/writel_relaxed daha yüksek performans sağlar.
Memory ordering gereksinimleri
MMIO yazma işlemlerinin sırası korunmalıdır; aksi hâlde cihaz yanlış komut sırasına maruz kalabilir. Ayrıca bir MMIO yazmasının donanıma ulaştığından emin olmak için bazen okuma yaparak "flush" gerekir.
#include <linux/io.h>
/* ── Standart accessor'lar (implicit barrier) ──────────────── */
/* readl/writel: çoğu mimaride mfence veya dsb benzeri işlem içerir */
static inline void mydev_reg_write(struct mydev *dev, u32 off, u32 val)
{
writel(val, dev->bar0 + off);
}
static inline u32 mydev_reg_read(struct mydev *dev, u32 off)
{
return readl(dev->bar0 + off);
}
/* ── Relaxed accessor'lar (barrier YOK — yüksek hızlı döngüler) ── */
static inline void mydev_reg_write_relaxed(struct mydev *dev,
u32 off, u32 val)
{
writel_relaxed(val, dev->bar0 + off);
}
static inline u32 mydev_reg_read_relaxed(struct mydev *dev, u32 off)
{
return readl_relaxed(dev->bar0 + off);
}
/* ── Kullanım örneği: descriptor ring doldurma ─────────────── */
static void mydev_fill_tx_ring(struct mydev *dev,
struct tx_desc *ring, int n)
{
int i;
/* Descriptor'ları relaxed ile yaz — sıra korunur (compiler barrier) */
for (i = 0; i < n; i++) {
writel_relaxed(ring[i].addr_lo, dev->bar0 + TX_DESC(i, ADDR_LO));
writel_relaxed(ring[i].addr_hi, dev->bar0 + TX_DESC(i, ADDR_HI));
writel_relaxed(ring[i].length, dev->bar0 + TX_DESC(i, LEN));
/* Son descriptor flag: barrier SONRASI yaz */
wmb(); /* write memory barrier */
writel_relaxed(ring[i].flags | DESC_VALID,
dev->bar0 + TX_DESC(i, FLAGS));
}
/* Doorbell: cihaza "yeni descriptor var" bildir */
/* Bu yazma önceki tüm yazmaların tamamlanmasını gerektirir */
wmb();
writel(n, dev->bar0 + REG_TX_DOORBELL);
}
/* ── Yazma flush (MMIO write posting) ─────────────────────── */
static void mydev_flush_write(struct mydev *dev)
{
/* Yazma flush: bir okuma yaparak posted write'ların bitmesini garantile */
(void)readl(dev->bar0 + REG_STATUS);
}
Barrier seçim rehberi
| Durum | Kullanılacak |
|---|---|
| Normal register okuma/yazma | readl / writel |
| Yüksek frekanslı döngü (descriptor doldurma) | readl_relaxed / writel_relaxed |
| Sonraki yazma önceki tüm yazmalar tamamlandıktan sonra | wmb() + writel |
| MMIO write'ın cihaza ulaştığını garanti etme | Ardından readl() ile flush |
| ARM64: relaxed okuma güvenli mi? | Evet; implicit ordering vardır (LD-LD sırası) |
Bu bölümde
- readl/writel: x86'da mfence, ARM64'te dmb(osh) içerir — genel kullanım için güvenli
- readl_relaxed/writel_relaxed: barrier yok; döngü içinde performans kritikse kullan
- wmb(): write memory barrier; descriptor valid flag'i en son yaz
- MMIO flush: bir sonraki readl() çağrısına kadar önceki writel()lar queued kalabilir
06 Sürücü örneği — basit PCIe NIC
RX/TX descriptor ring, DMA ve NAPI kullanan minimal bir PCIe ağ kartı sürücüsünün iskelet implementasyonu.
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
#include <linux/pci.h>
#define RX_RING_SIZE 256
#define TX_RING_SIZE 256
#define BUF_SIZE 2048
struct rx_desc {
__le64 buf_addr; /* DMA adresi */
__le16 length;
__le16 status;
__le32 reserved;
} __packed;
struct nic_priv {
struct pci_dev *pdev;
struct net_device *netdev;
void __iomem *bar0;
struct napi_struct napi;
/* RX ring */
struct rx_desc *rx_ring; /* descriptor ring (coherent DMA) */
dma_addr_t rx_ring_dma;
struct sk_buff *rx_skbs[RX_RING_SIZE];
dma_addr_t rx_dma[RX_RING_SIZE];
int rx_head;
};
/* NAPI poll: IRQ yerine NAPI ile paket al */
static int nic_napi_poll(struct napi_struct *napi, int budget)
{
struct nic_priv *priv = container_of(napi, struct nic_priv, napi);
int received = 0;
while (received < budget) {
struct rx_desc *desc = &priv->rx_ring[priv->rx_head];
struct sk_buff *skb;
u16 status, length;
/* Descriptor hazır değilse dur */
status = le16_to_cpu(desc->status);
if (!(status & 0x01)) /* DD (Descriptor Done) bit */
break;
length = le16_to_cpu(desc->length);
skb = priv->rx_skbs[priv->rx_head];
/* DMA unmap */
dma_unmap_single(&priv->pdev->dev,
priv->rx_dma[priv->rx_head],
BUF_SIZE, DMA_FROM_DEVICE);
/* skb hazırla ve protokol stack'e ver */
skb_put(skb, length);
skb->protocol = eth_type_trans(skb, priv->netdev);
napi_gro_receive(napi, skb);
priv->netdev->stats.rx_packets++;
priv->netdev->stats.rx_bytes += length;
/* Descriptor yenile */
/* ... yeni skb + DMA map ... */
priv->rx_head = (priv->rx_head + 1) % RX_RING_SIZE;
received++;
}
if (received < budget) {
napi_complete_done(napi, received);
/* Interrupt'ı yeniden etkinleştir */
writel(0x01, priv->bar0 + 0x100); /* RX IRQ enable */
}
return received;
}
/* MSI-X handler: NAPI schedule et */
static irqreturn_t nic_rx_irq(int irq, void *data)
{
struct nic_priv *priv = data;
/* Interrupt'ı devre dışı bırak; NAPI devralır */
writel(0x00, priv->bar0 + 0x100);
napi_schedule(&priv->napi);
return IRQ_HANDLED;
}
static int nic_open(struct net_device *netdev)
{
struct nic_priv *priv = netdev_priv(netdev);
napi_enable(&priv->napi);
netif_start_queue(netdev);
return 0;
}
static int nic_stop(struct net_device *netdev)
{
struct nic_priv *priv = netdev_priv(netdev);
netif_stop_queue(netdev);
napi_disable(&priv->napi);
return 0;
}
static const struct net_device_ops nic_ops = {
.ndo_open = nic_open,
.ndo_stop = nic_stop,
/* .ndo_start_xmit = nic_xmit, */
};
static int nic_probe(struct pci_dev *pdev, const struct pci_device_id *id)
{
struct net_device *netdev;
struct nic_priv *priv;
int ret;
netdev = alloc_etherdev(sizeof(*priv));
if (!netdev) return -ENOMEM;
priv = netdev_priv(netdev);
priv->pdev = pdev;
priv->netdev = netdev;
pci_set_drvdata(pdev, priv);
SET_NETDEV_DEV(netdev, &pdev->dev);
ret = pcim_enable_device(pdev);
if (ret) goto err_free;
pci_set_master(pdev);
ret = pcim_iomap_regions(pdev, BIT(0), "nic");
if (ret) goto err_free;
priv->bar0 = pcim_iomap_table(pdev)[0];
/* NAPI kurulumu */
netif_napi_add(netdev, &priv->napi, nic_napi_poll, 64);
netdev->netdev_ops = &nic_ops;
eth_hw_addr_random(netdev); /* gerçekte HW'den oku */
ret = register_netdev(netdev);
if (ret) goto err_napi;
return 0;
err_napi:
netif_napi_del(&priv->napi);
err_free:
free_netdev(netdev);
return ret;
}
static void nic_remove(struct pci_dev *pdev)
{
struct nic_priv *priv = pci_get_drvdata(pdev);
unregister_netdev(priv->netdev);
netif_napi_del(&priv->napi);
free_netdev(priv->netdev);
}
Bu bölümde
- RX ring: coherent DMA'da descriptor dizisi; her slot için skb + DMA adres kaydı
- NAPI: MSI-X handler RX IRQ'yu kapatır, napi_schedule() çağırır; poll budget ile işlem
- napi_gro_receive(): GRO (Generic Receive Offload) ile paketleri birleştir
- alloc_etherdev(): net_device + private veri tek alloc; SET_NETDEV_DEV ile sysfs bağlantısı
07 Hotplug ve power management — ASPM, D0/D3
PCIe hotplug ve ASPM (Active State Power Management), gömülü sistemlerde enerji tasarrufu ve dinamik cihaz yönetimi için kritiktir.
#include <linux/pm.h>
#include <linux/pm_runtime.h>
static int mydrv_suspend(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct mydev *mydev = pci_get_drvdata(pdev);
dev_info(dev, "suspend: D0 → D3\n");
netif_stop_queue(mydev->netdev); /* varsa */
napi_disable(&mydev->napi); /* varsa */
/* HW'i durdur */
mydev_write32(mydev, REG_CTRL, 0);
/* PCIe state: D3hot */
pci_save_state(pdev);
pci_disable_device(pdev);
pci_set_power_state(pdev, PCI_D3hot);
return 0;
}
static int mydrv_resume(struct device *dev)
{
struct pci_dev *pdev = to_pci_dev(dev);
struct mydev *mydev = pci_get_drvdata(pdev);
int ret;
dev_info(dev, "resume: D3 → D0\n");
pci_set_power_state(pdev, PCI_D0);
pci_restore_state(pdev);
ret = pci_enable_device(pdev);
if (ret) return ret;
pci_set_master(pdev);
mydev_hw_init(mydev);
napi_enable(&mydev->napi);
netif_start_queue(mydev->netdev);
return 0;
}
static const struct dev_pm_ops mydrv_pm_ops = {
SET_SYSTEM_SLEEP_PM_OPS(mydrv_suspend, mydrv_resume)
/* Runtime PM: */
/* SET_RUNTIME_PM_OPS(mydrv_runtime_suspend,
mydrv_runtime_resume, NULL) */
};
static struct pci_driver mydrv_driver = {
.name = "mydrv",
.id_table = mydrv_ids,
.probe = mydrv_probe,
.remove = mydrv_remove,
.driver.pm = &mydrv_pm_ops,
};
ASPM yönetimi
# ASPM durumunu sorgula
sudo lspci -vvv -s 01:00.0 | grep -i "aspm\|l0s\|l1"
# LnkCtl: ASPM L0s L1 Enabled; RCB 64 bytes, ...
# Tüm cihazlar için ASPM devre dışı bırak (gecikme kritik):
echo 0 | sudo tee /sys/module/pcie_aspm/parameters/policy
# veya: pcie_aspm=off kernel parametresi
# En agresif güç tasarrufu:
echo powersupersave | sudo tee /sys/module/pcie_aspm/parameters/policy
Bu bölümde
- dev_pm_ops: suspend/resume sistem uyku döngüsü; SET_SYSTEM_SLEEP_PM_OPS makrosu
- pci_save_state / pci_restore_state: config space yedekle/geri yükle
- Runtime PM: cihaz boştayken D3'e geç; pm_runtime_enable/get/put ile yönetim
- ASPM L1: PLL kapatılır, ~mW tasarruf; L0s: hızlı çıkış, daha az tasarruf
08 Pratik: PCIe loopback test cihazı sürücüsü
Basit bir PCIe loopback test cihazı için /dev arayüzü sağlayan minimal sürücü — write ile cihaza veri gönder, read ile geri al.
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define BUF_SIZE 4096
struct loopdev {
struct pci_dev *pdev;
void __iomem *bar0;
struct cdev cdev;
dev_t devno;
void *dma_buf;
dma_addr_t dma_handle;
struct mutex lock;
};
static ssize_t loopdev_write(struct file *f, const char __user *buf,
size_t len, loff_t *off)
{
struct loopdev *dev = f->private_data;
ssize_t ret;
if (len > BUF_SIZE) len = BUF_SIZE;
mutex_lock(&dev->lock);
if (copy_from_user(dev->dma_buf, buf, len)) {
ret = -EFAULT;
goto out;
}
/* Cihaza DMA transfer başlat */
mydev_write32((struct mydev *)dev, REG_DMA_LEN, (u32)len);
mydev_write32((struct mydev *)dev, REG_DMA_CMD, 0x01); /* TX start */
/* Basit polling tamamlanma bekleme (IRQ yerine) */
int timeout = 1000;
while (timeout--) {
u32 status = mydev_read32((struct mydev *)dev, REG_STATUS);
if (status & STATUS_READY) break;
udelay(10);
}
ret = (timeout >= 0) ? (ssize_t)len : -ETIMEDOUT;
out:
mutex_unlock(&dev->lock);
return ret;
}
static ssize_t loopdev_read(struct file *f, char __user *buf,
size_t len, loff_t *off)
{
struct loopdev *dev = f->private_data;
u32 avail;
mutex_lock(&dev->lock);
avail = mydev_read32((struct mydev *)dev, 0x20); /* RX length reg */
if (len > avail) len = avail;
if (copy_to_user(buf, dev->dma_buf, len)) {
mutex_unlock(&dev->lock);
return -EFAULT;
}
mutex_unlock(&dev->lock);
return len;
}
static int loopdev_open(struct inode *inode, struct file *f)
{
struct loopdev *dev = container_of(inode->i_cdev,
struct loopdev, cdev);
f->private_data = dev;
return 0;
}
static const struct file_operations loopdev_fops = {
.owner = THIS_MODULE,
.open = loopdev_open,
.read = loopdev_read,
.write = loopdev_write,
};
# Modülü yükle
sudo insmod myloopback.ko
# /dev cihazı oluşturuldu mu?
ls -la /dev/myloopback0
# Loopback testi: write → cihaz → read
echo "PCIe test verisi" | sudo tee /dev/myloopback0
sudo cat /dev/myloopback0
# PCIe test verisi
# Performans testi (dd ile bant genişliği)
dd if=/dev/urandom bs=4K count=1000 | \
sudo tee /dev/myloopback0 > /dev/null
# 4096000 bytes (4.1 MB) copied, 0.021 s, 195 MB/s
Bu bölümde
- cdev + file_operations: PCIe cihazına /dev arayüzü; standart read/write semantiği
- copy_from_user / copy_to_user: kullanıcı ↔ kernel bellek kopyası; hata dönüşü zorunlu
- mutex_lock: DMA transfer sırasında aynı anda iki işlem olmasını önler
- Polling vs IRQ: test sürücüsünde basit polling yeterli; üretimde IRQ + completion kullan