Platform Driver (1)

Linux kernel中大部分设备可以归结为平台设备,因此大部分的驱动是平台驱动(patform driver)

什么是平台设备

平台设备是linux的设备模型中一类设备的抽象。 内核中的描述:

Platform devices are devices that typically appear as autonomous entities in the system. This includes legacy port-based devices and host bridges to peripheral buses, and most controllers integrated into system-on-chip platforms. What they usually have in common is direct addressing from a CPU bus. Rarely, a platform_device will be connected through a segment of some other kind of bus; but its registers will still be directly addressable.

一句话来描述就是: CPU能够直接寻址的SOC上的外设

图1:Platform设备

图1中的uart控制器,I2C控制器,GPIO控制器等,都是平台设备。

可以说,paltform设备对Linux驱动工程师是非常重要的,因为我们编写的大多数设备驱动,都是为了驱动plaftom设备。

Platform设备在内核中的实现主要包括三个部分:

  • Platform Bus,基于底层bus模块,抽象出一个虚拟的Platform bus,用于挂载Platform设备;
  • Platform Device,基于底层device模块,抽象出Platform Device,用于表示Platform设备;
  • Platform Driver,基于底层device_driver模块,抽象出Platform Driver,用于驱动Platform设备

对于图1中的I2C控制器挂载在platform Bus上,因此我们在linux kernel中常说的I2C driver,都是指I2C controller driver,都是以platform driver的形式存在,当然,对应的控制器是platform device。

与此同时,kernel抽象出I2C bus(/sys/bus/i2c),用于挂载和I2C controller通过I2C总线连接的各个I2C slave device。

串口驱动开发

驱动开发框架

得益于设备模型,Linux kernel平台驱动的开发有了一套非常固定的框架

1)模块的入口和出口
用于注册/注销platform driver,这一部分的代码基本固定,包括函数和变量的命名方式也可固定,如下:

 /* 驱动模块加载 */static int __init xxxdriver_init(void){return platform_driver_register(&xxx_driver);}/* 驱动模块卸载 */static void __exit xxxdriver_exit(void){platform_driver_unregister(&xxx_driver);}module_init(xxxdriver_init);module_exit(xxxdriver_exit);MODULE_LICENSE("GPL V2");MODULE_AUTHOR("QianRuShi-ABC");

2)platform driver

基本的platform driver包含三要素:struct platform_driver变量、probe/remove函数、用于和device tree匹配的match table,如下:

/** platform 平台驱动结构体*/
static struct platform_driver xxx_driver = {.driver = {.name = "xxx",.of_match_table = xxx_of_match,},.probe = xxx_probe,.remove = xxx_remove,
};/** platform 驱动的 probe 函数* 驱动与设备匹配成功以后此函数就会执行*/static int xxx_probe(struct platform_device *dev){match = of_match_device(xxx_of_match, &pdev->dev);if (!match) {dev_err(&pdev->dev, "Error: No device match found\n");return -ENODEV;}return 0;}static int xxx_remove(struct platform_device *dev){....../* 函数具体内容 */return 0;}/* 匹配列表 */static const struct of_device_id xxx_of_match[] = {{ .compatible = "xxx-xxx" },{ /* Sentinel */ }};

注意,xxx_of_match中的.compatible需要和DTS文件中的compatible对应,一般格式是“厂商名称,芯片系列-模块名”,例如“actions,s900-serial”

##串口驱动
串口设备(serial or uart,后面不再区分)是TTY设备的一种,Linux kernel为了方便串口驱动的开发,在TTY framework的基础上,封装了一层串口框架(serial framework)。该框架尽可能的屏蔽了TTY有关的技术细节(比较难懂),驱动工程师在编写串口驱动的时候,只需要把精力放在串口以及串口控制器本身即可。

Linux kernel serial framework位于“drivers/tty/serial”目录中,其软件架构(如下面图2所示)比较简单:

图2: serialFramework

Serial core是Serial framework的核心实现,对上封装、屏蔽TTY的技术细节,对下为具体的串口驱动提供简单、统一的编程API。
Serial drivers就是具体的串口驱动。

Serial Core

serial core主要实现如下三类功能
1)将串口设备有关的物理对象(及其操作方法)封装成一个一个的数据结构,以达到用软件语言描述硬件的目的。

2)向底层driver提供串口驱动的编程接口。

3)基于TTY framework所提供的TTY driver的编写规则,将底层driver看到的serial driver,转换为TTY driver,并将所有的serial操作,转换为对应的tty操作。

关键数据结构

struct uart_port
struct uart_state
struct uart_ops
struct uart_driver

API:

int uart_register_driver(struct uart_driver *uart);
void uart_unregister_driver(struct uart_driver *uart);int uart_add_one_port(struct uart_driver *reg, struct uart_port *port);
int uart_remove_one_port(struct uart_driver *reg, struct uart_port *port);
int uart_match_port(struct uart_port *port1, struct uart_port *port2);int uart_suspend_port(struct uart_driver *reg, struct uart_port *port);
int uart_resume_port(struct uart_driver *reg, struct uart_port *port);static inline int uart_tx_stopped(struct uart_port *port)extern void uart_insert_char(struct uart_port *port, unsigned int status,unsigned int overrun, unsigned int ch, unsigned int flag);

串口驱动的移植步骤

  • 定义并注册uart driver
  • 注册uart port
  • 定义并实现uart ops

定义并注册uart driver

static struct uart_driver imx_reg = {.owner          = THIS_MODULE,.driver_name    = DRIVER_NAME,.dev_name       = DEV_NAME,.major          = SERIAL_IMX_MAJOR,.minor          = MINOR_START,.nr             = ARRAY_SIZE(imx_ports),.cons           = IMX_CONSOLE,
};

注册uart port

platform device代表uart控制器,是实体抽象。对应的,uart port代表“串口”. 因此,我们需要在platform device probe的时候(platform driver的probe接口),动态分配并注册一个uart port(struct uart_port)。在后续的串口操作中,都是以uart port指针为对象. 见下一章节代码中i.mx6的驱动代码。

定义并实现uart ops

struct uart_ops结构包含了各式各样的uart端口的操作函数,需要在添加uart port的时候提供.见下一章节代码中i.mx6的驱动代码

完整代码

对应驱动开发框架中,1)模块的入口和出口
static int __init imx_serial_init(void)
{int ret = uart_register_driver(&imx_reg);if (ret)return ret;ret = platform_driver_register(&serial_imx_driver);if (ret != 0)uart_unregister_driver(&imx_reg);return ret;
}static void __exit imx_serial_exit(void)
{platform_driver_unregister(&serial_imx_driver);uart_unregister_driver(&imx_reg);
}module_init(imx_serial_init);
module_exit(imx_serial_exit);MODULE_AUTHOR("Sascha Hauer");
MODULE_DESCRIPTION("IMX generic serial port driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:imx-uart");
对应驱动开发框架中,2)platform driver

基本的platform driver包含三要素:struct platform_driver变量、probe/remove函数、用于和device tree匹配的match table

  • struct platform_driver变量
static struct platform_driver serial_imx_driver = {.probe       = serial_imx_probe,.remove     = serial_imx_remove,.suspend   = serial_imx_suspend,.resume       = serial_imx_resume,.id_table  = imx_uart_devtype,.driver     = {.name   = "imx-uart",.of_match_table = imx_uart_dt_ids,},
};
  • robe/remove函数

serial_imx_probe


static int serial_imx_probe(struct platform_device *pdev)
{struct imx_port *sport;void __iomem *base;int ret = 0;struct resource *res;int txirq, rxirq, rtsirq;sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);if (!sport)return -ENOMEM;ret = serial_imx_probe_dt(sport, pdev);if (ret > 0)serial_imx_probe_pdata(sport, pdev);else if (ret < 0)return ret;
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
base = devm_ioremap_resource(&pdev->dev, res);
if (IS_ERR(base))return PTR_ERR(base);rxirq = platform_get_irq(pdev, 0);
txirq = platform_get_irq(pdev, 1);
rtsirq = platform_get_irq(pdev, 2);sport->port.dev = &pdev->dev;
sport->port.mapbase = res->start;
sport->port.membase = base;
sport->port.type = PORT_IMX,
sport->port.iotype = UPIO_MEM;
sport->port.irq = rxirq;
sport->port.fifosize = 32;
sport->port.ops = &imx_pops;
sport->port.rs485_config = imx_rs485_config;
sport->port.rs485.flags =SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;
sport->port.flags = UPF_BOOT_AUTOCONF;
init_timer(&sport->timer);
sport->timer.function = imx_timeout;
sport->timer.data     = (unsigned long)sport;sport->clk_ipg = devm_clk_get(&pdev->dev, "ipg");
if (IS_ERR(sport->clk_ipg)) {ret = PTR_ERR(sport->clk_ipg);dev_err(&pdev->dev, "failed to get ipg clk: %d\n", ret);return ret;
}sport->clk_per = devm_clk_get(&pdev->dev, "per");
if (IS_ERR(sport->clk_per)) {ret = PTR_ERR(sport->clk_per);dev_err(&pdev->dev, "failed to get per clk: %d\n", ret);return ret;
}sport->port.uartclk = clk_get_rate(sport->clk_per);
if (sport->port.uartclk > IMX_MODULE_MAX_CLK_RATE) {ret = clk_set_rate(sport->clk_per, IMX_MODULE_MAX_CLK_RATE);if (ret < 0) {dev_err(&pdev->dev, "clk_set_rate() failed\n");return ret;}
}
sport->port.uartclk = clk_get_rate(sport->clk_per);/** Allocate the IRQ(s) i.MX1 has three interrupts whereas later* chips only have one interrupt.*/
if (txirq > 0) {ret = devm_request_irq(&pdev->dev, rxirq, imx_rxint, 0,dev_name(&pdev->dev), sport);if (ret)return ret;ret = devm_request_irq(&pdev->dev, txirq, imx_txint, 0,dev_name(&pdev->dev), sport);if (ret)return ret;
} else {ret = devm_request_irq(&pdev->dev, rxirq, imx_int, 0,dev_name(&pdev->dev), sport);if (ret)return ret;
}
imx_ports[sport->port.line] = sport;
platform_set_drvdata(pdev, sport);
return uart_add_one_port(&imx_reg, &sport->port);

}

serial_imx_remove


static int serial_imx_remove(struct platform_device *pdev)
{struct imx_port *sport = platform_get_drvdata(pdev);return uart_remove_one_port(&imx_reg, &sport->port);
}

  • 用于和device tree匹配的match table
static const struct of_device_id imx_uart_dt_ids[] = {{ .compatible = "fsl,imx6q-uart", .data = &imx_uart_devdata[IMX6Q_UART], },{ .compatible = "fsl,imx1-uart", .data = &imx_uart_devdata[IMX1_UART], },{ .compatible = "fsl,imx21-uart", .data = &imx_uart_devdata[IMX21_UART], },{ /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, imx_uart_dt_ids);
对应串口驱动的移植步骤中,定义并注册uart driver

在驱动init函数中注册了uart driver

int ret = uart_register_driver(&imx_reg);

在linux serial framework中,uart driver是一个平行于platform driver的概念,用于驱动“虚拟”的“串口”设备。

#define DRIVER_NAME "IMX-uart"static struct uart_driver imx_reg = {.owner          = THIS_MODULE,.driver_name    = DRIVER_NAME,.dev_name       = DEV_NAME,.major          = SERIAL_IMX_MAJOR,.minor          = MINOR_START,.nr             = ARRAY_SIZE(imx_ports),.cons           = IMX_CONSOLE,
};
对应串口驱动的移植步骤中,注册uart port

假如一个soc中有5个串口控制器(也可称作uart控制器,后面我们不再区分),每个uart控制器都可引出一个串口(uart port)。那么:
每个uart控制器,都是一个platform device,由dts文件的一个node描述。而这5个platform device,可由同一个driver驱动,即platform driver。
相对于uart控制器实实在在的存在,我们更为熟悉的串口(uart port),则是虚拟的设备,它们由“struct uart_port”描述(后面会介绍),并在platform driver的probe接口中,注册到kernel。它们可由同一个driver驱动,即这里所说的uart driver。

struct imx_port {struct uart_port    port;struct timer_list  timer;unsigned int      old_status;unsigned int     have_rtscts:1;unsigned int      dte_mode:1;unsigned int     irda_inv_rx:1;unsigned int      irda_inv_tx:1;unsigned short        trcv_delay; /* transceiver delay */struct clk       *clk_ipg;struct clk     *clk_per;const struct imx_uart_data *devdata;/* DMA fields */unsigned int       dma_is_inited:1;unsigned int        dma_is_enabled:1;unsigned int       dma_is_rxing:1;unsigned int     dma_is_txing:1;struct dma_chan      *dma_chan_rx, *dma_chan_tx;struct scatterlist   tx_sgl[2];struct imx_dma_rxbuf  rx_buf;unsigned int     tx_bytes;unsigned int       dma_tx_nents;struct delayed_work    tsk_dma_tx;wait_queue_head_t    dma_wait;unsigned int            saved_reg[10];
#define DMA_TX_IS_WORKING 1unsigned long        flags;
};

在platform driver的probe函数中会动态分配并注册一个uart port(struct uart_port)然后初始化并注册其中的port变量。初始化完之后,直接调用uart_add_one_port接口,将该port添加到kernel serial core

再返回上面的static int serial_imx_probe(struct platform_device *pdev)展开看到

//分配struct xxx_port类型的指针
struct imx_port *sport;
sport = devm_kzalloc(&pdev->dev, sizeof(*sport), GFP_KERNEL);
//获取中断号,该串口对应的中断号, 一般是从DTS中解析得到的;rxirq = platform_get_irq(pdev, 0);txirq = platform_get_irq(pdev, 1);rtsirq = platform_get_irq(pdev, 2);
//初始化并注册其中的port变量sport->port.dev = &pdev->dev;sport->port.mapbase = res->start;sport->port.membase = base;sport->port.type = PORT_IMX,sport->port.iotype = UPIO_MEM;sport->port.irq = rxirq;sport->port.fifosize = 32;sport->port.ops = &imx_pops;sport->port.rs485_config = imx_rs485_config;sport->port.rs485.flags =SER_RS485_RTS_ON_SEND | SER_RS485_RX_DURING_TX;sport->port.flags = UPF_BOOT_AUTOCONF;init_timer(&sport->timer);sport->timer.function = imx_timeout;sport->timer.data     = (unsigned long)sport;

在proble函数的最后,调用uart_add_one_port接口,将该port添加到kernel serial core
return uart_add_one_port(&imx_reg, &sport->port);

对应串口驱动的移植步骤中,定义并实现uart ops

上面platform driver的probe函数serial_imx_probe,展开中看到
sport->port.ops = &imx_pops;
struct uart_ops结构包含了各式各样的uart端口的操作函数,需要在添加uart port的时候提供。

static struct uart_ops imx_pops = {.tx_empty    = imx_tx_empty,.set_mctrl  = imx_set_mctrl,.get_mctrl = imx_get_mctrl,.stop_tx   = imx_stop_tx,.start_tx    = imx_start_tx,.stop_rx    = imx_stop_rx,.enable_ms   = imx_enable_ms,.break_ctl = imx_break_ctl,.startup   = imx_startup,.shutdown    = imx_shutdown,.flush_buffer   = imx_flush_buffer,.set_termios    = imx_set_termios,.type        = imx_type,.config_port    = imx_config_port,.verify_port = imx_verify_port,
#if defined(CONFIG_CONSOLE_POLL).poll_init      = imx_poll_init,.poll_get_char  = imx_poll_get_char,.poll_put_char  = imx_poll_put_char,
#endif
};

个人公众号交流

#参考
http://www.wowotech.net/x_project/serial_driver_porting_1.html
http://www.wowotech.net/x_project/serial_driver_porting_2.html
http://www.wowotech.net/x_project/serial_driver_porting_3.html
http://www.wowotech.net/x_project/serial_driver_porting_4.html

http://www.wowotech.net/comm/serial_overview.html

Day5: platformDriver-1相关推荐

  1. Spark菜鸟学习营Day5 分布式程序开发

    Spark菜鸟学习营Day5 分布式程序开发 这一章会和我们前面进行的需求分析进行呼应,完成程序的开发. 开发步骤 分布式系统开发是一个复杂的过程,对于复杂过程,我们需要分解为简单步骤的组合. 针对每 ...

  2. 《从零开始学Swift》学习笔记(Day5)——我所知道的标识符和关键字

     Swift 2.0学习笔记(Day5)--我所知道的标识符和关键字   原创文章,欢迎转载.转载请注明:关东升的博客 好多计算机语言都有标识符和关键字,一直没有好好的总结,就是这样的用着,现在小 ...

  3. Python学习day5作业-ATM和购物商城

    Python学习day5作业 Python学习day5作业 ATM和购物商城 作业需求 ATM: 指定最大透支额度 可取款 定期还款(每月指定日期还款,如15号) 可存款 定期出账单 支持多用户登陆, ...

  4. Python学习day5作业

    目录 Python学习day5作业 ATM和购物商城 1. 程序说明 2. 基本流程图 3. 程序测试帐号 4. 程序结构: 5. 程序测试 title: Python学习day5作业 tags: p ...

  5. Java基础day5

    Java基础day5 Java基础day5 1 方法的定义和调用 2 无参数方法的练习 3. 带参数方法的定义和调用 3.1 带参数放的定义和调用 3.2 形参和实参 3.3 带参数方法练习 4 带返 ...

  6. SpaceEmacs Rock Day5 学习笔记

    <?xml version="1.0" encoding="utf-8"?> SpaceEmacs Rock Day5 学习笔记 SpaceEmac ...

  7. 8.8 正睿暑期集训营 Day5

    目录 2018.8.8 正睿暑期集训营 Day5 总结 A 友谊巨轮(线段树 动态开点) B 璀璨光滑 C 构解巨树 考试代码 A B C 2018.8.8 正睿暑期集训营 Day5 时间:3.5h( ...

  8. #6073. 「2017 山东一轮集训 Day5」距离(树链剖分 + 永久标记主席树)

    #6073. 「2017 山东一轮集训 Day5」距离 给定一颗有nnn个节点带边权的树,以及一个排列ppp,path(u,v)path(u, v)path(u,v)为u,vu, vu,v路径上的点集 ...

  9. python atm作业详解_Python学习day5作业-ATM和购物商城

    Python学习day5作业 Python学习day5作业 ATM和购物商城 作业需求 ATM:指定最大透支额度 可取款 定期还款(每月指定日期还款,如15号) 可存款 定期出账单 支持多用户登陆,用 ...

  10. Java核心篇之泛型--day5

    Java核心篇之泛型–day5 泛型是JDK5时引入的一个新特性,泛型提供了编译时类型安全检查的机制,该机制允许程序猿在编译时检测到非法的类型输入. 泛型的本质是参数化类型,也就是说操作的类型被指定为 ...

最新文章

  1. AgileEAS.NET SOA 中间件2013第四季度发布部分功能开源预告
  2. 腾讯8篇AI医疗论文入选国际学术顶会,涵盖癌症图像分类、CT病灶检测等领域...
  3. ad20如何画出pcb板大小_NAKANISHI分板机主轴在PCB分板上切削案例
  4. Elasticsearch核心知识图谱
  5. linux批量创建系统,linux系统批量创建用户
  6. MySQL数据监控案例_zabbix 监控多实例mysql
  7. LTE系统各种指标概念(EVM,ACLR)
  8. 转载 DOS/BAT批处理if exist else 语句的几种用法
  9. 谷歌翻译退出中国后如何恢复 Chrome 浏览器翻译的正常使用
  10. html 简繁转换 批量,[推荐]几行代码轻松搞定网页的【简繁转换】
  11. 增量表、全量表、拉链表的应用场景及优缺点详解
  12. ActivityThread: Performing stop of activity that is not resumed:
  13. vue@2.6.14购物车统计总价
  14. java全栈系列之JavaSE--命令行传递参数与可变参数021
  15. 云服务器php文件怎么运行,云服务器php文件怎么运行环境
  16. 静观花开花落,笑看云卷云舒
  17. 08.animation-----06.缩放
  18. TR200 ASSSD跑分测试
  19. 单闭环调速仿真matlab,利用Matlab仿真平台设计单闭环直流调速系统
  20. a = a++ 与 a = ++a

热门文章

  1. 前端移动端的rem适配计算原理
  2. [问题已处理]-kubernetes中2次不同的oom处理
  3. echarts世界地图更新,添加十段线
  4. 项目实训记录(四)——前端date类型和数据库timestamp类型比较
  5. 【东大自控笔记5】拉氏变换的基本性质
  6. 一个粗糙的二人贪吃蛇联机版实现
  7. StarGAN——生成你的明星脸
  8. try的动词用法_try是什么意思?try的用法是什么?
  9. 关于java.lang.NoClassDefFoundError: org/springframework/core/metrics/ApplicationStartup问题的解决方法
  10. 下班后如何进行自我提升?掌握这六种技能,你的人生会从此开挂