Ethernet Paketi Gönderme Olayı
(github.com/francisrstokes)- Bir mikrodenetleyicide TCP/IP stack'ini doğrudan kurmanın ilk adımı olarak, STM32F401 Nucleo ile Wiznet W5100 shield bağlanarak Ethernet frame göndermesi denendi
- W5100'ün donanımsal TCP/IP özelliği kullanılmadı; yalnızca MAC Raw modu kullanılarak, frame göndermek için gereken düşük seviyeli işlemler çipe bırakıldı
- İlk engel, Arduino Ethernet shield'ın SPI kablolamasının Nucleo ile uyuşmamasından kaynaklandı; ICSP header yönlendirmesi doğrulandıktan sonra kart üzerinde yapılan düzeltme kablolamasıyla çözüldü
- Ardından chip select zamanlamasıyla ilgili görünen MISO anormal yanıtları ve Wireshark'ta görünen çöp paketler yaşandı; logic analyser ve referans uygulama karşılaştırması temel hata ayıklama araçları hâline geldi
- Nihai neden,
w5100_write16()fonksiyonunun ikinci byte'ı aynı adrese yeniden yazmasına yol açan bir bug'dı; SPI capture CSV analiz aracı yazılması sayesinde düzgün paket göndermeye ulaşıldı
Doğrudan TCP/IP stack'i yazmak için başlangıç noktası
- Amaç, bir mikrodenetleyici üzerinde TCP/IP stack'ini sıfırdan uygulayan “Networking from scratch” serisine başlamaktı
- Bu aşamanın görünürdeki sonucu ilk Ethernet paketinin gönderilmesi olsa da asıl odak, donanım ile driver arasında ortaya çıkan bug'ı izleme sürecindeydi
- Kullanılan kart, STM32F401 tabanlı Nucleo geliştirme kartıydı
- ARM Cortex-M4
- En fazla 84MHz çalışma
- 96KiB RAM
- Birkaç paketi tutmak için yeterli bellek olduğu değerlendirildi
Ethernet ve W5100'ün rolü
- Ethernet yalnızca basit bir port veya frame biçimi değil; fiziksel katman donanımını, sinyal yöntemini, bus çakışması yönetimini ve frame yerleşimini de kapsayan bir teknoloji ve standartlar ailesidir
- Ethernet sinyal işlemesi karmaşık olduğundan, genellikle özel bir ASIC frame düzeyindeki veriyi alır ve kablodaki elektriksel sinyal işlemesini üstlenir
- Projede Wiznet W5100 çipli bir Arduino Ethernet shield kullanıldı
- Kullanılan kart düşük maliyetli bir klon olduğundan, düzgün çalışması için modifikasyon gerekiyordu
- W5100, Ethernet ASIC üzerine donanımsal TCP/IP stack yerleştirilmiş bir çiptir
- 4 “socket” sunar
- TCP, UDP, IP ve “MAC Raw” seviyesinde yapılandırılabilir
- Doğrudan TCP/IP stack'i uygulama amacı nedeniyle W5100'ün TCP/IP özellikleri kullanılmadı; tek bir socket yalnızca MAC Raw modunda kullanıldı
- Kullanıcı Ethernet frame'i verir
- W5100 gerçek gönderimi yapar
- Preamble ve start-of-frame marker elektriksel seviye unsurları olduğundan çip tarafından işlenir
- 32-bit CRC'yi de W5100 hesaplar
Sorun 1: SPI sinyalinin W5100'e ulaşmaması
- W5100 ile veri alışverişi SPI üzerinden yapılır
- MOSI: ana çip olan mikrodenetleyicinin çıkışı
- MISO: W5100 çıkışı
- clock: veri için referans saat
- chip select: bağımlı çiple iletişimde olunduğunu bildiren sinyal
- W5100 datasheet'i, SPI üzerinde 4 byte'lık bir komut protokolü tanımlar
- 1 byte operation
- 2 byte 16-bit big-endian address
- 1 byte value
- operation, yazma için
0xf0veya okuma için0x0fdeğeridir- Diğer değerler geçerli değildir ve yok sayılmalıdır
- W5100, her byte'ı clock out ederken MISO üzerinden bilinen bir değer döndürdüğü için iletişim sorunlarını kontrol etmek kolaydır
- Okuma komutunda 4. byte, belirtilen adresten okunan değer olur
- Başta MOSI üzerinden komut gönderildiğinde bile MISO'da çöp değerler görülüyordu
- Neden, Arduino Ethernet shield'ın SPI sinyallerini Arduino standart header'ına değil ICSP 6-pin header'ına yönlendiren tasarımıydı
- Resmî Arduino kartlarında ICSP ile standart header'daki SPI sinyalleri içeride bağlı olduğundan sorun yoktur
- Nucleo kartında ICSP header bulunmadığından, gönderilen SPI sinyali W5100'e ulaşmıyordu
- Bağlantı sorunu, multimetreyle güç rail'leri ile SPI sinyalleri arasındaki direnç ölçülerek doğrulandı
- Sonsuz direnç görülmesi kablonun kopuk olduğunu gösterir
- Lehim ve emaye kaplı bakır tel ile Nucleo ile W5100 arasındaki sinyaller doğrudan bağlandı
Sorun 2: chip select zamanlaması ve MISO anormal yanıtı
- SPI sinyali gerçekten bağlandıktan sonra W5100 ile iletişim mümkün hâle geldi ve sonraki aşamalar uygulandı
- W5100 raw Ethernet transmission ayarı
- MAC address ayarı
- TX/RX bellek segmenti ayarı
- Test Ethernet frame'ini TX belleğine yazıp gönderimi tetikleme
- Nucleo ve shield, CAT5 kabloyla dizüstü bilgisayara bağlanıp Wireshark çalıştırıldı, ancak paket görünmedi
- Düşük seviyeli sorunlarda hata mesajı veya stack trace yoktur; “elektronları salladım ama beklediğim şey olmadı” biçiminde oldukları için izlemek zordur
- Hata ayıklamada logic analyser kullanıldı
- Birden fazla kanalda dijital sinyallerin high/low geçişlerini örnekler
- SPI bus gibi sinyal gruplarını yazılım yorumlayarak transaction byte'larını gösterir
- CSV gibi yapılandırılmış formatlarda dışa aktarabilir
- Kullanılan cihaz Saleae Logic 8'di
- Yaklaşık €10 seviyesindeki düşük maliyetli analyser'lar da Saleae Logic2 ile çalışabilir, ancak düzgün ve tutarlı çalışma konusunda tradeoff'lar vardır
- İlk SPI komutu normal görünüyordu
0xf0 0x00 0x00 0x800x0000adresindeki Mode Register'da en üst biti ayarlayarak software reset tetikler- MISO,
0x00'dan0x03'e kadar normal yanıt verdi
- Sonraki okuma komutunda anormal yanıt ortaya çıktı
- MOSI:
0x0f 0x00 0x00 0x00 - MISO:
0x03 0xff 0xff 0xff
- MOSI:
0x03değerinin bir önceki transaction'ın son değeri olması, W5100'ün iç durumu veya zamanlamasıyla ilgili bir sorun olabileceğini düşündürdü- SPI clock, datasheet'teki yaklaşık 14MHz maksimum değerin çok altında kullanıldığı için neden olarak görülmedi
- Bunun yerine chip select sinyalinin çok hızlı high'a geçtiği ve çipi kötü bir duruma soktuğu varsayıldı; durum değişiminden önce birkaç mikrosaniyelik gecikme eklendi
- Sonrasında MISO yanıtları normale döndü
- Ayar değerleri tekrar okunduğunda da makul değerler geldi
- Bu sorunun kesin nedeni hâlâ tam olarak anlaşılmış değil
- Datasheet'in 66. sayfasındaki SPI timing diagram'da yer alan chip select ile ilgili kısıtlar karşılanıyordu
- Kesin sınır koşulları yeniden incelenebilir, ancak şimdilik projenin ilerlemesine öncelik veriliyor
Sorun 3: Wireshark'ta görünen çöp paket
- Wireshark tekrar çalıştırıldığında paketler görünüyordu, ancak amaçlanan paket değildi
- Mikrodenetleyici gönderim komutunu verdiği anda, asıl yerleştirilen veriden çok daha büyük bir raw Ethernet packet çöp verilerle dolu olarak göründü
- Yalnızca datasheet ve spesifikasyona bakarak uygulama yapma planı bırakıldı; bu aşamada çalıştığı doğrulanmış bir uygulamayla karşılaştırma yapılmasına karar verildi
- Arduino, hızlıca referans uygulama oluşturmak için kullanışlıdır
- Arduino, kütüphane ve yaklaşık 5 satır kodla karmaşık davranışlar doğrulanabilir
- raw Ethernet packet gönderme kütüphanesi bulmak daha uzun sürdü
- Çoğu Arduino kullanıcısı ağ işlevlerini sıfırdan uygulamaya çalışmaz
- Resmî kütüphane, public API'den raw packet gönderme desteğini kaldırmıştı
- Asgari düzeyde packet gönderip almayı yapan GitHub projesi W5100MacRaw bulundu
- Bu kaynak kod ile kendi uygulaması karşılaştırıldı, ancak hemen görünen farklar belirleyici değildi
- register read/write sırası farklıydı
- Yalnızca bir tarafta bulunan read/write işlemleri vardı
- Genellikle sıra bağımlılığı varsa bunun datasheet'te belirtilmiş olacağı varsayılır
- read/write sırası referans uygulamayla aynı hâle getirilse de Wireshark'ta hâlâ çöp paketler görünüyordu
Küçük bir araçla ortaya çıkan gerçek bug
- Sonraki strateji, Saleae Logic2'nin SPI capture CSV çıktısını parse edip register read/write listesini yeniden gösteren bir araç yazmaktı
- Araç Python ile yazıldı ve toplam uzunluğu 200 satırın biraz üzerindeydi
- Çoğu, datasheet'ten kopyalanan register adları ve adresleriydi
- Yazılması yaklaşık 1 saat sürdü
- Girdi ve çıktıyı netleştirmek için argument parsing de eklendi
- Arduino referans uygulamasından ve kendi uygulamasından ayrı ayrı SPI capture alınıp CSV olarak dışa aktarıldı; ardından araçla işlendi ve diff yapıldı
- Sorun
w5100_write16(u16 address, u16 value)yardımcı fonksiyonundaydı- W5100'deki birçok register 16-bit değerdir
- Komut biçimi tek seferde yalnızca 8-bit yazabildiğinden high/low byte olarak bölüp yazmak gerekir
- Bu fonksiyon ikinci byte'ı
address + 1adresine yazmak yerine aynı adrese yeniden yazıyordu
- Arduino referans log'u
S0_TX_WR0veS0_TX_WR1register'larına ayrı ayrı yazmıştıS0_TX_WR0 [0x0424] 0x00S0_TX_WR1 [0x0425] 0x3c
- Kendi log'unda ikisi de
S0_TX_WR0 [0x0424]adresine yazılıyorduS0_TX_WR0 [0x0424] 0x00S0_TX_WR0 [0x0424] 0x3c
- Bu register, Socket 0 transmit write pointer idi
- W5100'ün bu 16-bit register'ı sırayla okuması
- TX belleğine byte'ları yazması
- Yeni write pointer'ı tekrar yazması
- Ardından socket send command göndermesi beklenir
- İkinci byte yazılmamışken send çalıştırılınca çip tanımsız ve anormal bir duruma girdi; Wireshark'ta rastgele sonuçlara benzeyen paketler göründü
- Fonksiyon düzeltilince test paketi Wireshark'ta düzgün şekilde göründü
Hata ayıklama aracı yazmaya harcanan zamanın değeri
- İlk Ethernet paketini göndermek tek başına devasa bir başarı değildir, ancak proje için net bir başarı anı olarak kabul edilebilir
- Bug'ı geriye doğru izlemek kişisel projelerde keyiflidir; araç yazmaya ve hata ayıklama alanını keşfetmeye zaman ayırmak neredeyse her zaman değerlidir
- Bazı profesyonel ortamlarda doğrudan deliverable üretmeyen işler israf olarak görülme eğilimindedir
- Geliştirme süreci JIRA tarzı küçük parçalara bölündükçe, doğrudan checkbox doldurmayan işler düşük değerli sayılır
- Sistem henüz yeterince anlaşılmamışken araç yazmak, test yazmak kadar, hatta bazen daha da önemli olabilir
- Hata ayıklama, bilimsel yöntemin uygulanmasına yakındır
- Veri toplanır
- Tahmin oluşturulur
- Tahmin deneyle doğrulanır
- Yeni verilerle tahmin güncellenir
- Bu proje bundan sonra daha yüksek soyutlama seviyesindeki sorunlara geçer
- RFC yanlış anlamaları
- multitasking code yazımı
- Yeni bug'larla devam ediyor
1 yorum
Hacker News yorumları
Doğrudan küçük araçlar yapabilme becerisi bir süper güçtür ve çoğu zaman sıkça sözü edilen 10x programcının özünde yer alır.
Ne yazık ki bu tür beceriler genellikle gözlerden uzak yerlerde sessizce sergilenir.
Ama meraklı olan, ikincil etkileri ya da yakında gerekecek özellikleri düşünen biri için, bunu yapmayan geliştiricilerle çalışmak epey sancılı.
Yakın zamanda birlikte çalıştığım kişi kötü biri değildi, ama şu anda biletin söylediği şeyi %100 uygulayan bir projeyi toparlıyorum. Mevcut yazılımdaki sorunları değiştirmesi beklenen yeni projede, aynı nedenlerle mevcut sorunların tamamı aynen kalmıştı.
Yine de 10x geliştirici ifadesi biraz moda terim kokuyor. Çünkü aklıma, denetim olmadan “işi bitiren” gözü kara geliştiricilerin büyük maliyetler bırakması; her şeyin silo hâline gelmesi ve sonradan biri dokunduğunda ya da o kişi ayrıldığında iskambil kulesi gibi yıkılması geliyor.
İdeal olarak herkese keşif zamanı verilmeli, ama üst yönetim “daha hızlı bitirmeliyiz” dediğinde bile bunu sıkı biçimde koruyacak bir yöneticiye ihtiyaç var. Başka bir ekibin yöneticisi “O ekip kaybolan zamanı telafi etmek için daha fazla kişi alabilecek mi? O insan gücüne bizim de ihtiyacımız var” derse iş daha da zorlaşıyor.
Sonuçta kuruluş düzeyinde bir sisteme ihtiyaç var ve Google bile 20% zaman uygulamasından vazgeçti.
“Etik olmayan” çözüm, geliştirme tahminlerine bu zamanı biraz dahil etmek.
Uygun durumlarda kendi aracını yapmak faydalıdır, ama üretken bir geliştiricinin en az kodu yazıp zaten var olanı kullandığı da söylenebilir. Bu yazıdaki örnekten gidersek, kimsenin doğrudan TCP stack’ini baştan yapmasına gerek yok; zaten harika uygulamalar var.
Yine de doğrudan yapmak derin bir anlayış kazanmanın en iyi yolu olabilir ve derin anlayış, efsanevi 10x geliştiricinin bileşenlerinden biridir. Ancak işverenin bu süreç için para ödemesini beklemem.
Genelde bir gün içinde bitecek bir işse yaparım. Hiç yanlış çıkmadı, boşa da gitmedi. En kötü durumda daha sonra o kodu başka bir yere kopyalayıp yapıştırıyorum.
Sonra programcı olmayanların da kullanabilmesini istemeye başladı ve bir anda o araçlar beklediğimden çok daha büyük bir işe dönüştü.
Başlık oldukça muğlak; bu yazı, mikrodenetleyiciler için TCP/IP ve Ethernet framing stack’ini sıfırdan yapma serisinin başlangıcı.
Yazar TCP/IP’yi kendi işleyebilen W5100 çipini kullanıyor, ama önceden hazırlanmış Ethernet frame’leri vermeyi de destekliyor. Yine de preamble ve CRC hesaplamasını çip hallediyor.
Yazının büyük kısmı çipin kendisiyle iletişim kurma ve test paketi gönderme sürecine dair. Hardcode edilmiş paket gibi görünüyor, ama yazıda açıkça belirtilmiyor.
Kişisel olarak saçma denecek bir donanımda Ethernet’i bit banging ile yapan bir içerik olmasını ummuştum.
Püf noktalarından biri, yaygın bir PHY + MagJack kartını değiştirip clock sinyalini RP2040’ın üretmesini sağlamak. Böylece sinyal senkronize oluyor ve RMII’yi aşırı örneklemeye gerek kalmıyor.
Sadece 10Mb/s gerekiyorsa daha da kirli bir yöntemle de yapılabilir. RP2040’a DVI/HDMI görüntü çıkışı ve Ethernet’i birleştiren “modern” bir cam terminal çıkmasını hâlâ bekliyorum. Sadece telnet bile yeterli; SSH muhtemelen zor olur.
Yakın zamanda kariyerimi biraz sıra dışı biçimde Ethernet merkezli FPGA mühendisliğine kaydırdım.
Eğlenceli bir yolculuktu ve sonunda doğrudan Hard MAC IP tasarlayıp özel PHY IP üzerinden paket göndermeye kadar gittim.
Bu meydan okumayı “hard mode”da denemek isteyenlere kesinlikle tavsiye ederim. Ağ iletişimi kullanıcıdan o kadar soyutlanmış durumda ki, Ethernet kartlarının, modemlerin ve switch’lerin paketin her bölümünü nasıl birleştirip ayırdığını; PHY/PCS’nin link üzerindeki sinyali nasıl geri kazandığını anlamak gerçekten değerliydi.
Debug ve doğrulama için Verilator ile yapılmış simülasyon sürümünü Linux TUN/TAP aygıtına bağlayabilir hâle getirmiştik; böylece fiziksel donanımı meşgul etmeden doğrudan geliştirici makinesinden bağlanılabiliyordu. Tek bir donanım olduğu için özellikle kullanışlıydı.
Benim ağ bilgim OSI modeline dair çok temel bir anlayışla sınırlı, ama ağ dünyasını derinlemesine kurcalamak istiyorum.
MOSI/MISO’nun yeniden yorumlanmasını ilk kez gördüm. master out/slave in yerine main out/subordinate in denmesi.
Böyle olunca pin adları hâlâ MOSI/MISO olarak kalabiliyor; ben de kullanırım gibi geliyor. COPI/CIPO (controller out/peripheral in) alternatifi akılda pek kalmamıştı.
Yazarın W5100 Ethernet shield’i ve STM32F401 kullanma nedeni bana tuhaf geldi. Benzer şekilde, kullanımı kolay bir STM32F407 kartı kullansa yerleşik Ethernet MAC’i olurdu ve ucuz bir Ethernet PHY kartıyla birlikte geliştirme yapabilirdi
Örnek Ethernet projeleri de çok ve geliştirmesi STM32F401 kadar kolay
Ayrıca “Ethernet sinyal işlemenin karmaşıklığı nedeniyle genelde özel ASIC kullanılır” açıklamasının mikrodenetleyici bağlamında çoğunlukla doğru olmadığını düşünüyorum. Çoğu durumda Ethernet işlevi, STM32F407 veya ESP32 gibi mikrodenetleyicilerin içinde yerleşik çevre birimi olarak bulunuyor
Yerleşik Ethernet çevre birimi olan bir çip kullanmak kesinlikle daha mantıklı. Yine de bu, W5100 yapılandırmasının karmaşıklığını ST çevre birimi yapılandırmasının karmaşıklığıyla takas etmek anlamına da geliyor
Ağ kodu zaten gerçek çipi sürücü arayüzünün (read/write/ioctl benzeri) arkasında soyutladığı için port etmek oldukça kolay olacaktır
Bu seride STM32F407’yi değerlendireceğim
Ethernet paketlerle değil, frame’lerle ilgilenir
Paket bir IP kavramıdır
Ancak RFC 791, alttaki L2 ağının 128 baytlık paketler kullanan ARPAnet olma olasılığının yüksek olduğu dönemden kalma bir belgedir
Günümüzde bu ayrımın fiilen anlamsız olması gerekir. Büyük bir IP datagramını birden çok L2 paketiyle göndermek için IP parçalamaya bel bağlıyorsanız bir şeyler yanlış demektir. IPv6 ağ içi parçalamayı bile desteklemez
Pratikte zamanla ayrım neredeyse kayboldu. Çünkü genelde bir Ethernet frame’i bir IP datagramı taşır ve herkes buna kısaca paket der
https://en.wikipedia.org/wiki/Ethernet_frame
Mikrodenetleyicide kablolu Ethernet denemek istiyorsanız, daha büyük STM32 Nucleo kartlarından bazılarında 100 Mbps Ethernet yerleşik geliyor ve yaklaşık 25 dolar ile oldukça ucuzlar
STM32Cube yazılımı hakkındaki değerlendirmeler karışık olsa da, çalışan Ethernet iletişimi örnekleri üretiyor
https://www.st.com/en/evaluation-tools/nucleo-f439zi.html
MQTT, HTTP istemcisi ve sunucusu vb. birlikte geliyor
Cube-MX’i en son kullandığımda genel olarak çok tatsız bir deneyimdi. Yeniden STM32 kullanacak olsam stm32-hal veya libopencm3 tercih ederdim. Aracın kendisinde ve ürettiği kodda her türden kötü hata ve uç durum vardı; günlerce hata ayıklamak zorunda kaldım. Şimdi daha iyi olabilir
https://github.com/egnor/wt32-eth01
https://liliputing.com/waveshare-esp32-p4-nano-is-a-tiny-ris...
Az önce daha ucuz modüllerin de olduğunu gördüm. WT32-ETH01 gibi şeyler performans olarak daha zayıf olabilir
Linux’ta doğrudan kendi ağ yığınınızı yazmayı denemek istiyorsanız
socket(AF_PACKET, SOCK_RAW, htons(ETH_P_ALL))kullanarak bu yazıdan çok da farklı olmayan bir soyutlama düzeyinde çalışabilirsinizSOCK_RAWpaketleri, paket verisini değiştirmeden aygıt sürücüsüyle alıp verir. Alırken adres ayrıştırılır ve standartsockaddr_lladres yapısı olarak iletilirGönderirken kullanıcının sağladığı tamponda fiziksel katman başlığı bulunmalıdır ve o paket, hedef adresin belirttiği arayüzün ağ sürücüsü kuyruğuna değiştirilmeden girer
https://man7.org/linux/man-pages/man7/packet.7.html
Bunlar, VPN arayüzü gibi ağ yığınına sanal bir arayüz ekler. Paket soketleri ise tersine gerçek arayüzle doğrudan iletişim kurmanızı sağlar
Tam olarak Ethernet frame’i demek gerekir gibi geliyor
İyi yapılmış. Ben de az önce son 16 saatlik iş zamanımı bunun tersine harcadım: Ethernet II’yi (VLAN dahil), IPv4+6’yı ve UDP’yi otomotive yönelik IP protokolüne kadar ayrıştıran bir uygulama
Kullanım amacı, özel mülk bir bus yakalama akışını anlamak; bu akış Ethernet frame’lerinin içine iç içe geçmiş Ethernet frame’leri vb. taşıyor ve ben bunları raw socket ile alıyorum
Bu tür işler için Wireshark ve ChatGPT gerçekten paha biçilmez