19 Mart 2011 Cumartesi

Linux Sürücü Yazma-3

Bu yazı daha önce saltokunur.org e-dergisinde yayınlanmıştır.


Herkese merhabalar. İki aydır sürücü yazmak için gerekli bilgileri öğrendik. Artık bu kadar uğraşımızın meyvesini almaya, sürücümüzü yazmanın vakti geldi.
Daha önce öğrendiğimiz bilgiler ışığında ne yapmak istediğimizi kararlaştıralım. Biz kesmelerle çalışan, char tipli bir USB Fare için sürücü yazmaya çalışıyoruz.
Bu yazıda anlatılan kodların tamamı David Olivera’nın Logitech Media Play Cordless Mouse için yazdığı lmpcm_usb.c sürücüsü temel alınarak yazılmış ve A4Tech R7 Mouse için bazı değişiklikler yapılmıştır. Benim de hala anlamadığım ve açıklamakta zorlandığım noktalar var. Ama elimden geldiğince açıklamaya çalışacağım.

Dikkat!!! İLERİ DERECE C BÖLGESİ
Yazının buradan sonrası ağır derecede C içerir. Okuyucuların ileri derece C konuları ile aşina olması gerekliliği vardır. Yoksa okuduğunuzdan hiçbir şey anlamayabilirsiniz. Alıcılarınızın ayarlarıyla oynamayın !!! 

Sürücümüzü bir modül halinde yazacağımızı daha önce belirtmiştik. Sürücüsünü yazacağımız farenin marka ve modeli A4Tech R7 olduğu için sürümüze a4r7mouse ismini veriyoruz ve diskimizde a4r7mouse.c adında bir C dosyası oluşturuyoruz. Bu dosyaya ilk önce aşağıdaki satırlar ile gerekli kütüphaneleri ekliyoruz.

#include <linux kernel.h>
#include <linux slab.h>
#include <linux input.h>
#include <linux module.h>
#include <linux init.h>
#include <linux usb.h>

Sonra aşağıdaki tanımlamaları yapıp devamındaki makrolarla sürücünün bilgilerini çekirdeğe giriyoruz.

#define DRIVER_VERSION "v0.0.1"
#define DRIVER_AUTHOR "Hamza Apaydin hamzaapaydin@hotmail.com"
#define DRIVER_DESC "USB A4Tech Power Saver R7 Wireless Mouse Driver"
#define DRIVER_LICENSE "GPL"

MODULE_AUTHOR ( DRIVER_AUTHOR );
MODULE_DESCRIPTION ( DRIVER_DESC );
MODULE_LICENSE ( DRIVER_LICENSE );

Sonra ne işe yaradığını benim de bilmediğim, ama çekirdeğin kullandığını tahmin ettiğim bazı koşullu derleme komutlarını ekliyoruz.

#ifdef SLAB_ATOMIC
# define ATOMIC SLAB_ATOMIC
#else
# define ATOMIC GFP_ATOMIC
#endif

Sonra sürücümüzün en temel yapısını (struct) yazıyoruz. Bu yapı sisteme bağlanan A4Tech R7 Mouse’un çekirdekteki mantıksal (logic) karşılığı olacak. Biz bu yapının bir örneğini oluşturup yanına bu yapıdaki URB ‘ler için IH’ler yazıp, daha başka eklerle sisteme bir struct usb_driver örneği olarak kaydedeceğiz.

typedef struct usb_a4r7mouse {
 // Cihazın adı
 char name[128];  
 // USB gelen interrupt verisi. İşte cihazdan gelen verilere buradan erişeceğiz.
 signed char *data;  
 //TODO bunun ne anlama geldiğini bul
 char phys[64];
 // cihazın DMA adresi
 dma_addr_t data_dma;
 //USB portuna takılan cihazın logic gösterimini tutan struct
 struct usb_device *usbdev;
 // cihazdan girdileri almada kullanacağımız struct
 struct input_dev *inputdev;
 // cihazdan gelen USB Request block
 struct urb *urb;
 // cihazın açılma sayısı
 int open;
} a4r7mouse_t;
//En son olarak bu yapıdan a4r7mouse_t adında bir örnek oluşturuyoruz.

Sonra bu oluşturduğumuz örneğe ilk değerlerini atayacak olan (initialize) fonksiyonu yazıyoruz. memset ile hafızada a4r7mouse yapısının boyutu kadar yer ayırıyoruz.

void a4r7mouse_init ( a4r7mouse_t *a4r7mouse ) {
 memset(a4r7mouse, 0, sizeof(a4r7mouse_t));
 a4r7mouse->inputdev = NULL;
 a4r7mouse->urb = NULL;
 a4r7mouse->data = NULL;
}

Burada cihaz sistemden kaldırıldığında cihazın kullandığı kaynakları serbest bırakan fonksiyonu yazıyoruz. Cihazın urb’si boş değilse usb_free_urb ile, data’sı boş değilse de cihazın kullandığı tampon usb_buffer_free ile serbest bırakıyor.

void a4r7mouse_free ( a4r7mouse_t *a4r7mouse ) {
 if ( a4r7mouse->urb )
  usb_free_urb(a4r7mouse->urb);
 if ( a4r7mouse->data )
  usb_buffer_free(a4r7mouse->usbdev,8,a4r7mouse->data,a4r7mouse->data_dma);
 kfree(a4r7mouse);
}

Bu fonksiyon ise parametre olarak bir usb_device struct’ı alıp geriye yeni a4r7mouse nesnesi döndürüyor. Bu fonksiyonun kullanımını aşağıda probe fonksiyonu içinde çağırımından sonra daha iyi anlayacaksınız.
Buradaki usb_device sistemdeki USB Core’ı tarafından
Sisteme her USB cihaz bağlandığında cihazın bağlandığı USB interface alınır ve aşağıdaki probe fonksiyonunda

a4r7mouse_t *a4r7mouse_new ( struct usb_device *dev ) {
 a4r7mouse_t *a4r7mouse;
 // kmalloc ile struct’a bellekten yer ayrılıyor.
 if (!(a4r7mouse = kmalloc(sizeof(a4r7mouse_t), GFP_KERNEL)))
  return NULL;
 // az önce yazdığımız init fonk. Çağrılarak a4r7mouse örneği initialize ediyor.
 a4r7mouse_init(a4r7mouse);
 // Input device için yer ayrılıyor.
 if ( (a4r7mouse->inputdev = input_allocate_device()) == NULL ) {
  a4r7mouse_free(a4r7mouse);
  return NULL;
 }
 //  urb handler için bellekte yer ayır.
 if (!(a4r7mouse->urb = usb_alloc_urb(0, GFP_KERNEL))) {
  a4r7mouse_free(a4r7mouse);
  return NULL;
 }
 // urb deki data’yı göndermek ve almak için kullanılacak tamponu oluştur.
 if (!(a4r7mouse->data = usb_buffer_alloc(dev,8,ATOMIC,&a4r7mouse->data_dma))) {
  a4r7mouse_free(a4r7mouse);
  return NULL;
 }
 //  a4r7mouse nin  usb device alanına fonksiyona parametre olarak gelen usb_device’ı ata
 a4r7mouse->usbdev = dev;
 return a4r7mouse;
} 

İşte URB’den gelen datayı alıp, işleyip Linux çekirdeğine gönderen fonksiyonumuz. Burada data dizisinden bitmapte gösterildiği sırada verileri alıyoruz. input_report_key() fonksiyonları ile sisteme girdi tuşlarını kaydediyoruz. Bu fonksiyonun çağrılarında kullanılan BTN_LEFT gibi sabitler input.h’da tanımlıdır. Burada yapılacak herhangi bir değişiklik ile örneğin;

input_report_key(dev, BTN_LEFT,    GETBIT(btn,0)); 
satırında KEY_A yazılırsa farenin sol tuşuna basılınca, sol tıklama olayı değil de klavyeden A tuşuna basılmış gibi bir tepki verir bilgisayar.

void input_send_data ( struct input_dev *dev, char *data ) {
 //data dizisindeki bitleri alıyoruz.
 char btn = data[0], // ilk 8 bit. Sol, sağ, orta tuş için
  x = data[1], // X  ekseninde hareket
  y = data[2], // Y ekseninde hareket
  w = data[3]; // tekerleğin hareketi 

Burada printk ile data bitlerini yazdırırsak tuşların bit karşılıklarını buluruz ve bir çeşit tersine mühendislik yapmış oluruz. Biz de burada bunu yaparak faremizdeki ekstra tuşların bit haritasını elde edebiliriz.

input_report_key(dev, BTN_LEFT,    GETBIT(btn,0));
 input_report_key(dev, BTN_RIGHT,   GETBIT(btn,1));
 input_report_key(dev, BTN_MIDDLE,  GETBIT(btn,2));
 input_report_key(dev, BTN_SIDE,   GETBIT(btn,3));
 input_report_key(dev, BTN_EXTRA,   GETBIT(btn,4));

 input_report_rel(dev, REL_X,     x);
 input_report_rel(dev, REL_Y,     y);
 input_report_rel(dev, REL_WHEEL, w);
}

GETBIT(btn,0) 

İfadesi ise datanın bitlerini maskelemeye yarayan GETBIT makrosunun çağrısıdır. Bu makro data dizisinin ikinci parametrede belirtilen bitini döner. Tabii bu makronun tanımı da kodların arasına eklemelisiniz.

#define GETBIT(v,n)     ((v>>(n))0x01)
#define SETBIT(v,n)     (v |= (0x01<<(n))) 

Bu fonksiyon ise cihazımızdan her URB geldiğinde çalışacak olan Interrupt Handler. Burada URB’nin içinden fare nesnesi elde ediliyor. Taşıdığı data alınıyor. Az önce yazdığımız input_send_data() çağrısı ile basılan tuşlar sisteme kaydediliyor. En son usb_submit_urb çağrısı ile URB USB Core’a gönderiliyor.

static void usb_a4r7mouse_handle(struct urb *urb) {
 a4r7mouse_t *mouse = urb->context;
 signed char *data = mouse->data;
 struct input_dev *inputdev = mouse->inputdev;
 // Check returned status
 if (urb->status) return ;
 // Send data to input interface
 input_send_data(inputdev,data);
 input_sync(inputdev);
 usb_submit_urb(urb,ATOMIC);
} 

Cihazın açma ve kapatma fonksiyonları

static int usb_a4r7mouse_open(struct input_dev *dev) {
 a4r7mouse_t *mouse = input_get_drvdata(dev);
 if (mouse->open++)
  return 0;
 mouse->urb->dev = mouse->usbdev;
 if (usb_submit_urb(mouse->urb, GFP_KERNEL)) {
  mouse->open--;
  return -EIO;
 }
 return 0;
}

static void usb_a4r7mouse_close(struct input_dev *dev) {
 //a4r7mouse_t *mouse = dev->private;
 a4r7mouse_t *mouse = input_get_drvdata(dev);
 if (!--mouse->open)
  usb_kill_urb(mouse->urb);
} 

Yazdığımız modülün çekirdek tarafından sürücü olarak tanınması olarak tanınması için. Bir “struct usb_driver” örneği oluşturup, bu örneğin içini gerekli bilgilerle doldurmalıyız. Bu yapıda; name sürücünün adını belirtir ve sistemde benzersiz olmalıdır probe ise sürücünün iş yapan fonksiyonuna bir işaretçidir. Donanım ile iletişime geçen fonksiyon budur. Disconnect de cihaz sistemden kaldırıldığında çalışacak olan (muhtemelen cihaza ayrılan sistem kaynaklarının serbest bırakıldığı) fonksiyona bir işaretçidir. İd_table ise geçen sayıda anlattığımız bu sürücünün hangi cihazlar için yüklenmesi gerektiğini belirten, bu cihazların Vendor ve Product ID’lerinin tutulduğu bir “usb_device_id” dizisidir.

static struct usb_driver usb_a4r7mouse_driver = {
 .name  = "a4r7mouse_usb",
 .probe  = usb_a4r7mouse_probe,
 .disconnect = usb_a4r7mouse_disconnect,
 .id_table = usb_a4r7mouse_id_table
}; 

Bizim cihazımız için bu dizi şu biçimdedir:
static struct usb_device_id usb_a4r7mouse_id_table [] = {
 { USB_DEVICE(0x09da, 0x021f) },
 { }
}; 

Buradaki 09da değerinin geçen sayıda elde ettiğimiz Vendor ID’ye 021f değerinin ise Product ID’ye karşılık geldiğine dikkat edin. Bu yapının içini cihaz ve üretici numaraları ile değil de usb.h içinde tanımlı olan bazı sabitleri kullanarak sadece bir cihaz için değil de belli bir tür sınıf (cihazın ayar registerlarında tanımlı olan) cihaz için geçerli olmasını sağlayabiliriz. Şu makro ile id_table ımızın bir usb id_table’ı olduğunu belirtiyoruz.

MODULE_DEVICE_TABLE (usb, usb_a4r7mouse_id_table); 

Disconnect metodumuz ise şöyle:

static void usb_a4r7mouse_disconnect(struct usb_interface *intf) {
 a4r7mouse_t *mouse = usb_get_intfdata(intf);
 usb_set_intfdata(intf,NULL);
 if (mouse) {
  usb_kill_urb(mouse->urb);
  input_unregister_device(mouse->inputdev);
  a4r7mouse_free(mouse);
 }
} 

Burada cihazın bağlı olduğu usb interface’den somut bir a4r7mouse nesnesi oluşturuyoruz. Sonra bu interface’ı boşaltıyoruz. Cihaz sistemden ayrılmadan önce bir urb göndermiş olabilir bu yüzden geçerli urb’yi öldürüyoruz (kill). Cihazı sistemden silip (input_unregister_device), Mouse nesnemizi boşaltıyoruz. Sonra yazı dizimizin ikinci bölümünde bahsettiğim, modülümüzün init fonksiyonunu yazıyoruz.

static int __init usb_a4r7mouse_init(void) {
 int rv;
 // usb sürücüyü sisteme kaydet
 rv = usb_register(&usb_a4r7mouse_driver);
 return rv;
} 

Hatırlarsanız modülleri anlatırken “bir modül init fonksiyonu içinde sürücüyü sisteme kaydetmelidir” demiştir. İşte burada da

rv = usb_register(&usb_a4r7mouse_driver); 

Çağrısı ile tam olarak bunu yapıyoruz. Modülün exit fonksiyonunda sürücüyü sistemden kaldırıyoruz.

static void __exit usb_a4r7mouse_exit(void) {
 usb_deregister(&usb_a4r7mouse_driver);
} 

Ve şu iki makro ile init ve exit fonksiyonlarını çekirdeğe tanıtıyoruz.

module_init(usb_a4r7mouse_init);
module_exit(usb_a4r7mouse_exit); 

Dikkat ettiğiyseniz usb_a4r7mouse_driver yapısının probe fonksiyonun açıklamadan geçtim. Asıl işi yapacak bu fonksiyon olduğu ve çok uzun olduğu için sona sakladım. Açıklamayı adım adım yapacağım önce fonksiyonun tanımı verelim sonra içini dolduralım.

static int usb_a4r7mouse_probe(struct usb_interface *intf, const struct usb_device_id *id) { }

Bu fonksiyonu yazdığımız sürücünün hiçbir yerinde çağırmadığımıza dikkat edin. Bu fonksiyonu çekirdek cihazın bağlı olduğu interface ve cihazın vendor ve product id bilgisi ile birlikte çağırır ve sürücünün çalışması başlar. İlk satır ile fonksiyona gelen interface bilgisinden bir usb_device nesnesi elde ediyoruz.

struct usb_device *dev = interface_to_usbdev(intf);
struct usb_host_interface *interface;
struct usb_endpoint_descriptor *endpoint;
a4r7mouse_t *mouse;
int pipe, maxp;
char *buf; 

Her USB interface için o interface’in bilgilerini tutan endpoint denilen bir yapı vardır. Burada interface üzerinden endpoint alınıyor.

// Get mouse endpoint
interface = intf->cur_altsetting;
if ( interface->desc.bNumEndpoints != 1 ) return -ENODEV;
endpoint = &interface->endpoint[0].desc; 

Burada endpoint özellikleri kontrol ediliyor. Eğer endpointimiz interrupt ile veri gönderen bir endpoint ise bizim cihazımızın özelliklerini taşıyor demektir.

if (!(endpoint->bEndpointAddress & USB_DIR_IN))
  return -ENODEV;
if ((endpoint->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) != USB_ENDPOINT_XFER_INT)
  return -ENODEV; 

Endpoint ile iletişime geçecek pipe (kanal) oluşturuluyor.

pipe = usb_rcvintpipe(dev, endpoint->bEndpointAddress);
maxp = usb_maxpacket(dev, pipe, usb_pipeout(pipe)); 

Artık elimizdeki cihaz bir a4r7mouse nesnesi olabilecek niteliktedir. Elimizdeki dev nesnesinde bir a4r7mouse nesnesi oluşturuyoruz.

if (!(mouse = a4r7mouse_new(dev)))
 return -ENOMEM;
Ve bir input_device oluşturuyor ve initiliaze ediyoruz. Bu fonksiyonun açıklamasını daha sonra yapacağız.

input_device_init(mouse->inputdev,intf,mouse,dev); 

Bunların ne yaptığını ben de bilmiyorum :)

// Set device name
 if (!(buf = kmalloc(63, GFP_KERNEL))) {
  a4r7mouse_free(mouse);
  return -ENOMEM;
 }
 if (dev->descriptor.iManufacturer &&
  usb_string(dev, dev->descriptor.iManufacturer, buf, 63) > 0)
   strcat(mouse->name, buf);
 if (dev->descriptor.iProduct &&
  usb_string(dev, dev->descriptor.iProduct, buf, 63) > 0)
   sprintf(mouse->name, "%s %s", mouse->name, buf);
 if (!strlen(mouse->name))
sprintf(mouse->name, "a4r7mouse.c: A4Tech Power Saver R7 Wireless Mouse on usb%04x:%04x",
   mouse->inputdev->id.vendor, mouse->inputdev->id.product);
 kfree(buf); 

Burada bir başka önemli işlem yapılıyor. Bu cihaz için yazdığımız URB Handler (usb_a4r7mouse_handle) sisteme kaydediliyor. Ve cihazın dma modları ayarlanıyor.

usb_fill_int_urb(mouse->urb,dev,pipe,mouse->data,((maxp > 8)?8:maxp),usb_a4r7mouse_handle,mouse,endpoint->bInterval);
 mouse->urb->transfer_dma = mouse->data_dma;
 mouse->urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP;

Cihaz input device olarak kaydediliyor. Artık bu cihazdan gelen sinyaller input API’ye gönderilecek.

input_register_device(mouse->inputdev);

Üzerinde çalışığımız inteface’in device bilgisi bizim Mouse nesnemize set ediliyor.

usb_set_intfdata(intf,mouse);
 return 0;

Son olarak input_device_dev fonksiyonu.

static void input_device_init ( struct input_dev *inputdev, struct usb_interface *intf, void *private, struct usb_device *dev ) {}
 
Tekrardan fonksiyona void pointer ile geçirilen a4r7mouse nesnemizi elde ediyoruz.

char path[64];
a4r7mouse_t *mouse = (a4r7mouse_t *) private;

input device ın özellikleri atanıyor. Önce farenin tıklama ve bırakma olayları.

inputdev->evbit[0] = BIT(EV_KEY) | BIT(EV_REL);

Temel tuşlar

inputdev->keybit[BIT_WORD(BTN_MOUSE)] = BIT_MASK(BTN_LEFT) | BIT_MASK(BTN_RIGHT) | BIT_MASK(BTN_MIDDLE) |    BIT_MASK(BTN_SIDE) | BIT_MASK(BTN_EXTRA);

x-y ekreninde hareket

inputdev->relbit[0] = BIT_MASK(REL_X) | BIT_MASK(REL_Y);

Tekerin hareketi

inputdev->relbit[0] |= BIT_MASK(REL_WHEEL); 

Cihazla ilgili özellikler ayarlanıp fonksiyon bitiriliyor. (üretici, ad, versiyon, adres, açma kapama fonk.)

input_set_drvdata(inputdev, mouse);

 // Input file operations

 inputdev->open = usb_a4r7mouse_open;
 inputdev->close = usb_a4r7mouse_close;

 // Device

 inputdev->name = mouse->name;

 usb_make_path(dev,path,64);
 snprintf(mouse->phys,64,"%s/input0",path);

 inputdev->phys = mouse->phys;
 inputdev->id.bustype = BUS_USB;
 inputdev->id.vendor = dev->descriptor.idVendor;
 inputdev->id.product = dev->descriptor.idProduct;
 inputdev->id.version = dev->descriptor.bcdDevice;

Sürücüyü Çalıştır(ama)ma
Şimdi normalde daha önce verdiğim makefile ile bu modülü derleyip, insmod ile yüklediğinizde sürücünün çalışması lazım. Ancak daha önce sistemde hazır gelen sürücü fare sınıfındaki bütün cihazlar için tanımlandığı için bizim sürücümüzün önene geçmektedir. Yapmamız gereken bizim sürücümüzün bu genel sürücüden önce yüklenmesini sağlamaktır. Tabii bu söylendiği kadar kolay olmamaktadır. Bildiğiniz gibi linux’ta sürücüler modül olarak gelebildiği gibi çekirdeğin derlenmesinde sisteme yerleştirilerek gömülü olarak da gelebilmektedir. Burada yapmamız gereken dağıtımınıza bağlı olarak değişebildiği gibi genelde şudur. Linux’ta sisteme yüklenen bütün modüller “/etc/” dosyasında tanımlıdır. Bu dosyadan bizim sürücümüzü engelleyen sürücüyü bulup, “/etc/insmod/blacklist.conf” dosyasına “blacklist modüladi” şeklinde yazıp, sürücüyü kara listeye almak ve modülün yüklenmesini engellemektir.
Eğer sürücü çekirdeğe gömülü olarak geliyorsa yapmanız gereken bu sürücüyü çekirdekten çıkarıp tekrar derlemektir. Bu adımda yapacaklarınız bir yazı dizisinde daha bahsedilebilecek kadar bilgi gerektirdiğinden Allah size kolaylık versin diyorum.

Sonuç
Yukarıda anlattığım nedenlerden dolayı kendi sürücümüzü bir türlü sisteme yükleyemedik. Yani 3 aydır kafa patlattığımız yazımız sonucunda çalışır hiçbir kod elde edemedik (Hellomodül dışında). Eğer konu ilginizi çekti ve ben illaki bu sürücüyü çalıştıracağım diyorsanız, dağıtımınızın belgelerine bakmanızı, bizim örnek aldığımız David Olivera’nın Logitech sürücüsünü incelemenizi öneririm.

Uzun ve yorucu bir dizi oldu. Gelecek ay görüşmek dileğiyle.

Kaynaklar
Linux Device Drivers, Robert Corbert, O’Reilly, 2005
Linux Kernel Development, Robert Love, Novell Press, 2005
www.kernel.org
David Olivera Logitech Sürücüsü

2 yorum: