Enea LINX代码分析之二(ECM_RX)
ECM是Ethernet Connection Manager的简称,也就是LINX协议直接运行在链路层之上,不经过TCP/IP层的协议栈,处理效率更高,本文将对其代码进行分析。ecm也是以一个内核驱动模块(linx_eth_cm.ko)的形式存在,如果需要使用LINX Over Eth,除了insmod linx.ko之外,也需要insmod linx_eth_cm.ko。
作为一个内核驱动模块,首先看看其初始化函数。
1 static int __init ecm_init(void) 2 { 3 int status; 4 5 spin_lock_init(&ecm_lock); 6 memset(ecm_connection_array, 0, sizeof(ecm_connection_array)); 7 8 INIT_LIST_HEAD(&ecm_device_list); 9 INIT_LIST_HEAD(&ecm_orphan_list); 10 11 init_waitqueue_head(&ecm_waitq); 12 13 ecm_workq = create_singlethread_workqueue("ecm"); 14 if (ecm_workq == NULL) 15 return -ENOMEM; 16 17 status = register_netdevice_notifier(&ecm_notifier); 18 if (status < 0) { 19 destroy_workqueue(ecm_workq); 20 return status; 21 } 22 23 db_add_template(DB_KEY_ETHCM, &ecm_template); 24 db_proc_add(DB_KEY_ETHCM); 25 26 setup_timer(&ecm_release_cid_timer, ecm_release_cid, 0); 27 mod_timer(&ecm_release_cid_timer, tmo_ms(5000)); 28 29 return 0; 30 } 31 module_init(ecm_init);
初始化主要做下面几件事情:
- 初始化eth设备链表,因为设备上可能有多个eth设备,例如eth0,lo等。
- 创建一个只有一个线程的工作队列,作为一种可以阻塞的下半部机制用于处理各种工作。
- 注册 netdevice事件处理函数。
- 将eth的create()/destroy()函数加入到db中,主要在用于调用mkethcon的时候执行。
- 创建一个定时器,功能暂时不清楚。
下面主要看下对通知链的处理,注册对ECM_PROTOCOL(0x8911)协议数据的处理函数ecm_rx()。
1 static int net_event(struct notifier_block *nb, unsigned long event, void *data); 2 static struct notifier_block ecm_notifier = { 3 .notifier_call = net_event, 4 }; 5 6 7 static int net_event(struct notifier_block *nb, unsigned long event, void *data) 8 { 9 struct net_device *dev; 10 struct ecm_work *w; 11 struct ecm_work_net_event *p; 12 13 14 (void)nb; 15 dev = data; 16 17 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26)) 18 if (dev_net(dev) != &init_net) 19 return NOTIFY_DONE; 20 #endif 21 22 w = alloc_ecm_work(sizeof(*p), ECM_WORK_NET_EVENT, GFP_KERNEL); 23 if (w == NULL) 24 return NOTIFY_DONE; 25 26 p = w->data; 27 p->event = event; 28 p->dev = dev; 29 30 /* 31 * We need to call dev_hold(dev) before returning from this function, 32 * alloc_ecm_device() takes care of this. Note that dev_put(dev) must 33 * be done from the work queue. 34 */ 35 if (event == NETDEV_REGISTER) { 36 p->ecm_dev = alloc_ecm_device(dev); 37 if (p->ecm_dev == NULL) { 38 free_ecm_work(w); 39 return NOTIFY_DONE; 40 } 41 } 42 queue_work(ecm_workq, &w->work); 43 44 return NOTIFY_OK; 45 }
该函数主要处理从eth设备通知的各种事件,例如NETDEV_UP/NETDEV_DOWN/NETDEV_REGISTER/NETDEV_UNREGISTER等事件。驱动模块加载以后,内核即对当前系统中支持的每个eth设别通知这些事件,对事件的处理,将放到工作队列中执行。
下面是workqueue的处理入口,net_event中的ECM_WORK_NET_EVENT工作在这里由handle_work_net_event()处理。届此,发往本机的协议号为0x8911的数据包可以被接收。
1 ic void ecm_workq_func(struct work_struct *w) 2 { 3 struct ecm_work *p; 4 5 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)) 6 log_ecm_work(p = container_of(w, struct ecm_work, work)); 7 #else 8 log_ecm_work(p = (struct ecm_work *)w); 9 #endif 10 11 switch (p->opcode) { 12 case ECM_WORK_CONN_TMO: 13 handle_work_conn_tmo(p); 14 break; 15 case ECM_WORK_CONN_PKT: 16 handle_work_conn_pkt(p); 17 break; 18 case ECM_WORK_NET_EVENT: 19 handle_work_net_event(p); 20 break; 21 case ECM_WORK_CREATE: 22 handle_work_create(p); 23 break; 24 case ECM_WORK_DESTROY: 25 handle_work_destroy(p); 26 break; 27 case ECM_WORK_CLEANUP: 28 handle_work_cleanup(p); 29 break; 30 case ECM_WORK_DC_INIT: 31 handle_work_dc_init(p); 32 break; 33 case ECM_WORK_DC_FINI: 34 handle_work_dc_fini(p); 35 break; 36 case ECM_WORK_DC_CONN: 37 handle_work_dc_conn(p); 38 break; 39 case ECM_WORK_DC_DISC: 40 handle_work_dc_disc(p); 41 break; 42 case ECM_WORK_DISC: 43 handle_work_disc(p); 44 break; 45 default: 46 ERROR(); /* FIXME: just for now... */ 47 free_ecm_work(p); 48 break; 49 } 50 }
在netdevice_event中,最重要的不过NETDEV_REGISTER事件。这里主要调用start_ecm_device().该函数中调用dev_add_pack()注册对
1 static void handle_work_net_event(struct ecm_work *w) 2 { 3 struct ecm_work_net_event *p; 4 5 p = w->data; 6 7 switch (p->event) { 8 case NETDEV_REGISTER: 9 handle_netdev_register(p->dev, p->ecm_dev); 10 break; 11 case NETDEV_UNREGISTER: 12 handle_netdev_unregister(p->dev); 13 break; 14 case NETDEV_UP: 15 break; /* CONN_TMO takes care of this... */ 16 case NETDEV_DOWN: 17 handle_netdev_down(p->dev); 18 break; 19 case NETDEV_CHANGEMTU: 20 report_netdev_unsupported(p->dev, "NETDEV_CHANGEMTU"); 21 break; 22 case NETDEV_CHANGEADDR: 23 report_netdev_unsupported(p->dev, "NETDEV_CHANGEADDR"); 24 break; 25 case NETDEV_CHANGENAME: 26 report_netdev_unsupported(p->dev, "NETDEV_CHANGENAME"); 27 break; 28 default: 29 /* 30 * Silently ignore all other events, e.g. NETDEV_CHANGE, 31 * NETDEV_FEAT_CHANGE, NETDEV_GOING_DOWN and NETDEV_REBOOT. 32 */ 33 break; 34 } 35 36 free_ecm_work(w); 37 } 38 39 40 static void handle_netdev_register(struct net_device *dev, 41 struct ecm_device *ecm_dev) 42 { 43 struct list_head *item, *tmp; 44 struct RlnhLinkObj *co; 45 46 /* 47 * Any orphan connections? Move them to device's conn list before 48 * starting the device. No conn_list lock is needed since RX won't 49 * get any Linx packet, i.e. dev_add_pack hasn't been called. 50 */ 51 list_for_each_safe(item, tmp, &ecm_orphan_list) { 52 co = list_entry(item, struct RlnhLinkObj, node); 53 if (strcmp(co->dev_name, ecm_dev->dev->name) == 0) { 54 co->ecm_dev = ecm_dev; 55 list_move_tail(&co->node, &ecm_dev->conn_list); 56 } 57 } 58 59 /* Enable RX and TX... */ 60 start_ecm_device(ecm_dev); 61 } 62 63 64 static void start_ecm_device(struct ecm_device *p) 65 { 66 struct list_head *item; 67 struct RlnhLinkObj *co; 68 69 printk(KERN_INFO "start_ecm_device\n"); 70 71 list_add(&p->node, &ecm_device_list); 72 73 /* 74 * Reset transmit lock, i.e. allow TX to use the struct net_device 75 * object. RX won't get any LINX packets and destroy-work makes 76 * the connection in-visible, i.e. no lock is needed. 77 */ 78 list_for_each(item, &p->conn_list) { 79 co = list_entry(item, struct RlnhLinkObj, node); 80 reset_ecm_lock(&co->tx_lock, 1); 81 } 82 83 /* RX callback will get LINX packets... */ 84 dev_add_pack(&p->pt); 85 }
总结下,这个初始化注册流程为:
net_event()-->ecm_workq_func()-->handle_work_net_event()-->handle_netdev_register()-->start_ecm_device()-->dev_add_pack()。
下面简介下如何将参数传递给ecm_workq_func()的。
首选需要调用alloc_ecm_work分配一个ecm_work "w",然后将该工作类型对应的数据存入到w->data中。接着将w->work放入到工作队列中,工作队列处理函数再用
container_of() 函数找到 w,接着使用w->data。
1 static int net_event(struct notifier_block *nb, unsigned long event, void *data) 2 { 3 struct net_device *dev; 4 struct ecm_work *w; 5 struct ecm_work_net_event *p; 6 w = alloc_ecm_work(sizeof(*p), ECM_WORK_NET_EVENT, GFP_KERNEL); 7 if (w == NULL) 8 return NOTIFY_DONE; 9 10 p = w->data; 11 p->event = event; 12 p->dev = dev; 13 14 queue_work(ecm_workq, &w->work); 15 16 return NOTIFY_OK; 17 } 18 19 20 static void ecm_workq_func(struct work_struct *w) 21 { 22 struct ecm_work *p; 23 24 #if (LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,20)) 25 log_ecm_work(p = container_of(w, struct ecm_work, work)); 26 #else 27 log_ecm_work(p = (struct ecm_work *)w); 28 #endif 29 }