24 puan yazan GN⁺ 20 일 전 | 1 yorum | WhatsApp'ta paylaş
  • USB sürücüsü geliştirme genellikle çekirdek düzeyinde bir iş olarak görülse de, pratikte soket programlamaya benzer bir zorluk seviyesinde kullanıcı alanında da uygulanabilir
  • libusb kullanıldığında çekirdek kodu yazmadan aygıt numaralandırma, denetim aktarımı ve veri gönderip alma işlemlerinin tamamı yapılabilir
  • USB iletişimi Control, Bulk, Interrupt, Isochronous olmak üzere dört aktarım türü ve IN/OUT yönlerinden oluşur; her endpoint tek yönlü bir kanal gibi çalışır
  • Android cihazların Fastboot protokolü örnek alınarak, Bulk endpoint üzerinden komut ve yanıt alışverişinin nasıl yapıldığı kodla gösterilir
  • Kullanıcı alanında da tam teşekküllü bir USB sürücüsü uygulanabilir ve tüm USB protokolleri aynı temel yapıyı paylaşır

Giriş

  • USB aygıtları için sürücü yazmak, çekirdek koduyla uğraşmak gerektiği düşüncesi nedeniyle zor görünür; ancak gerçekte soket kullanan uygulama düzeyinde bir karmaşıklığa sahiptir
  • Donanım deneyimi çok fazla olmayan geliştiriciler de kullanıcı alanında USB ile çalışmayı öğrenebilir
  • USB'nin ayrıntılı çalışma biçimini ele alan kaynaklar vardır, ancak yeni başlayanlar için bunlara yaklaşmak zordur
  • USB kullanımı için gömülü sistem düzeyinde bilgi gerekmez; ağ soketlerine benzer şekilde ele alınabilir

USB aygıtı

  • Örnek olarak bootloader modundaki bir Android akıllı telefon kullanılır
    • Kolay bulunabilir, protokolü basittir ve işletim sisteminde varsayılan bir sürücüsü olmadığı için deney yapmaya uygundur
  • Bootloader moduna giriş cihaza göre değişir; genellikle güç düğmesi ile ses düğmelerinin kombinasyonuyla mümkündür

Aygıtın elle numaralandırılması

  • Numaralandırma (Enumeration), host'un aygıt bilgisini isteyerek onu tanımladığı süreçtir ve aygıt bağlandığında otomatik olarak yapılır
  • Standart aygıtlarda sürücüler USB sınıfı temelinde otomatik yüklenir; üreticiye özel aygıtlar ise VID (Vendor ID) ve PID (Product ID) kullanır
  • Linux'ta aygıt bilgileri lsusb komutuyla görülebilir
    • Örnek: ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot)
    • 18d1, Google'ın VID değeridir; 4ee0 ise Nexus/Pixel bootloader'ının PID değeridir
  • lsusb -t komutuyla sınıf ve sürücü durumu görülebilir
    • Class=Vendor Specific Class, Driver=[none] şeklinde görünür; yani işletim sistemi bir sürücü yüklemez
  • Windows'ta aynı bilgiler Device Manager veya USB Device Tree Viewer ile görülebilir

libusb ile aygıt numaralandırma

  • libusb kütüphanesi kullanılarak çekirdek kodu yazmadan kullanıcı alanında USB aygıtlarıyla iletişim kurulabilir
  • libusb_hotplug_register_callback() ile belirli bir VID:PID kombinasyonuna sahip aygıt bağlandığında bir callback çalışacak şekilde ayarlanabilir
  • Program çalışırken aygıt bağlandığında "Device plugged in!" mesajı yazdırılır
  • Linux'ta bu varsayılan olarak çalışır; gerekirse libusb_detach_kernel_driver() ile çekirdek sürücüsü ayrılabilir
  • Windows'ta Winusb.sys sürücüsü gerekir; yoksa Zadig aracıyla elle değiştirilebilir

Aygıtla iletişim

  • USB aygıtıyla ilk iletişim Control endpoint'i (adres 0x00) üzerinden yapılır
  • libusb_control_transfer() ile standart istek (GET_STATUS) gönderilerek aygıt durumu okunur
    • Örnek yanıt: 01 00 → ilk bayt Self-Powered, ikinci bayt ise Remote Wakeup desteklenmiyor anlamına gelir
  • Sonrasında GET_DESCRIPTOR isteğiyle aygıt descriptor'ı alınabilir
    • Dönen veride idVendor, idProduct, bDeviceClass gibi aygıt bilgileri bulunur
  • lsusb -v komutuyla tüm descriptor'lar (aygıt, yapılandırma, arayüz, endpoint vb.) ayrıntılı olarak incelenebilir
    • Örnek: Android Fastboot arayüzünde Bulk IN (0x81) ve Bulk OUT (0x02) endpoint'leri bulunur

Endpoint'ler

  • Endpoint, ağ portlarına benzer bir kavramdır; aygıtın veri gönderip aldığı kanaldır
  • Her endpoint'in türü ve yönü descriptor içinde tanımlanır
  • Control aktarım türü

    • Her aygıtta bir tane bulunur ve adresi her zaman 0x00 olur
    • İlk yapılandırma ve aygıt bilgisi istemek için kullanılır
    • Bir arayüze ait değildir; doğrudan aygıtın kendi parçasıdır
  • Bulk aktarım türü

    • Büyük miktarda gerçek zamanlı olmayan veri aktarımı için kullanılır
    • Örnek: Mass Storage, CDC-ACM (seri), RNDIS (Ethernet)
    • Bant genişliği yüksektir ama önceliği düşüktür
  • Interrupt aktarım türü

    • Az miktarda düşük gecikmeli veri aktarımı için kullanılır
    • Klavye, fare gibi aygıtlarda düğme girişlerini hızlı şekilde poll etmek için kullanılır
    • Gerçek bir donanım kesmesi değildir; host düzenli aralıklarla istekte bulunur
  • Isochronous aktarım türü

    • Zamana duyarlı yüksek hacimli veri için kullanılır (ses, video akışı)
    • Gecikme olursa kalite kaybı hemen fark edilir
    • libusb içinde asenkron biçimde işlenir
  • IN / OUT yönü

    • USB host merkezli bir yapıdır; aygıt, istek almadan veri göndermez
    • IN: host'un veri aldığı yön
    • OUT: host'un veri gönderdiği yön
    • Endpoint adresinin en yüksek biti (MSB) 1 ise IN, 0 ise OUT'tur
    • En fazla 127 adet kullanıcı tanımlı endpoint kullanılabilir (0x00 yalnızca Control içindir)
    • Endpoint'ler tek yönlüdür; Fastboot arayüzündeki gibi IN/OUT çifti halinde yapılandırılır

Fastboot protokolü

  • Fastboot, Android bootloader iletişim protokolüdür; komut dizgesi gönderilir, karşılığında 4 baytlık durum kodu ve veri alınır
    • Örnek:
      • Host: "getvar:version"Client: "OKAY0.4"
      • Host: "getvar:nonexistant"Client: "OKAY"
  • libusb kullanarak Fastboot komutu gönderen kod örneği
    • libusb_claim_interface() ile arayüz 0 sahiplenilir
    • "getvar:version" komutu Bulk OUT (0x02) endpoint'ine gönderilir
    • Yanıt Bulk IN (0x81) endpoint'inden alınır
    • Çıktı örneği:
      Request: getvar:version
      Response: OKAY0.4
      
    • OKAY başarılı durum kodudur, 0.4 ise Fastboot sürümüdür

Sonuç

  • Çekirdek kodu yazmadan kullanıcı alanında tam teşekküllü bir USB sürücüsü uygulanabilir
  • Tüm USB sürücüleri aynı temel ilkelere uyar; farklı olan yalnızca protokoldür
  • Daha karmaşık protokollerde de (MTP vb.) temel yapı aynıdır; soket iletişimine benzer kavramlarla yaklaşılabilir

1 yorum

 
GN⁺ 20 일 전
Hacker News yorumları
  • Zamanlama tam yerindeymiş. Yakında yerel Guitar Center'dan bir MOTU MIDI Express XT teslim alacağım
    İkinci el ekipman olduğu için yasal olarak belli bir süre bekletmeleri gerekiyor, o yüzden bekliyorum. Sorun şu ki bu cihaz standart MIDI-over-USB değil, özel bir protokol kullanıyor; bu yüzden Linux, OpenBSD ve Haiku gibi sistemlerimde USB üzerinden doğrudan kullanılamıyor
    Şimdilik yalnızca synth modülleriyle kontrolcüler arasında yönlendirme yapmam gerekiyor, o yüzden sorun değil. Ama PC tarafında da çalışır hale getirmek güzel olurdu
    Mevcut bir Linux sürücüsü var ama ne kadar kararlı olduğu belirsiz ve XT desteği de muğlak. Kernel panic sorunu çözülmüş deniyor ama açık issue'lar hâlâ var
    Bu yüzden kendim LibUSB tabanlı bir kullanıcı alanı sürücüsü yazmayı deneyeceğim. MIDI portlarını açığa çıkarıp yönlendirme araçları da eklersem oldukça kullanışlı olabilir

    • Guitar Center'ın bekleme süresi sadece çalıntı olup olmadığını kontrol etmek için değil. Yasal olarak bir rehin dükkânı (pawn shop) gibi belirli bir süre satış yasağına uymaları gerekiyor; yani asıl sahibinin geri alabileceği süre geçmeden satamıyorlar
    • Ben de aynı cihazı kullanıyorum ve o sürücüyü AUR için paketlemiştim. Binary blob çalışmıyordu ama basit bir MIDI yönlendirici olarak kullanmak için yeterliydi
  • Bunu Go diliyle denemek isteyen olursa, cgo olmadan USB erişimi sağlayan go-usb kütüphanesini yazdım
    Bununla ayrıca UVC cihazlarını kullanmak için go-uvc'yi de geliştirdim

    • Rust tarafında nusb'yi öneririm
  • Ben de yakın zamanda Macbook M3 üzerinde usbip sistemini benzer bir şekilde implemente ediyorum
    Ama güncel macOS'ta bazı kısıtlar var. Sistemin tanıdığı USB cihazları için libusb tabanlı kullanıcı alanı sürücüleri geliştiremiyorsunuz; bunu yapabilmek için güvenlik özelliklerini elle kapatmanız gerekiyor

    • Sürücü override işlemi yalnızca tek bir katmanı ayarlamayı gerektirdiği için bu durum hafifletilebilir
  • Bu yaklaşımda USB sürücüsü aslında uygulama kodunun rolünü de üstlenmiş oluyor. Yani bu, sürücüden çok kütüphane+program gibi
    Mesela bir USB-Ethernet cihazını işletim sistemine ağ bağdaştırıcısı olarak bağlamak isterseniz bunu nasıl yapacağınızı merak ediyorum

    • Standartlaştırılmış cihazlar genelde USB/CDC/ECM veya RNDIS kullandıkları için otomatik tanınır. Kullanıcı alanı erişimi daha çok standart dışı cihazlar için faydalı. Windows'ta bunu sürücü imzasına gerek olmadan libusb ile taşınabilir biçimde uygulayabilirsiniz
    • Linux'ta kullanıcı alanından kernel ile iletişim kurmak için bir tun/tap cihazı oluşturmanız ya da diğer alt sistemleri de kullanıcı alanında çalıştırmanız gerekir
  • Bu yazıyı birkaç yıl önce okumuş olsaydım dizüstü bilgisayar işlevlerini tersine mühendislikle anlamaya çalışırken çok daha kolay olurdu. Özellikle klavye LED kontrol programı hâlâ en sevdiğim projelerden biri

  • Gerçekten çok faydalı bir giriş yazısıydı. Düşük seviyeli donanım API'leri ile uğraşmak zor ama ödüllendirici. Modern işletim sistemlerindeki soyutlama katmanları sayesinde işler kolaylaştı ama onların altını anlamak hâlâ önemli

  • C++ kodu tuhaf görünüyordu. Ok karakterlerini doğrudan yazabilen bir klavye hiç görmedim

    • O, programlama fontlarındaki ligatür. Kopyalayınca aslında -> olarak görünür. Modern C++'taki trailing return type sözdizimi
    • Bazı geliştiriciler ligatürlü fontları seviyor. İki karakteri tek bir glifte birleştiriyor
    • Compose tuşu ayarlarsanız herhangi bir klavyeyle “→” yazabilirsiniz
    • Sonuçta bu sadece "->". Font onu ok olarak render ediyor
  • USB cihazlarının DMA desteği olup olmadığını merak etmiştim. Yalnızca host üzerinden mi mümkün, yoksa cihaz doğrudan belleğe erişebiliyor mu, bunu da öğrenmek istiyordum

    • USB cihazları PCIe veya FireWire gibi host belleğine doğrudan erişmez. Bunun yerine XHCI denetleyicisi DMA yapar ve çoğu cihaz denetleyicisi de kendi RAM'i ile USB arasında DMA desteği sunar
    • Tüm aktarım host tarafından başlatılır. Cihaz önce veri gönderiyormuş gibi görünse bile gerçekte host talep eder. Doğrudan DMA güvenlik açısından büyük bir risk olurdu
  • Geçmişte basit bir USB cihazı yapmaya çalışmıştım ama descriptor yazımıyla ilgili neredeyse hiç bilgi yoktu. Çoğu kaynak “benzer bir cihaz bul, kopyala ve değiştir” seviyesindeydi. USB'nin gerçekten ne kadar iyi bir standart olduğunu sorgulamıştım

    • Descriptor'lar bana da gizemli geliyordu ama sonunda bunların sadece sabit ikili struct'lar olduğunu fark ettim. Her USB sınıfının tanımladığı alanlar ve endpoint'ler doğruysa cihaz tanınıyor
    • USB fena değil ama elektriksel açıdan USB 1/2 gerçek diferansiyel sinyal değil
    • Neredeyse hiç öğretici kaynak yok ama büyük şirket standartlarına göre epey makul. Yine de fazla seçenek olduğu için ilgili spesifikasyonların çoğunu okumak gerekiyor
  • Benden “USB aygıt sürücüsünü doğrudan yaz” diye istense, cihazı geri verip önce bunun sanal COM portu olarak çözülemeyeceğini kontrol ederdim