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");

posted @ 2024-03-02 10:38  宁次  阅读(67)  评论(0编辑  收藏  举报