QEMU EDU设备模拟PCI设备驱动编写
环境安装
buildroot编译
buildroot下载,编译:
下载版本:https://www.buildroot.org/downloads/buildroot-2022.02.2.tar.gz
下载完成后,解压:
$ tar -vxf buildroot-2022.02.2.tar.gz
$ cd buildroot-2022.02.2/
$ make qemu_aarch64_virt_defconfig
which: no g++ in (/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/arm-linaro-4-9-4/gcc-linaro-4.9.4-2017.01-x86_64_arm-linux-gnueabihf/bin:/root/bin)
## 可以看出,我的环境下没有g++,需要安装
$ yum install -y gcc-c++
# g++安装完成后, 重新make config
$ make qemu_aarch64_virt_defconfig
#
# configuration written to /home/grace/QEMU/buildroot-2022.02.2/.config
#
# 然后执行make
$ make
/bin/make -j1 O=/home/grace/QEMU/buildroot-2022.02.2/output HOSTCC="/bin/gcc" HOSTCXX="/bin/g++" syncconfig
make[1]: Entering directory `/home/grace/QEMU/buildroot-2022.02.2'
make[1]: Leaving directory `/home/grace/QEMU/buildroot-2022.02.2'
Your Perl installation is not complete enough; at least the following
modules are missing: Data::Dumper
ExtUtils::MakeMaker
Thread::Queue
# 遇到没有依赖的问题
make: *** [dependencies] Error 1
# 解决方式
$ yum install perl*
$ make
checking whether mknod can create fifo without root privileges... configure: error: in `/home/grace/QEMU/buildroot-2022.02.2/output/build/host-tar-1.34':
configure: error: you should not run configure as root (set FORCE_UNSAFE_CONFIGURE=1 in environment to bypass this check)
See `config.log' for more details
make: *** [/home/grace/QEMU/buildroot-2022.02.2/output/build/host-tar-1.34/.stamp_configured] Error 1
# 遇到问题
# 解决方式
$ export FORCE_UNSAFE_CONFIGURE=1
# 遇到问题
You have PERL_MM_OPT defined because Perl local::lib
is installed on your system. Please unset this variable
before starting Buildroot, otherwise the compilation of
Perl related packages will fail
# 解决方式
$ unset PERL_MM_OPT
# 编译完成
Filesystem UUID: 0504b844-dbbf-4a0e-8799-ff3114bfc2aa
Superblock backups stored on blocks:
8193, 24577, 40961, 57345 Allocating group tables: done
Writing inode tables: done
Creating journal (4096 blocks): done
Copying files into the device: done
Writing superblocks and filesystem accounting information: done ln -sf rootfs.ext2 /home/grace/QEMU/buildroot-2022.02.2/output/images/rootfs.ext4
>>> Executing post-image script board/qemu/post-image.sh
QEMU编译和安装
编译
$ ./configure --prefix=$PWD --target-list=aarch64-softmmu --enable-debug --enable-sdl --enable-kvm --enable-tools --disable-curl
ERROR: User requested feature sdl
configure was not able to find it.
Install SDL2-devel
$ ./configure --prefix=$PWD --target-list=aarch64-softmmu --enable-debug --enable-vnc --enable-kvm --enable-tools --disable-curl
ERROR: glib-2.40 gthread-2.0 is required to compile QEMU
$ yum install glib2-devel.x86_64
$ ./configure --prefix=$PWD --target-list=aarch64-softmmu --enable-debug --enable-vnc --enable-kvm --enable-tools --disable-curl
ERROR: zlib check failed
Make sure to have the zlib libs and headers installed.
[root@localhost] /home/grace/QEMU/qemu-4.1.1
$ yum install zlib-devel.x86_64
$ ./configure --prefix=$PWD --target-list=aarch64-softmmu --enable-debug --enable-vnc --enable-kvm --enable-tools --disable-curl
ERROR: pixman >= 0.21.8 not present.
Please install the pixman devel package.
$ yum install pixman-devel.x86_64
$ ./configure --prefix=$PWD --target-list=aarch64-softmmu --enable-debug --enable-vnc --enable-kvm --enable-tools --disable-curl
Install prefix /home/grace/QEMU/qemu-4.1.1
BIOS directory /home/grace/QEMU/qemu-4.1.1/share/qemu
firmware path /home/grace/QEMU/qemu-4.1.1/share/qemu-firmware
binary directory /home/grace/QEMU/qemu-4.1.1/bin
library directory /home/grace/QEMU/qemu-4.1.1/lib
module directory /home/grace/QEMU/qemu-4.1.1/lib/qemu
libexec directory /home/grace/QEMU/qemu-4.1.1/libexec
include directory /home/grace/QEMU/qemu-4.1.1/include
config directory /home/grace/QEMU/qemu-4.1.1/etc
local state directory /home/grace/QEMU/qemu-4.1.1/var
Manual directory /home/grace/QEMU/qemu-4.1.1/share/man
ELF interp prefix /usr/gnemul/qemu-%M
Source path /home/grace/QEMU/qemu-4.1.1
GIT binary git
GIT submodules
C compiler cc
Host C compiler cc
C++ compiler c++
Objective-C compiler cc
ARFLAGS rv
CFLAGS -g
QEMU_CFLAGS -I/usr/include/pixman-1 -I$(SRC_PATH)/dtc/libfdt -pthread -I/usr/include/glib-2.0 -I/usr/lib64/glib-2.0/include -fPIE -DPIE -m64 -mcx16 -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64 -D_LARGEFILE_SOURCE -Wstrict-prototypes -Wredundant-decls -Wall -Wundef -Wwrite-strings -Wmissing-prototypes -fno-strict-aliasing -fno-common -fwrapv -std=gnu99 -Wendif-labels -Wno-missing-include-dirs -Wempty-body -Wnested-externs -Wformat-security -Wformat-y2k -Winit-self -Wignored-qualifiers -Wold-style-declaration -Wold-style-definition -Wtype-limits -fstack-protector-strong -Wno-missing-braces -I$(SRC_PATH)/capstone/include
LDFLAGS -Wl,--warn-common -Wl,-z,relro -Wl,-z,now -pie -m64 -g
QEMU_LDFLAGS -L$(BUILD_DIR)/dtc/libfdt
make make
install install
python python -B (2.7.5)
slirp support internal
smbd /usr/sbin/smbd
module support no
host CPU x86_64
host big endian no
target list aarch64-softmmu
gprof enabled no
sparse enabled no
strip binaries no
profiler no
static build no
SDL support no
SDL image support no
GTK support no
GTK GL support no
VTE support no
TLS priority NORMAL
GNUTLS support no
libgcrypt no
nettle no
libtasn1 no
PAM no
iconv support yes
curses support yes
virgl support no
curl support no
mingw32 support no
Audio drivers oss
Block whitelist (rw)
Block whitelist (ro)
VirtFS support no
Multipath support no
VNC support yes
VNC SASL support no
VNC JPEG support no
VNC PNG support no
xen support no
brlapi support no
bluez support no
Documentation no
PIE yes
vde support no
netmap support no
Linux AIO support no
ATTR/XATTR support yes
Install blobs yes
KVM support yes
HAX support no
HVF support no
WHPX support no
TCG support yes
TCG debug enabled yes
TCG interpreter no
malloc trim support yes
RDMA support no
PVRDMA support no
fdt support git
membarrier no
preadv support yes
fdatasync yes
madvise yes
posix_madvise yes
posix_memalign yes
libcap-ng support no
vhost-net support yes
vhost-crypto support yes
vhost-scsi support yes
vhost-vsock support yes
vhost-user support yes
Trace backends log
spice support no
rbd support no
xfsctl support no
smartcard support no
libusb no
usb net redir no
OpenGL support no
OpenGL dmabufs no
libiscsi support no
libnfs support no
build guest agent yes
QGA VSS support no
QGA w32 disk info no
QGA MSI support no
seccomp support no
coroutine backend ucontext
coroutine pool yes
debug stack usage no
mutex debugging yes
crypto afalg no
GlusterFS support no
gcov gcov
gcov enabled no
TPM support yes
libssh support no
QOM debugging yes
Live block migration yes
lzo support no
snappy support no
bzip2 support no
lzfse support no
NUMA host support no
libxml2 no
tcmalloc support no
jemalloc support no
avx2 optimization yes
replication support yes
VxHS block device no
bochs support yes
cloop support yes
dmg support yes
qcow v1 support yes
vdi support yes
vvfat support yes
qed support yes
parallels support yes
sheepdog support yes
capstone internal
docker no
libpmem support no
libudev no
default devices yes
warning: Python 2 support is deprecated
warning: Python 3 will be required for building future versions of QEMU
当您在编译 QEMU 源代码时,使用 ./configure
命令配置选项可以定制您的构建。让我为您解释一下这些选项的含义:
--prefix=$PWD
:这个选项指定了安装目录的前缀。$PWD
表示当前工作目录,因此编译后的二进制文件将被安装到当前目录下。如果您希望将其安装到其他目录,可以更改这个选项的值。--target-list=aarch64-softmmu
:这个选项指定了要构建的目标架构。在这里,我们选择了aarch64
架构,以便生成支持 64 位 ARM 的二进制文件。-softmmu
表示我们正在构建模拟器,而不是用户态工具。--enable-debug
:启用调试支持。这将在生成的二进制文件中包含调试信息,以便您可以使用调试器进行故障排除。--enable-vnc
:启用 VNC 支持。这允许您通过 VNC 连接到 QEMU 模拟的虚拟机图形界面。--enable-kvm
:启用 KVM 支持。KVM 是 Linux 内核的一部分,它允许 QEMU 利用硬件虚拟化扩展来提高性能。--enable-tools
:启用其他工具的构建。这将生成一些额外的实用程序,例如qemu-img
和qemu-nbd
。--disable-curl
:禁用 libcurl 支持。libcurl 是一个用于处理 URL 的库,但在某些情况下您可能不需要它。通过禁用它,您可以减小生成的二进制文件的大小。
安装
$ make install
install -d -m 0755 "/home/grace/QEMU/qemu-4.1.1/share/qemu/keymaps"
set -e; for x in da en-gb et fr fr-ch is lt no pt-br sv ar de en-us fi fr-be hr it lv nl pl ru th de-ch es fo fr-ca hu ja mk pt sl tr bepo cz; do \
install -c -m 0644 /home/grace/QEMU/qemu-4.1.1/pc-bios/keymaps/$x "/home/grace/QEMU/qemu-4.1.1/share/qemu/keymaps"; \
done
install -c -m 0644 /home/grace/QEMU/qemu-4.1.1/trace-events-all "/home/grace/QEMU/qemu-4.1.1/share/qemu/trace-events-all"
然后新建个目录,将qemu编译出的文件和buildroot编译出的文件拷贝到新建目录中:
$ mkdir release
$ cd release/
$ mkdir qemu-arm64
# 拷贝QEUM编译出的文件到新建目录
$ cp qemu-4.1.1/libexec/ release/qemu-arm64/ -rf
$ cp qemu-4.1.1/share/ release/qemu-arm64/ -rf
# 拷贝buildroot编译的文件到新建目录
[root@localhost] /home/grace/QEMU/buildroot-2022.02.2
$ cd output/images/
$ cp * ../../../release/qemu-arm64/bin/ -ra
$ cd ../../../release/qemu-arm64/bin/
# 修改启动脚本, 然后运行
[grace@localhost] ~/QEMU/release/qemu-arm64/bin
$ more start-qemu-bak.sh
./qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -smp 1 -kernel Image -dtb qemu-my.dtb -append "rootwait root=/dev/vda console=ttyAMA0" -netdev user,id=eth0 -device virtio-net-de
vice,netdev=eth0 -drive file=rootfs.ext4,if=none,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 -nographic -device edu
# 我没找到qemu-my.dtb,就没使用
$ more start-qemu.sh
./qemu-system-aarch64 -M virt -cpu cortex-a53 -nographic -smp 1 -kernel Image -append "rootwait root=/dev/vda console=ttyAMA0" -netdev user,id=eth0 -device virtio-net-device,netdev=eth0
-drive file=rootfs.ext4,if=none,format=raw,id=hd0 -device virtio-blk-device,drive=hd0 -nographic -device edu
QEMU网络配置
方式一
参考官网:文档/网络/NAT - QEMU --- Documentation/Networking/NAT - QEMU
操作步骤:
首先,Linux主机的IP要配置为192.168.53.0这个网段,为了不修改官方脚本,比如我配置为了:192.168.53.128
安装
yum install bridge-utils iptables dnsmasq
编写qemu-ifup 脚本,将其保存到
/etc/qemu-ifup
,并确保该文件具有执行权限chmod 755 /etc/qemu-ifup
qemu-ifup如下:
#!/bin/sh
#
# Copyright IBM, Corp. 2010
#
# Authors:
# Anthony Liguori <aliguori@us.ibm.com>
#
# This work is licensed under the terms of the GNU GPL, version 2. See
# the COPYING file in the top-level directory. # Set to the name of your bridge
BRIDGE=br0 # Network information
NETWORK=192.168.53.0
NETMASK=255.255.255.0
GATEWAY=192.168.53.1
DHCPRANGE=192.168.53.2,192.168.53.254 # Optionally parameters to enable PXE support
TFTPROOT=
BOOTP= do_brctl() {
brctl "$@"
} do_ifconfig() {
ifconfig "$@"
} do_dd() {
dd "$@"
} do_iptables_restore() {
iptables-restore "$@"
} do_dnsmasq() {
dnsmasq "$@"
} check_bridge() {
if do_brctl show | grep "^$1" > /dev/null 2> /dev/null; then
return 1
else
return 0
fi
} create_bridge() {
do_brctl addbr "$1"
do_brctl stp "$1" off
do_brctl setfd "$1" 0
do_ifconfig "$1" "$GATEWAY" netmask "$NETMASK" up
} enable_ip_forward() {
echo 1 | do_dd of=/proc/sys/net/ipv4/ip_forward > /dev/null
} add_filter_rules() {
do_iptables_restore <<EOF
# Generated by iptables-save v1.3.6 on Fri Aug 24 15:20:25 2007
*nat
:PREROUTING ACCEPT [61:9671]
:POSTROUTING ACCEPT [121:7499]
:OUTPUT ACCEPT [132:8691]
-A POSTROUTING -s $NETWORK/$NETMASK -j MASQUERADE
COMMIT
# Completed on Fri Aug 24 15:20:25 2007
# Generated by iptables-save v1.3.6 on Fri Aug 24 15:20:25 2007
*filter
:INPUT ACCEPT [1453:976046]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [1605:194911]
-A INPUT -i $BRIDGE -p tcp -m tcp --dport 67 -j ACCEPT
-A INPUT -i $BRIDGE -p udp -m udp --dport 67 -j ACCEPT
-A INPUT -i $BRIDGE -p tcp -m tcp --dport 53 -j ACCEPT
-A INPUT -i $BRIDGE -p udp -m udp --dport 53 -j ACCEPT
-A FORWARD -i $1 -o $1 -j ACCEPT
-A FORWARD -s $NETWORK/$NETMASK -i $BRIDGE -j ACCEPT
-A FORWARD -d $NETWORK/$NETMASK -o $BRIDGE -m state --state RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -o $BRIDGE -j REJECT --reject-with icmp-port-unreachable
-A FORWARD -i $BRIDGE -j REJECT --reject-with icmp-port-unreachable
COMMIT
# Completed on Fri Aug 24 15:20:25 2007
EOF
} start_dnsmasq() {
do_dnsmasq \
--strict-order \
--except-interface=lo \
--interface=$BRIDGE \
--listen-address=$GATEWAY \
--bind-interfaces \
--dhcp-range=$DHCPRANGE \
--conf-file="" \
--pid-file=/var/run/qemu-dnsmasq-$BRIDGE.pid \
--dhcp-leasefile=/var/run/qemu-dnsmasq-$BRIDGE.leases \
--dhcp-no-override \
${TFTPROOT:+"--enable-tftp"} \
${TFTPROOT:+"--tftp-root=$TFTPROOT"} \
${BOOTP:+"--dhcp-boot=$BOOTP"}
} setup_bridge_nat() {
if check_bridge "$1" ; then
create_bridge "$1"
enable_ip_forward
add_filter_rules "$1"
start_dnsmasq "$1"
fi
} setup_bridge_vlan() {
if check_bridge "$1" ; then
create_bridge "$1"
start_dnsmasq "$1"
fi
} setup_bridge_nat "$BRIDGE" if test "$1" ; then
do_ifconfig "$1" 0.0.0.0 up
do_brctl addif "$BRIDGE" "$1"
fi
现在启动带有 tap networking 的 qemu,将您的访客配置为使用 DHCP。
$ more start-qemu.sh
./qemu-system-aarch64 \
-M virt \
-cpu cortex-a53 \
-nographic \
-smp 1 \
-kernel Image \
-append "rootwait root=/dev/vda console=ttyAMA0" \
-netdev user,id=eth0 \
-drive file=rootfs.ext4,if=none,format=raw,id=hd0 \
-device virtio-blk-device,drive=hd0 -nographic \
-device edu \
-net tap \
-net nic
遗留问题:
- QEMU无法ping通外网,比如百度??
- Linux主机无法使用SSH和Samba??
方式二
参考:QEMU 网络配置一把梭 | CataLpa's Site (wzt.ac.cn)
博客中的配置步骤:
首先安装如下软件
$ yum install bridge-utils iptables dnsmasq
添加网桥,大部分操作都需要 root 权限:
ifconfig <你的网卡名称(能上网的那张)> down # 首先关闭宿主机网卡接口
brctl addbr br0 # 添加名为 br0 的网桥
brctl addif br0 <你的网卡名称> # 在 br0 中添加一个接口
brctl stp br0 off # 如果只有一个网桥,则关闭生成树协议
brctl setfd br0 1 # 设置 br0 的转发延迟
brctl sethello br0 1 # 设置 br0 的 hello 时间
ifconfig br0 0.0.0.0 promisc up # 启用 br0 接口
ifconfig <你的网卡名称> 0.0.0.0 promisc up # 启用网卡接口
dhclient br0 # 从 dhcp 服务器获得 br0 的 IP 地址
brctl show br0 # 查看虚拟网桥列表
brctl showstp br0 # 查看 br0 的各接口信息
当配置完成之后执行 ifconfig 结果应该如下:
$ ifconfig
br0: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST> mtu 1500
inet 192.168.53.128 netmask 255.255.255.0 broadcast 192.168.53.255
inet6 fe80::20c:29ff:fe2b:ec35 prefixlen 64 scopeid 0x20<link>
ether 00:0c:29:2b:ec:35 txqueuelen 1000 (Ethernet)
RX packets 657 bytes 52987 (51.7 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 122 bytes 13309 (12.9 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 ens33: flags=4419<UP,BROADCAST,RUNNING,PROMISC,MULTICAST> mtu 1500
inet 192.168.53.128 netmask 255.255.255.0 broadcast 192.168.53.255
ether 00:0c:29:2b:ec:35 txqueuelen 1000 (Ethernet)
RX packets 477 bytes 49868 (48.6 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 518 bytes 55515 (54.2 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0 lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
inet6 ::1 prefixlen 128 scopeid 0x10<host>
loop txqueuelen 1 (Local Loopback)
RX packets 12 bytes 1184 (1.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 12 bytes 1184 (1.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
此时网桥已经得到了 IP,并且能够连接网络的网卡 enp0s5 也加入了网桥,此时我们的网桥状态大致是这种情况:
桥的一端连接到 enp0s5,我们只需要再把另一端接到 QEMU 虚拟机(准确的说是 VLAN )上面就可以了。
创建一个 TAP 设备,作为 QEMU 一端的接口:
tunctl -t tap0 -u root # 创建一个 tap0 接口,只允许 root 用户访问
brctl addif br0 tap0 # 在虚拟网桥中增加一个 tap0 接口
ifconfig tap0 0.0.0.0 promisc up # 启用 tap0 接口
brctl showstp br0 # 显示 br0 的各个接口
此时网桥的信息应该是:
这样就相当于把两张网卡通过网桥连起来了:
现在只需要启动镜像,指定网络连接模式是 TAP 即可。
总的来说,就是需要执行下面的命令:
$ yum install bridge-utils iptables dnsmasq
# 配置命令, ens33是Linux主机的网卡名
ifconfig ens33 down
brctl addbr br0
brctl addif br0 ens33
brctl stp br0 off
brctl setfd br0 1
brctl sethello br0 1
ifconfig br0 0.0.0.0 promisc up
ifconfig ens33 0.0.0.0 promisc up
dhclient br0
brctl show br0
brctl showstp br0
tunctl -t tap0 -u root
brctl addif br0 tap0
ifconfig tap0 0.0.0.0 promisc up
brctl showstp br0
遇到的问题,安装tunctl失败,解决方式:https://www.jianshu.com/p/0c13a00104ac
创建配置文件:
vim /etc/yum.repos.d/nux-misc.repo
,配置文件中的信息如下:[nux-misc]
name=Nux Misc
baseurl=http://li.nux.ro/download/nux/misc/el7/x86_64/
enabled=0
gpgcheck=1
gpgkey=http://li.nux.ro/download/nux/RPM-GPG-KEY-nux.ro
安装tunctl软件包:
yum --enablerepo=nux-misc install tunctl
启动QEMU时,增加下面的参数:
-net nic -net tap,ifname=tap0,script=no,downscript=no
- -net nic 表示希望 QEMU 在虚拟机中创建一张虚拟网卡
- -net tap 表示连接类型为 TAP,并且指定了网卡接口名称(就是刚才创建的 tap0,相当于把虚拟机接入网桥)。
- script 和 downscript 两个选项的作用是告诉 QEMU 在启动系统的时候是否调用脚本自动配置网络环境,如果这两个选项为空,那么 QEMU 启动和退出时会自动选择第一个不存在的 tap 接口(通常是 tap0)为参数,调用脚本 /etc/qemu-ifup 和 /etc/qemu-ifdown。由于我们已经配置完毕,所以这两个参数设置为 no 即可。
疑问:
- 按照上述的方式配置之后,QEMU可以ping通百度和笔记本,但是不能ping通虚拟机(虚拟机中安装的Linux,再在Linux中运行的QEMU)。
- Linux主机无法使用ssh和samba?
- 解决方式:???
测试
pcie edu介绍
官网介绍:EDU device — QEMU documentation
简介:EDU Device是用于编写(内核)驱动程序的教育设备。它的初衷是支持在马萨里克大学教授的 Linux 内核讲座。学生将获得此虚拟设备,并应编写具有 I/O、IRQ、DMA 等的驱动程序。
PCI ID: 1234:11e8
PCI Region 0: I/O 内存,大小为 1 MB。用户应该通过此内存与卡进行通信。
相关寄存器:
- 0x00 (RO):identification,返回值格式:0xRRrr00ed, -
RR
– 主要版本 -rr
– 次要版本 - 0x04 (RW):卡活体检查,对写入的值取反,并返回
- 0x08 (RW):因子计算,写入一个数值,返回该数值的阶乘
- 0x20 (RW):状态寄存器,其低1位为只读,记录设备是否在进行阶乘操作,完成则为0,否则为1;其从低向高第8位为 读写,记录设备是否在完成阶乘操作后发起中断,发起则为 1,否则为 0。
- 0x24 (RO):中断状态寄存器,记录中断状态,
0x00000001
为阶乘中断,0x00000100
为 DMA 中断。 - 0x60 (WO):触发中断寄存器,引发中断,中断状态将放入中断状态寄存器(使用按位 OR)。
- 0x64(WO):用于清除中断,将
0x24
寄存器清零并阻止设备继续发出中断。
pcie测试
测试程序分成两部分,内核态和用户态:
- 内核态:完成两部分事情,1)创建一个字符设备和用户态进行通讯,使用了ioctl和mmap操作函数;2)注册pci设备驱动,当探测到edu设备时,获取Bar空间信息,并注册中断处理函数。
- 用户态:完成两部分事情,1)用户态程序在初始化的时候使用ioctl获取到EDU Bar空间信息,然后使用mmap进行映射,这样就可以在用户空间访问EDU Bar空间寄存器;2)创建一个线程,用户接收内核态edu的中断,并注册一个中断回调函数。
内核态代码
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include "edu.h"
#define EDU_PRINTK(level, format, arg...) printk(level "[Kernel: %s - %d] " format, __FUNCTION__, __LINE__, ##arg)
#define EDU_ERR(format, arg...) EDU_PRINTK(KERN_ERR, format, ##arg)
#define EDU_INFO(format, arg...) EDU_PRINTK(KERN_INFO, format, ##arg)
/* 定义 PCI 设备 ID */
#define EDU_DEVICE_VENDOR_ID 0x1234 /* Vendor ID */
#define EDU_DEVICE_DEVICE_ID 0x11e8 /* Device ID */
/* 定义 character device 的名称以及类名 */
#define EDU_DEVICE_NAME "edu"
#define EDU_CLASS_NAME "edu"
/* EDU 寄存器读写操作 */
#define EDU_READ_REG(addr) readl(g_edu_dev->ioaddr + (addr))
#define EDU_WRITE_REG(addr, data) writel((data), g_edu_dev->ioaddr + (addr))
struct edu_ioctl {
uint64_t start; /* pci bar0起始地址 */
uint64_t end; /* pci bar0结束地址 */
uint64_t len; /* pci bar0空间大小 */
};
/* EDU设备管理结构体 */
struct edu_pci_dev {
struct pci_dev *dev;
void __iomem *ioaddr; /* 映射后的地址 */
uint64_t start; /* pci bar0起始地址 */
uint64_t end; /* pci bar0结束地址 */
uint64_t len; /* pci bar0空间大小 */
int irq; /* edu设备中断号 */
/* character device 所需的内容 */
int chr_major;
struct class *chr_class;
struct device *chr_device;
wait_queue_head_t irq_wq; /* 等待队列,用于阻塞EDU_WAIT_IRQ请求 */
atomic_t irq_handled; /* 用于阻塞用户态中断请求,作为条件变量存在 */
};
/* 创建一个字符设备, 和用户空间进行通讯 */
static int edu_mmap(struct file *filp, struct vm_area_struct *vma);
static long edu_ioctl(struct file *file, unsigned int cmd, unsigned long arg);
static long edu_compat_ioctl(struct file *f, unsigned int cmd, unsigned long arg);
static struct file_operations g_edu_fops = {
.owner = THIS_MODULE,
.mmap = edu_mmap,
.unlocked_ioctl = edu_ioctl,
.compat_ioctl = edu_compat_ioctl,
};
static struct edu_pci_dev *g_edu_dev;
// 定义 PCI 设备表
static const struct pci_device_id edu_table[] = {
{PCI_DEVICE(EDU_DEVICE_VENDOR_ID, EDU_DEVICE_DEVICE_ID)},
{0},
};
// 定义 PCI 驱动
static int edu_probe(struct pci_dev *dev, const struct pci_device_id *id);
static void edu_remove(struct pci_dev *dev);
static struct pci_driver g_edu_driver = {
.name = "edu",
.id_table = edu_table,
.probe = edu_probe,
.remove = edu_remove,
};
static int edu_mmap(struct file *filp, struct vm_area_struct *vma)
{
size_t size = vma->vm_end - vma->vm_start;
phys_addr_t offset = (phys_addr_t)vma->vm_pgoff << PAGE_SHIFT;
if ((offset >> PAGE_SHIFT) != vma->vm_pgoff) {
return -EINVAL;
}
if ((offset + (phys_addr_t)size - 1) < offset) {
return -EINVAL;
}
if (!pfn_valid(vma->vm_pgoff)) {
vma->vm_page_prot = phys_mem_access_prot(filp, vma->vm_pgoff, size, vma->vm_page_prot);
}
if (remap_pfn_range(vma, vma->vm_start, vma->vm_pgoff, size, vma->vm_page_prot) != 0) {
return -EAGAIN;
}
return 0;
}
static long edu_compat_ioctl(struct file *f, unsigned int cmd, unsigned long arg)
{
return edu_ioctl(f, cmd, (unsigned long)((void __user *)(unsigned long)(arg)));
}
static long edu_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct edu_ioctl ioctl;
switch (cmd) {
case EDU_WAIT_IRQ:
EDU_INFO("Edu live reg 0x%x\n", EDU_READ_REG(IO_DEV_CARD_LIVENESS));
EDU_INFO("Edu ioctl wait irq!\n");
wait_event_interruptible(g_edu_dev->irq_wq, atomic_read(&g_edu_dev->irq_handled) != 0);
atomic_set(&g_edu_dev->irq_handled, 0);
break;
case EDU_ENABLE_IRQ:
EDU_INFO("Edu ioctl enable irq!\n");
EDU_WRITE_REG(IO_DEV_STATUS, 0x80);
EDU_INFO("Edu ioctl: IO_DEV_STATUS 0x%x\n", EDU_READ_REG(IO_DEV_STATUS));
break;
case EDU_GET_BAR_INFO:
EDU_INFO("Edu get bar information!\n");
ioctl.start = g_edu_dev->start;
ioctl.end = g_edu_dev->end;
ioctl.len = g_edu_dev->len;
break;
default:
return -EINVAL;
}
if (copy_to_user((void *)arg, &ioctl, sizeof(struct edu_ioctl))) {
return -1;
}
return 0;
}
// 定义中断处理函数
static irqreturn_t edu_irq_handler(int irq, void *dev)
{
uint32_t value = 0;
uint32_t irq_status = 0;
/* 关闭中断 */
EDU_WRITE_REG(IO_DEV_STATUS, 0x0);
EDU_INFO("edu: IO_DEV_STATUS 0x%x\n", EDU_READ_REG(IO_DEV_STATUS));
/* 读取中断状态 */
irq_status = EDU_READ_REG(IO_DEV_IRQ_STATUS);
EDU_INFO("edu: IRQ %d triggered, %d\n", irq, irq_status);
/* 将中断状态写入中断确认寄存器,清除中断
* TODO? 不清楚为什么不能放到用户态执行 (现象: 放到用户态会一直上报中断)
*/
EDU_WRITE_REG(IO_DEV_IRQ_ACK, irq_status);
/* 读取阶乘结果 */
value = EDU_READ_REG(IO_DEV_VALUE);
EDU_INFO("edu: value read from device: 0x%x\n", value);
/* 唤醒用户态中断处理线程 */
atomic_set(&g_edu_dev->irq_handled, 1);
wake_up_interruptible(&g_edu_dev->irq_wq);
return IRQ_HANDLED;
}
// 定义 PCI 设备的 probe 函数
static int edu_probe(struct pci_dev *dev, const struct pci_device_id *id)
{
int retval = 0;
EDU_INFO("Irq num: %d\n", dev->irq);
EDU_INFO("Vendor id: 0x%x\n", dev->vendor);
EDU_INFO("Device id: 0x%x\n", dev->device);
// 首先打开设备
if (pci_enable_device(dev)) {
EDU_ERR("edu: Cannot enable PCI device\n");
retval = -EIO;
goto out_edu_all;
}
// 复制设备的中断号到结构体并检查
g_edu_dev->irq = dev->irq;
if (g_edu_dev->irq < 0) {
EDU_ERR("edu: Invalid IRQ number\n");
goto out_edu_all;
}
// 请求设备的内存区域
retval = pci_request_regions(dev, "edu");
if (retval) {
EDU_ERR("edu: Cannot request regions\n");
goto out_edu_all;
}
g_edu_dev->start = pci_resource_start(dev, 0);
g_edu_dev->end = pci_resource_end(dev, 0);
g_edu_dev->len = pci_resource_len(dev, 0);
EDU_INFO("Bar0 address start: 0x%llx\n", g_edu_dev->start);
EDU_INFO("Bar0 address end: 0x%llx\n", g_edu_dev->end);
EDU_INFO("Bar0 address size: 0x%llx\n", g_edu_dev->len);
// 映射设备的内存区域
g_edu_dev->ioaddr = pci_ioremap_bar(dev, 0);
if (!g_edu_dev->ioaddr) {
EDU_ERR("edu: Cannot map device memory\n");
retval = -ENOMEM;
goto out_regions;
}
EDU_INFO("Bar0 ioaddr: 0x%px\n", g_edu_dev->ioaddr);
// 设置中断处理函数
retval = request_irq(dev->irq, edu_irq_handler, IRQF_SHARED, "edu", g_edu_dev);
if (retval) {
EDU_ERR("edu: Cannot set up IRQ handler\n");
goto out_ioremap;
}
// 启用设备的中断发起
EDU_WRITE_REG(IO_DEV_STATUS, 0x80);
g_edu_dev->dev = dev;
return 0;
out_ioremap:
pci_iounmap(dev, g_edu_dev->ioaddr);
out_regions:
pci_release_regions(dev);
out_edu_all:
return retval;
}
// 定义 PCI 设备的 remove 函数
static void edu_remove(struct pci_dev *dev)
{
// 释放中断
free_irq(g_edu_dev->irq, g_edu_dev);
// 释放内存区域
pci_iounmap(dev, g_edu_dev->ioaddr);
pci_release_regions(dev);
// 停用设备
pci_disable_device(dev);
EDU_INFO("edu remove done!\n");
}
// 驱动的初始化函数与卸载函数
static int __init edu_init(void)
{
int32_t retval = 0;
/* 为EDU设备结构体分配内存 */
g_edu_dev = kmalloc(sizeof(struct edu_pci_dev), GFP_KERNEL);
if (!g_edu_dev) {
EDU_ERR("edu: Cannot allocate memory for the device\n");
return -ENOMEM;
}
/* 先注册一个字符设备 */
g_edu_dev->chr_major = register_chrdev(0, EDU_DEVICE_NAME, &g_edu_fops);
if (g_edu_dev->chr_major < 0) {
EDU_ERR("edu: Cannot register char device\n");
goto out_edu_all;
}
g_edu_dev->chr_class = class_create(THIS_MODULE, EDU_CLASS_NAME);
if (IS_ERR(g_edu_dev->chr_class)) {
EDU_ERR("edu: Cannot create class\n");
goto out_edu_chr_device;
}
g_edu_dev->chr_device = device_create(g_edu_dev->chr_class, NULL, MKDEV(g_edu_dev->chr_major, 0), NULL, EDU_DEVICE_NAME);
if (IS_ERR(g_edu_dev->chr_device)) {
EDU_ERR("edu: Cannot create device\n");
goto out_edu_class;
}
/* pci device register */
retval = pci_register_driver(&g_edu_driver);
if (retval) {
EDU_ERR("pci_register_driver fail!\n");
goto out_pci_register;
}
/* 初始化等待队列 */
init_waitqueue_head(&g_edu_dev->irq_wq);
atomic_set(&g_edu_dev->irq_handled, 0);
return retval;
out_pci_register:
device_destroy(g_edu_dev->chr_class, MKDEV(g_edu_dev->chr_major, 0));
out_edu_class:
class_destroy(g_edu_dev->chr_class);
out_edu_chr_device:
unregister_chrdev(g_edu_dev->chr_major, EDU_DEVICE_NAME);
out_edu_all:
kfree(g_edu_dev);
return retval;
}
static void __exit edu_exit(void)
{
/* 字符设备注销 */
device_destroy(g_edu_dev->chr_class, MKDEV(g_edu_dev->chr_major, 0));
class_destroy(g_edu_dev->chr_class);
unregister_chrdev(g_edu_dev->chr_major, EDU_DEVICE_NAME);
/* pci注销 */
pci_unregister_driver(&g_edu_driver);
kfree(g_edu_dev);
EDU_INFO("edu ko exit done!\n");
}
// 注册驱动的初始化函数与卸载函数
module_init(edu_init);
module_exit(edu_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MrLayfolk");
MODULE_DESCRIPTION("edu driver");
MODULE_VERSION("1.0.0");
用户态代码
#include <stdio.h>
#include <string.h>
#include <stdint.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <pthread.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include "edu.h"
static int g_edu_filep = -1;
static void *g_edu_bar_vaddr = NULL; /* Bar空间虚拟地址 */
struct edu_ioctl {
uint64_t start; /* pci bar0起始地址 */
uint64_t end; /* pci bar0结束地址 */
uint64_t len; /* pci bar0空间大小 */
};
int edu_read_reg(uint32_t addr)
{
volatile uint32_t *vaddr32 = NULL;
vaddr32 = (uint32_t *)((uint8_t *)g_edu_bar_vaddr + addr);
return *vaddr32;
}
void edu_write_reg(uint32_t addr, uint32_t data)
{
volatile uint32_t *vaddr32 = NULL;
vaddr32 = (uint32_t *)((uint8_t *)g_edu_bar_vaddr + addr);
*vaddr32 = data;
}
void print_edu_regs(void)
{
printf("IO_DEV_CARD_ID (0x00) = 0x%x\n", edu_read_reg(IO_DEV_CARD_ID));
printf("IO_DEV_CARD_LIVENESS (0x04) = 0x%x\n", edu_read_reg(IO_DEV_CARD_LIVENESS));
printf("IO_DEV_VALUE (0x08) = 0x%x\n", edu_read_reg(IO_DEV_VALUE));
printf("IO_DEV_STATUS (0x20) = 0x%x\n", edu_read_reg(IO_DEV_STATUS));
printf("IO_DEV_IRQ_STATUS (0x24) = 0x%x\n", edu_read_reg(IO_DEV_IRQ_STATUS));
printf("IO_DEV_IRQ_ACK (0x64) = 0x%x\n", edu_read_reg(IO_DEV_IRQ_ACK));
}
void *edu_mmap(uint32_t size, off_t target, uint64_t *v_addr)
{
void *map_base = NULL;
void *virt_addr = NULL;
unsigned page_size = 0;
unsigned mapped_size = 0;
unsigned offset_in_page = 0;
mapped_size = page_size = getpagesize();
offset_in_page = (unsigned)target & (page_size - 1);
if (offset_in_page + size > page_size) {
mapped_size = ((offset_in_page + size) / page_size) * page_size;
if ((offset_in_page + size) % page_size) {
mapped_size += page_size;
}
}
map_base = mmap(NULL, mapped_size, (PROT_READ | PROT_WRITE), MAP_SHARED,
g_edu_filep, target & ~(off_t)(page_size - 1));
if (map_base == MAP_FAILED) {
printf("mmap target addr 0x%x failed.\n", target);
return MAP_FAILED;
}
virt_addr = (char *)map_base + offset_in_page;
return virt_addr;
}
void edu_irq_handler(void)
{
printf("irq handler in userspace start ... \n");
print_edu_regs();
printf("irq handler in userspace done ... \n");
}
void *edu_irq_thread_fn(void *arg)
{
printf("Thread Running\n");
/* 内核态:
* 1. 使能中断, 等待中断产生;
* 2. 中断产生后, 关闭中断, 清除中断, 唤醒用户态中断线程;
* 用户态:
* 1. 初始化请求中断, 等待中断产生;
* 2. 中断产生后, 进行相关处理, 然后使能中断
*/
while (1) {
/* 等待中断 */
ioctl(g_edu_filep, EDU_WAIT_IRQ);
/* 中断处理函数 */
edu_irq_handler();
/* 使能中断 */
ioctl(g_edu_filep, EDU_ENABLE_IRQ);
}
}
int main(int argc, char **argv)
{
int ret = 0;
struct edu_ioctl edu_ioctl = {0};
pthread_t thread_id;
g_edu_filep = open("/dev/edu", O_RDWR);
if (g_edu_filep < 0) {
printf("open %s failed!\n", "/dev/edu");
}
/* 获取pci bar信息 */
ioctl(g_edu_filep, EDU_GET_BAR_INFO, &edu_ioctl);
printf("Bar0 address start: 0x%llx\n", edu_ioctl.start);
printf("Bar0 address end: 0x%llx\n", edu_ioctl.end);
printf("Bar0 address size: 0x%llx\n", edu_ioctl.len);
g_edu_bar_vaddr = edu_mmap(edu_ioctl.len, (off_t)edu_ioctl.start, NULL);
printf("Bar0 vaddr: 0x%p\n", g_edu_bar_vaddr);
/* 寄存器读写测试 */
print_edu_regs();
edu_write_reg(IO_DEV_CARD_LIVENESS, 0x2);
print_edu_regs();
/* 创建中断处理线程 */
if (pthread_create(&thread_id, NULL, edu_irq_thread_fn, NULL) != 0) {
printf("Failed to create thread\n");
return 1;
}
/* 等待线程结束 */
pthread_join(thread_id, NULL);
return 0;
}
内核态用态态公共头文件
#ifndef EDU_H
#define EDU_H
#include <linux/ioctl.h>
/* 定义要用到的寄存器偏移量:
- 0x04 (RW):卡活体检查,对写入的值取反,并返回
- 0x08 (RW):因子计算,写入一个数值,返回该数值的阶乘
- 0x20 (RW):状态寄存器,其低1位为只读,记录设备是否在进行阶乘操作,完成则为0,否则为1;
其从低向高第8位为读写,记录设备是否在完成阶乘操作后发起中断,发起则为1,否则为0。
- 0x24 (RO):中断状态寄存器,记录中断状态,0x00000001为阶乘中断,0x00000100为 DMA 中断。
- 0x60 (WO):触发中断寄存器,引发中断,中断状态将放入中断状态寄存器(使用按位 OR)。
- 0x64 (WO):用于清除中断,将0x24寄存器清零并阻止设备继续发出中断。
*/
#define IO_DEV_CARD_ID 0x00
#define IO_DEV_CARD_LIVENESS 0x04
#define IO_DEV_VALUE 0x08
#define IO_DEV_STATUS 0x20
#define IO_DEV_IRQ_STATUS 0x24
#define IO_DEV_IRQ_ACK 0x64
/* 定义 ioctl 的操作号 */
#define EDU_IOCTL_CMD_MAGIC 'e'
#define EDU_WAIT_IRQ _IO(EDU_IOCTL_CMD_MAGIC, 1) /* 等待EDU中断 */
#define EDU_ENABLE_IRQ _IO(EDU_IOCTL_CMD_MAGIC, 2) /* 使能EDU中断 */
#define EDU_GET_BAR_INFO _IO(EDU_IOCTL_CMD_MAGIC, 3) /* 获取EDU Bar信息 */
#endif
Makefile
# 指定交叉编译工具链的路径
CROSS_COMPILE := /home/grace/output/host/bin/aarch64-buildroot-linux-uclibc-
# 指定内核源码路径
KERNEL_DIR := /home/grace/output/build/linux-5.15.18/
# 指定要编译的模块文件名(例如:my_module.ko)
obj-m := edu.o
# 编译规则
all:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) ARCH=arm64 CROSS_COMPILE=$(CROSS_COMPILE) modules
$(CROSS_COMPILE)gcc -o edu edu_user.c
# 清理规则
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean
rm -rf edu
测试代码:
#include <linux/module.h>
static int __init edu_init(void)
{
printk(KERN_INFO "edu: Hello, World!\n");
return 0;
}
static void __exit edu_exit(void)
{
printk(KERN_INFO "edu: Goodbye, World!\n");
}
module_init(edu_init);
module_exit(edu_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("SpartaEN <i@evo.moe>");
MODULE_DESCRIPTION("edu driver");
MODULE_VERSION("0.0.1");
测试
QEMU上电启动后,先使用lspci命令查看是否有edu设备:
# lspci -vvv
00:01.0 Class 0200: 1af4:1000
00:00.0 Class 0600: 1b36:0008
00:02.0 Class 00ff: 1234:11e8
可以看到,有edu设备,但是-vvv
参数并没有打印出详细信息,这是由于我使用buildroot编译的工具裁剪了,可以自己编译一个lspci工具,具体方式参考下一个章节。
使用自己编译的工具查看,可以看到详细的信息:
# ./lspci -s 00:02.0 -vv
00:02.0 Class 00ff: Device 1234:11e8 (rev 10)
Subsystem: Device 1af4:1100
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-
Interrupt: pin A routed to IRQ 0
Region 0: Memory at 10000000 (32-bit, non-prefetchable) [disabled] [size=1M]
Capabilities: [40] MSI: Enable- Count=1/1 Maskable- 64bit+
Address: 0000000000000000 Data: 0000
./lspci -s 00:02.0 -mm
00:02.0 "Class 00ff" "Vendor 1234" "Device 11e8" -r10 -p00 "Unknown vendor 1af4" "Device 1100"
测试步骤:
插入KO,可看出中断号为53,Bar0起始地址为0x10000000,Bar0空间大小为0x100000B(1MB)
# insmod edu.ko
edu: loading out-of-tree module taints kernel.
[Kernel: edu_probe - 181] Irq num: 53
[Kernel: edu_probe - 182] Vendor id: 0x1234
[Kernel: edu_probe - 183] Device id: 0x11e8
edu 0000:00:02.0: enabling device (0000 -> 0002)
[Kernel: edu_probe - 209] Bar0 address start: 0x10000000
[Kernel: edu_probe - 210] Bar0 address end: 0x100fffff
[Kernel: edu_probe - 211] Bar0 address size: 0x100000
[Kernel: edu_probe - 221] Bar0 ioaddr: 0xffffffc008e00000
执行用户态程序,以背景线程方式运行:
# ./edu &
# [Kernel: edu_ioctl - 130] Edu get bar information!
Bar0 address start: 0x10000000
Bar0 address end: 0x100fffff
Bar0 address size: 0x100000
Bar0 vaddr: 0x0x7fa3307000 # mmap映射后的虚拟地址
IO_DEV_CARD_ID (0x00) = 0x10000ed
IO_DEV_CARD_LIVENESS (0x04) = 0xffffffff
IO_DEV_VALUE (0x08) = 0x0
IO_DEV_STATUS (0x20) = 0x80
IO_DEV_IRQ_STATUS (0x24) = 0x0
IO_DEV_IRQ_ACK (0x64) = 0xffffffff
# 更改IO_DEV_CARD_LIVENESS后的值
IO_DEV_CARD_ID (0x00) = 0x10000ed
IO_DEV_CARD_LIVENESS (0x04) = 0xfffffffd
IO_DEV_VALUE (0x08) = 0x0
IO_DEV_STATUS (0x20) = 0x80
IO_DEV_IRQ_STATUS (0x24) = 0x0
IO_DEV_IRQ_ACK (0x64) = 0xffffffff
Thread Running
[Kernel: edu_ioctl - 119] Edu live reg 0xfffffffd
[Kernel: edu_ioctl - 120] Edu ioctl wait irq!
使用devmem命令模拟中断发生,可以看出内核态接收到了中断,并上报到了内核态,然后用户态中断线程继续等待中断到来。
# devmem 0x10000008 32 2
# [Kernel: edu_irq_handler - 154] edu: IO_DEV_STATUS 0x0
[Kernel: edu_irq_handler - 158] edu: IRQ 53 triggered, 1
[Kernel: edu_irq_handler - 167] edu: value read from device: 0x2
irq handler in userspace start ...
IO_DEV_CARD_ID (0x00) = 0x10000ed
IO_DEV_CARD_LIVENESS (0x04) = 0xfffffffd
IO_DEV_VALUE (0x08) = 0x2
IO_DEV_STATUS (0x20) = 0x0
IO_DEV_IRQ_STATUS (0x24) = 0x0
IO_DEV_IRQ_ACK (0x64) = 0xffffffff
irq handler in userspace done ...
[Kernel: edu_ioctl - 125] Edu ioctl enable irq!
[Kernel: edu_ioctl - 127] Edu ioctl: IO_DEV_STATUS 0x80
[Kernel: edu_ioctl - 119] Edu live reg 0xfffffffd
[Kernel: edu_ioctl - 120] Edu ioctl wait irq!
devmem使用说明:
Usage: devmem ADDRESS [WIDTH [VALUE]] Read/write from physical address ADDRESS Address to act upon
WIDTH Width (8/16/...)
VALUE Data to be written
lspci工具编译
编译参考:pciutils交叉编译 - 只道寻常 | Blog (copyright1999.github.io)
lspci使用的详细命令可参考:lspci Command: What Is It and How to Use It {7 Examples} (phoenixnap.com)
步骤:
下载源码,我下载的是:pciutils-3.12.0 (linuxfromscratch.org)
修改顶层Makefile,需要修改两个变量:
# Host OS and release (override if you are cross-compiling)
HOST=arm-linux
CROSS_COMPILE=/home/grace/output/host/bin/aarch64-buildroot-linux-uclibc-
编译,直接执行
make
编译完成后,生成的
lspci
工具就在顶层目录$ file lspci
lspci: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked (uses shared libs), not stripped
使用lspci查看edu设备详细信息,可看出插入KO前后的变化:
Mem-
变成了Mem+
:表示可以进行Bar空间访问了- 中断号变成了32
Region 0
状态由[disabled]
变成了(32-bit, non-prefetchable)
# ./lspci -s 00:02.0 -vvvvvv
00:02.0 Class 00ff: Device 1234:11e8 (rev 10)
Subsystem: Device 1af4:1100
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-
Interrupt: pin A routed to IRQ 0
Region 0: Memory at 10000000 (32-bit, non-prefetchable) [disabled] [size=1M]
Capabilities: [40] MSI: Enable- Count=1/1 Maskable- 64bit+
Address: 0000000000000000 Data: 0000
#
# insmod edu.ko
edu: loading out-of-tree module taints kernel.
[Kernel: edu_probe - 181] Irq num: 53
[Kernel: edu_probe - 182] Vendor id: 0x1234
[Kernel: edu_probe - 183] Device id: 0x11e8
edu 0000:00:02.0: enabling device (0000 -> 0002)
[Kernel: edu_probe - 209] Bar0 address start: 0x10000000
[Kernel: edu_probe - 210] Bar0 address end: 0x100fffff
[Kernel: edu_probe - 211] Bar0 address size: 0x100000
[Kernel: edu_probe - 221] Bar0 ioaddr: 0xffffffc008e00000
#
# ./lspci -s 00:02.0 -vvvvvv
00:02.0 Class 00ff: Device 1234:11e8 (rev 10)
Subsystem: Device 1af4:1100
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-
Interrupt: pin A routed to IRQ 53
Region 0: Memory at 10000000 (32-bit, non-prefetchable) [size=1M]
Capabilities: [40] MSI: Enable- Count=1/1 Maskable- 64bit+
Address: 0000000000000000 Data: 0000
Kernel driver in use: edu
QEMU EDU设备模拟PCI设备驱动编写的更多相关文章
- 【DPDK】谈谈DPDK如何实现bypass内核的原理 其一 PCI设备与UIO驱动
[前言] 随着网络的高速发展,对网络的性能要求也越来越高,DPDK框架是目前的一种加速网络IO的解决方案之一,也是最为流行的一套方案.DPDK通过bypass内核协议栈与内核驱动,将驱动的工作从内核态 ...
- Linux PCI设备驱动的实现思路与思想
概述 1.PCI设备一般都具有双重身份,一方面作为PCI设备注册到Linux内核,另一方面,作为字符设备或者块设备,或者网络设备注册到Linux内核,所以,在看PCI设备时一定要注意到这点. 2. 一 ...
- 2.3 PCI桥与PCI设备的配置空间
PCI设备都有独立的配置空间,HOST主桥通过配置读写总线事务访问这段空间.PCI总线规定了三种类型的PCI配置空间,分别是PCI Agent设备使用的配置空间,PCI桥使用的配置空间和Cardbus ...
- 3.1 PCI设备BAR空间的初始化
在PCI Agent设备进行数据传送之前,系统软件需要初始化PCI Agent设备的BAR0~5寄存器和PCI桥的Base.Limit寄存器.系统软件使用DFS算法对PCI总线进行遍历时,完成这些寄存 ...
- 3.2 PCI设备的数据传递
PCI设备的数据传递使用地址译码方式,当一个存储器读写总线事务到达PCI总线时,在这条总线上的所有PCI设备将进行地址译码,如果当前总线事务使用的地址在某个PCI设备的BAR空间中时,该PCI设备将使 ...
- 008 PCI设备BAR空间的初始化
一.PCI设备BAR空间的初始化 在PCI Agent设备进行数据传送之前,系统软件需要初始化PCI Agent设备的BAR0~5寄存器和PCI桥的Base.Limit寄存器.系统软件使用DFS算法对 ...
- Linux PCI网卡驱动的详细分析
学习应该是一个先把问题简单化,在把问题复杂化的过程.一开始就着手处理复杂的问题,难免让人有心惊胆颤,捉襟见肘的感觉.读Linux网卡驱动也是一 样.那长长的源码夹杂着那些我们陌生的变量和符号,望而生畏 ...
- Linux I2C设备驱动编写(二)
在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_driver.i2c_client.三者的关系也在上一节进行了描述.应该已经算是对Linux I2C子系统有了初步 ...
- Linux I2C设备驱动编写(一)
在Linux驱动中I2C系统中主要包含以下几个成员: I2C adapter 即I2C适配器 I2C driver 某个I2C设备的设备驱动,可以以driver理解. I2C client 某个I2C ...
- 【转】Linux I2C设备驱动编写(二)
原文网址:http://www.cnblogs.com/biglucky/p/4059582.html 在(一)中简述了Linux I2C子系统的三个主要成员i2c_adapter.i2c_drive ...
随机推荐
- iOS企业证书开发的APP证书过期时间监控
大家都知道iOS的企业证书开发的APP,证书都是一年有效期,满一年得新建证书重新打包,否则无法继续使用. 我们一个企业账号下有几十个APP,一个个去看也很麻烦--搞个监控呗!!! 写个脚本放Jenki ...
- 力扣61(java&python)-旋转链表(中等)
题目: 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置. 示例1: 输入:head = [1,2,3,4,5], k = 2 输出:[4,5,1,2,3] 示例2: 输 ...
- 【ModelScope】5分钟让你在大火的多模态领域权威榜单VQA上超越人类
简介: ModelScope上开源了达摩院众多业界最强多模态模型,其中就有首超人类的多模态视觉问答模型mPLUG,小编从页面体验(一探).开发体验(二探).开放测试(三探)来探究多模态预训练模型能力. ...
- SpringBoot Admin2.0 集成 Java 诊断神器 Arthas 实践
简介: 项目最初使用 Arthas 主要有两个目的: 1. 通过 arthas 解决实现测试环境.性能测试环境以及生产环境性能问题分析工具的问题. 2. 通过使用 jad.mc.redefine 功能 ...
- ARMv9刷屏 —— 号称十年最大变革,Realm机密计算技术有什么亮点?
简介: 让我们看下ARMv9机密计算相关的新特性Realm. ARMv9的新闻刷屏了.ARMv9号称十年以来最重大变革,因此让我们看下ARMv9中机密计算相关的新特性Realm.(注:本文是对I ...
- 漫画 | 一口气搞懂 Serverless !
简介: 第二届云原生编程挑战赛为热爱技术的年轻人提供一个挑战世界级技术问题的舞台,希望用技术为全社会创造更大价值. 作者 | 刘欣 呃,我可能是别人眼中所说的不用奋斗的一代. 大家喜欢听的什么多姿多 ...
- LlamaIndex 是什么
LlamaIndex 是一个基于 LLM(大语言模型)的应用程序数据框架,适用于受益于上下文增强的场景. 这类 LLM 系统被称为 RAG(检索增强生成)系统. LlamaIndex 提供了必要的抽象 ...
- Docker镜像基本原理
前言 Docker系列文章: 如果没有安装过Docker请参考本文最后部分,大家从现在开始一定要按照我做的Demo都手敲一遍,印象会更加深刻的,加油! 为什么学习Docker Docker基本概念 什 ...
- OSI模型之网络层
一.简介 网络层是OSI参考模型中的第三层,同时也是TCP/IP模型的第二层.它介于传输层和数据链路层之间,主要任务是把分组从源端传到目的端,为分组交换网上的不同主机提供通信服务.网络层传输单位是数据 ...
- SAP集成技术(十一)SAP混合集成平台
愿景 SAP产品之间实现无缝集成还需要一些时间,目前可能还存在一些技术挑战或者需要进一步的开发工作,以便在未来能够轻松地把所有SAP产品整合在一起.让SAP产品能够顺利地与非SAP的解决方案连接,这也 ...