轉載請注明出處:http://blog.csdn.net/Righthek 謝謝!
CAN總線原理
由於Socket CAN涉及到CAN總線協議、套接字、Linux網絡設備驅動等。因此,為了能夠全面地了解Socket CAN的原理。我們需要了解以下幾個方面的知識點:
(1)CAN總線協議;
(2)Socket原理;
(3)Linux網絡設備驅動;
當熟悉以下三個方面的知識點后,我們再去分析基於Linux的Socket CAN的驅動。這樣的話理解起來更加容易、易懂。
(4)Socket CAN的驅動;
一、CAN總線協議
由於CAN總線協議的內容太多,作為博文來說,不適宜很詳細的講解。需要深入了解的朋友們可以Google一下。以下只是作些簡要的說明。
CAN是ControllerArea Network(控制器局域網)的縮寫。CAN通信協議在1986年由德國電氣商博世公司所開發,主要面向汽車的通信系統。現已是ISO國際標准化的串行通信協議。根據不同的距離、不同的網絡,可配置不同的速度,最高速度為1MBit/s。
CAN被細分為三個層次:
(1)CAN對象層(the object layer);
(2)CAN傳輸層(the transfer layer);
(3)CAN物理層(the phyical layer);
對象層和傳輸層包括所有由ISO/OSI模型定義的數據鏈路層的服務和功能。
對象層的作用范圍包括:
(1)查找被發送的報文。
(2)確定由實際要使用的傳輸層接收哪一個報文。
(3)為應用層相關硬件提供接口。
傳輸層的作用主要:
(1)傳送規則,也就是控制幀結構、執行仲裁、錯誤檢測、出錯標定、故障界定。
(2)總線上什么時候開始發送新報文及什么時候開始接收報文,均在傳輸層里確定。
(3)位定時的一些普通功能也可以看作是傳輸層的一部分。
(4)傳輸層的修改是受到限制的。
物理層的作用:
在不同節點之間根據所有的電氣屬性進行位信息的實際傳輸。當然,同一網絡內,物理層對於所有的節點必須是相同的。盡管如此,在選擇物理層方面還是很自由的。
CAN具有以下的屬性:
(1)報文(Messages):簡單來說就是具有固定格式的數據包。
(2)信息路由(Information Routing):即,報文尋找結點的方式。
(3)位速率(Bit rate):數據位的傳輸速度。
(4)優先權(Priorities):即報文發送的優先權。
(5)遠程數據請求(Remote Data Request):通過發送遠程幀,需要數據的節點可以請求另一節點發送相應的數據幀。
(6)多主機(Multimaster):總線空閑時,任何結點都可以開始傳送報文。
(7)仲裁(Arbitration):當2個及以上的單元同時開始傳送報文,那么就會有總線訪問沖突。仲裁是確定哪個單元的具有發送優先權。
(8)安全性(Safety):CAN的每一個節點均采取了強有力的措施以進行錯誤檢測、錯誤標定及錯誤自檢。
(9)錯誤檢測(Error Detection):包括監視、循環冗余檢查、位填充、報文格式檢查。
(10)錯誤檢測的執行(Performance of Error Detection)
(11)錯誤標定和恢復時間(Error Sinalling and Recovery Time):任何檢測到錯誤的結點會標志出已損壞的報文。此報文會失效並將自動地開始重新傳送。如果不再出現新的錯誤,從檢測到錯誤到下一報文的傳送開始為止,恢復時間最多為29個位的時間。
(12)故障界定(Fault Confinement):CAN結點能夠把永久故障和短暫擾動區分開來。永久故障的結點會被關閉。
(13)連接(Connections):CAN串行通訊鏈路是可以連接許多結點的總線。理論上,可連接無數多的結點。但由於實際上受延遲時間或者總線線路上電氣負載的影響,連接結點的數量是有限的。
(14)單通道(Single Channel):總線是由單一進行雙向位信號傳送的通道組成。
(15)總線值(Bus value):總線可以具有兩種互補的邏輯值之一:“顯性”(可表示為邏輯0)或“隱性”(可表示為邏輯1)。
(16)應答(Acknowledgment):所有的接收器檢查報文的連貫性。對於連貫的報文,接收器應答;對於不連貫的報文,接收器作出標志。
(17) 睡眠模式/喚醒(Sleep Mode / Wake-up):為了減少系統電源的功率消耗,可以將CAN器件設為睡眠模式以便停止內部活動及斷開與總線驅動器的連接。CAN器件可由總線激活,或系統內部狀態而被喚醒。
1、CAN總線的報文格式
CAN傳輸的報文,可分為五種類型:
(1)數據幀:用於發送結點向接收結點傳送數據的幀。
(2)遠程幀:總線結點發出遠程幀,請求發送具有同一識別符的數據幀。
(3)錯誤幀:任何結點檢測到一總線錯誤就發出錯誤幀。
(4)過載幀:過載幀用以在先行的和后續的數據幀(或遠程幀)之間提供一附加的延時。
(5)幀間隔:用於將數據幀及遠程幀與前面的幀分離開來的幀。
數據幀由7個不同的位場組成:
幀起始 仲裁場 控制場 數據場 CRC場 ACK場 幀結束
數據幀有標准格式和和遠程格式,以下是其格式表示:
圖2 數據幀格式
遠程幀由6個不同的位場組成:
幀起始 仲裁場 控制場 CRC場 ACK場 幀結束
遠程幀沒有數據幀的數據場,以下是其格式表示:
圖3 遠程幀格式
錯誤幀用於在接收和發送消息時檢測出錯誤,通知錯誤的幀。錯誤幀由錯誤標志和錯誤界定符構成。
錯誤標志包括主動錯誤標志和被動錯誤標志兩種。
主動錯誤標志:6個位的顯性位,處於主動錯誤狀態的單元檢測出錯誤時輸出的錯誤標志。
被動錯誤標志:6個位的隱性位,處於被動錯誤狀態的單元檢測出錯誤時輸出的錯誤標志。
錯誤界定符由8個位的隱性位構成。
錯誤幀格式如下表示:
圖4 錯誤幀格式
過載幀是用於接收單元通知其尚未完成接收准備的幀。過載幀由過載標志和過載界定符構成。過載幀格式如下表示:
圖5 過載幀格式
幀間隔是用於分隔數據幀和遠程幀的幀。數據幀和遠程幀可通過插入幀間隔將本幀與前面的任何幀(數據幀、遠程幀、錯誤幀、過載幀)分開。過載幀和錯誤幀前不能插入幀間隔。幀間隔如下圖所示:
圖6 幀間隔格式
2、CAN總線的仲裁方式
在總線空閑態,最先開始發送消息的單元獲得發送權。多個單元同時開始發送時,各發送單元從仲裁段的第一位開始進行仲裁。連續輸出顯性電平最多的單元可繼續發送。即逐位地對比各個結點發出的報文ID。由於線與的關系,顯示位“0”可以覆蓋隱性位“1”,因此ID最小的節點贏得仲裁,總線上表現為該結點的報文,其他結點失去仲裁,退出發送,轉為接收狀態。
標准格式ID與具有相同ID的遠程幀或者擴展格式的數據幀在總線上競爭時,標准格式的RTR位為顯性位的具有優先權,可繼續發送。
圖7 仲裁方式
3、位填充(BitStuffing)
位填充是為了防止突發錯誤而設定的功能。位填充的規則如下:
(1)5位連續相同電平之后,必須填充一位反向位,即不允許有6個連續相同位;
(2)SOF之前為總線空閑狀態,不需要同步,因此不需要位填充;
(3)CRC之后為固定格式,不允許填充;
(4)由CAN控制器自動實現;
4、CAN的錯誤處理
CAN控制器檢測錯誤共有以下5種:
(1)位填充錯誤;
在使用位填充的幀場內,結點如果檢測到6個連續相同的位值,則產生位填充錯誤,在下一位開始時,該結點將發送一個錯誤幀。
(2)位錯誤;
在發送期間,結點檢測到總線的位值與自身發送的位值不一致時,則產生位錯誤,在下一位開始時,該結點將發送一個錯誤幀。
(3)CRC錯誤;
接收結點計算的CRC碼與數據幀本身自帶的CRC碼不一致,接收結點將丟棄該幀,並在ACK界定符之后發送一個錯誤幀。
(4)應答錯誤;
發送結點在ACK Slot位會發送隱性位,同時監聽總線是否為顯性位,如果是顯性位,則表明至少一個節點正確收到該幀;如果是隱性位,將產生ACK錯誤,發送結點發送一個錯誤幀。
(5)格式錯誤;
發送結節在(CRC界定符、ACK界定符、幀結束EOF)固定格式的位置檢測到顯性位時,將發生格式錯誤,並發送一個錯誤幀。
5、CAN總線同步
CAN總線的通信方式為NRZ方式。各個位的開關或者結尾都沒有附加同步信號。發送結點以與位時序同步的方式開始發送數據。另外,接收結點根據總線上電平的變化進行同步並進行接收工作。
但是,發送結點和接收結點存在的時鍾頻率誤差及傳輸路徑上的(電纜、驅動器等)相位延遲會引進同步偏差。因此接收結點需要通過同步的方式調整時序進行接收。
同步的作用是盡量使本地位時序與總結信號的位時序一致(本地同步段與總結信號邊沿同步)。只有接收結點需要同步;同步只會發生在隱性到顯性電平的跳沿。
同步的方式為硬件同步和再同步。
以上是CAN總線協議的一些簡要總結,不足之處,請指出,謝謝!下一篇文章將開始Socket原理的講解。
Socket原理及應用
為了能夠對Socket CAN的深入理解,我們需要了解Socket的機制。
Socket的中文翻譯為“插座”,在計算機世界里稱為套接字。Socket最初是作為網絡上不同主機之間進程的通信接口,后來應用越來越廣,在同一主機上的不同進程之間通信也可以用Socket。簡單來說,當網絡上不同主機之間的兩個進程(A、B)采用Socket進行通信時,那么它們之間需要建立一個通信端點,即創建Socket,創建Socket時就分配端口號和網絡地址。當進程A向進程B發送數據時,那么進程A必須要知道進程B的網絡地址及端口號。
Socket采用C/S模型進行設計的,即Client/Server,面向客戶端—服務器模型。
每一個Socket都用一個半相關描述:
{協議,本地地址,本地端口}
一個完整的Socket則用一個相關描述:
{協議,本地地址,本地端口,遠程地址,遠程端口}
一、Socket的類型
Socket有三種類型:
1、字節流套接字(SOCK_STREAM)
字節流的套接字可以提供可靠的數據傳輸、面向連接的通訊流。數據按何種順序發送,就按何種順序接收。例如,當我們按順序發送A-B-C,那么在數據到達接收端時,它的順序也是A-B-C。字節流套接字采用的是TCP(Transmission Control Protocol)協議。保證了數據傳輸的可靠性。
2、數據報套接字(SOCK_DGRAM)
數據報套接字定義了一種無連接的服務。所謂無連接服務,簡單來說,即在發送數據時,無需在收發兩端建立類似TCP那樣的握手連接,在發送時,將數據打包,然后加上遠程IP地址,即可把該數據包發送出去。
數據通過相互獨立的報文進行傳輸。並且是無序的、不可靠的傳輸。
3、原始套接字(SOCK_ROW)
原始套接字是我們需要關心的,因為我們的Socket CAN采用的即是原始套接字。該接口允許對較底層協議進行操作,如IP、ICMP等。原始套接字常用於檢驗新的協議實現或訪問現有服務中配置的新設備。
套接字的工作流程如下:
先啟動服務器,通過調用socket()函數建立一個套接字,然后調用bind()函數將該套接字和本地網絡地址聯系在一起,再調用listen()函數使套接字做好偵聽的准備,並規定它的請求隊列的長度,之后就調用accept()函數來接收連接。客戶端在建立套接字之后就可調用 connect()和服務器建立連接。連接一旦建立,客戶端和服務器之間就可以通過調用recv()/recvfrom()函數和send()/sendto函數來進行發收數據。最后,待數據傳送結束后,雙方調用close()函數關閉套接字。
下面我們來寫兩個簡單的基於Socket的CAN應用程序,但是我們采用的是SOCK_ROW,因此在套接字工作流程上有區別於SOCK_STREAM和SOCK_DGRAM。由於Socket采用C/S模型進行設計的,所以我們的這兩個程序也分別為Server和Client。
首先是server端的程序,我們需要寫一個服務器的程序,該程序接收來自客戶端發來的數據,代碼如下:
int can_recv()
{
int sock_fd;
unsigned long nbytes, len;
struct sockaddr_can addr;
struct ifreq ifr;
/*為了能夠接收CAN報文,我們需要定義一個CAN數據格式的結構體變量*/
struct can_frame frame;
struct can_frame *ptr_frame;
/*建立套接字,設置為原始套接字,原始CAN協議 */
sock_fd = socket(PF_CAN,SOCK_RAW,CAN_RAW);
/*以下是對CAN接口進行初始化,如設置CAN接口名,即當我們用ifconfig命令時顯示的名字 */
strcpy(ifr.ifr_name,"can0");
ioctl(sock_fd, SIOCGIFINDEX, &ifr);
printf("can0 can_ifindex = %x\n",ifr.ifr_ifindex);
/*設置CAN協議 */
addr.can_family = AF_CAN;
addr.can_ifindex = 0;
/*將剛生成的套接字與網絡地址進行綁定*/
bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr));
/*開始接收數據*/
nbytes = recvfrom(sock_fd, &frame, sizeof(struct can_frame), 0, (struct sockaddr *)&addr, &len);
/*get interface name of the received CAN frame*/
ifr.ifr_ifindex = addr.can_ifindex;
ioctl(sock_fd, SIOCGIFNAME, &ifr);
printf("Received a CAN frame from interface %s\n",ifr.ifr_name);
/*將接收到的CAN數據打印出來,其中ID為標識符,DLC為CAN的字節數,DATA為1幀報文的字節數*/
printf("CAN frame:\n ID = %x\n DLC = %x\n" \
"DATA = %s\n",frame.can_id,frame.can_dlc,frame.data);
ptr_frame = &frame;
return 0;
}
接下來是CAN的發送程序,即客戶端,代碼如下:
int can_send()
{
int sock_fd;
unsigned long nbytes;
struct sockaddr_can addr;
struct ifreq ifr;
struct can_frame frame;
/*建立套接字,設置為原始套接字,原始CAN協議 */
sock_fd = socket(PF_CAN,SOCK_RAW,CAN_RAW);
/*以下是對CAN接口進行初始化,如設置CAN接口名,即當我們用ifconfig命令時顯示的名字 */
strcpy((char *)(ifr.ifr_name), "can0");
1 ioctl(sock_fd, SIOCGIFINDEX, &ifr); 2 3 printf("can0 can_ifindex = %x\n", ifr.ifr_ifindex); 4 5 addr.can_family = AF_CAN; 6 7 addr.can_ifindex = ifr.ifr_ifindex; 8 9 /*將剛生成的套接字與CAN套接字地址進行綁定*/ 10 11 bind(sock_fd, (struct sockaddr*)&addr, sizeof(addr)); 12 13 /*設置CAN幀的ID號,可區分為標准幀和擴展幀的ID號*/ 14 15 frame.can_id = 0x1122; 16 17 strcpy((char *)frame.data,"hello"); 18 19 frame.can_dlc = strlen(frame.data); 20 21 printf("Send a CAN frame from interface %s\n", ifr.ifr_name); 22 23 /*開始發送數據*/ 24 25 nbytes = sendto(sock_fd, &frame, sizeof(struct can_frame), 0, (struct sockaddr*)&addr, sizeof(addr)); 26 27 return 0; 28 29 } 30
上面兩個程序看完后,大家可能會有疑問,為什么這兩個程序沒有listen()和accept()函數呢?其實這兩個程序是獨立的運行的,並不像字節流套接字(SOCK_STREAM)和數據報套接字(SOCK_DGRAM),需要先運行服務器進行偵聽。SOCK_STREAM和SOCK_DGRAM的兩個server和client程序是通過網絡相互收發數據。而CAN的socket的server和client程序收發數據的對象是CAN總線。server從CAN總線上接收數據,client將數據發到CAN總線上,當CAN總線上有數據時,server才能接收數據,當CAN總線空閑時,client才能將數據發送出去。
以上是對套接字的簡單理解,並附上socket CAN的簡單上層應用代碼,如有不足之處,敬請諒解!
SOCKET CAN發送數據流程
轉載請注明出處:http://blog.csdn.net/Righthek 謝謝!
對於本文,我們將從用戶層使用Socket CAN進行數據發送時,數據從用戶空間到底層驅動的整個通信流程,用戶層使用Socket CAN可參考上一篇文章《對Socket CAN的理解(2)——【Socket的原理及使用】》。
當我們在用戶層通過socket進行CAN數據的發送時,需要進行以下操作:
(1) 創建一個套接字socket,采用AF_CAN協議;
(2)將創建的套接字返回描述符sockfd,綁定到本地的地址;
(3)通過sendto系統調用函數進行發送;
sendto的函數聲明如下:
int sendto(int sockfd, const void *msg, intlen,unsigned intflags, const struct sockaddr *to, int tolen);
主要參數說明如下:
sockfd:通過socket函數生成的套接字描述符;
msg:該指針指向需要發送數據的緩沖區;
len:是發送數據的長度;
to:目標主機的IP地址及端口號信息;
sendto的系統調用會發送一幀數據報到指定的地址,在CAN協議調用之前把該地址移到內核空間和檢查用戶空間數據域是否可讀。
在net/socket.c源文件中,sendto函數的系統調用如下代碼:
SYSCALL_DEFINE6(sendto, int, fd, void __user *, buff, size_t, len, unsigned, flags, structsockaddr __user *, addr, int, addr_len)
{
structsocket *sock;
structsockaddr_storage address;
interr;
structmsghdr msg;
structiovec iov;
intfput_needed;
if(len > INT_MAX)
len= INT_MAX;
sock= sockfd_lookup_light(fd, &err, &fput_needed);
if(!sock)
gotoout;
iov.iov_base= buff;
iov.iov_len= len;
msg.msg_name= NULL;
msg.msg_iov= &iov;
msg.msg_iovlen= 1;
msg.msg_control= NULL;
msg.msg_controllen= 0;
msg.msg_namelen= 0;
/*把用戶空間的地址移動到內核空間中*/
if(addr) {
err= move_addr_to_kernel(addr, addr_len,(struct sockaddr *)&address);
if(err < 0)
gotoout_put;
msg.msg_name= (struct sockaddr *)&address;
msg.msg_namelen= addr_len;
}
if(sock->file->f_flags & O_NONBLOCK)
flags|= MSG_DONTWAIT;
msg.msg_flags= flags;
err= sock_sendmsg(sock, &msg, len);
}
在sendto的系統調用(sys_sendto)里,會調用到sock_sendmsg()函數,該函數代碼如下:
int sock_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{
structkiocb iocb;
structsock_iocb siocb;
intret;
init_sync_kiocb(&iocb,NULL);
iocb.private= &siocb;
ret= __sock_sendmsg(&iocb, sock, msg, size);
if(-EIOCBQUEUED == ret)
ret= wait_on_sync_kiocb(&iocb);
returnret;
}
接下來調用__sock_sendmsg()函數。
static inline int __sock_sendmsg(struct kiocb *iocb,struct socket *sock, struct msghdr *msg, size_t size)
{
interr = security_socket_sendmsg(sock, msg, size);
returnerr ?: __sock_sendmsg_nosec(iocb, sock, msg, size);
}
再往下一步就是__sock_sendmsg_nosec函數。在__sock_sendmsg_nosec()函數中會返回一個sendmsg函數指針。
static inline int __sock_sendmsg_nosec(struct kiocb*iocb, struct socket *sock, struct msghdr *msg, size_t size)
{
structsock_iocb *si = kiocb_to_siocb(iocb);
sock_update_classid(sock->sk);
si->sock= sock;
si->scm= NULL;
si->msg= msg;
si->size= size;
returnsock->ops->sendmsg(iocb, sock,msg, size);
}
在/net/can/raw.c源文件中,將raw_sendmsg函數地址賦給sendmsg函數指針,即在函數__sock_sendmsg_nosec()中return sock->ops->sendmsg(iocb,sock, msg, size),返回的函數指針將指向raw_sendmsg()函數。
static const struct proto_ops raw_ops = {
.family = PF_CAN,
.release = raw_release,
.bind = raw_bind,
.connect = sock_no_connect,
.socketpair = sock_no_socketpair,
.accept = sock_no_accept,
.getname = raw_getname,
.poll = datagram_poll,
.ioctl = can_ioctl, /* use can_ioctl() from af_can.c */
.listen = sock_no_listen,
.shutdown = sock_no_shutdown,
.setsockopt = raw_setsockopt,
.getsockopt = raw_getsockopt,
.sendmsg = raw_sendmsg,
.recvmsg = raw_recvmsg,
.mmap = sock_no_mmap,
.sendpage = sock_no_sendpage,
};
static int raw_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_tsize)
{
structsock *sk = sock->sk;
structraw_sock *ro = raw_sk(sk);
structsk_buff *skb;
structnet_device *dev;
intifindex;
interr;
if(msg->msg_name) {
structsockaddr_can *addr =
(structsockaddr_can *)msg->msg_name;
if(msg->msg_namelen < sizeof(*addr))
return-EINVAL;
if(addr->can_family != AF_CAN)
return-EINVAL;
ifindex= addr->can_ifindex;
}else
ifindex= ro->ifindex;
if(size != sizeof(struct can_frame))
return-EINVAL;
dev= dev_get_by_index(&init_net, ifindex);
if(!dev)
return-ENXIO;
skb= sock_alloc_send_skb(sk, size, msg->msg_flags & MSG_DONTWAIT,
&err);
if(!skb)
gotoput_dev;
err= memcpy_fromiovec(skb_put(skb, size), msg->msg_iov, size);
if(err < 0)
gotofree_skb;
err= sock_tx_timestamp(sk, &skb_shinfo(skb)->tx_flags);
if(err < 0)
gotofree_skb;
/*to be able to check the received tx sock reference in raw_rcv() */
skb_shinfo(skb)->tx_flags|= SKBTX_DRV_NEEDS_SK_REF;
skb->dev= dev;
skb->sk = sk;
err= can_send(skb,ro->loopback);
dev_put(dev);
if(err)
gotosend_failed;
returnsize;
}
在net/can/af_can.c源文件中,can_send函數負責CAN協議層的數據傳輸,即傳輸一幀CAN報文(可選本地回環)。參數skb指針指向套接字緩沖區和在數據段的CAN幀。loop參數是在本地CAN套接字上為監聽者提供回環。
int can_send(struct sk_buff *skb, int loop)
{
structsk_buff *newskb = NULL;
structcan_frame *cf = (struct can_frame *)skb->data;
interr;
if(skb->len != sizeof(struct can_frame) || cf->can_dlc > 8) {
kfree_skb(skb);
return-EINVAL;
}
if(skb->dev->type != ARPHRD_CAN) {
kfree_skb(skb);
return-EPERM;
}
if(!(skb->dev->flags & IFF_UP)) {
kfree_skb(skb);
return-ENETDOWN;
}
skb->protocol= htons(ETH_P_CAN);
skb_reset_network_header(skb);
skb_reset_transport_header(skb);
if(loop) {
/*local loopback of sent CAN frames */
/*indication for the CAN driver: do loopback */
skb->pkt_type= PACKET_LOOPBACK;
if(!(skb->dev->flags & IFF_ECHO)) {
/*
* If the interface is not capable to doloopback
* itself, we do it here.
*/
newskb= skb_clone(skb, GFP_ATOMIC);
if(!newskb) {
kfree_skb(skb);
return-ENOMEM;
}
newskb->sk= skb->sk;
newskb->ip_summed= CHECKSUM_UNNECESSARY;
newskb->pkt_type= PACKET_BROADCAST;
}
}else {
/*indication for the CAN driver: no loopback required */
skb->pkt_type= PACKET_HOST;
}
/*send to netdevice */
err= dev_queue_xmit(skb);
if(err > 0)
err= net_xmit_errno(err);
if(err) {
kfree_skb(newskb);
returnerr;
}
if(newskb)
netif_rx_ni(newskb);
/*update statistics */
can_stats.tx_frames++;
can_stats.tx_frames_delta++;
return0;
}
int dev_queue_xmit(struct sk_buff *skb)
{
structnet_device *dev = skb->dev;
structnetdev_queue *txq;
structQdisc *q;
intrc = -ENOMEM;
/*Disable soft irqs for various locks below. Also
* stops preemption for RCU.
*/
rcu_read_lock_bh();
txq= dev_pick_tx(dev, skb);
q= rcu_dereference_bh(txq->qdisc);
#ifdef CONFIG_NET_CLS_ACT
skb->tc_verd= SET_TC_AT(skb->tc_verd, AT_EGRESS);
#endif
trace_net_dev_queue(skb);
if(q->enqueue) {
rc= __dev_xmit_skb(skb, q, dev, txq);
gotoout;
}
if(dev->flags & IFF_UP) {
intcpu = smp_processor_id(); /* ok because BHs are off */
if(txq->xmit_lock_owner != cpu) {
if(__this_cpu_read(xmit_recursion) > RECURSION_LIMIT)
gotorecursion_alert;
HARD_TX_LOCK(dev,txq, cpu);
if(!netif_tx_queue_stopped(txq)) {
__this_cpu_inc(xmit_recursion);
rc= dev_hard_start_xmit(skb, dev, txq);
__this_cpu_dec(xmit_recursion);
if(dev_xmit_complete(rc)) {
HARD_TX_UNLOCK(dev,txq);
gotoout;
}
}
HARD_TX_UNLOCK(dev,txq);
if(net_ratelimit())
printk(KERN_CRIT"Virtual device %s asks to "
"queue packet!\n", dev->name);
}else {
/*Recursion is detected! It is possible,
* unfortunately
*/
recursion_alert:
if(net_ratelimit())
printk(KERN_CRIT"Dead loop on virtual device "
"%s, fix it urgently!\n",dev->name);
}
}
rc= -ENETDOWN;
rcu_read_unlock_bh();
kfree_skb(skb);
returnrc;
out:
rcu_read_unlock_bh();
returnrc;
}
int dev_hard_start_xmit(struct sk_buff *skb, struct net_device *dev, structnetdev_queue *txq)
{
conststruct net_device_ops *ops = dev->netdev_ops;
intrc = NETDEV_TX_OK;
unsignedint skb_len;
if(likely(!skb->next)) {
u32features;
/*
* If device doesn't need skb->dst, releaseit right now while
* its hot in this cpu cache
*/
if(dev->priv_flags & IFF_XMIT_DST_RELEASE)
skb_dst_drop(skb);
if(!list_empty(&ptype_all))
dev_queue_xmit_nit(skb,dev);
skb_orphan_try(skb);
features= netif_skb_features(skb);
if(vlan_tx_tag_present(skb) &&
!(features & NETIF_F_HW_VLAN_TX)) {
skb = __vlan_put_tag(skb,vlan_tx_tag_get(skb));
if(unlikely(!skb))
gotoout;
skb->vlan_tci= 0;
}
if(netif_needs_gso(skb, features)) {
if(unlikely(dev_gso_segment(skb, features)))
gotoout_kfree_skb;
if(skb->next)
gotogso;
}else {
if(skb_needs_linearize(skb, features) &&
__skb_linearize(skb))
gotoout_kfree_skb;
/*If packet is not checksummed and device does not
* support checksumming for this protocol,complete
* checksumming here.
*/
if(skb->ip_summed == CHECKSUM_PARTIAL) {
skb_set_transport_header(skb,
skb_checksum_start_offset(skb));
if(!(features & NETIF_F_ALL_CSUM) &&
skb_checksum_help(skb))
gotoout_kfree_skb;
}
}
skb_len= skb->len;
rc= ops->ndo_start_xmit(skb, dev);
trace_net_dev_xmit(skb,rc, dev, skb_len);
if(rc == NETDEV_TX_OK)
txq_trans_update(txq);
returnrc;
}
}
以下開始進行到CAN的底層驅動代碼了,由於CAN驅動是編譯進內核中,所以在系統啟動時會注冊CAN驅動,注冊CAN驅動過程中會初始化d_can_netdev_ops結構體變量。在這個過程中,d_can_netdev_ops結構體變量定義了3個函數指針,其中(*ndo_start_xmit)函數指針指向d_can_start_xmit函數的入口地址。
static const struct net_device_ops d_can_netdev_ops = {
.ndo_open= d_can_open,
.ndo_stop= d_can_close,
.ndo_start_xmit =d_can_start_xmit,
};
static netdev_tx_t d_can_start_xmit(struct sk_buff*skb, structnet_device *dev)
{
u32msg_obj_no;
structd_can_priv *priv = netdev_priv(dev);
structcan_frame *frame = (struct can_frame *)skb->data;
if(can_dropped_invalid_skb(dev, skb))
returnNETDEV_TX_OK;
msg_obj_no= get_tx_next_msg_obj(priv);
/*prepare message object for transmission */
d_can_write_msg_object(dev,D_CAN_IF_TX_NUM, frame, msg_obj_no);
can_put_echo_skb(skb,dev, msg_obj_no - D_CAN_MSG_OBJ_TX_FIRST);
/*
* we have to stop the queue in case of a wraparound or
* if the next TX message object is still inuse
*/
priv->tx_next++;
if(d_can_is_next_tx_obj_busy(priv, get_tx_next_msg_obj(priv)) ||
((priv->tx_next& D_CAN_NEXT_MSG_OBJ_MASK) == 0))
netif_stop_queue(dev);
returnNETDEV_TX_OK;
}
在d_can_start_xmit()函數中,會調用d_can_write_msg_object()函數准備消息報文進行傳輸。
static void d_can_write_msg_object(struct net_device *dev, intiface, struct can_frame *frame, int objno)
{
inti;
unsignedint id;
u32dataA = 0;
u32dataB = 0;
u32flags = 0;
structd_can_priv *priv = netdev_priv(dev);
if(!(frame->can_id & CAN_RTR_FLAG))
flags|= D_CAN_IF_ARB_DIR_XMIT;
if(frame->can_id & CAN_EFF_FLAG) {
id= frame->can_id & CAN_EFF_MASK;
flags|= D_CAN_IF_ARB_MSGXTD;
}else
id= ((frame->can_id & CAN_SFF_MASK) << 18);
flags|= D_CAN_IF_ARB_MSGVAL;
d_can_write(priv,D_CAN_IFARB(iface), IFX_WRITE_IDR(id) | flags);
for(i = 0; i < frame->can_dlc; i++) {
if(frame->can_dlc <= 4)
dataA|= (frame->data[i] << (8 * i));
else{
if(i < 4)
dataA|= (frame->data[i] << (8 * i));
else
dataB|= (frame->data[i] << (8 * (i - 4)));
}
}
/*DATA write to Message object registers DATAA and DATAB */
if(frame->can_dlc <= 4)
d_can_write(priv,D_CAN_IFDATA(iface), dataA);
else{
d_can_write(priv,D_CAN_IFDATB(iface), dataB);
d_can_write(priv,D_CAN_IFDATA(iface), dataA);
}
/*enable TX interrupt for this message object */
d_can_write(priv,D_CAN_IFMCTL(iface),
D_CAN_IF_MCTL_TXIE| D_CAN_IF_MCTL_EOB |
D_CAN_IF_MCTL_TXRQST| D_CAN_IF_MCTL_NEWDAT |
frame->can_dlc);
/*Put message data into message RAM */
d_can_object_put(dev,iface, objno, D_CAN_IF_CMD_ALL);
}
以上即是作者對Socket CAN進行數據發送的理解。接下來,我們將分析Socket CAN的數據接收!
現在我們來分析一下CAN總線的接收數據流程,對於網絡設備,數據接收大體上采用中斷+NAPI機制進行數據的接收。同樣,我們現在的CAN模塊也是采用同樣的方式進行數據的接收。由於我們只針對CAN總線接收數據這條主線進行分析。因些,會忽略一些針對CAN協議的設置及初始化等相關代碼。
在初始化CAN設備時,我們需要給CAN設備分配NAPI功能。我們通過netif_napi_add()函數將CAN設備添加到NAPI機制列表中。源碼如下:
struct net_device *alloc_d_can_dev(intnum_objs)
{
structnet_device *dev;
structd_can_priv *priv;
dev= alloc_candev(sizeof(struct d_can_priv), num_objs/2);
if(!dev)
returnNULL;
priv= netdev_priv(dev);
netif_napi_add(dev, &priv->napi,d_can_poll, num_objs/2);
priv->dev= dev;
priv->can.bittiming_const= &d_can_bittiming_const;
priv->can.do_set_mode= d_can_set_mode;
priv->can.do_get_berr_counter= d_can_get_berr_counter;
priv->can.ctrlmode_supported= (CAN_CTRLMODE_LOOPBACK |
CAN_CTRLMODE_LISTENONLY|
CAN_CTRLMODE_BERR_REPORTING|
CAN_CTRLMODE_3_SAMPLES);
returndev;
}
以上將CAN設備添加到NAPI機制列表中后,那么如何去調用它呢?(關於NAPI機制,請查看文章《曾經的足跡——對CAN驅動中的NAPI機制的理解》)接下來就是中斷做的事情了。在中斷處理函數d_can_isr中,我們通過napi_schedule()函數調度已經在NAPI機制列表中的d_can_poll()函數。該函數會通過輪詢的方式接收數據。而根據NAPI機制,當中斷產生后,會調度輪詢機制同時關閉所有的中斷。流程如下圖:
static irqreturn_t d_can_isr(intirq, void *dev_id)
{
structnet_device *dev = (struct net_device *)dev_id;
structd_can_priv *priv = netdev_priv(dev);
priv->irqstatus= d_can_read(priv, D_CAN_INT);
if(!priv->irqstatus)
returnIRQ_NONE;
/*disable all interrupts and schedule the NAPI */
d_can_interrupts(priv,DISABLE_ALL_INTERRUPTS);
napi_schedule(&priv->napi);
returnIRQ_HANDLED;
}
當中斷產生時,會調用以下函數d_can_poll(),該函數即采用輪詢的方式進行數據的接收。由於CAN總線狀態中斷具有最高優先權,在接收數據之前,需要對CAN總線的狀態進行判斷。而對於CAN總線錯誤狀態有三種:
(1) 主動錯誤;
(2) 被動錯誤;
(3) 總線關閉;
static int d_can_poll(structnapi_struct *napi, int quota)
{
intlec_type = 0;
intwork_done = 0;
structnet_device *dev = napi->dev;
structd_can_priv *priv = netdev_priv(dev);
if(!priv->irqstatus)
gotoend;
/*status events have the highest priority */
if(priv->irqstatus == STATUS_INTERRUPT) {
priv->current_status= d_can_read(priv, D_CAN_ES);
/*handle Tx/Rx events */
if(priv->current_status & D_CAN_ES_TXOK)
d_can_write(priv,D_CAN_ES,
priv->current_status& ~D_CAN_ES_TXOK);
if(priv->current_status & D_CAN_ES_RXOK)
d_can_write(priv,D_CAN_ES,
priv->current_status& ~D_CAN_ES_RXOK);
/*handle state changes */
if((priv->current_status & D_CAN_ES_EWARN) &&
(!(priv->last_status& D_CAN_ES_EWARN))) {
netdev_dbg(dev,"entered error warning state\n");
work_done+= d_can_handle_state_change(dev,
D_CAN_ERROR_WARNING);
}
if((priv->current_status & D_CAN_ES_EPASS) &&
(!(priv->last_status& D_CAN_ES_EPASS))) {
netdev_dbg(dev,"entered error passive state\n");
work_done+= d_can_handle_state_change(dev,
D_CAN_ERROR_PASSIVE);
}
if((priv->current_status & D_CAN_ES_BOFF) &&
(!(priv->last_status& D_CAN_ES_BOFF))) {
netdev_dbg(dev,"entered bus off state\n");
work_done +=d_can_handle_state_change(dev,
D_CAN_BUS_OFF);
}
/*handle bus recovery events */
if((!(priv->current_status & D_CAN_ES_BOFF)) &&
(priv->last_status& D_CAN_ES_BOFF)) {
netdev_dbg(dev,"left bus off state\n");
priv->can.state= CAN_STATE_ERROR_ACTIVE;
}
if((!(priv->current_status & D_CAN_ES_EPASS)) &&
(priv->last_status& D_CAN_ES_EPASS)) {
netdev_dbg(dev,"left error passive state\n");
priv->can.state= CAN_STATE_ERROR_ACTIVE;
}
priv->last_status= priv->current_status;
/*handle lec errors on the bus */
lec_type= d_can_has_handle_berr(priv);
if(lec_type)
work_done+= d_can_handle_bus_err(dev, lec_type);
}else if ((priv->irqstatus >= D_CAN_MSG_OBJ_RX_FIRST) &&
(priv->irqstatus<= D_CAN_MSG_OBJ_RX_LAST)) {
/*handle events corresponding to receive message objects */
work_done+= d_can_do_rx_poll(dev, (quota - work_done));
}else if ((priv->irqstatus >= D_CAN_MSG_OBJ_TX_FIRST) &&
(priv->irqstatus<= D_CAN_MSG_OBJ_TX_LAST)) {
/*handle events corresponding to transmit message objects */
d_can_do_tx(dev);
}
end:
if(work_done < quota) {
napi_complete(napi);
/*enable all IRQs */
d_can_interrupts(priv,ENABLE_ALL_INTERRUPTS);
}
returnwork_done;
}
當總線狀態數據狀態正常時,進行數據的接收。
static int d_can_do_rx_poll(structnet_device *dev, int quota)
{
structd_can_priv *priv = netdev_priv(dev);
unsignedint msg_obj, mctrl_reg_val;
u32num_rx_pkts = 0;
u32intpnd_x_reg_val;
u32intpnd_reg_val;
for(msg_obj = D_CAN_MSG_OBJ_RX_FIRST; msg_obj <= D_CAN_MSG_OBJ_RX_LAST
&"a > 0; msg_obj++) {
intpnd_x_reg_val= D_CAN_GET_XREG_NUM(priv, D_CAN_INTPND_X);
intpnd_reg_val= d_can_read(priv,
D_CAN_INTPND(intpnd_x_reg_val));
/*
* as interrupt pending register's bit n-1corresponds to
* message object n, we need to handle the sameproperly.
*/
if(intpnd_reg_val & (1 << (msg_obj - 1))) {
d_can_object_get(dev,D_CAN_IF_RX_NUM, msg_obj,
D_CAN_IF_CMD_ALL&
~D_CAN_IF_CMD_TXRQST);
mctrl_reg_val= d_can_read(priv,
D_CAN_IFMCTL(D_CAN_IF_RX_NUM));
if(!(mctrl_reg_val & D_CAN_IF_MCTL_NEWDAT))
continue;
/*read the data from the message object */
d_can_read_msg_object(dev, D_CAN_IF_RX_NUM,
mctrl_reg_val);
if(mctrl_reg_val & D_CAN_IF_MCTL_EOB)
d_can_setup_receive_object(dev,D_CAN_IF_RX_NUM,
D_CAN_MSG_OBJ_RX_LAST,0, 0,
D_CAN_IF_MCTL_RXIE| D_CAN_IF_MCTL_UMASK
|D_CAN_IF_MCTL_EOB);
if(mctrl_reg_val & D_CAN_IF_MCTL_MSGLST) {
d_can_handle_lost_msg_obj(dev,D_CAN_IF_RX_NUM,
msg_obj);
num_rx_pkts++;
quota--;
continue;
}
if(msg_obj < D_CAN_MSG_OBJ_RX_LOW_LAST)
d_can_mark_rx_msg_obj(dev,D_CAN_IF_RX_NUM,
mctrl_reg_val,msg_obj);
else if (msg_obj >D_CAN_MSG_OBJ_RX_LOW_LAST)
/*activate this msg obj */
d_can_activate_rx_msg_obj(dev,D_CAN_IF_RX_NUM,
mctrl_reg_val,msg_obj);
elseif (msg_obj == D_CAN_MSG_OBJ_RX_LOW_LAST)
/*activate all lower message objects */
d_can_activate_all_lower_rx_msg_objs(dev,
D_CAN_IF_RX_NUM,mctrl_reg_val);
num_rx_pkts++;
quota--;
}
}
returnnum_rx_pkts;
}
以下函數是從CAN模塊的接收寄存器中接收數據。
static int d_can_read_msg_object(structnet_device *dev, int iface, int ctrl)
{
inti;
u32dataA = 0;
u32dataB = 0;
unsignedint arb_val;
unsignedint mctl_val;
structd_can_priv *priv = netdev_priv(dev);
structnet_device_stats *stats = &dev->stats;
structsk_buff *skb;
structcan_frame *frame;
skb= alloc_can_skb(dev, &frame);
if(!skb) {
stats->rx_dropped++;
return-ENOMEM;
}
frame->can_dlc= get_can_dlc(ctrl & 0x0F);
arb_val= d_can_read(priv, D_CAN_IFARB(iface));
mctl_val= d_can_read(priv, D_CAN_IFMCTL(iface));
if(arb_val & D_CAN_IF_ARB_MSGXTD)
frame->can_id= (arb_val & CAN_EFF_MASK) | CAN_EFF_FLAG;
else
frame->can_id= (arb_val >> 18) & CAN_SFF_MASK;
if(mctl_val & D_CAN_IF_MCTL_RMTEN)
frame->can_id|= CAN_RTR_FLAG;
else{
dataA= d_can_read(priv, D_CAN_IFDATA(iface));
dataB= d_can_read(priv, D_CAN_IFDATB(iface));
for(i = 0; i < frame->can_dlc; i++) {
/*Writing MO higher 4 data bytes to skb */
if(frame->can_dlc <= 4)
frame->data[i]= dataA >> (8 * i);
else{
if(i < 4)
frame->data[i]= dataA >> (8 * i);
else
frame->data[i] = dataB >> (8 *(i-4));
}
}
}
netif_receive_skb(skb);
stats->rx_packets++;
stats->rx_bytes+= frame->can_dlc;
return0;
}
以上是對底層CAN接收數據的分析,並沒有涉及到用戶空間的調用。
SOCKET CAN控制器的初始化過程
對於一般的CAN模塊,進行初始化時,最關鍵的是以下兩步:
1、 配置CAN的位時序;
2、 配置CAN的消息報文;
下面,我們來詳細分析上面提到的關鍵兩步。
一、初始化步驟:
1、 第一步,進入初始化模式,在CAN控制寄存器中,將Init位置1;
2、 第二步,在CAN控制寄存器中,將CCE位置1;
3、 第三步,等待Init位置1,此步聚為了確保已經進入初始化模式;
4、 第四步,將位時序的值寫入到位時序寄存器(BTR)中;
5、 第五步,將CCE和Init位置為0;
6、 第六步,等待清除Init位,此步聚為了確保已經退出初始化模式;
解釋完CAN的初始化步驟后,我們來看看代碼實現:
我們先初始化一個CAN的位時序常量。
/* CAN Bittiming constants as per D_CAN specs */
static structcan_bittiming_const d_can_bittiming_const = {
.name = D_CAN_DRV_NAME,
.tseg1_min = 1, /* Time segment 1 = prop_seg + phase_seg1 */
.tseg1_max = 16,
.tseg2_min = 1, /* Time segment 2 = phase_seg2 */
.tseg2_max = 8,
.sjw_max = 4,
.brp_min = 1,
.brp_max = 1024, /* 6-bit BRP field + 4-bit BRPE field*/
.brp_inc = 1,
};
在打開CAN設置時,進行初始化。初始化完成之后,恢復正常模式,使能收發I/O控制引腳,最后配置消息報文。代碼如下:
static void d_can_init(structnet_device *dev)
{
struct d_can_priv *priv =netdev_priv(dev);
u32 cnt;
netdev_dbg(dev, "resetting d_can...\n");
d_can_set_bit(priv, D_CAN_CTL,D_CAN_CTL_SWR);
/* Enterinitialization mode by setting the Init bit */
d_can_set_bit(priv, D_CAN_CTL,D_CAN_CTL_INIT);
/* enableautomatic retransmission */
d_can_set_bit(priv, D_CAN_CTL,D_CAN_CTL_ENABLE_AR);
/* Set theConfigure Change Enable ( CCE) bit */
d_can_set_bit(priv, D_CAN_CTL,D_CAN_CTL_CCE);
/* Wait forthe Init bit to get set */
cnt = D_CAN_WAIT_COUNT;
while (!d_can_get_bit(priv, D_CAN_CTL,D_CAN_CTL_INIT) && cnt != 0) {
--cnt;
udelay(10);
}
/* setbittiming params */
d_can_set_bittiming(dev);
d_can_clear_bit(priv, D_CAN_CTL,D_CAN_CTL_INIT | D_CAN_CTL_CCE);
/* Wait for the Init bit to get clear*/
cnt = D_CAN_WAIT_COUNT;
while (d_can_get_bit(priv, D_CAN_CTL,D_CAN_CTL_INIT) && cnt != 0) {
--cnt;
udelay(10);
}
if (priv->can.ctrlmode &(CAN_CTRLMODE_LOOPBACK |
CAN_CTRLMODE_LISTENONLY))
d_can_test_mode(dev);
else
/* normal mode*/
d_can_write(priv, D_CAN_CTL,D_CAN_CTL_EIE | D_CAN_CTL_IE1 |
D_CAN_CTL_IE0);
/* Enable TXand RX I/O Control pins */
d_can_write(priv, D_CAN_TIOC,D_CAN_TIOC_FUNC);
d_can_write(priv, D_CAN_RIOC, D_CAN_RIOC_FUNC);
/* configuremessage objects */
d_can_configure_msg_objects(dev);
/* set a LECvalue so that we can check for updates later */
d_can_write(priv, D_CAN_ES,LEC_UNUSED);
}
執行完第一、第二步之后,設置位時序的參數,代碼如下:
static intd_can_set_bittiming(struct net_device *dev)
{
struct d_can_priv *priv =netdev_priv(dev);
const struct can_bittiming *bt =&priv->can.bittiming;
u32 can_btc;
can_btc = ((bt->phase_seg2 - 1)& 0x7) << D_CAN_BTR_TSEG2_SHIFT;
can_btc |= ((bt->phase_seg1 +bt->prop_seg - 1)
& 0xF) <<D_CAN_BTR_TSEG1_SHIFT;
can_btc |= ((bt->sjw - 1) & 0x3)<< D_CAN_BTR_SJW_SHIFT;
/* Ten bitscontains the BRP, 6 bits for BRP and upper 4 bits for brpe*/
can_btc |= ((bt->brp - 1) &0x3F) << D_CAN_BTR_BRP_SHIFT;
can_btc |= ((((bt->brp - 1) >>6) & 0xF) << D_CAN_BTR_BRPE_SHIFT);
d_can_write(priv, D_CAN_BTR, can_btc);
netdev_info(dev, "setting CAN BT =%#x\n", can_btc);
return 0;
}
以上內容並不是Linux CAN的驅動初始化過程,只是屬於其中的一部分。更具體地說,是針對CAN控制器的初始化過程。
————————————————
版權聲明:本文為CSDN博主「Wayne-Woo」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Righthek/article/details/17515465