inside rtl8139

rtl8139(蟹チップ)のドライバ

パケット送信について調べてみた。

ドライバのソースは
/drivers/net/8139too.c

パケット送信は以下の関数で行われている模様。
626 static int rtl8139_start_xmit (struct sk_buff *skb,
627              struct net_device *dev);

その関数は以下の様になっています。
1706 static int rtl8139_start_xmit (struct sk_buff *skb, struct net_device *dev)
1707 {
1708   struct rtl8139_private *tp = netdev_priv(dev);
1709   void __iomem *ioaddr = tp->mmio_addr;
1710   unsigned int entry;
1711   unsigned int len = skb->len;
1712   unsigned long flags;
1713
1714   /* Calculate the next Tx descriptor entry. */
1715   entry = tp->cur_tx % NUM_TX_DESC;
1716
1717   /* Note: the chip doesn't have auto-pad! */
1718   if (likely(len < TX_BUF_SIZE)) {
1719     if (len < ETH_ZLEN)
1720       memset(tp->tx_buf[entry], 0, ETH_ZLEN);
1721     skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);
1722     dev_kfree_skb(skb);
1723   } else {
1724     dev_kfree_skb(skb);
1725     tp->stats.tx_dropped++;
1726     return 0;
1727   }
1728
1729   spin_lock_irqsave(&tp->lock, flags);
1730   RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),
1731        tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));
1732
1733   dev->trans_start = jiffies;
1734
1735   tp->cur_tx++;
1736   wmb();
1737
1738   if ((tp->cur_tx - NUM_TX_DESC) == tp->dirty_tx)
1739     netif_stop_queue (dev);
1740   spin_unlock_irqrestore(&tp->lock, flags);
1741
1742   if (netif_msg_tx_queued(tp))
1743     printk (KERN_DEBUG "%s: Queued Tx packet size %u to slot %d.\n",
1744       dev->name, len, entry);
1745
1746   return 0;
1747 }
 


関数の実態を探ります。まずはこのあたり。

1718   if (likely(len < TX_BUF_SIZE)) {
1719     if (len < ETH_ZLEN)
1720       memset(tp->tx_buf[entry], 0, ETH_ZLEN);
1721     skb_copy_and_csum_dev(skb, tp->tx_buf[entry]);
1722     dev_kfree_skb(skb);
1723   } else {
1724     dev_kfree_skb(skb);
1725     tp->stats.tx_dropped++;
1726     return 0;
1727   }
 

likely()

  • /src/linux-2.6.18/include/linux/compiler.h
 62 #define likely(x) __builtin_expect(!!(x), 1)
この先はありません。今の僕には理解できませんでした。

memset()

  • /src/linux-2.6.18/arch/i386/lib/memcpy.c
17 void *memset(void *s, int c, size_t count)
18 {
19   return __memset(s, c, count);
20 }
  • /src/linux-2.6.18/include/asm-i386/string.h
462 #define __memset(s, c, count) \
463 (__builtin_constant_p(count) ? \
464  __constant_count_memset((s),(c),(count)) : \
465  __memset_generic((s),(c),(count)))
( A ? B : C)の形の3項演算子ですね。Aが真ならB、そうでないならCという感じだったと思います。
__builtin_constant_p()
GNUコンパイラの組み込み関数だそうです。これも今の僕には理解できませんでした。こちらに詳しく載っています。
参考URL

__constant_count_memset()
  • /src/linux-2.6.18/include/asm-i386/string.h
361 /* we might want to write optimized versions of these later */
362 #define __constant_count_memset(s,c,count) __memset_generic((s),(c),(count))

__memset_generic()
  • /src/linux-2.6.18/include/asm-i386/string.h
349 static inline void * __memset_generic(void * s, char c,size_t count)
350 {
351 int d0, d1;
352 __asm__ __volatile__(
353   "rep\n\t"
354   "stosb"
355   : "=&c" (d0), "=&D" (d1)
356   :"a" (c),"1" (s),"0" (count)
357   :"memory");
358 return s;
359 }

ということでmemsetはインラインアセンブラになるみたいですね。
インラインアセンブラの制約条件などについてはこちらに詳しく書いてあります。
http://yashiromann.sakura.ne.jp/memo/GCC-Inline-Assembly-HOWTO.html#ss6.1

skb_copy_and_csum_dev()

  • /src/linux-2.6.18/net/core/skbuff.c
1396 void skb_copy_and_csum_dev(const struct sk_buff *skb, u8 *to)
1397 {
1398   unsigned int csum;
1399   long csstart;
1400
1401   if (skb->ip_summed == CHECKSUM_HW)
1402     csstart = skb->h.raw - skb->data;
1403   else
1404     csstart = skb_headlen(skb);
1405
1406   BUG_ON(csstart > skb_headlen(skb));
1407
1408   memcpy(to, skb->data, csstart);
1409
1410   csum = 0;
1411   if (csstart != skb->len)
1412     csum = skb_copy_and_csum_bits (skb, csstart, to + csstart,
1413                 skb->len - csstart,  0);
1414
1415   if (skb->ip_summed == CHECKSUM_HW) {
1416     long csstuff = csstart + skb->csum;
1417
1418     *((unsigned short *)(to +  sstuff)) = csum_fold(csum);
1419   }
1420 }
です。関数としては
  • BUG_ON
  • memcpy
  • skb_copy_and_csum_bits
  • csum_fold
ですね。

BUG_ON()
探したら定義が2つあった。
BUG_ON()一つ目。
  • /src/linux-2.6.18/include/asm-generic/bug.h
14 #ifndef HAVE_ARCH_BUG_ON
15 #define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0)
16 #endif
中では
  • unlikely()
  • BUG()
が実行されています。BUG()も複数定義があるのでよくわかりません。
一応二つの定義をこぴぺしておきます。
BUG()
一つ目。
  • /src/linux-2.6.18/include/asm-generic/bug.h
 6 #ifdef CONFIG_BUG
 7 #ifndef HAVE_ARCH_BUG
 8 #define BUG() do { \
 9   printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __FUNCTION__); \
10   panic("BUG!"); \
11 } while (0)
12 #endif
二つ目。
  • /src/linux-2.6.18/include/asm-generic/bug.h
27 #else /* !CONFIG_BUG */
28 #ifndef HAVE_ARCH_BUG
29 #define BUG()
30 #endif

BUG_ON()2つ目。
  • /src/linux-2.6.18/include/asm-generic/bug.h
32 #ifndef HAVE_ARCH_BUG_ON
33 #define BUG_ON(condition) do { if (condition) ; } while(0)
34 #endif
なんかコンパイルフラグですね。よくわかりません。

memcpy()
  • /src/linux-2.6.18/arch/i386/lib/memcpy.c
 7 void *memcpy(void *to, const void *from, size_t n)
 8 {
 9 #ifdef CONFIG_X86_USE_3DNOW
10   return __memcpy3d(to, from, n);
11 #else
12   return __memcpy(to, from, n);
13 #endif
14 }
コンパイルフラグですね。
  • __memcpy3d
  • __memcpy
の二つです。

__memcpy3d()
  • /src/linux-2.6.18/include/asm-i386/string.h
300 static __inline__ void *__memcpy3d(void *to, const void *from, size_t len)
301 {
302   if (len < 512)
303     return __memcpy(to, from, len);
304   return _mmx_memcpy(to, from, len);
305 }
_mmx_memcpy()は結構長いインラインアセンブラでした。
  • src/linux-2.6.18/arch/i386/lib/mmx.c
にありあます。長いので省きます。コピーをする長さで条件分岐しているんですね。mmxはmulti media extentionの略らしいです。

__memcpy()
  • /src/linux-2.6.18/include/asm-i386/string.h
203 static __always_inline void * __memcpy(void * to, const void * from, size_t n)
204 {
205 int d0, d1, d2;
206 __asm__ __volatile__(
207   "rep ; movsl\n\t"
208   "movl %4,%%ecx\n\t"
209   "andl $3,%%ecx\n\t"
210 #if 1 /* want to pay 2 byte penalty for a chance to skip microcoded rep? */
211   "jz 1f\n\t"
212 #endif
213   "rep ; movsb\n\t"
214   "1:"
215   : "=&c" (d0), "=&D" (d1), "=&S" (d2)
216   : "0" (n/4), "g" (n), "1" ((long) to), "2" ((long) from)
217   : "memory");
218 return (to);
219 }
こちらはインラインアセンブラになりますね。
skb_copy_and_csum_bits()
長い・・・
  • /src/linux-2.6.18/net/core/skbuff.c
1317 unsigned int skb_copy_and_csum_bits(const struct sk_buff *skb, int offset,
1318             u8 *to, int len, unsigned int csum)
1319 {
1320   int start = skb_headlen(skb);
1321   int i, copy = start - offset;
1322   int pos = 0;
1323
1324   /* Copy header. */
1325   if (copy > 0) {
1326     if (copy > len)
1327       copy = len;
1328     csum = csum_partial_copy_nocheck(skb->data + offset, to,
1329              copy, csum);
1330     if ((len -= copy) == 0)
1331       return csum;
1332     offset += copy;
1333     to     += copy;
1334     pos = copy;
1335   }
1336
1337   for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) {
1338     int end;
1339
1340     BUG_TRAP(start <= offset + len);
1341
1342     end = start + skb_shinfo(skb)->frags[i].size;
1343     if ((copy = end - offset) > 0) {
1344       unsigned int csum2;
1345       u8 *vaddr;
1346       skb_frag_t *frag = &skb_shinfo(skb)->frags[i];
1347
1348       if (copy > len)
1349         copy = len;
1350       vaddr = kmap_skb_frag(frag);
1351       csum2 = csum_partial_copy_nocheck(vaddr +
1352                 frag->page_offset +
1353                 offset - start, to,
1354                 copy, 0);
1355       kunmap_skb_frag(vaddr);
1356       csum = csum_block_add(csum, csum2, pos);
1357       if (!(len -= copy))
1358         return csum;
1359       offset += copy;
1360       to     += copy;
1361       pos    += copy;
1362     }
1363     start = end;
1364   }
1365
1366   if (skb_shinfo(skb)->frag_list) {
1367     struct sk_buff *list = skb_shinfo(skb)->frag_list;
1368
1369     for (; list; list = list->next) {
1370       unsigned int csum2;
1371       int end;
1372
1373       BUG_TRAP(start <= offset + len);
1374
1375       end = start + list->len;
1376       if ((copy = end - offset) > 0) {
1377         if (copy > len)
1378           copy = len;
1379         csum2 = skb_copy_and_csum_bits(list,
1380                      offset - start,
1381                      to, copy, 0);
1382         csum = csum_block_add(csum, csum2, pos);
1383         if ((len -= copy) == 0)
1384           return csum;
1385         offset += copy;
1386         to     += copy;
1387         pos    += copy;
1388       }
1389       start = end;
1390     }
1391   }
1392   BUG_ON(len);
1393   return csum;
1394 }
1395
 
これの実態を追いかけるのは面倒だなー。省略。
csum_fold()
  • /src/linux-2.6.18/include/asm-i386/checksum.h
99 static inline unsigned int csum_fold(unsigned int sum)
100 {
101   __asm__(
102     "addl %1, %0    ;\n"
103     "adcl $0xffff, %0 ;\n"
104     : "=r" (sum)
105     : "r" (sum << 16), "0" (sum & 0xffff0000)
106   );
107   return (~sum) >> 16;
108 }
 

次はこのあたり

1729   spin_lock_irqsave(&tp->lock, flags);
1730   RTL_W32_F (TxStatus0 + (entry * sizeof (u32)),
1731        tp->tx_flag | max(len, (unsigned int)ETH_ZLEN));
1732
1733   dev->trans_start = jiffies;
1734
1735   tp->cur_tx++;
1736   wmb();
 

  • spin_lock_irqsave
  • RTL_W32_F
  • wmb
ですね。

RTL_W32_F()

  • 8139too.c
同じソース内に定義がありました。
648 #define RTL_W32_F(reg, val32) do { iowrite32 ((val32), ioaddr + (reg)); ioread32 (ioaddr + (reg)); } while (0)
  • iowrite
  • ioread
この二つの関数で構成されています。ioread,iowriteは同じような命令に帰着するのでwriteだけしか調べません。

iowrite
  • /src/linux-2.6.18/lib/iomap.c
91 void fastcall iowrite32(u32 val, void __iomem *addr)
92 {
93   IO_COND(addr, outl(val,port), writel(val, addr));
94 }
  • /src/linux-2.6.18/lib/iomap.c
42 #define IO_COND(addr, is_pio, is_mmio) do {     \
43   unsigned long port = (unsigned long __force)addr; \
44   if (port < PIO_RESERVED) {        \
45     VERIFY_PIO(port);       \
46     port &= PIO_MASK;       \
47     is_pio;           \
48   } else {            \
49     is_mmio;          \
50   }             \
51 } while (0)
渡されたアドレスがPIO_RESERVEDより小さいときはoutlが実行される。
そうではないときはwritelが実行される。
writel
  • /src/linux-2.6.18/include/asm-i386/io.h
188 static inline void writel(unsigned int b, volatile void __iomem *addr)
189 {
190   *(volatile unsigned int __force *) addr = b;
191 }
outl
  • src/linux-2.6.18/include/asm-i386/io.h
334 #define BUILDIO(bwl,bw,type) \
335 static inline void out##bwl##_local(unsigned type value, int port) { \
336   __asm__ __volatile__("out" #bwl " %" #bw "0, %w1" : : "a"(value), "Nd"(port)); \
337 } \
338 static inline unsigned type in##bwl##_local(int port) { \
339   unsigned type value; \
340   __asm__ __volatile__("in" #bwl " %w1, %" #bw "0" : "=a"(value) : "Nd"(port)); \
341   return value; \
342 } \
343 static inline void out##bwl##_local_p(unsigned type value, int port) { \
344   out##bwl##_local(value, port); \
345   slow_down_io(); \
346 } \
347 static inline unsigned type in##bwl##_local_p(int port) { \
348   unsigned type value = in##bwl##_local(port); \
349   slow_down_io(); \
350   return value; \
351 } \
352 __BUILDIO(bwl,bw,type) \
353 static inline void out##bwl##_p(unsigned type value, int port) { \
354   out##bwl(value, port); \
355   slow_down_io(); \
356 } \
357 static inline unsigned type in##bwl##_p(int port) { \
358   unsigned type value = in##bwl(port); \
359   slow_down_io(); \
360   return value; \
361 } \
362 static inline void outs##bwl(int port, const void *addr, unsigned long count) { \
363   __asm__ __volatile__("rep; outs" #bwl : "+S"(addr), "+c"(count) : "d"(port)); \
364 } \
365 static inline void ins##bwl(int port, void *addr, unsigned long count) { \
366   __asm__ __volatile__("rep; ins" #bwl : "+D"(addr), "+c"(count) : "d"(port)); \
367 }
368
369 BUILDIO(b,b,char)
370 BUILDIO(w,w,short)
371 BUILDIO(l,,int)
372
プリプロセッサを通すと、下のようなものになります。
static inline void outl_local(unsigned int value, int port) { __asm__ __volatile__("out" "l" " %" "" "0, %w1" : : "a"(value), "Nd"(port)); }
static inline unsigned int inl_local(int port) { unsigned int value; __asm__ __volatile__("in" "l" " %w1, %" "" "0" : "=a"(value) : "Nd"(port)); return value; }
static inline void outl_local_p(unsigned int value, int port) { outl_local(value, port); slow_down_io(); }
static inline unsigned int inl_local_p(int port) { unsigned int value = inl_local(port); slow_down_io(); return value; }
static inline void outl(unsigned int value, int port) { outl_local(value, port); } static inline unsigned int inl(int port) { return inl_local(port); }
static inline void outl_p(unsigned int value, int port) { outl(value, port); slow_down_io(); }
static inline unsigned int inl_p(int port) { unsigned int value = inl(port); slow_down_io(); return value; }
static inline void outsl(int port, const void *addr, unsigned long count) { __asm__ __volatile__("rep; outs" "l" : "+S"(addr), "+c"(count) : "d"(port)); }
static inline void insl(int port, void *addr, unsigned long count) { __asm__ __volatile__("rep; ins" "l" : "+D"(addr), "+c"(count) : "d"(port)); }
inlなどの定義も混じってるけど気にしない。
結局のところインラインアセンブリのOUT系の命令になるようです。

PIOなどの定義は↓
  • /src/linux-2.6.18/lib/iomap.c
24 #ifndef HAVE_ARCH_PIO_SIZE
25 /*
26  * We encode the physical PIO addresses (0-0xffff) into the
27  * pointer by offsetting them with a constant (0x10000) and
28  * assuming that all the low addresses are always PIO. That means
29  * we can do some sanity checks on the low bits, and don't
30  * need to just take things for granted.
31  */
32 #define PIO_OFFSET  0x10000UL
33 #define PIO_MASK  0x0ffffUL
34 #define PIO_RESERVED  0x40000UL
35 #endif
英文によれば、低アドレスは全部PIO用にしておくので細かくチェックしなくても良い。らしいです。
最終更新:2008年04月15日 20:29
ツールボックス

下から選んでください:

新しいページを作成する
ヘルプ / FAQ もご覧ください。