FreeBSD核心探讨.8.驱动程序篇
2.3 Device Driver
进程的io要求到这里说的差不多了。上面也解说了对于文字型,块型的驱动程序接口,就是dev_spec_vnodeop_opv_desc里定义的子函数那些。参考设备驱动程序,在sys/conf.h
里定义的结构体。block型是
struct bdevsw{
d_open_t *d_open;
d_close_t *d_close;
d_strategy_t *d_strategy;
d_ioctl_t *d_ioctl;
d_dump_t *d_dump;
d_psize_t *d_psize; /*得到容量*/
int *d_flags;
char *d_name; /*device 名*/
struct cdesw *d_cdev; /*对应的文字型*/
int d_maj; /*major号*/
}
文字型的则是
struct cdevsw{
d_open_t *d_open;
d_close_t *d_close;
d_read_t *d_read; /* rawread() */
d_write_t *d_write; /* rawwrite()*/
d_ioctl_t *d_ioctl;
d_stop_t *d_stop; /* nostop()*/
d_reset_t *d_reset; /* nullreset()*/
d_devtotty_t *d_devtotty; /* nodevtotty*/
d_select_t *d_select; /* deltrue*/
d_mmap_t *d_mmap; /* nommap*/
d_strategy_t *d_strategy
char *d_name; /*device名*/
struct bdevsw *d_bdev; /*对应block型*/
int d_may; /*major号*/
}
两方面共同的部分有
xx_open(dev_t dev,int oflags,int devtype,struct proc *p)
xx_close(dev_t dev,int fflag,int devtype,struct proc *p)
xx_ioctl(dev_t dev,int cmd,caddr_t data,int fflag,struct proc *p)
xx_open()用于打开device号的设备。xx_close()则用于关闭它。xx_ioctl()则对设备的动作状态,机能的取得,设置等进行控制,它通过int cmd命令和参数caddr_t data对之进行处理。xx_open()的oflags则是系统调用open()里指定的标志。xx_close()和xx_ioctl()的fflag是每个文件描述符设定的标志。int devtype用来区别设备类型是文字型的还是块型的。struct proc *p是本次要求的进程号。
在文字型的操作里,有这三个函数
xx_read(dev_t dev,struct uio *uio,int ioflag)
xx_write(dev_t dev,struct uio *uio,int ioflag)
xx_select(dev_t dev,int which, struct proc *p)
xx_read()/xx_write()是对device号的io,struct uio *uio 是io的buffer,int ioflag标志io动作的option。例如,输入data没准备好的场合不用进入等待状态也可以。
xx_select()检查是否可以进行io要求。
在块设备的操作中,有一个函数
xx_strategy(struct buf *bp)
它处理io要求。struct buf *bp里面包含着device号,输入还是输出,io的buffer等。
device号中的major号,对文字型的struct cdevsw *cdevsw[],对块型的struct
bdevsw *bdevsw[],作为配列的添加字使用。向这些配列登记,就可以调出device driver
的登记routine。
对cdevsw[]登记的过程在kern/kern_conf.c,它使用
int cdevsw_add(
dev_t *descrip, /*收集device号的变量的指针*/
struct cdevsw *newentry,/*设置struct cdevsw的指针*/
struct cdevsw **oldentry,/*旧的设定内容的返回领域*/
)
另一方面,对bdevsw[]的登记过程则使用
int bdevsw_add_generic(
int bdev, /*block型的major号*/
int cdev, /*文字型的major浩*/
struct bdevsw *bdevsw, /*设定struct bdevsw的指针,对应d_cdev*/
)
block型的device和char型的device有着一定的对应关系。这些结构体相互参考。
bdevsw_add_generic()从block的结构体开始,对作为char型的device的结构体进行初始化。
还有,network interface的devive driver,并没有向cdevsw[]和bdevsw[]登记。而且也没有
device号。网络间的package流,和进程间与网络间的package流也没有特别指明。
调用登记routine的时候,可以把文件系统的modules作为特殊设备文件参考。登记 routine在什么地方都可以调用。
。main()(@kern/init_mail.c)的初始化过程中登记的routine调用的时候,各个
device driver的modules里由宏SYSIN99v()准备进行。
。确认device driver里的io设备的存在的时候,调用登记routine。
当调用登记程序段的时候,如果major号和/dev/MAKEDEV的major号有冲突的时候,就调用全部无关性device file的处理routine,也可能没有预期的的灾难事情。还有别的以外事情,就是当/dev里没有对应的特殊设备文件的时候,也就不能从进程进行参考。
FreeBSD核心探讨.9.驱动程序篇
2.3.1驱动程序初始化
从文件系统的模块可以看出来,如果要对驱动程序的物理设备进行io,必须先对它们进行初始化,否则不能处理process的io。核心初始化的过程里,一共登记了两个基本的操作过程。
1.probe 确认io设备
2.attach 设置device driver内部的数据结构,使它能够对io设备进行操作。登记中断子程序。
在device driver中的处理过程有:
1.i/o地址
i/o命令使用的地址,使io设备的控制硬件和数据交换。
2.中断号
io设备的状态变化的时候,向cpu发出通知。
3.共有内存地址
根据设备的不同,使用一部分内存空间进行cpu和数据的交换。
4.DMA通道
不用通过cpu做中介,设备和内存直接交换数据时候采用的通道的识别号。
cpu可以在数据传送的时候同时执行它的机器语言。
前两种是必须有的。设备根据他连接的总线设备不一样,处理过程也就不同。
这个在核心的configure中反映出来。
各种总线设备的device driver的初始化
驱动程序的初始化在main()初始化的过程中调用configure()
(@i386/i386autoconf.c).
EISA bus
连接EISA bus的io设备用的device driver的初始化在eisa_configure()
(@i386/eisa/eisaconf.c)。各个device driver在module里对struct eisa_driver
XXX(@i386/eisa/eisaconf.h)进行probe,attach等的设置,准备在宏DATA_SET
(eisadriver_set,XXX)进行登记。
eisa_configure()(@i386/eisa/eisaconf.c)对连接EISA bus的全部io设备标志和i/o地址进行检测。之后便调用登记的probe子程序。在probe子程序中,通过eisa_match_dev()(@i386/eisa/eisaconf.c)对自身检测,查找io设备,检测i/o中断号,然后进行使用预定,之后用eisa_registerdev()(@i386/eisa/eisaconf.c)在struct eisa_driver XXX对这个设备操作,作为device driver登记。全部的io设备的控制device driver登记完毕后,eisa_configure()就调用device driver的attach 子程序。attach子程序则进行中断处理程序的登记和device driver的数据的初始化。
核心的configure文件登记了以下的一些device driver:
--------------------------------------------------------------------------
device device driver的情报 source 参考
--------------------------------------------------------------------------
mainboard_drv i386/eisa/eisaconf.c
ahb ahb_eisa_driver i386/eisa/aha1742.c scsi adapt
ahc ahc_eisa_driver i386/eisa/aic7770.c scsi adapt
bt bt_eisa_driver i386/eisa/bt74x.c scsi adapt
ep ep_eisa_driver i386/eisa/3c5x9.c network interface
fea pdq_eisa_driver i386/eisa/if_fea.c network interface
vx vx_eisa_driver i386/eida/if_vx_eisa.c network interface
--------------------------------------------------------------------------
PCI bus
连接pci bus的设备的初始化在pci_configure()(@pci/pci.c)进行。各个device driver在module内的struct pci_device XXX(@pci/pcivar.h)设置probe和attach,在通过宏DATA_SET(pcidevice_est,XXX)进行登记。
DATA_SET(pcibus_set,i386pci)(@i386/isa/pcibus.c)登记的子程序可以得到有关pci bus的一些信息。之后和eisa bus处理过程一样进行各种各样的调用。
核心的configure文件登记了以下的一些device driver:
--------------------------------------------------------------------------
device device driver的情报 source 参考
--------------------------------------------------------------------------
ahc ahc_pci_driver pci/aic7870.c scsi adapt
bt bt_pci_driver pci/bt9xx.c scsi adapt
ncr ncr_device pci/ncr.c scsi adapt
amd trmamd_device pci/tek390.c scsi adapt
cy cy_device pci/cy_pci.c serial port
meteor met_device pci/meteor.c meteor通道
stl stlpcidriver i386/isa/stallion.c serial port
wdc wdc_pci_driver pci/wdc_p.c ide control
de dedevice pci/if_de.c network interface
ed ed_pci_driver pci/if_ed_p.c network interface
fpa pfadevice pci/if_pfa.c network interface
fxp fxp_device pci/if_pxp.c network interface
lnc lnc_pci_driver pci/if_lnc_p.c network interface
sr sr_pci_driver pci/if_sr_p.c network interface
vx vxdevice pci/if_vx_pci.c network interface
-------------------------------------------------------------------------
ISA bus
连接ISA bus的io设备的device driver的初始化在isa_configure()(@i386/ isa/isa.c)进行。和EISA,PCI很大的一个区别就是,在核心的配置文件中,要指定所有的io地址等。
configure文件中,有象如下的记录
controller 控制设备名 at isa?...
device device名 at isa?...
这些内容在编译核心的目录下作为ioconf.c的struct isa_device
isa_devtab_XXX[]的初始值由config命令写进去。在struct isa_device(@i386/isa/isa_device.h)的上,其次的成员变量由configure文件的记录内容进行设定。但是,“名字”是控制设备名/device名的数字除外的部分。
-------------------------------------------------------------------------
member名 configure的记述内容
-------------------------------------------------------------------------
id_driver 名字drvier
id_iobase prot I/O address
id_irq irq号
id_drq drq DMA通道号
id_maddr iomem共有memory address
id_msize iosiz共有memory长度
id_intr vector device driver的中断处理程序名
id_unit 名字的后的数字(?)
id_flags flags
-------------------------------------------------------------------------
但是,和控制设备/device名有关的一些东西如bio,net,tty出现的场合,这些一般成为isa_devtab_bio[],isa_devtab_net[],isa_devtab_tty[]数组的初始值。
没有的情况,则成为isa_tab_null[]的初始值。还有一个就是名字driver,它是各个device driver的module内部的struct isa_driver(@i386/isa/isa_device.h)一个东西。对isa bus设备的device driver,这个是一个固定值。
象这样的记录:
------------------------------------------------------------
disk device名 at 控制设备名 driver 数字
tape device名 at 控制设备名 driver 数字
------------------------------------------------------------
每个数字除外控制设备名(wdc或者fdc),总结起来就是写进一个叫做isa_biotab_控制设备名[]的数组的某个元素的初始设定值。但对unit成员填入数字外,其他的也就和isa_devtab_bio[]的内容一样。
isa_configure()依照isa_devtab_bio[],isa_devtab_net[],
isa_devtab_tty[]的设定值调用probe子程序对设备的有无进行确认。有的话就继续调用attach子程序。
probe子程序对设备进行确认,不同的probe子程序也有可能对同样的io地址进行操作。所以为了防止这个问题,isa_configure()对已经确认过的的io地址不再给别的probe进行动作。
同样,错认的可能性也有的。必要的时候没连接的设备的probe要禁止使用,(在boot的参数的时候)。