PCI-E linux 開發 CH368L 評估板

CH368L EVT

是一款 PCI-E 開發板,板載 24、25 存儲器,有 IO 接口,可以接內存類總線。有指示燈,電源切換跳線,有 IOPort、 MMIO 2種 BAR 空間。提供 windows 、linux 驅動源碼和測試程序。

產品資料下載

https://www.wch.cn/search?t=all&q=CH368

開發環境爲 i5 普通臺式電腦,ubuntu 22.0.4 kernel 6.5.0 文中的代碼編譯測試通過,不同的 kernel 稍有區別,請自行解決。

PCI-E 配置描述

image

訪問配置描述的方法:

用戶態:直接讀取解析 /sys/bus/pci/devices/0000:03:00.0/config 文件

內核態:pci_read_config_byte() pci_read_config_word(pdev, PCI_VENDOR_ID, &valw);

lspci 查看產品特性

lspci # 列表所有 pci 設備
01:00.0 Ethernet controller: Intel Corporation 82576 Gigabit Network Connection (rev 01)
01:00.1 Ethernet controller: Intel Corporation 82576 Gigabit Network Connection (rev 01)
03:00.0 Network and computing encryption device: Device 1c00:5834 (rev 10)
04:00.0 Ethernet controller: Beijing Wangxun Technology Co., Ltd. WX1860AL2 Gigabit Ethernet Controller (rev 01)
04:00.1 Ethernet controller: Beijing Wangxun Technology Co., Ltd. WX1860AL2 Gigabit Ethernet Controller (rev 01)

# 查看 CH368L EVT 詳細配置 pci 地址爲 03:00.0
lspci -s 03:00.0 -vvv
03:00.0 Network and computing encryption device: Device 1c00:5834 (rev 10)
        Subsystem: Device 1c00:5834
        Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx-
        Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
        Latency: 0, Cache Line Size: 32 bytes
        Interrupt: pin A routed to IRQ 10
        Region 0: I/O ports at d000 [size=256]
        Region 1: Memory at f0000000 (32-bit, prefetchable) [size=32K]
        Region 2: I/O ports at d100 [size=4]
        Expansion ROM at f7b00000 [disabled] [size=32K]
        Capabilities: <access denied>

可以看到有 Region 0 Region 1 Region 2 , 0 和2 是 I/O 端口。 1 是 MMIO (Memory-Mapped Input/Output)內存空間資源

# 直接查看 resource 描述文件
/sys/bus/pci/devices/0000:03:00.0$ cat resource
0x000000000000d000 0x000000000000d0ff 0x0000000000040101 ----< Region 0 I/O ports
0x00000000f0000000 0x00000000f0007fff 0x0000000000042208 ----< Region 1 Memory
0x000000000000d100 0x000000000000d103 0x0000000000040101 ----< Region 2 I/O ports
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x00000000f7b00000 0x00000000f7b07fff 0x0000000000046200 ----< Expansion ROM
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000
0x0000000000000000 0x0000000000000000 0x0000000000000000

# 查看所有資源
/sys/bus/pci/devices/0000:03:00.0$ ls |grep resource
resource
resource0
resource1
resource1_wc
resource2

prefetchable non-prefetchable 是否允許 CPU 緩存

arm64
Region 0: Memory at 0000000010284000 (64-bit, non-prefetchable)
Region 4: Memory at 00000000102a4000 (64-bit, non-prefetchable)

編譯安裝驅動

sudo dmesg |grep ch36
[ 3608.688221] ch36x: PCI/PCIe driver for chip ch365/ch367/ch368, etc.
[ 3608.688229] ch36x: V1.3 On 2023.07
[ 3608.688295] ch36xpci 0000:03:00.0: ch36x_pci_probe
[ 3608.688459] ch36xpci 0000:03:00.0: ch36x map succeed.
[ 3608.688464] ch36xpci 0000:03:00.0: ***********I/O Port**********
[ 3608.688466] ch36xpci 0000:03:00.0: phy addr: 0xd000  ----<  Region 0: I/O ports at d000 [size=256]
pci_resource_start(pdev, 0)
[ 3608.688469] ch36xpci 0000:03:00.0: io len: 256
pci_resource_len(pdev, 0)

[ 3608.688472] ch36xpci 0000:03:00.0: ***********I/O Memory**********
[ 3608.688474] ch36xpci 0000:03:00.0: phy addr: 0xf0000000 ----< Region 1: Memory at f0000000 (32-bit, prefetchable) [size=32K]
pci_resource_start(pdev, 1)

[ 3608.688476] ch36xpci 0000:03:00.0: mapped addr: 0xffffa6af80580000
pci_iomap(pdev, 1, ch36x_dev->memlen);

[ 3608.688479] ch36xpci 0000:03:00.0: mem len: 32768
pci_resource_len(pdev, 1)

[ 3608.688500] ch36xpci 0000:03:00.0: irq number is: 18
[ 3608.688968] ch36xpci 0000:03:00.0: ch36x_pci_probe ch36x_pci_probe function finshed.

有打印出來一些信息,總線地址,bar 地址、長度等。這裏面打印的信息和 查看用戶態的 lspci 返回的信息是一致的。

pci-e 的 bar 地址是物理地址由 系統在啓動的時候分配的,內核中不能直接操作設備物理地址,需要使用 pci_resource_start() pci_resource_len() pci_iomap() 等映射以後才能訪問。 使用 outb() ioread8() 等進行操作,對於 MMIO 類資源,還可以使用 指紋直接進行控制。

取 io address

unsigned long iobase;
ioctl(fd, CH36x_GET_IO_BASE_ADDR, (unsigned long)ioaddr);
case CH36x_GET_IO_BASE_ADDR:
  retval = put_user(ch36x_dev->ioaddr, (long __user *)ch36x_arg);

static int ch36x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
  static int ch36x_map_device(struct pci_dev *pdev, struct ch36x_dev *ch36x_dev)
  /* map the memory mapped i/o registers */
    ch36x_dev->ioaddr = pci_resource_start(pdev, 0);

取 int ch36x_get_memaddr(int fd, void *memaddr)

int ch36x_get_memaddr(int fd, void *memaddr)
ch36x_dev->memaddr = pci_iomap(pdev, 1, ch36x_dev->memlen);

測試 工具 flash 讀寫

f
---------- flash read write test ----------
input flash addr:
1
please input string to write:
abcd
spi flash addr [0x1 - 0x5] erased successfully.
spi flash addr [0x1 - 0x5] wrote successfully.
input read length:
1
---------- read spi flash from addr 1 ----------
obuffer[0]: 0x61

# 調用分析
static void ch36x_demo_flash_operate(int fd)
  int ch36x_set_stream(int fd, uint8_t mode)
    ioctl(fd, CH36x_SET_STREAM, (unsigned long)&mode);
    
static int ch36x_fops_ioctl_do(struct ch36x_dev *ch36x_dev, unsigned int cmd, unsigned long ch36x_arg)
  	case CH36x_SET_STREAM:
		get_user(arg1, (u8 __user *)ch36x_arg);
		ch36x_dev->spimode = arg1;
		
ch36x_flash_erase(fd, addr, strlen(ibuffer));
int ch36x_flash_erase(int fd, uint32_t addr, uint32_t ilen)
{
	struct ch36x_stream_spi_t {
		uint32_t addr;
		uint32_t ilen;
	} __attribute__((packed));
	struct ch36x_stream_spi_t ch36x_stream_spi;

	if (ilen < 0) {
		return -1;
	}
	ch36x_stream_spi.addr = addr;
	ch36x_stream_spi.ilen = ilen;

	return ioctl(fd, CH36x_FLASH_ERASE, (unsigned long)&ch36x_stream_spi);
}

static int ch36x_fops_ioctl_do(struct ch36x_dev *ch36x_dev, unsigned int cmd, unsigned long ch36x_arg)
  case CH36x_FLASH_ERASE:
    get_user(arg1, (u32 __user *)ch36x_arg);
    get_user(arg2, ((u32 __user *)ch36x_arg + 1));
      ch36x_flash_erase(ch36x_dev, arg1, arg2)
      
static bool ch36x_flash_erase(struct ch36x_dev *ch36x_dev, u32 startaddr, u32 len)
  flashID = ch36x_flash_id(ch36x_dev);
    regval = inb(ch36x_dev->ioaddr + offsetof(mCH367_IO_REG, mCH367SPICtrl));
    
    typedef struct _CH367_IO_REG { // CH367 IO space
	u8 mCH367IoPort[0xE8]; // 00H-E7H, 232 bytes standard IO bytes
	u8 mCH367GPOR;	       // E8H General output register
	u8 mCH367GPVR;	       // E9H General variable register
	u8 mCH367GPIR;	       // EAH General input register
	u8 mCH367IntCtr;       // EBH Interrupt control register
	union {
		u8 mCH367IoBuf8;   // ECH 8-bit passive parallel interface data buffer
		u32 mCH367IoBuf32; // ECH 32-bit passive parallel interface data buffer
	};
	union {
		u16 mCH368MemAddr; // F0H Memory Interface: A15-A0 address setting register
		struct {
			u8 mCH368MemAddrL; // F0H Memory Interface: A7-A0 address setting register
			union {
				u8 mCH368MemAddrH; // F1H Memory Interface: A15-A8 address setting register
				u8 mCH367GPOR2;	   // F1H General output register 2
			};
		} ASR;
	};
	u8 mCH367IORESV2; // F2H
	u8 mCH368MemData; // F3H Memory Interface: Memory data access register
	union {
		u8 mCH367Data8Sta;    // F4H D7-D0 port status register
		u32 mCH367SData32Sta; // F4H D31-D0 port status register
	};
	u8 mCH367status;    // F8H Miscellaneous control and status register
	u8 mCH367IO_RESV3;  // F9H
	u8 mCH367Speed;	    // FAH Speed control register
	u8 mCH367PDataCtrl; // FBH Passive parallel interface control register
	u8 mCH367IoTime;    // FCH Hardware loop count register
	u8 mCH367SPICtrl;   // FDH SPI control register
	u8 mCH367SPIData;   // FEH SPI data register
	u8 mCH367IO_RESV4;  // FFH
} mCH367_IO_REG, *mPCH367_IO_REG;

  u8 mCH367SPICtrl;   // FDH SPI control register

linux 操作函數

https://blog.51cto.com/u_15061935/4560687

PMIO:端口映射I/O(Port-mapped I/O)。將I/O設備獨立看待,並使用CPU提供的專用I/O指令(如X86架構的in和out)訪問。

端口映射I/O,又叫做被隔離的I/O(isolated I/O),它提供了一個專門用於I/O設備“註冊”的地址空間,該地址空間被稱爲I/O地址空間,最大尋址範圍爲64K.

爲了使I/O地址空間與內存地址空間隔離,要麼在CPU物理接口上增加一個I/O引腳,要麼增加一條專用的I/O總線。因此,並不是所有的平臺都支持PMIO,常見的ARM平臺就不支持PMIO。支持PMIO的CPU通常具有專門執行I/O操作的指令,例如在Intel-X86架構的CPU中,I/O指令是in和out,這兩個指令可以讀/寫1、2、4個字節(outb, outw, outl)從內存到I/O接口上。

MMIO:內存映射I/O(Memory-mapped I/O)。將I/O設備看作內存的一部分,不使用單獨的I/O指令,而是使用內存讀寫指令訪問。

在MMIO中,物理內存和I/O設備共享內存地址空間(注意,這裏的內存地址空間實際指的是內存的物理地址空間)
當CPU訪問某個虛擬內存地址時,該虛擬地址首先轉換爲一個物理地址,對該物理地址的訪問,會通過南北橋(現在被合併爲I/O橋)的路由機制被定向到物理內存或者I/O設備上。因此,用於訪問內存的CPU指令也可用於訪問I/O設備,並且在內存(的物理)地址空間上,需要給I/O設備預留一個地址區域,該地址區域不能給物理內存使用。

MMIO是應用得最爲廣泛的一種I/O方式,由於內存地址空間遠大於I/O地址空間,I/O設備可以在內存地址空間上暴露自己的內存或者寄存器,以供主機進行訪問。

PCI設備

PCI及其衍生的接口(如PCIE)主要服務於高速I/O設備(如顯卡或網卡),使用PCI接口的設備又被稱爲PCI設備。與慢速I/O設備不同,計算機既需要訪問它們的寄存器,也需要訪問它們的內存。

每個PCI設備都有一個配置空間(實際就是設備上一組連續的寄存器),大小爲256byte。配置空間中包含了6個BAR(Base Address Registers,基址寄存器),BAR中記錄了設備所需要的地址空間類型、基址以及其他屬性,格式如下:

image

可以看到,PCI設備能夠申請兩類地址空間,即內存地址空間和I/O地址空間,它們用BAR的最後一位區別開來。因此,PCI設備可以通過PMIO和MMIO將自己的I/O存儲器(Registers/RAM/ROM)暴露給CPU(通常寄存器使用PMIO,而內存使用MMIO的方式暴露)。

配置空間中的每個BAR可以映射一個地址空間,因此每個PCI設備最多能映射6段地址空間,但實際上很多設備用不了這麼多。PCI配置空間的初始值是由廠商預設在設備中的,也就是說,設備需要哪些地址空間都是其自己定的,這可能會造成不同的PCI設備所映射的地址空間衝突,因此在PCI設備枚舉(也叫總線枚舉,由BIOS或者OS在啓動時完成)的過程中,會重新爲其分配地址空間,然後寫入PCI配置空間中。

在PCI總線之前的ISA總線是使用跳線帽來分配外設的物理地址,每插入一個新設備都要改變跳線帽以分配物理地址,這是十分麻煩且易錯的,但這樣的方式似乎我們更容易理解。能夠分配自己總線上掛載設備的物理地址這也是PCI總線相較於I2C、SPI等低速總線一個最大的特色。

pci_resource_start() pci_iomap()

pci_resource_start() 返回物理地址
pci_iomap() 返回虛擬地址
pci_iomap_range()
void __iomem *pci_iomap_range(struct pci_dev *dev,
			      int bar,
			      unsigned long offset,
			      unsigned long maxlen)
{
	resource_size_t start = pci_resource_start(dev, bar);
	resource_size_t len = pci_resource_len(dev, bar);
	unsigned long flags = pci_resource_flags(dev, bar);

	if (len <= offset || !start)
		return NULL;
	len -= offset;
	start += offset;
	if (maxlen && len > maxlen)
		len = maxlen;
	if (flags & IORESOURCE_IO)
		return __pci_ioport_map(dev, start, len);
	if (flags & IORESOURCE_MEM)
		return ioremap(start, len);
	/* What? */
	return NULL;
}

pci_iomap() 是把 pci_resource_start() 返回的物理地址 轉換爲 虛擬地址

resource wc 的意義

https://android.googlesource.com/kernel/common/+/bcmdhd-3.10/Documentation/filesystems/sysfs-pci.txt

       file		   function
       ----		   --------
       class		   PCI class (ascii, ro)
       config		   PCI config space (binary, rw)
       device		   PCI device (ascii, ro)
       enable	           Whether the device is enabled (ascii, rw)
       irq		   IRQ number (ascii, ro)
       local_cpus	   nearby CPU mask (cpumask, ro)
       remove		   remove device from kernel's list (ascii, wo)
       resource		   PCI resource host addresses (ascii, ro)
       resource0..N	   PCI resource N, if present (binary, mmap, rw[1])
       resource0_wc..N_wc  PCI WC map resource N, if prefetchable (binary, mmap)
       rom		   PCI ROM resource, if present (binary, ro)
       subsystem_device	   PCI subsystem device (ascii, ro)
       subsystem_vendor	   PCI subsystem vendor (ascii, ro)
       vendor		   PCI vendor (ascii, ro)

讀寫 IO Port

https://blog.csdn.net/WCH_TechGroup/article/details/128287488

  • e-demo程序演示 偏移地址0x00~0XE7爲標準本地IO端口,通過D0 ~ D31雙向數據信號線輸入輸出,雙向數據線內置上拉電阻默認爲高電平,進行IO寫功能時,數據總線D0~D7會直接輸出信號,可通過LED直接觀察輸出結果。

    測試IO讀功能時,可將D0數據線接,此時D7~D0位數據爲1111 1110(0xFE),demo演示讀取一個字節的數據進行對比

kernel

ioaddr = pci_resource_start(pdev, 0); 返回物理地址

寫 byte
outb(~0x0, ch36x_dev->ioaddr + 0);
msleep(500);
outb(~(1<<1), ch36x_dev->ioaddr + 0);
msleep(500);
outb(~(1<<2), ch36x_dev->ioaddr + 0);
msleep(500);
outb(~(1<<3), ch36x_dev->ioaddr + 0);
msleep(500);

讀 byte
inb(ch36x_dev->ioaddr + offsetof(mCH367_IO_REG, mCH367Speed));

讀寫 MMIO 空間

內核態 需要使用 pci_iomap(pdev, 1, ch36x_dev->memlen); 轉換爲 虛擬地址

#if 1
char reg = ioread8(ch36x_dev->memaddr + 0);
dev_info(&pdev->dev, "read mem 0 reg:0x%x\n", reg);
#endif

用戶態直接映射 bar 空間讀寫 可以直接控制 MMIO空間 ,IO 端口直接失敗

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdint.h>
// #include <cstddef>

#define u8 unsigned char
#define u16 unsigned short
#define u32 unsigned int

#define LOG(fmt,...) printf("" fmt "\n", ##__VA_ARGS__);

typedef struct _CH367_IO_REG { // CH367 IO space
	u8 mCH367IoPort[0xE8]; // 00H-E7H, 232 bytes standard IO bytes
	u8 mCH367GPOR;	       // E8H General output register
	u8 mCH367GPVR;	       // E9H General variable register
	u8 mCH367GPIR;	       // EAH General input register
	u8 mCH367IntCtr;       // EBH Interrupt control register
	union {
		u8 mCH367IoBuf8;   // ECH 8-bit passive parallel interface data buffer
		u32 mCH367IoBuf32; // ECH 32-bit passive parallel interface data buffer
	};
	union {
		u16 mCH368MemAddr; // F0H Memory Interface: A15-A0 address setting register
		struct {
			u8 mCH368MemAddrL; // F0H Memory Interface: A7-A0 address setting register
			union {
				u8 mCH368MemAddrH; // F1H Memory Interface: A15-A8 address setting register
				u8 mCH367GPOR2;	   // F1H General output register 2
			};
		} ASR;
	};
	u8 mCH367IORESV2; // F2H
	u8 mCH368MemData; // F3H Memory Interface: Memory data access register
	union {
		u8 mCH367Data8Sta;    // F4H D7-D0 port status register
		u32 mCH367SData32Sta; // F4H D31-D0 port status register
	};
	u8 mCH367status;    // F8H Miscellaneous control and status register
	u8 mCH367IO_RESV3;  // F9H
	u8 mCH367Speed;	    // FAH Speed control register
	u8 mCH367PDataCtrl; // FBH Passive parallel interface control register
	u8 mCH367IoTime;    // FCH Hardware loop count register
	u8 mCH367SPICtrl;   // FDH SPI control register
	u8 mCH367SPIData;   // FEH SPI data register
	u8 mCH367IO_RESV4;  // FFH
} mCH367_IO_REG, *mPCH367_IO_REG;

#define BIT(i) (1 << i)

static const char *config = "/sys/bus/pci/devices/0000:03:00.0/config";
static const char *bar0   = "/sys/bus/pci/devices/0000:03:00.0/resource0";
static const char *bar1   = "/sys/bus/pci/devices/0000:03:00.0/resource1";
static const char *bar1wc = "/sys/bus/pci/devices/0000:03:00.0/resource1_wc";
static const char *bar2   = "/sys/bus/pci/devices/0000:03:00.0/resource2";

/*
lspci -s 03:00.0 -vvv
03:00.0 Network and computing encryption device: Device 1c00:5834 (rev 10)
	Subsystem: Device 1c00:5834
	Control: I/O+ Mem+ BusMaster+ SpecCycle- MemWINV- VGASnoop- ParErr- Stepping- SERR+ FastB2B- DisINTx-
	Status: Cap+ 66MHz- UDF- FastB2B- ParErr- DEVSEL=fast >TAbort- <TAbort- <MAbort- >SERR- <PERR- INTx-
	Latency: 0, Cache Line Size: 32 bytes
	Interrupt: pin A routed to IRQ 16
	Region 0: I/O ports at d000 [size=256]
	Region 1: Memory at f0000000 (32-bit, prefetchable) [size=32K]
	Region 2: I/O ports at d100 [size=4]
	Expansion ROM at f7d00000 [disabled] [size=32K]
	Capabilities: <access denied>
	Kernel driver in use: ch36xpci
*/

static int bar0_fd   = -1;
static int bar1_fd   = -1;
static int bar1wc_fd = -1;
static int bar2_fd   = -1;

static int bar0_length   = 256;
static int bar1_length   = 32*1024;
static int bar1wc_length = 32*1024;
static int bar2_length   = 4;

static void *bar0_mmap   = NULL;
static void *bar1_mmap   = NULL;
static void *bar1wc_mmap = NULL;
static void *bar2_mmap   = NULL;

static unsigned int bar0_phys = 0;
static unsigned int bar1_phys = 0;
static unsigned int bar1wc_phys = 0;
static unsigned int bar2_phys = 0;

unsigned int get_bar_phys_addr(int bar)
{
	unsigned int phys = 0;

	int fd =  open(config, O_RDONLY | O_SYNC);
	if (0 > fd)
	{
		printf("open %s failed\n", config);
		return 0;
	}
	if(0 > lseek(fd, 0x10 + 4*bar, SEEK_SET))
	{
		printf("lseek %s failed\n", config);
	}
	if(0 > read(fd, &phys, 4))
	{
		printf("read %s failed\n", config);
	}
	close(fd);

	printf("bar:%d phys:0x%x\n", bar, phys);
	return phys;
}

int get_file_size(const char *path)
{
	struct stat statbuf;
	int fd =  open(path, O_RDONLY | O_SYNC);
	if (0 > fd)
	{
		printf("open %s failed\n", path);
		return 0;
	}

	if(0 > fstat(fd, &statbuf))
	{
		printf("fstat %s failed\n", path);
		return 0;
	}
	close(fd);

	printf("path:%s size:%ld\n", path, statbuf.st_size);
	return statbuf.st_size;
}

int test_bar()
{
	bar0_length = get_file_size(bar0);
	bar1_length = get_file_size(bar1);
	bar1wc_length = get_file_size(bar1wc);
	bar2_length = get_file_size(bar2);
	bar0_fd   = open(bar0, O_RDWR);
	bar1_fd   = open(bar1, O_RDWR);
	bar1wc_fd = open(bar1wc, O_RDWR);
	bar2_fd   = open(bar2, O_RDWR);

	if (-1 == bar0_fd)
	{
		LOG("open bar0 failed");
	}
	if (-1 == bar1_fd)
	{
		LOG("open bar1 failed");
	}
	if (-1 == bar1wc_fd)
	{
		LOG("open bar1wc failed");
	}
	if (-1 == bar2_fd)
	{
		LOG("open bar2 failed");
	}

	bar0_mmap = mmap(NULL, bar0_length, PROT_READ | PROT_WRITE, MAP_SHARED, bar0_fd, 0);
	bar1_mmap = mmap(NULL, bar1_length, PROT_READ | PROT_WRITE, MAP_SHARED, bar1_fd, 0);
	bar1wc_mmap = mmap(NULL, bar1wc_length, PROT_READ | PROT_WRITE, MAP_SHARED, bar1wc_fd, 0);
	bar2_mmap = mmap(NULL, bar2_length, PROT_READ | PROT_WRITE, MAP_SHARED, bar2_fd, 0);

	if (! bar0_mmap)
	{
		LOG("bar0_mmap failed");
	}
	if (! bar1_mmap)
	{
		LOG("bar1_mmap failed");
	}
	if (! bar1wc_mmap)
	{
		LOG("bar1wc_mmap failed");
	}
	if (! bar2_mmap)
	{
		LOG("bar2_mmap failed");
	}

	bar0_phys = get_bar_phys_addr(0);
	bar1_phys = get_bar_phys_addr(1);
	bar2_phys = get_bar_phys_addr(2);

	//ioaddr = pci_resource_start(pdev, 0); Region 0: I/O ports at d000 [size=256]
	void *ioaddr = bar0_mmap;
	//Region 1: Memory at f0000000 (32-bit, prefetchable) [size=32K]
	void *iomem = bar1_mmap;

// crash
#if 0
	//ioaddr 0xea
	u8 regval = 0;

	//bar0_phys 0xd001
	unsigned int offset = ((bar0_phys & 0xFFFFFFF0) % 0x1000);
	regval = ~(1<<0);
	memcpy(ioaddr + offset, &regval, sizeof(regval));
	sleep(1);

	regval = ~(1<<1);
	memcpy(ioaddr + offset, &regval, sizeof(regval));
	sleep(1);

	regval = ~(1<<2);
	memcpy(ioaddr + offset, &regval, sizeof(regval));
	sleep(1);

	regval = ~(1<<3);
	memcpy(ioaddr + offset, &regval, sizeof(regval));
	sleep(1);

#endif

#if 1
	u8 regval = 0;

	//bar1_phys 0xd001
	unsigned int offset = ((bar1_phys & 0xFFFFFFF0) % 0x1000);
	memcpy(&regval, iomem + offset, sizeof(regval));
	LOG("offset:0x%x", offset);
	LOG("regval:0x%x", regval);
#endif

	//clean
	close(bar0_fd);
	close(bar1_fd);
	close(bar1wc_fd);
	close(bar2_fd);
	return 0;
}

int main()
{
	return test_bar();
}

編寫 pci-e 內核驅動

//#define DEBUG

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fcntl.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/ioport.h>
#include <linux/irq.h>
#include <linux/kernel.h>
#include <linux/list.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/pci.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/types.h>
#include <linux/uaccess.h>
#include <linux/wait.h>
#include <asm/io.h>
#include <asm/signal.h>
#include <linux/delay.h>

#define CH36X_MAX_NUM  16
#define CH36X_DRV_NAME "new_ch36xpci"

#define CH365_VID 0x4348 // Vendor id
#define CH365_DID 0x5049 // Device id

#define CH368_VID     0x1C00 // Vendor id
#define CH368_DID     0x5834 // Device id
#define CH368_SUB_VID 0x1C00 // Subsystem Vendor id
#define CH368_SUB_DID 0x5834 // Subsystem Device id

struct ch36x_dev {
	struct pci_dev *ch36x_pdev;
	struct cdev cdev;
	dev_t ch36x_dev;
	unsigned long ioaddr;
	unsigned long iolen;
	void __iomem *memaddr;
	unsigned long memlen;
	int irq;
	char dev_file_name[20];
	struct mutex io_mutex;
	struct fasync_struct *fasync;
	u8 spimode;
};

/* global varibles */
static struct class *ch36x_class = NULL;
static int ch36x_major = 0;
static long unsigned int mem_addr = 0;

static struct pci_device_id ch36x_id_table[] = {
	{ PCI_DEVICE(CH365_VID, CH365_DID) },
	{ PCI_DEVICE_SUB(CH368_VID, CH368_DID, CH368_SUB_VID, CH368_SUB_DID) },
	{}
};

MODULE_DEVICE_TABLE(pci, ch36x_id_table);

static void ReadConfig(struct pci_dev * pdev)
{
#ifdef DEBUG
	int i;
	u8 valb;
	u16 valw;
	u32 valdw;
	unsigned long reg_base, reg_len;

	return ;

	/* Read PCI configuration space */標準PCI 配置寄存器
	dev_info(&pdev->dev, "PCI Configuration Space:\n");
	for(i = 0; i < 0x40; i++)
	{
		pci_read_config_byte(pdev, i, &valb);
		dev_info(&pdev->dev, "0x%x ", valb);
		if((i % 0x10) == 0xf)
			dev_info(&pdev->dev, "\n");
	}
	dev_info(&pdev->dev, "\n");
	/* Now read each element - one at a time */

	/* Read Vendor ID */
	pci_read_config_word(pdev, PCI_VENDOR_ID, &valw);
	dev_info(&pdev->dev, "Vendor ID: 0x%x, ", valw);

	/* Read Device ID */
	pci_read_config_word(pdev, PCI_DEVICE_ID, &valw);
	dev_info(&pdev->dev, "Device ID: 0x%x, ", valw);

	/* Read Command Register */
	pci_read_config_word(pdev, PCI_COMMAND, &valw);
	dev_info(&pdev->dev, "Cmd Reg: 0x%x, ", valw);

	/* Read Status Register */
	pci_read_config_word(pdev, PCI_STATUS, &valw);
	dev_info(&pdev->dev, "Stat Reg: 0x%x, ", valw);

	/* Read Revision ID */
	pci_read_config_byte(pdev, PCI_REVISION_ID, &valb);
	dev_info(&pdev->dev, "Revision ID: 0x%x, ", valb);

	/* Read Class Code */
	/*
	pci_read_config_dword(pdev, PCI_CLASS_PROG, &valdw);
	printk("Class Code: 0x%lx, ", valdw);
	valdw &= 0x00ffffff;
	printk("Class Code: 0x%lx, ", valdw);
	*/
	/* Read Reg-level Programming Interface */
	pci_read_config_byte(pdev, PCI_CLASS_PROG, &valb);
	dev_info(&pdev->dev, "Class Prog: 0x%x, ", valb);

	/* Read Device Class */
	pci_read_config_word(pdev, PCI_CLASS_DEVICE, &valw);
	dev_info(&pdev->dev, "Device Class: 0x%x, ", valw);

	/* Read Cache Line */
	pci_read_config_byte(pdev, PCI_CACHE_LINE_SIZE, &valb);
	dev_info(&pdev->dev, "Cache Line Size: 0x%x, ", valb);

	/* Read Latency Timer */
	pci_read_config_byte(pdev, PCI_LATENCY_TIMER, &valb);
	dev_info(&pdev->dev, "Latency Timer: 0x%x, ", valb);

	/* Read Header Type */
	pci_read_config_byte(pdev, PCI_HEADER_TYPE, &valb);
	dev_info(&pdev->dev, "Header Type: 0x%x, ", valb);

	/* Read BIST */
	pci_read_config_byte(pdev, PCI_BIST, &valb);
	dev_info(&pdev->dev, "BIST: 0x%x\n", valb);

	/* Read all 6 BAR registers */
	for(i = 0; i <= 5; i++)
	{
		/* Physical address & length */
		reg_base = pci_resource_start(pdev, i);
		reg_len = pci_resource_len(pdev, i);
		dev_info(&pdev->dev, "BAR%d: Addr:0x%lx Len:0x%lx,  ", i, reg_base, reg_len);

		/* Flags */
		if((pci_resource_flags(pdev, i) & IORESOURCE_MEM))
			dev_info(&pdev->dev, "Region is for memory\n");
		else if((pci_resource_flags(pdev, i) & IORESOURCE_IO))
			dev_info(&pdev->dev, "Region is for I/O\n");
	}
	dev_info(&pdev->dev, "\n");

	/* Read CIS Pointer */
	pci_read_config_dword(pdev, PCI_CARDBUS_CIS, &valdw);
	dev_info(&pdev->dev, "CardBus CIS Pointer: 0x%x, ", valdw);

	/* Read Subsystem Vendor ID */
	pci_read_config_word(pdev, PCI_SUBSYSTEM_VENDOR_ID, &valw);
	dev_info(&pdev->dev, "Subsystem Vendor ID: 0x%x, ", valw);

	/* Read Subsystem Device ID */
	pci_read_config_word(pdev, PCI_SUBSYSTEM_ID, &valw);
	dev_info(&pdev->dev, "Subsystem Device ID: 0x%x\n", valw);

	/* Read Expansion ROM Base Address */
	pci_read_config_dword(pdev, PCI_ROM_ADDRESS, &valdw);
	dev_info(&pdev->dev, "Expansion ROM Base Address: 0x%x\n", valdw);

	/* Read IRQ Line */
	pci_read_config_byte(pdev, PCI_INTERRUPT_LINE, &valb);
	dev_info(&pdev->dev, "IRQ Line: 0x%x, ", valb);

	/* Read IRQ Pin */
	pci_read_config_byte(pdev, PCI_INTERRUPT_PIN, &valb);
	dev_info(&pdev->dev, "IRQ Pin: 0x%x, ", valb);

	/* Read Min Gnt */
	pci_read_config_byte(pdev, PCI_MIN_GNT, &valb);
	dev_info(&pdev->dev, "Min Gnt: 0x%x, ", valb);

	/* Read Max Lat */
	pci_read_config_byte(pdev, PCI_MAX_LAT, &valb);
	dev_info(&pdev->dev, "Max Lat: 0x%x\n", valb);
#endif
}

static void ch36x_unmap_device(struct pci_dev *pdev, struct ch36x_dev *ch36x_dev)
{
	pci_iounmap(pdev, ch36x_dev->memaddr);
}

static int ch36x_map_device(struct pci_dev *pdev, struct ch36x_dev *ch36x_dev)
{
	int ret = -ENOMEM;

	ch36x_dev->iolen = pci_resource_len(pdev, 0);
	ch36x_dev->memlen = pci_resource_len(pdev, 1);
	/* map the memory mapped i/o registers */
	ch36x_dev->ioaddr = pci_resource_start(pdev, 0);
	if (!ch36x_dev->ioaddr) {
		dev_err(&pdev->dev, "Error mapping io\n");
		goto out;
	}
	ch36x_dev->memaddr = pci_iomap(pdev, 1, ch36x_dev->memlen);
	if (ch36x_dev->memaddr == NULL) {
		dev_err(&pdev->dev, "Error mapping mem\n");
		goto out;
	}
	ch36x_dev->irq = pdev->irq;

	//mmap phys
	mem_addr = virt_to_phys(kmalloc(1024, GFP_KERNEL));
	mem_addr = mem_addr >> PAGE_SHIFT;

#ifdef DEBUG
	dev_info(&pdev->dev, "ch36x map succeed.\n");
	dev_vdbg(&pdev->dev, "***********I/O Port**********\n");
	dev_vdbg(&pdev->dev, "phy addr: 0x%lx\n", (unsigned long)pci_resource_start(pdev, 0));
	dev_vdbg(&pdev->dev, "io len: %ld\n", ch36x_dev->iolen);
	dev_vdbg(&pdev->dev, "***********I/O Memory**********\n");
	dev_vdbg(&pdev->dev, "phy addr: 0x%lx\n", (unsigned long)pci_resource_start(pdev, 1));
	dev_vdbg(&pdev->dev, "mapped addr: 0x%lx\n", (unsigned long)ch36x_dev->memaddr);
	dev_vdbg(&pdev->dev, "mem len: %ld\n", ch36x_dev->memlen);
	dev_info(&pdev->dev, "irq number is: %d", ch36x_dev->irq);
#endif

	//test
	{
		#if 0 //控制 LED 燈亮滅
		//IO port LED ctrl
		int i = 3;
		while(i--)
		{
			outb(~0x0, ch36x_dev->ioaddr + 0);
			msleep(500);
			outb(~(1<<1), ch36x_dev->ioaddr + 0);
			msleep(500);
			outb(~(1<<2), ch36x_dev->ioaddr + 0);
			msleep(500);
			outb(~(1<<3), ch36x_dev->ioaddr + 0);
			msleep(500);
		}
		#endif

		#if 1 //讀取 MMIO 總線
		char reg = ioread8(ch36x_dev->memaddr + 0);
		dev_info(&pdev->dev, "read mem 0 reg:0x%x\n", reg);
		#endif
	}

	return 0;
out:
	return ret;
}

static irqreturn_t ch36x_isr(int irq, void *dev_id)
{
#if 0
	unsigned char intval;
	struct ch36x_dev *ch36x_dev = (struct ch36x_dev *)dev_id;

	dev_vdbg(&ch36x_dev->ch36x_pdev->dev, "%s occurs\n", __func__);

	{
		intval = inb(ch36x_dev->ioaddr + offsetof(mCH367_IO_REG, mCH367status));
		switch (ch36x_dev->intmode) {
		case INT_RISING:
		case INT_FALLING:
			if (!(intval & CH367_MICSR_INTA_BIT))
				return IRQ_NONE;
			break;
		case INT_HIGH:
			if (!(intval & CH367_MICSR_INTS_BIT))
				return IRQ_NONE;
			break;
		case INT_LOW:
			if (intval & CH367_MICSR_INTS_BIT)
				return IRQ_NONE;
			break;
		default:
			return IRQ_NONE;
		}
	}
	kill_fasync(&ch36x_dev->fasync, SIGIO, POLL_IN);

	/* interrupt status clear */
	{
		outb(intval & ~CH367_MICSR_INTA_BIT, ch36x_dev->ioaddr + offsetof(mCH367_IO_REG, mCH367status));
	}
#endif
	return IRQ_HANDLED;
}

static int ch36x_mmap(struct file *f, struct vm_area_struct *vma)
{
	printk("new_ch36xpci mmap\n");
	vm_flags_set(vma, VM_IO);
	vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
	if (remap_pfn_range(vma,//虛擬內存區域,即設備地址將要映射到這裏  
	vma->vm_start,//虛擬空間的起始地址  
	mem_addr,//與物理內存對應的頁幀號,物理地址右移12位  
	vma->vm_end - vma->vm_start,//映射區域大小,一般是頁大小的整數倍  
	vma->vm_page_prot))//保護屬性,  
	{
		printk("new_ch36xpci mmap failed\n");
		return -EAGAIN;
	}
	return 0;
}

static const struct file_operations ch36x_fops = {
	.owner = THIS_MODULE,
	.mmap  = ch36x_mmap,
	// .open = ch36x_fops_open,
	// .release = ch36x_fops_release,
	// .read = ch36x_fops_read,
	// .write = ch36x_fops_write,
	// .unlocked_ioctl = ch36x_fops_ioctl,
	// .fasync = ch36x_fops_fasync,
};

static int ch36x_pci_probe(struct pci_dev *pdev, const struct pci_device_id *ent)
{
	int retval = -ENOMEM;
	struct ch36x_dev *ch36x_dev = NULL;
	struct device *dev;

	dev_info(&pdev->dev, "%s\n", __func__);
	ch36x_dev = kzalloc(sizeof(*ch36x_dev), GFP_KERNEL);
	if (!ch36x_dev)
		goto out;

	ch36x_dev->ch36x_pdev = pdev;

	dev_info(&pdev->dev, "pdev->device:0x%x\n", pdev->device);

	retval = pci_enable_device(pdev);
	if (retval)
		goto free;

	pci_set_master(pdev);

	retval = pci_request_regions(pdev, CH36X_DRV_NAME);
	if (retval)
		goto disable;

	mutex_init(&ch36x_dev->io_mutex);

	retval = ch36x_map_device(pdev, ch36x_dev);
	if (retval)
		goto free_regions;

	ReadConfig(pdev);

	pci_set_drvdata(pdev, ch36x_dev);
	sprintf(ch36x_dev->dev_file_name, "%s%c", CH36X_DRV_NAME, '0');
	retval = request_irq(ch36x_dev->irq, ch36x_isr, IRQF_SHARED, ch36x_dev->dev_file_name, (void *)ch36x_dev);
	if (retval) {
		dev_err(&pdev->dev, "Could not request irq.\n");
		goto unmap;
	}

	cdev_init(&ch36x_dev->cdev, &ch36x_fops);
	ch36x_dev->cdev.owner = THIS_MODULE;
	ch36x_dev->ch36x_dev = MKDEV(ch36x_major, 0);
	retval = cdev_add(&ch36x_dev->cdev, ch36x_dev->ch36x_dev, 1);
	if (retval) {
		dev_err(&pdev->dev, "Could not add cdev\n");
		goto remove_isr;
	}

	dev = device_create(ch36x_class, &pdev->dev, ch36x_dev->ch36x_dev, NULL, "%s", ch36x_dev->dev_file_name);
	if (IS_ERR(dev))
		dev_err(&pdev->dev, "Could not create device node.\n");

	dev_info(&pdev->dev, "%s ch36x_pci_probe function finshed.", __func__);

	return 0;

remove_isr:
	free_irq(pdev->irq, ch36x_dev);
unmap:
	ch36x_unmap_device(pdev, ch36x_dev);
free_regions:
	pci_release_regions(pdev);
disable:
	pci_disable_device(pdev);
free:
	kfree(ch36x_dev);
out:
	return retval;
}

static void ch36x_pci_remove(struct pci_dev *pdev)
{
	struct ch36x_dev *ch36x_dev = pci_get_drvdata(pdev);

	if (!ch36x_dev)
		return;

	dev_info(&pdev->dev, "%s", __func__);
	device_destroy(ch36x_class, ch36x_dev->ch36x_dev);
	cdev_del(&ch36x_dev->cdev);
	free_irq(pdev->irq, ch36x_dev);
	ch36x_unmap_device(pdev, ch36x_dev);
	pci_release_regions(pdev);
	kfree(ch36x_dev);
}

static struct pci_driver ch36x_pci_driver = {
	.name = CH36X_DRV_NAME,
	.id_table = ch36x_id_table,
	.probe = ch36x_pci_probe,
	.remove = ch36x_pci_remove,
};

static int __init ch36x_init(void)
{
	int error;
	dev_t dev;

	ch36x_class = class_create("ch36x_class");
	if (IS_ERR(ch36x_class)) {
		error = PTR_ERR(ch36x_class);
		goto out;
	}

	error = alloc_chrdev_region(&dev, 0, CH36X_MAX_NUM, CH36X_DRV_NAME);
	if (error)
		goto class_destroy;

	ch36x_major = MAJOR(dev);

	error = pci_register_driver(&ch36x_pci_driver);
	if (error)
		goto chr_remove;

	return 0;
chr_remove:
	unregister_chrdev_region(dev, CH36X_MAX_NUM);
class_destroy:
	class_destroy(ch36x_class);
out:
	return error;
}

static void __exit ch36x_exit(void)
{
	pci_unregister_driver(&ch36x_pci_driver);
	unregister_chrdev_region(MKDEV(ch36x_major, 0), CH36X_MAX_NUM);
	class_destroy(ch36x_class);
}

module_init(ch36x_init);
module_exit(ch36x_exit);
MODULE_LICENSE("GPL");

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章