帧缓冲驱动与网络接口卡驱动详解
帧缓冲驱动
帧缓冲驱动是Linux图形驱动中较为简单的一种,它对硬件进行了高度抽象,所需的实现工作较少。
驱动注册与移除
在编写帧缓冲驱动时,需要进行注册和移除操作。注册时,使用
register_framebuffer(info)
函数将驱动注册到内核,并调用
hardware_enable_controller(my_private_struct)
来启用硬件控制器。示例代码如下:
/* Register with the kernel */
ret = register_framebuffer(info);
hardware_enable_controller(my_private_struct);
return 0;
移除驱动时,需要释放
probe()
函数中获取的资源,执行
iounmap()
和
release_mem_region()
操作,反向处理DMA,禁用硬件控制器,先注销帧缓冲,再释放内存。示例代码如下:
static int myfb_remove(struct platform_device *pdev)
{
/* iounmap() memory and release_mem_region() */
[...]
/* Reverse DMA, dma_free_*();*/
[...]
hardware_disable_controller(fbi);
/* first unregister, */
unregister_framebuffer(info);
/* and then free the memory */
framebuffer_release(info);
return 0;
}
如果使用资源分配的管理版本,只需使用
unregister_framebuffer()
和
framebuffer_release()
,其他操作由内核完成。
详细的
fb_ops
操作
fb_ops
结构中定义了一些钩子函数,用于处理帧缓冲的各种操作。
-
检查信息
:
fb_ops->fb_check_var
钩子函数用于检查帧缓冲参数,并将其调整为有效值。示例代码如下:
static int myfb_check_var(struct fb_var_screeninfo *var,
struct fb_info *info)
{
if (var->xres_virtual < var->xres)
var->xres_virtual = var->xres;
if (var->yres_virtual < var->yres)
var->yres_virtual = var->yres;
if ((var->bits_per_pixel != 32) &&
(var->bits_per_pixel != 24) &&
(var->bits_per_pixel != 16) &&
(var->bits_per_pixel != 12) &&
(var->bits_per_pixel != 8))
var->bits_per_pixel = 16;
switch (var->bits_per_pixel) {
case 8:
/* Adjust red*/
var->red.length = 3;
var->red.offset = 5;
var->red.msb_right = 0;
/*adjust green*/
var->green.length = 3;
var->green.offset = 2;
var->green.msb_right = 0;
/* adjust blue */
var->blue.length = 2;
var->blue.offset = 0;
var->blue.msb_right = 0;
/* Adjust transparency */
var->transp.length = 0;
var->transp.offset = 0;
var->transp.msb_right = 0;
break;
case 16:
[...]
break;
case 24:
[...]
break;
case 32:
var->red.length = 8;
var->red.offset = 16;
var->red.msb_right = 0;
var->green.length = 8;
var->green.offset = 8;
var->green.msb_right = 0;
var->blue.length = 8;
var->blue.offset = 0;
var->blue.msb_right = 0;
var->transp.length = 8;
var->transp.offset = 24;
var->transp.msb_right = 0;
break;
}
/*
* Any other field in *var* can be adjusted
* like var->xres, var->yres, var->bits_per_pixel,
* var->pixclock and so on.
*/
return 0;
}
-
设置控制器参数
:
fp_ops->fb_set_par钩子函数用于将参数发送到硬件,根据用户设置(info->var)对硬件进行编程。示例代码如下:
static int myfb_set_par(struct fb_info *info)
{
struct fb_var_screeninfo *var = &info->var;
/* Make some compute or other sanity check */
[...]
/*
* This function writes value to the hardware,
* in the appropriate registers
*/
set_controller_vars(var, info);
return 0;
}
-
屏幕消隐
:
fb_ops->fb_blank钩子函数用于处理屏幕消隐操作。blank_mode参数有以下几种取值:
| 模式 | 说明 |
| ---- | ---- |
|FB_BLANK_UNBLANK| 屏幕未消隐,水平同步和垂直同步开启 |
|FB_BLANK_NORMAL| 屏幕消隐,水平同步和垂直同步开启 |
|FB_BLANK_VSYNC_SUSPEND| 屏幕消隐,水平同步开启,垂直同步关闭 |
|FB_BLANK_HSYNC_SUSPEND| 屏幕消隐,水平同步关闭,垂直同步开启 |
|FB_BLANK_POWERDOWN| 屏幕消隐,水平同步和垂直同步关闭 |
示例代码如下:
static int myfb_blank(int blank_mode, struct fb_info *info)
{
pr_debug("fb_blank: blank=%d\n", blank);
switch (blank) {
case FB_BLANK_POWERDOWN:
case FB_BLANK_VSYNC_SUSPEND:
case FB_BLANK_HSYNC_SUSPEND:
case FB_BLANK_NORMAL:
myfb_disable_controller(fbi);
break;
case FB_BLANK_UNBLANK:
myfb_enable_controller(fbi);
break;
}
return 0;
}
消隐操作应禁用控制器、停止时钟并关闭电源,取消消隐则执行相反操作。
-
加速方法
:用户的视频操作,如混合、拉伸、移动位图或动态渐变生成等,需要图形加速来获得可接受的性能。可以使用
struct fp_ops结构中的以下字段实现帧缓冲加速方法: -
.fb_imageblit():在显示器上绘制图像。 -
.fb_copyarea():将矩形区域从一个屏幕区域复制到另一个区域。 -
.fb_fillrect():以优化方式用像素线填充矩形。
如果帧缓冲控制器没有硬件加速机制,需要使用内核通用例程填充这些方法,如
cfb_imageblit()
、
cfb_copyarea()
和
cfb_fillrect()
。
编写帧缓冲驱动的步骤
编写帧缓冲驱动需要完成以下步骤:
1. 填充
struct fb_var_screeninfo
结构,提供帧缓冲可变属性信息,这些属性可由用户空间更改。
2. 填充
struct fb_fix_screeninfo
结构,提供固定参数。
3. 设置
struct fb_ops
结构,提供必要的回调函数,供帧缓冲子系统响应用户操作。
4. 如果设备支持,在
struct fb_ops
结构中提供加速函数回调。
5. 设置
struct fb_info
结构,将前面步骤填充的结构作为参数传入,并调用
register_framebuffer()
将其注册到内核。
可以参考
drivers/video/fbdev/vfb.c
编写简单的帧缓冲驱动,通过
CONFIG_FB_VIRTUAL
选项在内核中启用。
从用户空间访问帧缓冲
通常使用
mmap()
命令将帧缓冲内存映射到系统RAM的一部分,这样在屏幕上绘制像素就变成了简单的内存赋值操作。使用
ioctl
命令(如
FBIOGET_VSCREENINFO
和
FBIOGET_FSCREENINFO
)提取屏幕参数。完整列表可在内核源码的
include/uapi/linux/fb.h
中找到。
以下是在帧缓冲上绘制一个300*300正方形的示例代码:
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#define FBCTL(_fd, _cmd, _arg) \
if(ioctl(_fd, _cmd, _arg) == -1) { \
ERROR("ioctl failed"); \
exit(1); }
int main()
{
int fd;
int x, y, pos;
int r, g, b;
unsigned short color;
void *fbmem;
struct fb_var_screeninfo var_info;
struct fb_fix_screeninfo fix_info;
fd = open(FBVIDEO, O_RDWR);
if (tfd == -1 || vfd == -1) {
exit(-1);
}
/* Gather variable screen info (virtual and visible) */
FBCTL(fd, FBIOGET_VSCREENINFO, &var_info);
/* Gather fixed screen info */
FBCTL(fd, FBIOGET_FSCREENINFO, &fix_info);
printf("****** Frame Buffer Info ******\n");
printf("Visible: %d,%d \nvirtual: %d,%d \n line_len %d\n",
var_info.xres, this->var_info.yres,
var_info.xres_virtual, var_info.yres_virtual,
fix_info.line_length);
printf("dim %d,%d\n\n", var_info.width, var_info.height);
/* Let's mmap frame buffer memory */
fbmem = mmap(0, v_var.yres_virtual * v_fix.line_length, \
PROT_WRITE | PROT_READ, \
MAP_SHARED, fd, 0);
if (fbmem == MAP_FAILED) {
perror("Video or Text frame buffer mmap failed");
exit(1);
}
/* upper left corner (100,100). The square is 300px width */
for (y = 100; y < 400; y++) {
for (x = 100; x < 400; x++) {
pos = (x + vinfo.xoffset) * (vinfo.bits_per_pixel / 8)
+ (y + vinfo.yoffset) * finfo.line_length;
/* if 32 bits per pixel */
if (vinfo.bits_per_pixel == 32) {
/* We prepare some blue color */
*(fbmem + pos) = 100;
/* adding a little green */
*(fbmem + pos + 1) = 15+(x-100)/2;
/* With lot of read */
*(fbmem + pos + 2) = 200-(y-100)/5;
/* And no transparency */
*(fbmem + pos + 3) = 0;
} else { /* This assume 16bpp */
r = 31-(y-100)/16;
g = (x-100)/6;
b = 10;
/* Compute color */
color = r << 11 | g << 5 | b;
*((unsigned short int*)(fbmem + pos)) = color;
}
}
}
munmap(fbp, screensize);
close(fbfd);
return 0;
}
还可以使用
cat
或
dd
命令将帧缓冲内存转储为原始图像,例如:
# cat /dev/fb0 > my_image
将图像写回帧缓冲:
# cat my_image > /dev/fb0
可以通过
/sys/class/graphics/fb<N>/blank
文件控制屏幕消隐和取消消隐,写入
1
消隐屏幕,写入
0
取消消隐。例如:
# echo 0 > /sys/class/graphics/fb0/blank
# echo 1 > /sys/class/graphics/fb0/blank
网络接口卡驱动
网络是Linux内核的重要组成部分,Linux为开发者提供了API,用于编写网络设备驱动或进行内核网络开发。
驱动数据结构
处理网络接口卡(NIC)设备时,需要使用两个重要的数据结构:
-
struct sk_buff
:定义在
include/linux/skbuff.h
中,是Linux网络代码中的基本数据结构,用于处理发送和接收的每个数据包。
struct sk_buff {
struct sk_buff * next;
struct sk_buff * prev;
ktime_t tstamp;
struct rb_node rbnode; /* used in netem & tcp stack */
struct sock * sk;
struct net_device * dev;
unsigned int len;
unsigned int data_len;
__u16 mac_len;
__u16 hdr_len;
unsigned int len;
unsigned int data_len;
__u16 mac_len;
__u16 hdr_len;
__u32 priority;
dma_cookie_t dma_cookie;
sk_buff_data_t tail;
sk_buff_data_t end;
unsigned char * head;
unsigned char * data;
unsigned int truesize;
atomic_t users;
};
-
struct net_device:定义在include/linux/netdevice.h中,代表内核中的任何NIC设备,是数据传输的接口。
struct net_device {
char name[IFNAMSIZ];
char *ifalias;
unsigned long mem_end;
unsigned long mem_start;
unsigned long base_addr;
int irq;
netdev_features_t features;
netdev_features_t hw_features;
netdev_features_t wanted_features;
int ifindex;
struct net_device_stats stats;
atomic_long_t rx_dropped;
atomic_long_t tx_dropped;
const struct net_device_ops *netdev_ops;
const struct ethtool_ops *ethtool_ops;
unsigned int flags;
unsigned int priv_flags;
unsigned char link_mode;
unsigned char if_port;
unsigned char dma;
unsigned int mtu;
unsigned short type;
/* Interface address info. */
unsigned char perm_addr[MAX_ADDR_LEN];
unsigned char addr_assign_type;
unsigned char addr_len;
unsigned short neigh_priv_len;
unsigned short dev_id;
unsigned short dev_port;
unsigned long last_rx;
/* Interface address info used in eth_type_trans() */
unsigned char *dev_addr;
struct device dev;
struct phy_device *phydev;
};
此外,还需要包含
include/linux/etherdevice.h
(用于MAC和以太网相关函数)和
include/linux/ethtool.h
(用于ethtool支持)。
套接字缓冲区结构
struct sk_buff
结构的部分元素说明如下:
| 元素 | 说明 |
| ---- | ---- |
|
next
和
prev
| 列表中前后的缓冲区 |
|
sk
| 与数据包关联的套接字 |
|
tstamp
| 数据包到达或离开的时间 |
|
rbnode
| 红黑树表示的替代结构 |
|
dev
| 数据包到达或离开的设备 |
|
len
| 数据包的总字节数 |
|
mac_len
| MAC头部的长度 |
|
csum
| 数据包的校验和 |
|
priority
| 数据包在QoS中的优先级 |
|
truesize
| 数据包占用的系统内存字节数,包括
struct sk_buff
结构本身 |
|
users
| 用于SKB对象的引用计数 |
|
head
、
data
和
tail
| 指向套接字缓冲区不同区域的指针 |
|
end
| 指向套接字缓冲区的末尾 |
完整描述可在
include/linux/skbuff.h
中找到。
套接字缓冲区分配
套接字缓冲区的分配需要至少三个不同的函数:
1. 使用
netdev_alloc_skb()
函数分配足够大的内存,以包含数据包和以太网头部。
struct sk_buff *netdev_alloc_skb(struct net_device *dev,
unsigned int length)
-
使用
skb_reserve()函数增加并对齐头部空间。
void skb_reserve(struct sk_buff *skb, int len)
-
使用
skb_put()函数扩展缓冲区的使用数据区域,使其与数据包大小相同。
unsigned char *skb_put(struct sk_buff *skb, unsigned int len)
分配的套接字缓冲区应使用
netif_rx_ni()
函数转发到内核网络层:
int netif_rx_ni(struct sk_buff *skb)
以下是套接字缓冲区分配过程的mermaid流程图:
graph TD;
A[开始] --> B[使用netdev_alloc_skb()分配内存];
B --> C[使用skb_reserve()预留对齐内存];
C --> D[使用skb_put()扩展数据区域];
D --> E[使用netif_rx_ni()转发到内核网络层];
E --> F[结束];
网络接口结构
网络接口在内核中由
struct net_device
结构表示,使用
alloc_etherdev()
函数分配NIC:
struct net_device *alloc_etherdev(int sizeof_priv);
该函数失败时返回
NULL
,
sizeof_priv
参数表示为附加到该NIC的私有数据结构分配的内存大小,可以使用
netdev_priv()
函数提取该私有数据:
void *netdev_priv(const struct net_device *dev)
综上所述,帧缓冲驱动和网络接口卡驱动在Linux系统中都有着重要的作用。帧缓冲驱动为图形显示提供了基础,而网络接口卡驱动则保障了网络通信的正常进行。通过深入理解这些驱动的原理和实现方法,可以更好地开发和优化相关的应用程序。
帧缓冲驱动与网络接口卡驱动详解(续)
网络接口卡驱动(续)
NIC 驱动架构与方法描述
NIC 驱动的主要工作包括数据包的传输和接收。下面详细介绍其架构和相关方法。
-
数据包传输
:当需要发送数据包时,驱动程序会将数据包封装在
struct sk_buff结构中,并通过网络设备的操作接口(struct net_device_ops)中的函数将数据包发送出去。通常,这个过程涉及到硬件的配置和数据的写入。例如,在某些网卡驱动中,可能需要将数据包的数据复制到网卡的发送缓冲区,并设置相应的寄存器来触发发送操作。 -
数据包接收
:当网卡接收到数据包时,会产生中断信号通知内核。内核会调用驱动程序中注册的中断处理函数,该函数会从网卡的接收缓冲区中读取数据包,并将其封装成
struct sk_buff结构,然后通过netif_rx_ni()函数将数据包传递给内核网络层进行进一步处理。
以下是一个简单的数据包接收流程的 mermaid 流程图:
graph TD;
A[网卡接收到数据包] --> B[产生中断信号];
B --> C[内核调用中断处理函数];
C --> D[从接收缓冲区读取数据包];
D --> E[封装成 struct sk_buff 结构];
E --> F[使用 netif_rx_ni() 传递给内核网络层];
F --> G[结束];
开发测试用的虚拟 NIC 驱动
为了测试网络驱动的功能,可以开发一个虚拟的 NIC 驱动。下面是一个简单的虚拟 NIC 驱动的示例代码:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <linux/etherdevice.h>
static struct net_device *dummy_dev;
static int dummy_open(struct net_device *dev)
{
netif_start_queue(dev);
return 0;
}
static int dummy_stop(struct net_device *dev)
{
netif_stop_queue(dev);
return 0;
}
static netdev_tx_t dummy_xmit(struct sk_buff *skb, struct net_device *dev)
{
dev_kfree_skb(skb);
return NETDEV_TX_OK;
}
static const struct net_device_ops dummy_netdev_ops = {
.ndo_open = dummy_open,
.ndo_stop = dummy_stop,
.ndo_start_xmit = dummy_xmit,
};
static void dummy_setup(struct net_device *dev)
{
ether_setup(dev);
dev->netdev_ops = &dummy_netdev_ops;
}
static int __init dummy_init(void)
{
dummy_dev = alloc_etherdev(0);
if (!dummy_dev) {
return -ENOMEM;
}
dummy_setup(dummy_dev);
if (register_netdev(dummy_dev)) {
free_netdev(dummy_dev);
return -EIO;
}
return 0;
}
static void __exit dummy_exit(void)
{
unregister_netdev(dummy_dev);
free_netdev(dummy_dev);
}
module_init(dummy_init);
module_exit(dummy_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("Dummy NIC Driver");
这个虚拟 NIC 驱动实现了基本的打开、关闭和发送功能。在
dummy_xmit()
函数中,直接释放了接收到的数据包,因为这只是一个测试用的虚拟驱动,不需要实际发送数据包。
总结
帧缓冲驱动和网络接口卡驱动在 Linux 系统中扮演着重要的角色。帧缓冲驱动为图形显示提供了基础,通过一系列的操作函数和数据结构,实现了对帧缓冲设备的管理和控制。而网络接口卡驱动则保障了网络通信的正常进行,通过对数据包的处理和传输,使得系统能够与外界进行数据交换。
在开发这些驱动时,需要深入理解相关的数据结构和操作方法,如
struct fb_info
、
struct sk_buff
和
struct net_device
等。同时,要根据硬件的特性和需求,合理地实现各种操作函数,如
fb_check_var()
、
fb_set_par()
、
dummy_xmit()
等。
通过本文的介绍,希望读者能够对帧缓冲驱动和网络接口卡驱动有更深入的了解,并能够在实际开发中运用这些知识。在实际开发过程中,可以参考内核源码中的示例驱动,如
drivers/video/fbdev/vfb.c
和虚拟 NIC 驱动示例,以加快开发进度和提高代码质量。
同时,对于不同的硬件设备,可能需要根据其具体特性进行相应的调整和优化。例如,在帧缓冲驱动中,可能需要根据不同的显示控制器进行硬件参数的设置;在网络接口卡驱动中,可能需要根据不同的网卡芯片进行寄存器的配置和数据的读写操作。
总之,掌握帧缓冲驱动和网络接口卡驱动的开发技术,对于 Linux 系统的开发和优化具有重要的意义。
2250

被折叠的 条评论
为什么被折叠?



