Linux CAN子系统:基于M_CAN解读
Linux下CAN驱动属于网络设备驱动。
CAN在内核中大致框架如下:
1 CAN配置
对CAN设备的配置,首先需要打开CAN子系统,然后打开CAN设备驱动,其他还包括调试信息等。
Networking support
->CAN bus subsystem support
->Raw CAN Protocol (raw access with CAN-ID filtering)--支持BSD socket API方位CAN总线。
->Broadcast Manager CAN Protocol (with content filtering)--
->CAN Gateway/Router (with netlink configuration)--路由CAN帧。
->CAN Device Drivers--CAN设备驱动。
->Platform CAN drivers with Netlink support--支持Netlink的CAN platform总线通用框架。
->CAN bit-timing calculation
->Bosch M_CAN support--Bosch M_CAN驱动。
->Bosch M_CAN support for io-mapped devices
->CAN devices debugging messages--CAN设备调试信息。
CAN子系统初始化包括:网络设备回调通知处理LED trigger名称;注册Rtnetlink处理函数。
can_dev_init
->can_led_notifier_init--注册对NETDEV_CHANGENAME消息的响应处理函数。
->register_netdevice_notifier
->can_led_notifier
->rtnl_link_register--Rtnetlink基于netlink,允许对内核的路由表进行读写。主要用来进行内核与用户空见得通信以及内核中子系统之间的通信。操作函数集为can_link_ops。
can_dev_exit
->rtnl_link_unregister
->can_led_notifier_exit
can模块:
- 创建can_receiver缓存。
- 注册PF_CAN协议族。
- 增加ETH_P_CAN和ETH_P_CANFD两种类型packet处理。
can_init
->kmem_cache_create--创建can_receiver缓存。
->register_pernet_subsys
->sock_register--注册PF_CAN协议族。
->register_netdevice_notifier
->dev_add_pack--增加ETH_P_CAN/ETH_P_CANFD两种packet处理。
can_gw模块,带Netlink接口的CAN帧Gateway/Router/Bridge初始化:
cgw_module_init--创建can_gw模块。
->register_pernet_subsys--操作函数集为cangw_pernet_ops。
->register_netdevice_notifier
->rtnl_register_module--注册CAN协议的RTM_GETROUTE/RTM_NEWROUTE/RTM_DELROUTE消息处理。
2 CAN文件
CAN子系统文件主要包括Core实现和各驱动文件。
drivers/net/can/
├── dev.c--CAN子系统Core函数实现。 ├── m_can │ ├── m_can.c--通用M_CAN设备驱动接口。 │ ├── m_can_platform.c--基于M_CAN IP的platform驱动。 ├── rx-offload.c--通过使能NAPI对CAN接收offload。
CAN网络相关文件如下:
net/can/ ├── af_can.c--CAN Address Family模块初始化。 ├── bcm.c--过滤或发送CAN内容广播管理。 ├── gw.c--CAN Gateway/Router/Bridge处理。 ├── proc.c--can和can-bcm的proc节点。 ├── raw.c--SOCK_RAW类型的CAN协议。
3 CAN子系统数据结构和API
struct can_frame表示CAN总线通信的基本单位内容。
struct can_frame { canid_t can_id; /* 32 bit CAN_ID + EFF/RTR/ERR flags */ __u8 can_dlc; /* frame payload length in byte (0 .. CAN_MAX_DLEN) */ __u8 __pad; /* padding */ __u8 __res0; /* reserved / padding */ __u8 __res1; /* reserved / padding */ __u8 data[CAN_MAX_DLEN] __attribute__((aligned(8)));--表示携带的数据。 };
3.1 分配和释放CAN设备
alloc_candev/alloc_candev_mqs分配并配置CAN网络设备。free_candev释放CAN网络设备空间。
alloc_candev_mqs
->alloc_netdev_mqs--分配一个net_device,并创建tx/rx队列。
->can_setup--对设备的初始化回调函数。
->初始化restart_work,函数为can_restart()。
->can_flush_echo_skb
->alloc_can_err_skb
->netif_rx
->netif_carrier_on
3.2 注册和注销CAN设备
register_candev/unregister_candev用于注册或注销CAN设备。
int register_candev(struct net_device *dev); void unregister_candev(struct net_device *dev);
CAN设备是一个网络设备,注册到网络子系统中:
register_candev
->初始化网络设备rtnl_link_ops为can_link_ops。
->netif_carrier_off
->register_netdev
unregister_candev
->unregister_netdev
CAN作为网络设备的Rtnetlink操作函数如下:
static struct rtnl_link_ops can_link_ops __read_mostly = { .kind = "can", .maxtype = IFLA_CAN_MAX, .policy = can_policy, .setup = can_setup, .validate = can_validate, .newlink = can_newlink, .changelink = can_changelink, .dellink = can_dellink, .get_size = can_get_size, .fill_info = can_fill_info, .get_xstats_size = can_get_xstats_size, .fill_xstats = can_fill_xstats, };
3.3 CAN设备操作
CAN Core提供如下核心函数给驱动调用:
int open_candev(struct net_device *dev); void close_candev(struct net_device *dev); int can_change_mtu(struct net_device *dev, int new_mtu); int can_restart_now(struct net_device *dev); void can_bus_off(struct net_device *dev);
4 M_CAN驱动
M_CAN是Bosch提供的支持CAN/CAN FD(Flexible Data-rate)协议控制器。官方说明参考《M_CAN》,用户手册参考《M_CAN User's Manual》。
4.1 M_CAN dts配置
soc { m_can1: can@4400e000 { compatible = "bosch,m_can"; reg = <0x4400e000 0x400>, <0x44011000 0x1400>; reg-names = "m_can", "message_ram"; interrupts = <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 21 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "int0", "int1"; clocks = <&scmi0_clk CK_SCMI0_HSE>, <&rcc FDCAN_K>; clock-names = "hclk", "cclk"; bosch,mram-cfg = <0x0 0 0 32 0 0 2 2>; status = "disabled"; }; m_can2: can@4400f000 { compatible = "bosch,m_can"; reg = <0x4400f000 0x400>, <0x44011000 0x2800>; reg-names = "m_can", "message_ram"; interrupts = <GIC_SPI 20 IRQ_TYPE_LEVEL_HIGH>, <GIC_SPI 22 IRQ_TYPE_LEVEL_HIGH>; interrupt-names = "int0", "int1"; clocks = <&scmi0_clk CK_SCMI0_HSE>, <&rcc FDCAN_K>; clock-names = "hclk", "cclk"; bosch,mram-cfg = <0x1400 0 0 32 0 0 2 2>; status = "disabled"; }; };
4.2 M_CAN驱动程序
M_CAN驱动初始化主要包括:
- 获取时钟、中断、寄存器等。
- 使能时钟,获取版本;然后创建net_device并注册。
- 解析共享内存配置。
- 初始化LED。
- 获取max-bitrate信息。
m_can_plat_driver
->m_can_plat_probe
->devm_clk_get--获取时钟hclk和cclk。
->platform_get_resource_byname--获取寄存器m_can,获取共享消息内存message_ram。
->获取"bosch,mram-cfg"配置参数。
->alloc_candev--
->netdev_priv--找到net_device私有数据,然后再初始化。
->m_can_clk_start--使能时钟,后续读取M_CAN版本号。
->m_can_dev_setup
->m_can_check_core_release--检查M_CAN版本号。
->netif_napi_add--把net_device和NAPI绑定,当需要轮询是调用m_can_poll函数。
->register_m_can_dev
->register_candev--注册CAN网络设备,设备操作函数集为m_can_netdev_ops。
->m_can_of_parse_mram--解析从bosch,mram-cfg中获取的额参数。
->devm_can_led_init
->of_can_transceiver--获取当前CAN的max-bitrate。
->m_can_plat_remove
->unregister_m_can_dev--去注册net_device设备。
->free_candev--释放net_device资源。
4.2.1 M_CAN poll处理
m_can_poll是使用poll方式读取M_CAN数据:
m_can_poll--根据读取到的irqstatus进行处理。
->m_can_handle_state_errors--处理错误状态。
->m_can_handle_bus_errors--处理总线错误。
->m_can_do_rx_poll--轮询读取CAN数据并向上层传输。
->判断CAN fifo中是否有数据。
->m_can_read_fifo
->alloc_canfd_skb/alloc_can_skb--根据是否CAN或者CANFD选择不同的分配skb函数。
->读取寄存器填充CAN帧数据。
->响应rx fifo 0.
->netif_receive_skb--将接收到的CAN帧上传处理。
->m_can_read--读取RX FIFO状态,以供是否继续读取提供依据。
4.2.2 M_CAN net_device操作函数
m_can_netdev_ops是M_CAN作为net_device的操作函数集:
static const struct net_device_ops m_can_netdev_ops = { .ndo_open = m_can_open,--打开M_CAN设备。 .ndo_stop = m_can_close,--关闭M_CAN设备。 .ndo_start_xmit = m_can_start_xmit,--通过M_CAN收发送数据。 .ndo_change_mtu = can_change_mtu,--M_CAN设备一次最大传输单元。 };
m_can_open打开CAN设备并准备好数据传输:
- 打开时钟。
- 打开CAN设备。
- 注册中断处理函数。
- 使能CAN控制器。
- 使能NAPI以及队列。
m_can_open
->m_can_clk_start--使能M_CAN时钟。
->open_candev
->request_irq
->m_can_isr--M_CAN中断处理函数。
->m_can_start
->can_led_event
->napi_enable
->netif_start_queue
m_can_start_xmit将数据通过M_CAN发送出去:
m_can_start_xmit
->can_dropped_invalid_skb--放弃无效skb。
->m_can_tx_fifo_full--检查CAN FIFO是否满。
->m_can_fifo_write--填充CAN Frame。
->can_put_echo_skb
->m_can_write--触发CAN进行传输。
5 CAN调试节点
/proc/net/can调试节点如下:
/proc/net/can |-- rcvlist_all |-- rcvlist_eff |-- rcvlist_err |-- rcvlist_fil |-- rcvlist_inv |-- rcvlist_sff |-- reset_stats |-- stats `-- version
6 CAN相关调试程序
查看CAN网络接口:ifconfig -a。
can0 Link encap:UNSPEC HWaddr 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00 NOARP MTU:16 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:10 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B) Interrupt:58
设置CAN接口速率:
ip link set can0 type can bitrate 1000000
打开can0网卡:
ifconfig can0 up
接收CAN数据:
candump can0--从CAN总线接口接收数据并以十六进制形式打印出来。
发送CAN数据,帧号为0x5A1,数据内容为16进制8个字节11.22.33.44.55.66.77.88:
cansend can0 5A1#11.22.33.44.55.66.77.88--往指定的CAN接口发送数据。
关闭can0:
ifconfig can0 down
其他的can相关工具还包括:
can-calc-bit-timing - 计算CAN时序参数。
cangen - 生成一个CAN帧。
canbusload - 监控CAN总线负荷。
cangw - 管理CAN网关。
canfdtest - CAN FD测试程序。
cansequence - 往指定CAN接口自动重复递增数字,也可以指定接收并校验检查接收的递增数字。
以及canplayer、canlogserver、cansniffer。