Linux 8250串口驱动学习
8250串口驱动注册1. 8250核心层
核心层的文件为drivers/tty/serial/8250/8250_core.c
1.核心层会初始化一定数目的uart_8250_port
2.注册uart_driver
3.添加一个name为serial8250的platform_device
4.注册前面初始化的uart_8250_port ,实在是调用uart_add_one_port添加一定数目的uart_port
5.注册一个name为serial8250的platform_driver,如许就会和第三步注册的platform_device匹配
6.匹配后调用probe函数,该函数里面什么也没做,因为在第三步只是添加了一个platform_device,并没有设置platform_data,所以该函数直接返回
static int __init serial8250_init(void)
{
//1.初始化10个uart_8250_port,这个数量根据CONFIG_SERIAL_8250_RUNTIME_UARTS 宏确定
serial8250_isa_init_ports();
//2.注册uart_driver
uart_register_driver(&serial8250_reg);
//3.创建一个platform_device 用来和下面的platform_driver匹配
serial8250_isa_devs = platform_device_alloc("serial8250", PLAT8250_DEV_LEGACY);
//4.添加platform_device
platform_device_add(serial8250_isa_devs);
//5.定义了CONFIG_ARCH_ROCKCHIP宏,瑞芯微平台此函数没有作用
serial8250_register_ports(&serial8250_reg, &serial8250_isa_devs->dev);
//6.注册platform_driver和platform_device匹配
platform_driver_register(&serial8250_isa_driver);
}
2. 初始化8250_port
uart_8250_port初始化,调用drivers/tty/serial/8250/8250_port.c中的函数,设置每个uart_port的uart_ops ,这个利用函数集在应用层open read write时最终被调用
static const struct uart_8250_ops univ8250_driver_ops = {
.setup_irq = univ8250_setup_irq,
.release_irq = univ8250_release_irq,
};
static void __init serial8250_isa_init_ports(void)
{
for (i = 0; i < nr_uarts; i++) {
//调用8250_port.c中的设置uart_port
serial8250_init_port(up);
port->ops = &serial8250_pops; //struct uart_ops serial8250_pops定义在8250_port.c
//设置uart_8250_ops,主要用来申请和释放中断
up->ops = &univ8250_driver_ops;
}
}
//drivers/tty/serial/8250/8250_port.c
static const struct uart_ops serial8250_pops = {
.tx_empty = serial8250_tx_empty,
.set_mctrl = serial8250_set_mctrl,
.get_mctrl = serial8250_get_mctrl,
.stop_tx = serial8250_stop_tx,
.start_tx = serial8250_start_tx,
.throttle = serial8250_throttle,
.unthrottle = serial8250_unthrottle,
.stop_rx = serial8250_stop_rx,
.enable_ms = serial8250_enable_ms,
.break_ctl = serial8250_break_ctl,
.startup = serial8250_startup,
.shutdown = serial8250_shutdown,
.set_termios = serial8250_set_termios,
.set_ldisc = serial8250_set_ldisc,
.pm = serial8250_pm,
.type = serial8250_type,
.release_port = serial8250_release_port,
.request_port = serial8250_request_port,
.config_port = serial8250_config_port,
.verify_port = serial8250_verify_port,
#ifdef CONFIG_CONSOLE_POLL
.poll_get_char = serial8250_get_poll_char,
.poll_put_char = serial8250_put_poll_char,
#endif
};
void serial8250_init_port(struct uart_8250_port *up)
{
struct uart_port *port = &up->port;
spin_lock_init(&port->lock);
//设置uart_ops 为每一个uart_port 设置uart_ops,在open read write 时最终会调用这里面的回调函数
port->ops = &serial8250_pops;
port->has_sysrq = IS_ENABLED(CONFIG_SERIAL_8250_CONSOLE);
up->cur_iotype = 0xFF;
}
3. uart_driver相干布局体先容
注册uart_drivers,在8250串口驱动中此步调为第二步
在串口驱动框架中,tty层,串口核心层,详细的串口驱动层硬件相干。
内核中对应的源码位置为:
tty层:drivers/tty/tty_io.c
串口核心层:drivers/tty/serial/serial_core.c
驱动硬件相干:drivers/tty/serial/imx.c(imx6ull)、drivers/tty/serial/8250/ (8250)
每家串口装备都有自己的驱动,为了管理这些各种各样的驱动程序,串口核心层用一个uart_driver 来体现一种串口的驱动:所有的串口硬件层驱动都必要构造好一个uart_drver,并向串口核心层注册它。
好比imx6ull 的串口驱动imx.c 会向serial_core.c 注册一个uart_driver,体现imx6ull上串口的驱动;8250 串口驱动8250_core.c 会向serial_core.c 注册一个uart_driver,体现8250 串口的驱动。
一个uart_driver可以包含多个串口端口,每一个端口都会有一个uart_state和uart_port与之对应,也就是一个uart_driver对应多个uart_state,多个uart_port。
//uart_driver 结构体,每个平台的串口驱动会向核心层注册一个uart_driver
struct uart_driver {
struct module *owner;
const char *driver_name;//驱动名
const char *dev_name;//设备名 如:ttyS
int major;//主设备号
int minor;//次设备号
int nr;//表示对应多少个uart端口
struct console *cons;//与console相关
struct uart_state *state;//每一个uart端口都会有一个uart_state与之对应
struct tty_driver *tty_driver;//在底层硬件驱动中不需要初始化他,留给tty层设置
};
//uart_state 每个串口驱动在注册时,会根据驱动支持的串口个数申请与之对应的uart_state
struct uart_state {
struct tty_port port;//一个tty_port 对应一个uart_state 对应一个uart_port
enum uart_pm_state pm_state;
struct circ_buf xmit;
atomic_t refcount;
wait_queue_head_t remove_wait;
struct uart_port *uart_port;//一个uart_port 对应一个uart_state 对应一个tty_port
};
//一个uart_port对应一个实际的uart端口,下面说明几个重要的成员
struct uart_port {
spinlock_t lock; /* port lock */
unsigned long iobase; /* in/out */
unsigned char __iomem *membase; /* read/write */
//用于读取uart 硬件寄存器
unsigned int (*serial_in)(struct uart_port *, int);
//用于写入uart 硬件寄存器
void (*serial_out)(struct uart_port *, int, int);
//设置串口
void (*set_termios)(struct uart_port *, struct ktermios *new, struct ktermios *old);
//串口的硬件操作函数这个是串口驱动最底层的硬件相关操作
const struct uart_ops *ops;
//代表是那个串口如ttyS1 ttyS2 这个line就是后面的数字
unsigned int line;
}
//串口的硬件相关的操作函数集,真正的硬件相关的代码,如发送,接收,有厂家编写这些函数会操作串口寄存器完成相关功能
struct uart_ops {
unsigned int (*tx_empty)(struct uart_port *);//判断串口发送fifo是否为空
void (*set_mctrl)(struct uart_port *, unsigned int mctrl);
unsigned int (*get_mctrl)(struct uart_port *);
void (*stop_tx)(struct uart_port *);//停止发送
void (*start_tx)(struct uart_port *);//开始发送
void (*throttle)(struct uart_port *);
void (*unthrottle)(struct uart_port *);
void (*send_xchar)(struct uart_port *, char ch);
void (*stop_rx)(struct uart_port *);
void (*enable_ms)(struct uart_port *);
void (*break_ctl)(struct uart_port *, int ctl);
int (*startup)(struct uart_port *);//启动串口,当应用层open是最终会调用它
void (*shutdown)(struct uart_port *);
void (*flush_buffer)(struct uart_port *);
void (*set_termios)(struct uart_port *, struct ktermios *new,
struct ktermios *old);
void (*set_ldisc)(struct uart_port *, struct ktermios *);
void (*pm)(struct uart_port *, unsigned int state,
unsigned int oldstate);
const char *(*type)(struct uart_port *);
void (*release_port)(struct uart_port *);
int (*request_port)(struct uart_port *);
void (*config_port)(struct uart_port *, int);
int (*verify_port)(struct uart_port *, struct serial_struct *);
int (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
int (*poll_init)(struct uart_port *);
void (*poll_put_char)(struct uart_port *, unsigned char);
int (*poll_get_char)(struct uart_port *);
#endif
};
/*
tty_driver表示一个tty驱动,tty_driver 支持多种硬件设备如串口,显示屏等。tty层提供了相应的函数注册tty_driver,如串口驱动,串口驱动核心层提供了uart_register_driver注册串口驱动,最终调用tty_register_driver向tty层注册tty_driver 。
tty_driver 中的成员需要注意的是 cdevs(struct cdev)、ttys(struct tty_struct)、ports(struct tty_port)和termios(struct ktermios)这4个结构体的二级指针(二级指针用来指向一个指针数组的首地址)。
在创建tty_driver 的过程中,会根据uart_driver->nr (串口端口的数量) 申请多个结构体的指针:nr * sizeof(struct cdev*); nr* sizeof(struct tty_struct*); nr* sizeof(struct tty_port*); nr* sizeof(stuct ktermios*); 并让二级指针指向它们的首地址。(只是申请了指针内存,并未申请实际结构体的内存)
cdev 代表着字符设备,每一个字符设备都会有一个struct cdev,在调用tty_port_register_device_attr 注册一个tty_port 时会为这个tty_port 创建cdev,并按照端口序号放入指针数组对应的位置。(ttyS0、ttyS1 … 每一个都是一个字符设备,它们都有一个唯一的cdev 和次设备号,因为属于同一个uart_driver 的关系它们有相同的主设备号)
tty_struct 是操作串口过程中比较重要的数据结构,它会在open ttyxx 的时候为对应的端口(tty_port) 申请一个tty_struct 内存,按序号放入指针数组对应的位置。(创建的同时会初始化tty_struct,让tty_struct->ops(const struct tty_operations *) 指向tty_driver->ops,之后就可以用tty_struct 调用到struct tty_operations 操作集)
tty_port 表示一个tty端口,在调用tty_port_register_device_attr 注册tty_port 时会将该tty_port 地址按端口序号放入数组。
ktermios 表示一个终端设备,每个tty端口对应一个,在open 过程中会每个端口创建struct ktermios并初始化它(波特率等等),按次序放入指针数组。
*/
struct tty_driver {
int magic; /* magic number for this structure */
struct kref kref; /* Reference management */
struct cdev **cdevs; //cdevs指针数组,在uart_add_one_port时会申请cedev并设置file_opreatuins,这里会记录每个串口的cdev,操作对应串口时就会使用相应串口的操作函数集
struct module *owner;
const char *driver_name;
const char *name;
int name_base; /* offset of printed name */
int major; /* major device number */
int minor_start; /* start of minor device number */
unsigned int num; //表示支持几个串口端口
short type; /* type of tty driver */
short subtype; /* subtype of tty driver */
struct ktermios init_termios; /* Initial termios */
unsigned long flags; /* tty driver flags */
struct proc_dir_entry *proc_entry; /* /proc fs entry */
struct tty_driver *other; /* only used for the PTY driver */
struct tty_struct **ttys; //tty_struct指针数组,在应用层open时会分配设置
struct tty_port **ports; //tty_port指针数组
struct ktermios **termios; //ktermios指针数组
void *driver_state; //指向下层的driver结构体,比如串口uart_driver(为了绑定tty_driver和uart_driver)
/*
* Driver methods
*/
const struct tty_operations *ops;
struct list_head tty_drivers;
} __randomize_layout;
/*
tty_port表示一个tty端口,如果是串口的话,那个一个tty_port对应一个uart_port,他的成员tty_port->ops(struct tty_port_operations)和client_ops比较重要,在open时会调用
*/
struct tty_port {
struct tty_bufhead buf; /* Locked internally */
struct tty_struct *tty; /* Back pointer */
struct tty_struct *itty; /* internal back ptr */
const struct tty_port_operations *ops; /* Port operations */
const struct tty_port_client_operations *client_ops; /* Port client operations */
spinlock_t lock; /* Lock protecting tty field */
int blocked_open; /* Waiting to open */
int count; /* Usage count */
wait_queue_head_t open_wait; /* Open waiters */
wait_queue_head_t delta_msr_wait; /* Modem status change */
unsigned long flags; /* User TTY flags ASYNC_ */
unsigned long iflags; /* Internal flags TTY_PORT_ */
unsigned char console:1, /* port is a console */
low_latency:1; /* optional: tune for latency */
struct mutex mutex; /* Locking */
struct mutex buf_mutex; /* Buffer alloc lock */
unsigned char *xmit_buf; /* Optional buffer */
unsigned int close_delay; /* Close port delay */
unsigned int closing_wait; /* Delay for output */
int drain_delay; /* Set to zero if no pure time
based drain is needed else
set to size of fifo */
struct kref kref; /* Ref counter */
void *client_data;
};
/*
tty_struct 和 tty_port一一对应,里面包含端口拥有的读写缓冲去等一些重要的数据
*/
struct tty_struct {
int magic;
struct kref kref;
struct device *dev;
struct tty_driver *driver;
const struct tty_operations *ops;
int index;
/* Protects ldisc changes: Lock tty not pty */
struct ld_semaphore ldisc_sem;
struct tty_ldisc *ldisc;
struct mutex atomic_write_lock;
struct mutex legacy_mutex;
struct mutex throttle_mutex;
struct rw_semaphore termios_rwsem;
struct mutex winsize_mutex;
spinlock_t ctrl_lock;
spinlock_t flow_lock;
/* Termios values are protected by the termios rwsem */
struct ktermios termios, termios_locked;
char name;
struct pid *pgrp; /* Protected by ctrl lock */
/*
* Writes protected by both ctrl lock and legacy mutex, readers must use
* at least one of them.
*/
struct pid *session;
unsigned long flags;
int count;
struct winsize winsize; /* winsize_mutex */
unsigned long stopped:1, /* flow_lock */
flow_stopped:1,
unused:BITS_PER_LONG - 2;
int hw_stopped;
unsigned long ctrl_status:8, /* ctrl_lock */
packet:1,
unused_ctrl:BITS_PER_LONG - 9;
unsigned int receive_room; /* Bytes free for queue */
int flow_change;
struct tty_struct *link;
struct fasync_struct *fasync;
wait_queue_head_t write_wait;
wait_queue_head_t read_wait;
struct work_struct hangup_work;
void *disc_data;
void *driver_data;
spinlock_t files_lock; /* protects tty_files list */
struct list_head tty_files;
#define N_TTY_BUF_SIZE 4096
int closing;
unsigned char *write_buf;
int write_cnt;
/* If the tty has a pending do_SAK, queue it here - akpm */
struct work_struct SAK_work;
struct tty_port *port;
} __randomize_layout;
/*
ktermios
这个就是我们在应用层初始化串口时要设置的波特率、停止位、校验位等等,都在ktermis 中。
*/
struct ktermios {
tcflag_t c_iflag; /* input mode flags */
tcflag_t c_oflag; /* output mode flags */
tcflag_t c_cflag; /* control mode flags */
tcflag_t c_lflag; /* local mode flags */
cc_t c_line; /* line discipline */
cc_t c_cc; /* control characters */
speed_t c_ispeed; /* input speed */
speed_t c_ospeed; /* output speed */
};
/*
在上面提到的tty_driver 和 tty_port 分别有一个tty_operations和 tty_port_operations 比较重要在open时会进行调用
在串口核心层提供的uart_register_driver 中会创建tty_driver并初始化实现tty_opreations和tty_port_opreations 然后向tty层注册tty_driver并且初始化N个tty_port
*/
open(应用层)
->struct file_operations //在uart_add_one_port时tty层会分配设置cdev
->tty_struct->ops->open //struct tty_operations 由下层实现向tty层注册,如串口是在核心层serial_core.c中实现
->tty_port->ops->activate //struct tty_port_operations 同样由下层实现,如串口是在uart_register_driver时设置
->uart_state->ops->startup //struct uart_ops 有具体的串口驱动提供
/*
serial_core.c 中tty_operations和tty_port_operations的实现
*/
static const struct tty_operations uart_ops = {
.install = uart_install,
.open = uart_open,
.close = uart_close,
.write = uart_write,
.put_char = uart_put_char,
.flush_chars = uart_flush_chars,
.write_room = uart_write_room,
.chars_in_buffer= uart_chars_in_buffer,
.flush_buffer = uart_flush_buffer,
.ioctl = uart_ioctl,
.throttle = uart_throttle,
.unthrottle = uart_unthrottle,
.send_xchar = uart_send_xchar,
.set_termios = uart_set_termios,
.set_ldisc = uart_set_ldisc,
.stop = uart_stop,
.start = uart_start,
.hangup = uart_hangup,
.break_ctl = uart_break_ctl,
.wait_until_sent= uart_wait_until_sent,
#ifdef CONFIG_PROC_FS
.proc_show = uart_proc_show,
#endif
.tiocmget = uart_tiocmget,
.tiocmset = uart_tiocmset,
.set_serial = uart_set_info_user,
.get_serial = uart_get_info_user,
.get_icount = uart_get_icount,
#ifdef CONFIG_CONSOLE_POLL
.poll_init = uart_poll_init,
.poll_get_char = uart_poll_get_char,
.poll_put_char = uart_poll_put_char,
#endif
};
static const struct tty_port_operations uart_port_ops = {
.carrier_raised = uart_carrier_raised,
.dtr_rts = uart_dtr_rts,
.activate = uart_port_activate,
.shutdown = uart_tty_port_shutdown,
};
4. uart_driver注册分析
串口核心层向下提供了uart_driver注册的接口,不通的串口驱动调用uart_register_driver 向核心层注册串口驱动,如8250驱动在初始化的第二步调用uart_register_driver 注册uart_driver。
在向串口核心层注册时重要做了那些事变:
[*]为注册的uart_driver申请了与之所支持串口数目相等的uart_state内存空间
https://i-blog.csdnimg.cn/direct/643725dabf89415fbcbbc7f194fac145.png
[*]申请的tty_driver,并为tty_driver中的ttys(struct tty_struct),cdevs(struct cdev),ports(struct tty_port),ktermios(struct ktermios)成员分别申请相应串口数目的指针,即为tty_driver中的指针数组申请对应数目的布局体指针成员,调用关系为:alloc_tty_driver->tty_alloc_driver->__tty_alloc_driver
https://i-blog.csdnimg.cn/direct/b03e5d86c363470a8b8ec1e8e6f9c204.png
[*]设置tty_driver,将uart_driver的成员赋值给申请的tty_driver,并将tty_driver和uart_driver举行绑定(normal->driver_state = drv),设置tty_driver的tty_operations(tty_set_operations(normal, &uart_ops))
https://i-blog.csdnimg.cn/direct/24b0740d0d5a4778ad39590f48188a3a.png
[*]初始化uart_driver的每个uart_state 的tty_port ,并设置operations(tty_port_operations)
https://i-blog.csdnimg.cn/direct/1044779ac9564fc5891be8df2e1bc734.png
[*]利用tty_register_driver 向tty层注册tty_driver,注册时会向内核注册一组字符装备号,但是这里没有添加cdev和设置file_operations,添加cdev和设置fops在uart_add_one_port调用时实现
https://i-blog.csdnimg.cn/direct/49ac6b229f0143179f73cd299226613a.pnghttps://i-blog.csdnimg.cn/direct/edb0a1c6e8974b0b82f984e0df3cfe9d.png
5. 8250核心层初始化10个虚拟端口
在8250核心层初始化第五步,因为8250驱动支持10个串口,所以先初始化了10个虚拟的端口,而且调用uart_add_one_port 添加了10个uart_port,此处我们不做过多分析,后再面真正匹配硬件端口时我们在分析添加uart_port时发生了什么。
https://i-blog.csdnimg.cn/direct/1d68144c29564041b603d93ae160f836.png
6. 详细驱动匹配
TI 串口驱动匹配
上面的分析中,8250核心层注册了一个serial8250的platform driver,并注册了10个uart_8250_port 体现支持该驱动支持10个串口,但是这10个串口相称于时虚拟串口并没有和真正的硬件端口绑定。下面以8250_omap为例讲解详细的硬件串口端口的添加。代码位置在drivers/tty/serial/8250/8250_omap.c
可以看到详细硬件端口的驱动是一个platform驱动,当和uart装备匹配后会最终调用驱动的probe函数,omap8250_probe
https://i-blog.csdnimg.cn/direct/761ad536660a46c8a0686e6a6b6471df.png
在omap8250_probe函数中重要是初始化uart_8250_port布局体,这个是布局体是8250串口驱动框架描述串口的布局体,里面包含了一个uart_port 布局体,这个布局体是内核用来描述一个串口端口的布局体,注册串口驱动时最终就是向内核添加一个uart_port布局体用来描述一个串口,一个驱动可能会支持多个串口所以会添加多个uart_port。此处是初始化uart_8250_port而且初始化uart_port ,然后调用8250核心层的函数注册添加uart_8250_port,内部调用uart_add_one_port向内核注册添加uart_port
设置uart_8250_port 和 uart_port
https://i-blog.csdnimg.cn/direct/333eceea4f7845daa2d19b4ef5d08029.png
https://i-blog.csdnimg.cn/direct/bae4f2372c494973b7d297a1d22a5649.png
https://i-blog.csdnimg.cn/direct/7f2f0301ea8a43c599e7067ab7d667ad.png
rockchip 串口驱动匹配
rk的驱动匹配乐成后和详细步调和上面TI的驱动基本雷同,rk的驱动没有实现自己的uart_ops,而是利用 serial8250_isa_init_ports 中设置的uart_ops即 8250_port.c 中提供的通用的uart_ops。dw8250_handle_irq 为中断处理函数,在应用层open时调用startup函数,该函数中会举行中断申请request_irq。
https://i-blog.csdnimg.cn/direct/68b6d2d03e0c41d28f3c3d68c64f31d3.png
https://i-blog.csdnimg.cn/direct/3c72206b843143188d2407643dd41857.png
uart_add_one_port调用过程
核心层serial8250_register_8250_port注册uart_8250_port 时会先移除一个uart_port,然后将omap_8250_probe中设置的uart_8250_port赋值给之前开始注册的虚拟的uart_8250_port,然后再调用uart_add_one_port重新添加uart_port,uart_add_one_port在serial_core.c 中定义。
https://i-blog.csdnimg.cn/direct/b660861b0afe4f3f8506c72979c37f72.png
https://i-blog.csdnimg.cn/direct/661bf4982fd547dfb150ba9d96671876.png
在调用uart_add_one_port添加uart_port时,会先从uart_driver中找到对应的uart_state和uart_port前面说过一个端口对应一个uart_state,对应一个uart_port,对应一个tty_port,而且将tty_port设置到tty_driver中的tty_port指针数组中,之后调用tty_port_register_device_attr_serdev注册tty_port
https://i-blog.csdnimg.cn/direct/8fbe1ca09ef747f3ad7325e303662a7e.png
将tty_port 绑定到 tty_driver 中的 tty_ports 指针数组中。
https://i-blog.csdnimg.cn/direct/77b72c2a7fae4068b9a262376ffe6fc4.png
下面会举行uart_state->tty_port和uart_port->tty_groups注册,根据 num_groups 的值申请 num_groups 个 const struct attribute_group * 指针。uport->tty_groups是一个 const struct attribute_group ** 二级指针。
https://i-blog.csdnimg.cn/direct/f13c909ddf344885a5165ada67e31464.png
tty_port_register_device_attr_serdev 注册uart_state->tty_port 和uart_port->tty_groups
https://i-blog.csdnimg.cn/direct/bc5a72af55854cf7839e911fd8a2c436.png
serdev_tty_port_register中举行tty_port 的注册,最关键的是 tty_register_device_attr 。
https://i-blog.csdnimg.cn/direct/17745d0241a4414e8a9d616837deaca2.png
tty_register_device_attr 中会初始化devices布局体,设置装备号,类(tty_class),parent(uart_port->devices),groups,name(/dev/目次下的装备名),最终调用devices_register 注册devices,最终调用 tty_cdev_add 注册字符装备并添加字符装备利用集。
https://i-blog.csdnimg.cn/direct/b6dc8e4455244ccc9e5b5ebddb580b75.png
https://i-blog.csdnimg.cn/direct/b6d86c3755c24653bf7ad6d7ca3481b5.png
tty_cdev_add 中会添加 cdev,设置 cdev 的 fops 为 tty_fops,在应用层 open,read,write,/dev/ttySxx 装备时,就会通过tty_fop 中的利用函数往下调用,最终调用到串口驱动中的相应的函数。
https://i-blog.csdnimg.cn/direct/ce812fb182d94f29a19aa8df60d1d1d1.png
装备节点和属性文件的创建
在上面利用中初始化并注册了devices,在调用 devices_register 注册 devices 时会在 /dev 目次下创建装备节点,在 rootfs 中创建attribute 属性文件。和注册字符装备时会向 /dev 目次下创建一个装备节点通常是调用 device_create 来创建装备,先创建 devices 再添补信息,最终调用 devices_register 向内核注册,但是没有传递 attribute_group 不能创建一些属性文件。
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...)
device_create_groups_vargs(class, parent, devt, drvdata, NULL, fmt, vargs);
device_initialize(dev);
dev->devt = devt;
dev->class = class;
dev->parent = parent;
dev->groups = groups;
dev->release = device_create_release;
dev_set_drvdata(dev, drvdata);
retval = kobject_set_name_vargs(&dev->kobj, fmt, args);
if (retval)
goto error;
retval = device_add(dev);
所以创建和初始化一个 struct devices 然后调用devices_register 就可以创建一个 /dev/xxx 装备节点,如何初始化 devices 时提供了 devices->groups 还可以创建一些属性文件。利用 device_create_file 也可以创建装备属性文件。下面是串口装备的属性文件。
https://i-blog.csdnimg.cn/direct/fa5958ff41264926ac69fefe35b9119b.png
总结:uart_add_one_port 的重要做了以下三件事
1、将uart_port 添补到uart_driver 中端口对应的state,uart_state->uart_port,从而绑定uart_driver、uart_state、uart_port 三者关系,把uart_port 添加到uart_driver。 实在是4者绑定,uart_state 与tty_port 是绑定的。
2、uart_port 中console、minor、name等成员的设置,这些都是在创建uart_driver 时初始化好的,必要从uart_driver中赋值已往。其它console 的设置。
3、设置uart_port->tty_groups,调用tty_port_register_device_attr_serdev 注册uart_state->tty_port 和uart_port->tty_groups。
7. rockchip 驱动open
在上面 uart_add_one_port 时,最终注册/dev/ttySxx装备,在调用 tty_cdev_add 注册字符装备时,设置的 cdev 函数利用集为 tty_fops,所以在应用层 open 一个 /dev/ttySxx 装备时会调用 tty_fops 中的 open 函数。
https://i-blog.csdnimg.cn/direct/00940520b0564b18a2720f88f7c0f51b.png
tty_fops.open 即 tty_open 其重要调用流程如下:
[*]查找 tty_driver , tty_driver 在调用 uart_register_driver 时申请,并添补。查找到tty_driver 后,拿出 tty_driver 中tty_struct 指针数组中的某一个tty_struct 指针,为获取到的 tty_struct 指针分配空间,设置 tty_struct , tty_struct->driver = tty_driver,tty_struct->ops = tty_driver->ops。另有行规程相干初始化
[*]调用 tty_struct->ops 中的open,即调用tty_driver->ops中的 open
tty_driver->ops->open // ops是struct tty_operations 指针类型,所以调用其中的 uart_open
tty_port_open // 打开要操作的某个串口
port->ops->activate(port,tty) // ops是struct tty_port_operations 指针类型
uart_startup(tty, state, 0)
uart_port_startup(tty, state, init_hw)
uart_port->ops->startup // ops 是 struct uart_ops 指针类型
[*]调用tty_open,获取 tty_struct,先调用 tty_open_current_tty 该函数内部判断 major=TTYAUX_MAJOR(5),串口装备主装备号为4,所以返回NULL。然后调用 tty_open_by_driver 获取tty_struct。
https://i-blog.csdnimg.cn/direct/0239c122642942c79d97b16f9918f2e9.png
1.1 先调用 tty_open_current_tty 获取tty_struct ,此处串口主装备号不为5,直接返回NULL。
https://i-blog.csdnimg.cn/direct/071a11382ab245baab8ca0c440e650db.png
1.2 继续调用 tty_open_by_driver 查找 tty_driver,并获取tty_driver中tty_struct指针数组的index项的tty_struct指针,并通过tty_init_dev 分配和设置tty_struct。
https://i-blog.csdnimg.cn/direct/2f4763be61964ff3b6f2f84711db5b8f.png
1.2.1 调用tty_lookup_driver 获取tty_driver和对应的串口号index,调用get_tty_driver从tty_driver链表中获取tty_driver,再根据装备号计算出当前串口序号。 https://i-blog.csdnimg.cn/direct/cc8b18b44b5e4f6685716c3202026d06.png
1.2.2 调用tty_driver_lookup_tty获取tty_driver中tty_struct指针数组中对应串口序号index的tty_struct 指针。
https://i-blog.csdnimg.cn/direct/76876a695e2c422d8d9947d94e6240fa.png
1.2.3 调用tty_init_dev 分配和设置tty_struct,举行行规程初始化和设置。
https://i-blog.csdnimg.cn/direct/93cb3a822e4b4c418329fbad3e349991.png
tty_ldisc_setup中会调用行规程的open函数。
https://i-blog.csdnimg.cn/direct/6e7cd58286e34538b62686251bd9219f.png
https://i-blog.csdnimg.cn/direct/56c5527ac5ed4212b9aa21b5c2719a38.png
1.2.3.1 调用alloc_tty_struct 申请一个tty_struct 举行设置和行规程初始化。 https://i-blog.csdnimg.cn/direct/cac33b4fdfc9471a972d6eb7597c3cb4.png
行规程也有自己的ops,当应用层调用open read write时会调用到行规程的tty_ldisc_ops->open , tty_ldisc_ops->read , tty_ldisc_ops->write。
https://i-blog.csdnimg.cn/direct/ebffaf1405bc469a9a81ee1e2cd5cf12.png
1.3 上面分配设置完tty_struct后调用tty->ops->open,也就是调用tty_driver->ops->open即uart_register_driver时设置的struct tty_operations 中的uart_open函数。
https://i-blog.csdnimg.cn/direct/200ee57c42cd4d0bb574e80d61be2ddf.png
1.3.1 tty_port_open会调用tty_port->ops, tty_port->ops在uart_register_driver时设置即struct tty_port_operations https://i-blog.csdnimg.cn/direct/93e4797f2a8c4799be8e074b4b3edbdd.png
在uart_port_activate 中调用uart_startup,详细调用过程如下:
uart_port_activate;
uart_startup;
uart_port_startup;
uport->ops->startup(uport); //struct uart_ops->startup 在8250_port.c中设置
https://i-blog.csdnimg.cn/direct/ea5606a210e34a00bc9cbfbd5f312021.png
https://i-blog.csdnimg.cn/direct/e9960fdad7794064807cd51dbddf4f77.png
https://i-blog.csdnimg.cn/direct/86bbfe1e4bde4462bb343980c6cc17c4.png
uport->ops->startup为uart_ops中的startup函数,在8250_port.c中实现
https://i-blog.csdnimg.cn/direct/9d3eb51b2a41439c93dd72ceac032af1.png
调用startup最终调用serial8250_startup。
https://i-blog.csdnimg.cn/direct/9f122892fa904cb18feef3b7eb64d590.png
https://i-blog.csdnimg.cn/direct/5344298ca4c14339bef03f20fafedfa0.png
在8250核心层初始化uart_8250_port时设置uart_8250_ops
https://i-blog.csdnimg.cn/direct/c4d50f3448504b5e8f9a1f2a0b085a92.png
在serial250_do_startuo中申请中断
https://i-blog.csdnimg.cn/direct/cbbfce1275434d9aa270234d3ed3188b.png
https://i-blog.csdnimg.cn/direct/6d754b5b35694a9b9b96c971164b1eb9.png
中断处理流程,rk的为例最终会调用rk驱动中设置的函数:
https://i-blog.csdnimg.cn/direct/02ba63aa9e1c4bdaa7bd2681ccd2c531.png
https://i-blog.csdnimg.cn/direct/820d275a585e4aaf9e224ccabc75af59.png
dw8250_handle_irq中调用8250_port.c中通用的中断处理函数
https://i-blog.csdnimg.cn/direct/5e8ab9f1ebff47ad8a0f0ef62880ceb0.png
最终调用serial8250_tx_chars和serial8250_rx_chars举行数据的收发。
https://i-blog.csdnimg.cn/direct/489a5b74c5b54900976990a23dfd900a.png
在应用层write时会将数据拷贝到串口的环形缓冲区中,在串口fifo空中断来时serial8250_tx_chars中将环形缓冲区的数据一个一个发送出去。
https://i-blog.csdnimg.cn/direct/462ba49c01ef40af812cb55670fca9d9.png
在接收中断来时,serial8250_rx_chars中将数据刷新到tty_port的buf中。
https://i-blog.csdnimg.cn/direct/f696bcaf5496469d9364d823bf389a86.png
8. uart驱动write
应用层调用write() 发送数据,调用到tty层的file_operations->write 即tty_write,tty_write 中会调用行规程的tty_ldisc->ops->write 而且传递来自应用空间的user_buffer。
串口所用的行规程为n_tty,那么tty_ldisc->ops->write 就是n_tty_write,n_tty_write 将要发送的数据从user_buffer 拷贝到行规程 ldisc_buffer (copy_from_user)。拷贝完成之后n_tty_write 会调用tty_struct->ops->write 函数向下层发送数据,根据前面的分析我们知道tty_struct->ops 是串口核心层提供的tty_operations,那么tty_struct->ops->write 就是uart_write。uart_write是串口核心层定义的,不涉及详细硬件,所以它会调用串口硬件驱动层提供的uart_port->ops(struct uart_ops)->start_tx 开始发送,开始发送通常是设置串口发送FIFO空中断,在中断处理函数中将buffer中的数据发走。
代码剖析:
https://i-blog.csdnimg.cn/direct/3fd18b74b097428f9f1a9f41293227d2.png
拷贝用户空间数据到tty_struct->write_buf,并调用行规程write。
https://i-blog.csdnimg.cn/direct/7dcc275c139248cca94e7bfabf1c2286.png
行规程write就是n_tty_write,然后会调用tty_struct->write即uart_write。
https://i-blog.csdnimg.cn/direct/8c0cce8206d544878eb4ca2cd7b3aa93.png
https://i-blog.csdnimg.cn/direct/2aab03d0bb034c32ba2b70d7b434c68c.png
tty->ops->write就是tty_driver的ops的uart_write,会将数据拷贝到对应串口的uart_state布局体的环形buf中,并开启传输。
https://i-blog.csdnimg.cn/direct/1acfabdc9ab247b2aa9268c9867a0305.png
https://i-blog.csdnimg.cn/direct/74a1b74986c54adeb5eafa95bbb330a2.png
__uart_start就是serila8250_start_tx里面会设置中断,在中断中举行数据发送。
https://i-blog.csdnimg.cn/direct/bc20e78eb8d44a9789b42c7ea868078d.png
https://i-blog.csdnimg.cn/direct/360724c19d2e4ff488e27230c97daf4d.png
设置中断后,最终会在中断处理函数中调用serial8250_tx_chars将数据从环形BUF中发送出去。
9. uart驱动read
应用层调用read() 接收数据,调用到tty层的file_operations->read 即tty_read,tty_read 中会调用行规程的tty_ldisc->ops->read ,在没有数据时休眠,硬件在中断中接收数据,然后将数据存入行规程buf,行规程叫醒APP,APP被叫醒后从行规程读取数据。
代码剖析调用tty_read
https://i-blog.csdnimg.cn/direct/6a195e9448fd4368b4727e9c3f721ca5.png
https://i-blog.csdnimg.cn/direct/2f36b7bf9a8a433096e18a8c31e58606.png
行规程的n_tty_read在n_tty.c中实现。查抄是否有数据可读,无数据则进入休眠等待,等待超时时返回timeout = 0;实行break 跳出while循环。
如果有数据或等待过程中数据到了则调用canon_copy_from_read_buf 或copy_from_read_buf 读取数据,返回。
https://i-blog.csdnimg.cn/direct/3b1c22d7449a49c0a4db6d1b1318f54c.png
在串口中断中会将数据拷贝到行规程中。
https://i-blog.csdnimg.cn/direct/fb7691de2abf4408ac2cf384d1d64e99.png
https://i-blog.csdnimg.cn/direct/a4a6302835b84e68bd84171bb81d63b9.png
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
页:
[1]