- 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) vePID(Product ID) kullanır - Linux'ta aygıt bilgileri
lsusbkomutuyla görülebilir- Örnek:
ID 18d1:4ee0 Google Inc. Nexus/Pixel Device (fastboot) 18d1, Google'ın VID değeridir;4ee0ise Nexus/Pixel bootloader'ının PID değeridir
- Örnek:
lsusb -tkomutuyla sınıf ve sürücü durumu görülebilirClass=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 birVID:PIDkombinasyonuna 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.syssü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
- Örnek yanıt:
- Sonrasında GET_DESCRIPTOR isteğiyle aygıt descriptor'ı alınabilir
- Dönen veride
idVendor,idProduct,bDeviceClassgibi aygıt bilgileri bulunur
- Dönen veride
lsusb -vkomutuyla tüm descriptor'lar (aygıt, yapılandırma, arayüz, endpoint vb.) ayrıntılı olarak incelenebilir- Örnek:
Android Fastbootarayüzünde Bulk IN (0x81) ve Bulk OUT (0x02) endpoint'leri bulunur
- Örnek:
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
0x00olur - İ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
- Her aygıtta bir tane bulunur ve adresi her zaman
-
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önOUT: host'un veri gönderdiği yön- Endpoint adresinin en yüksek biti (MSB)
1ise IN,0ise OUT'tur - En fazla 127 adet kullanıcı tanımlı endpoint kullanılabilir (
0x00yalnı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"
- Örnek:
- 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 OKAYbaşarılı durum kodudur,0.4ise 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
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
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
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
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
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
->olarak görünür. Modern C++'taki trailing return type sözdizimi"->". Font onu ok olarak render ediyorUSB 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
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
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