sayfa başı

Elektronik etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster
Elektronik etiketine sahip kayıtlar gösteriliyor. Tüm kayıtları göster

30 Haziran 2015 Salı

Beagle Bone Konsola Debug Portu ile Erişim




Beagle Bone Konsola Debug Portu ile Erişim


Amaç
BeagleBone Konsolune Erişim
2.1 PUTTY ile IP Üzerinden Erişim
2.2 PUT ile Seri Port Üzerinden Erişim
2.3 Board Üzerinde Bulunan Seri Port Pinleri İle Konsola Erişim


  1. Amaç

Bu dökümanda BeagleBone konsoluna nasıl erişileceği anlatılmaktadır.
  1. BeagleBone Konsolune Erişim

BeagleBone konsoluna erişmek içini üç farklı yöntem bulunmaktadır. PUTY ile IP üzerinden erişim, PUTY ile seri port(USB seri port) üzerinden erişim, board üzerinde olan seri port pinleri ile konsola erişim olara gruplandırabiliriz.

2.1 PUTTY ile IP Üzerinden Erişim

İlk olarak Putty bu linkten indirin. Daha sonra linux makine ile fiziksel teması kurmak için ürünle beraber gelen USB kabloyu bağlayın.
Usb kablo bağlandıktan sonra makine enerjisini buradan aldığı için açılmaya başlayacaktır. Bilgisayarımızın bu USB girişini seri port olarak görmesi için driver kurulması gerekir.


Windows 64 bit için:


Windows 32 bit için:


Linux için:
Bu script  /etc/udev/rules.d/ altında 73-beaglebone.rules isminde yeni bir dosya oluşturacaktır. Bu script koşmak gerekecektir.


Cihaz açıldıktan sonra ve Windos tarafında driver kurulumu bittikten sonra PUTTY çalıştıralım. Putty ile SSH üzerinden cihaza bağlanmak için cihazın IP adresi gerekmektedir. BeagleBone fabrika çıkışı olara USB girişine sanal bir static IP adresi vermektedir. Sanal IP diyorum çünkü ethernet kablosu bağlı olmadan makine IP almaz. Bu sanal IP 192.168.7.2. Host Name (or IP address) kısmına bu IP adresini yazalım. Port numarası olarak 22 kalmalı.
Bu ayarlar yapıldıktan sonra “Open” diyerek cihaza bağlanalım. Cihaza ilk bağlandığımızda güvenlik uyarısı verecektir. Yes diyerek devam edelim. Cihaza bağlandımızda ”login as” promp ile hangi kullanıcı olarak devam edeceğimizi soracaktır.”root” deyip cihazın konsoluna erişelim.
Tüm bu adımlardan sonra BeagleBone konsoluna erişmiş oluyoruz.

2.2 PUT ile Seri Port Üzerinden Erişim

Putty çalıştıralım ve “connection type” kısmından “Serial” seçelim. Daha sonra seri port için gerekli ayarları girelim. Doğru Com Port numarasını girdikten sonra baudrate olarak 115200 değerini girelim. Bu adımlardan sonra “Open” dedikten sonra BeagleBone(BB) konsoluna erişmiş olacağız. Enter tuşuna basarak hangi kullanıcı ile bağlandığımızı ve pwd komutu ile bulunduğumuz dizini görebiliriz.

2.3 Board Üzerinde Bulunan Seri Port Pinleri İle Konsola Erişim

Board üzerinde bulunan 6 pinli header konnektor(konsol seri portu) ile BeagleBone konsoluna erişebilinir.(Debug portu) Embedded linux kartlarda genellikle bu yol ile makine konsoluna erişim yapılmaktadır. Bu yüzden BB USB seri portunu değil de bu portu kullanacağız.
Bord üzerindeki debug portunun yerini ve gerekli olan TX, RX ve GND pinlerini gösteren resim incelendikten sonra bağlantılar yapılmalıdır. Burada USB-TTL UART dönnüştürücünüzün 5V ya da 3.3V çıkışlarını bağlanmamasına dikkat ediniz. Sadece TX, RX ve GND pinleri bağlanmalıdır.
USB-TTL UART dönüştürücümüzün TX pini BeableBone RX ile, RX pini BeagleBone TX pini ile bağlanmalıdır.
Bağlantılar yapıldıktan sonra seri port terminal programımız(SecureCRTPortable) açıp konsola erişebiliriz.


USB-TTL RS232 Dönüştürücü









14 Mart 2015 Cumartesi

Adım Adım Linux Driver Yazıyoruz -3-

     Yolu buraya düşen herkse ilk olarak Merhaba. Bugün yazdığımız modülü derleyip çalıştıracağız. Bir önceki yazımda makefile oluşturacağımzı bahsetmiştim. Bu yazımda da Makefile hakkında kısa bir yazı yazmıştım. Bu yazılardan sonra kaldığımız yerden devam edelim.
    İlk olarak modülümüz derlememize yardımcı olacak Makefile kodunu aşağıda bulunmakta. Makefile dosyası ile .c uzantılı modül kodumuzu aynı dizine koyalım ve konsoldan o dizine gidelim. Derleme için tek yapmamız gereken konsola make yazıp entere basmak. Hepsi bu. Belki KERNEL_SOURCE := /usr/src/linux-headers-3.13.0-32-generic bağlı olarak hata alabilirsiniz. Onu da kendi PC bulunan ayara göre düzenleyerek çözebilirsiniz. Zaten /usr/src/ dizinine giderseniz orada linux-...... diye birşey görürsünüz. Gerisi kopyala yapıştır.

# Makefile – makefile of our first driver

# if KERNELRELEASE is not defined, we've been called directly from the command line.
# Invoke the kernel build system.
ifeq (${KERNELRELEASE},)
 KERNEL_SOURCE := /usr/src/linux-headers-3.13.0-32-generic
 PWD := $(shell pwd)
default:
 ${MAKE} -C ${KERNEL_SOURCE} SUBDIRS=${PWD} modules

clean:
 ${MAKE} -C ${KERNEL_SOURCE} SUBDIRS=${PWD} clean

# Otherwise KERNELRELEASE is defined; we've been invoked from the
# kernel build system and can use its language.
else
 obj-m := zafer.o //derlenecek .c dosyasının ismi verilmeli
endif
    Derleme işleminden sonra elimizde .ko uzantılı bir dosya oluştu. Bundan sonra yapmamız gereken ilk iş ya seriport üzerinden ya da tftp ile bu dosyayı cihaza aktarmamız olacak. Bu adımı da başarılı bir şekilde yaptığımızı varsayarak sıradaki işlemlere geçelim.

    Cihazımıza .ko uzantılı kernel objesini  yükledik. Peki bu dosyayı nasıl çalıştıracağız. Tabiki normal çalıştırılabilir dosyalar gibi ./isim.ko şeklinde çalıştıramayız. Modül dosyalarını çalıştırmak ya da durdurmak için tanımlanmış komutlar vardır. Özetle onlara bir göz atalım.
  • lsmod: yüklenmiş modül dosyalarını listeler
  • insmod: modülü Kernel yükler yani çalışmasını sağlar
  • rmmod: Kernel yüklenen modülü geri alır. Çalışmasını durdurur.
    Şu bilgiyi hatırlayalım: yüklenmiş modüller major numarası ve isimleri ile /proc/devices/ dizini altında görülür. Yazdığımız modülün Kernel yüklenmesi yani çalışır duruma gelmesi için insmod komutunu koşalım $insmod modül_ismi koşalım. Şimdi modülümüz Kernel yüklenmiş oldu. Teyit etmek için cat /proc/devices/ komutunu kullanalım. Verdiğimiz isim ve major numarası ile cihazımız tam karşımızda olması lazım. Major numarasını bir yere not edelim. Kısa bir süre bu numara bizim için önemli olacak.
    Madem insmod ile modülü yükledim, o zaman /dev/ altında normal cihazlar gibi görüp kullanmam gerekir diyenlere bunun şimdilik imkansız olduğunu söylemek gerekir. Cihaz dosyasını /dev/ altında oluşturma işini şimdilik elle yapacağız. 
             sudo mknod /dev/isim c 250 0
   Buradaki isim bağımsız olarak verebileceğimz bir isim, istediğiniz isimi verebilirsiniz. c char device file. 250 hani kaydettiğimiz major numarası vardı ya işte o değer, 0 ise cihaza atayacağımız minor numarası. Bu adımdan sonra ls -l /dev/ yaptığıızda /dev/ altında yeni oluşan dosyayı görebiliriz.


Otomatik Cihaz dosyasını(Device File) Oluşturma

    Bir önceki adımda cihaz dosyasını(Device File) mknod komutu yardımıyla elle oluşturduk. Şimdiki adımımız bu işlemi otomatik olarak yapma.
    2.4 Kernel versiyonna kadar device file oluşturma işi devfs ait uygun API ile Kernel tarafından otomatik olarak yapılırdı. Kernel' ın gelişmesiyle geliştiriciler cihaz dosyasının kullanıcı katmanına ait bir olduğunu düşünerek bunun kullanıcılar tarafından yapılmasına karar verdiler.
    <linux/device.h> altındaki fonksiyonlara bakarak bu işlemi gerçekleştirelim.
struct class *cl;

if (IS_ERR(cl = class_create(THIS_MODULE, MY_DRIVER_CLASS_NAME)))
{
 rv = PTR_ERR(cl);
 goto err_class_create;
}

if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, MY_DRIVER_DEVICE_FILE_NAME)))
{
 rv = PTR_ERR(dev_ret);
 goto err_device_create;
}
    Bu kod parçasını init fonksiyomuz içine koyarak cihaz dosyasını otomatik olarak oluşturmuş oluruz. IS_ERR makrosu ile fonksiyonların geri dönüş değerleri test edildi. Her ne kadar C kullananlar goto kullanmayı sevmese de burada goto iyi bir tercih. “Linux Device Drivers” kitabinda sayfa 33' de goto için yazılanlar aynen size aktarıyorum. "We normally hate to use goto, but in our opinion, this is one situation where it is useful."  Bu cümlenin ardından verdikleri örnek de şu şekilde:
int __init my_init_function(void)
{
   int err;

   /* registration takes a pointer and a name */
   err = register_this(ptr1, "skull");
   if (err) goto fail_this;
   err = register_that(ptr2, "skull");
   if (err) goto fail_that;
   err = register_those(ptr3, "skull");
   if (err) goto fail_those;
   return 0; /* success */
   fail_those: unregister_that(ptr2, "skull");
   fail_that: unregister_this(ptr1, "skull");
   fail_this: return err; /* propagate the error */
}
    Peki IS_ERR hata olduğunu bildirir ve goto nun gösterdiği yere gidersek burada ne yapacağız. 
err_device_create:
 class_destroy(cl);
err_class_create:
 cdev_del(&c_dev);
    Burada bir şeye dikkat çekmek isterim. İlk sorguya ait hata işleme yeri ikinci sorguya ait hata işleme yerinden daha sonra yazılmış yani en son adım hata oluşursa kendinden önceki tüm adımlarda yapılan işlemler hatalıymış gibi onlar da geri alınmalı. Hataları ele alma yeri init fonksiyonun sonunda return hemen önce olmalı. Ayrıca exit fonksiyonumuzda da bu durumları ele almalıyız. Bu kadar sözel açıklamadan sonra kodun son halini görerek anlatılanların resmine bakalım.
#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>

static dev_t dev;          //dev device number
static struct cdev c_dev;   //character device structure
static struct class *cl;   //device class


static int __init myModule_init(void) /* Constructor */
{
 int retVal = -EACCES;
 struct device *dev_ret;

 if ((retVal = alloc_chrdev_region(&dev, 0, 1, "zafer")) < 0)
 {
  goto err_alloc_dev_region;
 }

 if (IS_ERR(cl = class_create(THIS_MODULE, "chardriver")))
 {
  retVal = PTR_ERR(cl);
  goto err_class_create;
 }

        /*Burada verdigimiz isim /dev/ altinda gorulur "myDevice"*/
 if (IS_ERR(dev_ret = device_create(cl, NULL, dev, NULL, "myDevice"))) 
 {
  retVal = PTR_ERR(dev_ret);
  goto err_device_create;
 }
 return 0;

err_device_create:
 class_destroy(cl);
err_class_create:
 cdev_del(&c_dev);
err_cdev_add:
 unregister_chrdev_region(dev, 1);
err_alloc_dev_region:
 return retVal;
}

static void __exit myModule_exit(void) /* Destructor */
{
 cdev_del(&c_dev);
 device_destroy(cl, dev);
 class_destroy(cl);
 unregister_chrdev_region(dev, 1);
 printk(KERN_INFO "myModule unregistered");
}

module_init(myModule_init);
module_exit(myModule_exit);

MODULE_AUTHOR("Zafer Satilmis");
MODULE_DESCRIPTION("First Character Driver");
    Artık modülümüz hazır. /dev/ altında da cihaz dosyamızı görür durumdayız. Bundan sonraki adım kullanıcı katmanında bu cihaz dosyası ile cihazı kontrol etmek. Bunun için son adım olan dosya operasyonları kodunu yazacağız. Dosya operasyonları ile cihazımız üzerinde okuma, yazma işlemleri yapacağız. ioctl(input/output control) ile cihaza kendimizin tanımladığı komutları vereceğiz. Bu işlemler sonucunda tam kullanışlı bir iş yapmış olacağız. 

Faydalanacağını düşündüğünüz kişilere iletin, eksik ya da yanlış olduğunu düşündüğünüz yerleri belirtin.


Şimdi Yazmaya Devam ....




8 Mart 2015 Pazar

Adım Adım Linux Driver Yazıyoruz -2-

Adım Adım Linux Driver Yazıyoruz

    Adım Adım Linux Driver Yazıyoruz başlıklı yazıma devam ediyorum. Eğer bu yazının ilk kısmını okumadıysanız buradan ulaşabilirsiniz. Bu yazımda biraz daha teorik bilgiler ile açıklama yaptıktan sonra iştahla beklediğimiz kod örneklerine ulaşacağız. Biraz sabırla devam edelim ki konuyu tam anlamıyla öğrenelim.

Kernel Fonksiyonlarının Geri Dönüş Değerleri

    Kernel fonksiyonları kendini çağırana belli bir değer ile geri döner. Bu değerler belli bir kurala tabi tutulmuştur. Burada oluşturulan yöntem oldukça basittir, hatta kullanıcı katmanında da(user space) bu yöntemi uyguladığımızda oluyor. Bu yüzden bu konuya uzak değiliz.
  • Hata Durumu: Kernel fonksiyonları hata durumlarını iletmek için negatif değer döner. Fonksiyonun geri dönüş değerini sorguladığınızda eğer bir negatif değer ile karşılaşırsanız işler rast gitmiyor demektir. Tıpkı user space olduğu gibi Kernel seviyesinde de aldığınız hata mesajlarını açıklayan bir başlık dosyası bulunmaktadır. <linux/errno.h> İşte bu dosyada hata numaraları açıklanmaktadır. Ayrıca kendi yazdığınız fonksiyonların da geri dönüş değeri olacaktır. Kernel fonksiyonları gibi hata durumlarını iletmeniz gerekecektir. Bu yüzden fonksiyonun geri dönüş tipi int olmalıdır. Şunu da söylemek gerekir başlık dosyasını incelediğinizde tüm hata mesajlarının pozitif sayı olduğunu görürsünüz. Bu yüzden kendi fonksiyonlarınızda hata durumlarınızı dönerken bu başlık dosyasında tanımlı hata makrolarının önüne "-" işareti koyarak sayıyı negatif yapmalısınız.
  • Başarı durumu: Kernel fonksiyonları işlerini başarı ile yaptıklarını iletmek için sıfır değeri ile geri döner. Tabi bu durumun bazı istisnaları vardır. Örneğin read ve write fonksiyonları gibi. Bu fonksiyonlar başarı sağladıkları karakter sayısı ile geri dönerler. Ama sonuçta negatif değildir. 
    Not: Konuya devam etmeden önce bu başlık için bir referans kitabı belirtmek istiyorum. Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman tarafından yazılmış “Linux Device Drivers” kitabını okumanızı tavsiye ederim. Zaten pdf olarak bulmanız çok kolay. Hatta bu linkten hızlıca temin edebilirsiniz.

    Linux cihazları üç farklı türde farklılaştırmıştır ve modül yazımında bu farklılığa göre kodunuzu oluşturmalısınız. İlk olarak cihazın türünü belirlemek ve sonra o türe ait modül yazılımını yapmak gerekiyor. char module, block module, network module işte ayrıma sebep olan modül isimleri. Biz yazılacak olan modül için char device ele alacağız.


Char Devices

    Char Devices byte akışı ile erişim sağlanan cihazlardır. Bu cihazlara ait modül de bu davranışa göre oluşturulmalıdır. Byte akışı ile erişim olduğundan bu cihazların sürücüleri(driver) open, close, read, ve write sistem çağrıların içerirler. Konsol ekranı(dev/console), seri port(dev/ttyS0) ... karakter cihazlara örnektir.
    Karakter cihaz sürücüleri normal dosyalar gibi kullanıcı katmanında karşımıza çıkar. Karakter cihaz sürücüleri(Char Driver) üzerinde open, close, read, ve write işlemleri yapabilmemize rağmen normal dosyalardan ayıran fark ise onların normal dosyalar gibi kalıcı bilgiler taşımaması ve normal dosyaların kullanıcı iznine bağlı olarak silme, yer değiştirme ve isim verme gibi işlemlerinin yapılamamasıdır. Durumu daha iyi açıklamak için bir audio device dosyasını ele alalım. Bu dosyaya ne yazarsak direk cihaza doğru yönlendirilir ve bu yazılanların etkisiyle cihazdan çıktı alırız. Fakat normal dosya gibi okumak istediğimizde ise bir önceki yazdığımız verilere ulaşamayız. Durumun tam tersini düşünelim ve bir mikrofon ile kayıt yapıyor olalım bu sefer okuma fonksiyonu ile mikrofondan gelen verileri alıyor olacaktık.
  Bu konuda daha fazla bilgiye referans olarak belirttiğim “Linux Device Drivers” kitabının 42 sayfasında ulaşabilirsiniz. 

Kullanıcı Katmanından Cihaza Kadar Olan Bağlantının Sağlanması

Kullanıcı Katmanından başlayarak char device nasıl erişildiğini aşağıdaki resim açıklamaktadır.

Figure 7: Character driver overview
Char Driver Çalışması

    Görüldüğü gibi kullanıcı katmanında cihazı kontrol etmek için(ulaşmak için) cihaza ait dosya kullanılır.(char device file -CDF-) Uygulamalar sadece bu dosyalar üzerinde bildiğimiz dosya işlemlerini yaparak basitçe cihazı kontrol edebilirler. Bu durumun bir güzel yanı da tüm cihazlar için kullanıcı katmanında aynı fonksiyonların kullanılması. Cihazımız ne olursa olsun o cihazı okumak ya da yazmak için hep aynı fonksiyonları kullanırız.
    Peki bu nasıl oluyor? Yukarıdaki resmi incelersek her char device file -CDF- tek bir noktada birleşiyor. Virtual File System. Virtual file system uygulama seviyesindeki CDF gelen isteğin hangi cihaz için olduğunu çözümler ve isteği o cihazın sürücüsüne yönlendirir. Bu sayede istek doğru cihazı bulmuş olur. İsteğin çözümlenmesi ve yönlendirilip ilgili cihaz sürücüsüne ulaşma işlemleri Kernel seviyesinde olur. Son olarak artık isteğin cihaz üzerinde işlenmesi donanım seviyesinde(Hardware Space) olur.
    Uygulamalar  open fonksiyonu ile device file erişirler. Device file VFS ile Device Driver bağlanırlar. Device Driver cihaza özgü olan alt seviye özel işlemler ile cihaza ulaşır ve cihaz üzerinde yapılmak istenen işlem tamamlamış olur.


Major ve Minor Numaraları

     Major ve minor numaraları konusuna giriş için ilk olarak şu komutu koşalım; $ls -l /dev/ Bu komutu koştuğumuzda sistemimizdeki cihazlara ait dosyaları (device file) görürüz. Burada dosya isimlerinden başka konumuz olan major ve minor numaralarını da görürüz. Major ve minor cihazı tanımlayan özel bir sayı çiftidir.


    Resimde /dev/ altında cihazlara ait major ve minor numarı görülmektedir. Major numarası cihazları gruplandırmak için kullanılır. Resimde tüm cihazların major numarası dörttür. Çünkü bu cihazlar serial interface grubunda olup, o gruba ait major numarasını taşırlar. Minor numarası ise tekildir. Yani bir grup içinde her cihazın minor numarası ayrıdır. Kernel hangi cihaza erişim olacağını minor numarası ayrımı ile yapar.
  Kullanıcı katmanındaki uygulamalar sadece device file gördüklerinden onlara isimleri ile ulaşırlar. Her ne kadar kullanıcı katmanında dosya ismi kullanılsa da cihaz sürücüsüne (device driver) ulaşmak için artık numaralar kullanılır. Bu numaraları kullanma kullanıcı seviyesinde yapılan bir iş değildir. Bizim kullandığımız dosya ismi kernel seviyesinde cihaz sürücüsüne ait olan major ve minor numaralarına dönüşür.
  Not: Major ve Minor her ikisi Kernel 2.6 versiyonundan sonra desteklenmektedir.
    Kernel modülü yazılırken bu major ve minor numaralarının nasıl ele alındığına bakalım. İlk olarak <linux/types.h> dosyasını modül yazımında dahil edilmelidir. Çünkü cihaza yüklenecek olan major ve minor numaraları dev_t türündeki bir değişkenin içinde saklanacaktır. dev_t türü 32 bitlik bir değişken olup 12 bitinde major numarası, 20 bitinde de minor numarasını içerir.
Major ve minor numaralarını ayrı ayrı görmek için <linux/kdev_t.h> tanımlanan makroları kullanılır.

MAJOR(dev_t dev) // major numrasını verir
MINOR(dev_t dev) // minor numarasını verir
MKDEV(int major, int minor) // major ve minor numarası ile dev oluşturur.
Bu konuda daha fazla bilgiye “Linux Device Drivers” kitabının 43-44 sayfalarından ulaşabilirsiniz.


Cihaz Numaralarını Oluşturmak

    Modül yazılırken ilk olarak cihaz numaraları (major ve minor numaraları) oluşturulmalıdır. Cihaz numaraları oluşturulduktan sonra sitem cihazımızı bu numaralar ile tanıyacaktır.
int register_chrdev_region(dev_t first, unsigned int cnt, char *name);
int alloc_chrdev_region(dev_t *first, unsigned int firstminor, unsigned int cnt, char *name);
    İlk fonksiyonda first, almak istediğimiz device number başlangıcıdır. Bu değere genellikle 0 olarak değer geçilir.  cnt ise en yakın cihaz numarasına ulaşmak için kullanılır. Son olarak name cihaza verdiğimiz isimdir. Bu isim oluşturulan cihaz numarası ile ilişkilendirilir. İlk fonksiyon kullanımımıza sunulmuş olunsa da genellikle bu fonksiyon yerine ikinci fonksiyon kullanılır. Çünkü çoğunlukla cihazımızın hangi major numarasını kullanacağını bilemeyiz. Bu yüzden ikinci fonksiyonun kullanımı daha kolaydır.
    Eğer çağırdığımız fonksiyon başarı ile döner ise verdiğimiz isim major numarası ile /proc/device/ altında görülür.
    Bu fonksiyonlar modülümüzün init fonksiyonu içinde çağrılmalıdır ki modül dinamik olarak yüklendiğinde cihaza ait veriler oluşsun. Şimdi tüm bu anlatılanlara yönelik kodu yazalım.

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

static dev_t dev;

static int __init myModule_init(void) /* Constructor */
{
      int retVal;
    
      retVal = alloc_chrdev_region(&dev, 0, 1, "ZAFER");
      if (retVal < 0)
      {
          return retVal;
      }
      printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(dev), MINOR(dev));
      return 0; // success
}

static void __exit myModule_exit(void) /* Destructor */
{
      unregister_chrdev_region(dev, 1);
      printk(KERN_INFO "myModule_exit\n");
}

module_init(myModule_init);
module_exit(myModule_exit);

MODULE_AUTHOR("Zafer Satilmis");
MODULE_DESCRIPTION("First Char Driver");
   Kodu yazdıktan sonra tabi ki derlenmesi gerekiyor. Bir sonraki yazımda derleme işlemini nasıl yapacağımızı (Makefile oluşturma), cihaza nasıl yükleyip modülü nasıl çalıştırılacağını aktaracağım. 

Faydalanacağını düşündüğünüz kişilere iletin, eksik ya da yanlış olduğunu düşündüğünüz yerleri belirtin.

Şimdi Yazmaya Devam ....

Kuşadası

6 Mart 2015 Cuma

Adım Adım Linux Driver Yazıyoruz -1-

Adım Adım Linux Driver Yazıyoruz
Giriş
Bu yazımda Linux driver eklemeyi ele alacağım. Kullanacağımız başlık dosyalarından(header file) yazacağımız koda, teorik bilgilerden uygulamalı anlatıma hatta konsol komutları ve çıktılarına kadar değinip anlaşılması ve öğrenilmesi kolay bir içerik oluşturmaya çalışacağım. Bu sayede daha öncesinde örümcek ağı gibi gelen konular ve sorular gittikçe açılacak. Hatta yazının sonunda bunlar o kadar da zor değilmiş diyeceksiniz ki bu da zor olduğu halde bilinen(becerilen) iş kolay iştir cümlesini size hatırlatacak.


   Her ne kadar basit bir anlatım oluşturmayı amaçlasam da bu dokümanda bulunanları tam olarak anlamak için işin bir kısmı da sizin üzerinizde. Size yüklediğim görev yazılan her kodu satır satır bakarak yazmak, ismi geçen başlık dosyasını bulup bahsi geçen fonksiyonlara göz atmak, koşulan konsol komutlarını koşmak ve buradaki çıktılar ile karşılaştırmak olacak. Her zaman söylendiği gibi “Yazılım” nasıl öğrenileceğini kendi içinde saklar. “YAZ ılım”. Bu yüzden yazın yazın yazın tekrar tekrar yazın.
Kernel Nedir?
Eğer bir sözlüğe bakarsanız kernel kelimesinin anlamının çekirdek, öz olduğunu görürsünüz. Fakat  Kernel bizim bildiğimiz gibi bir işletim sisteminin tüm çekirdeğidir. Kernel sistem yöneticisidir. Tüm sistem kaynaklarını yönetir. Sistem kaynakları nelerdir diyecek olursanız eğer: CPU, memory, input/output, network...

   Kernel' dan neden bu kadar az bahsettim derseniz ilk olarak uzun uzun yazıp konu dışına çıkmamak için bu özet bilgi ile yetindim diyebilirim. İkinci olarak ilerde anlatılacak konuların, özellikle yazılacak kodların bu katmana ait olduğunu belirtmek isterim. Burada aklınızda bir soru oluşmasını bekliyorum. “Başka katmanlar da mı var?” sorusu ya da en azından şu kelimenin aklınıza gelmesini umut ediyorum. User space(kullanıcı katmanı). Fark ettiyseniz iki katmanımız oluştu, kernel ve user space. User space çok yakın olduğumuz alan hatta sürekli kullandığımız alan. Hani şu printf ile “Hello World” uygulamasını koşup çıktısını konsolda gördüğümüz katman.
Kernel' dan bu kadar az bahsetmemin sebebinin anlaşıldığını umarak ana konunun derinliklerine doğru giriş yapıyorum.

Kernel C (Pure C)
  Kernel modül yazmadan önce Kernel katmanında kullanacağımız Kernel C tanımamız gerekir. Bu sayede yazılan kodları daha kolay ve hızlı kavrayabiliriz. Kernel C dedim, bu sizi yanıltmasın. Hemen telaş edip "Kernel modüle yazmak için yeni bir dil mi öğreneceğim?" sorusunu aklınıza getirmeyin. Kernel C normal kullandığımız C dilidir. Sadece Kernel seviyesinde GNU uzantıları ile kullanılır. Biraz daha açıklayıp sizi rahatlatmak için şöyle söyleyeyim: Kernel C, kullanıcı katmanında kullandığımız kütüphaneleri içermez. Yani C dilini elimizde glibc kütüphaneleri olmadan kullanacağız. (glibc için busiteye ve bu siteye gözatabilirsiniz. glibc bahsetmişken şu sayfaya da göz atın derim.) Peki elimizde o çok sevdiğimiz başlık dosyaları olmadan(stdio.h, stdlib.h, string.h) nasıl kod yazacağız derseniz eğer imdadımıza Kernel seviyesine ait başlık dosyaları yetişir. <kernel_source>/kernel/lib. Ufak bir örnek #include <linux/module.h> İşte Kernel C ile kullandığımız C arasındaki fark bu, başlık dosyaları.
   
    Kernel seviyesinde çalışırken bile en sevdiğimiz dil C kullanmamız işin en güzel yanlarından. Fakat burada nesne dayalı programlama mantığı kullanılmıştır. Biraz C++ bilenler için şu iki kelime constructor ve destructor tanıdık gelecektir. C++ bilmeyenler bu durumu dert etmesine gerek yok. Çünkü bu işleri yine basit bir fonksiyonla yapacağız. Bu kadar yazıdan sonra basit bir modül kodu örneğine bakalım.

Şekil 1 Basit Bir Kernel Modul Kodu

   Gördüğünüz gibi modül constructor ve destructor fonksiyonlarını içerir. mymodule_init modülün constructor fonksiyonu, mymodule_exit modülün destructor fonksiyonudur. C++ bilenler constructor fonksiyonunun obje yaratılırken çağrıldığını bilirler. Fakat burada constructor (kurucu fonksiyon) fonksiyonu -mymodule_init- modül dinamik olarak Kernel yüklenirken çağrılır. Peki destructor fonksiyonu -mymodule_exit- ne zaman  ne zaman çağrılır. Tabi ki de modül Kernel' dan çıkartılırken yani modül sistemden geri alınırken.

Kernel' ın Mesaj Kayıtcısı printk()

   Basit Bir Kernel Modul Kodunda kullanılan constructor ve destructor fonksiyonlarının dışında biraz bu basit modülde ismi geçen printk() fonksiyonundan bahsedelim.  printk(), çok yakından bildiğimiz printf() in Kernel seviyesinde çalışan eşdeğeridir. Tabi kullanımı ve görevi printf()' den biraz farklıdır.

   printk, printf gibi çıktılarını standart output basmaz. Peki printk nereye yazar? Kullanıcı katmanında(user space) çalışan syslog daemon bu mesajları toplar ve son işlem olarak çeşitli cihazlara yönlendirir. Yani kısaca bazı mesajları konsol komutuna yolladığı gibi bazılarını da sadece dosya da saklar. Tüm printk çıktılarını
default olarak /var/log/messages dosyasına basılır. Ayrıca ayarlanarak bazı printk mesajları seri port üzerinden
 konsola(/dev/ttyS0) basılabilir. Örneğin KERN_ERR mesajları konsolda gözlenebilir.



NOT 1: /var/log/messages dosyası normal kullanıcılar için okunabilir biri dosya değildir. Fakat $dmesg komutu ile bu verilere ulaşabilirsiniz

NOT 2: kernel seviyesinde çağrılan tüm printk mesajlarını konsol ekranına basmak için şu komutu koşmanız yeterli:    $dmesg  

NOT 3:dmesg kullanıma yönelik pratik bilgi: $dmesg koştuktan
sonra ekranda bir sürü kernel seviyesinden gelen mesaj göreceksiniz. Bunların içinde kaybolmamak için bir anahtar kelime ile arama yapabilirsiniz. $dmesg | grep my_key_word















printk teknik özelliğinin yanında birazda kullanım biçiminden bahsedelim. Basit Bir Kernel Modul Kodunda kullanımına tekrar bakalım. printk(KERN_INFO "BYE BYE My Module"); İlk göze çarpan KERN_INFO makrosu oluyor. Aslında bu makro sabit bir stringden başka bir şey değil. KERN_INFO kullanımından sonra virgül kullanılmadığına dikkat ediniz. (Arada bir şey kullanılmamış.) Bu durum bize C nin kurallarından biri olan string birleştirmeyi hatırlatması gerekir. 
printf("first text" " second text\n");   [çıktısı: first text second text] 
kodu her ne kadar geçerli ise prink çağrımı da o kadar geçerli ve aynı. Bu açıklamadan sonra printk gizemi gittikçe azalıyor olması lazım. Artık printk ya da bizim printf gibi bir yakınlık duymamız lazım.

Printk sadece KERN_INFO makrosu kullanılmaz. #include<linux/kernel.h> başlık dosyasında printk mesaj durumunu anlatan sekiz adet makro bulunmaktadır.



Yazının devamı olan Adım Adım Linux Driver Yazıyoruz -2- de bu uyku getirici teorik bilgilerden biraz daha uzaklaşıp daha fazla kod göreceğiz. Hatta ilk modülümüzü derleyeceğiz ve cihaza yükleyip çalıştıracağız. Böyle zevkli işler olacak pek yakında. Yazının devamında görüşmek üzere.

Faydalanacağını düşündüğünüz kişilere iletin, eksik ya da yanlış olduğunu düşündüğünüz yerleri belirtin.

Şimdi Yazmaya Devam ....

3 Mart 2015 Salı

Linux GPIO Kontrolü Uygulaması

    İlk olarak bloguma uğrayarak bu yazıyı okuyan herkese merhaba. Konumuz geçen yazımda belirttiğim üzere uygulama seviyesinde GPIO kontrolünü bir C uygulaması ile yapmak olacak. Gerekli malzemeler üzerinde çalıştığınız bir linux bord ve bu borda bulunan işlemciye ait compiler. (Eğer bu yazıyı okumaya devam edecekseniz zaten daha öncesinde cross compiler kurup çalışır hale getirmişsinizdir demektir) Kod geliştirme ortamını da unutmamak gerek, Eclipse vazgeçilmezler arasında.
   Gpio kontrol eden kodları yazmaya başlamadan önce eğer okumadıysanız bu yazımı okuyun derim. Çünkü yazacağımız kod bunların C ile run time (çalışma zamanı) yapılması. Kısaca kodumuzda yapacaklarımızı özetler isek;
  1. GPIO dosyasını açmak ve kapamak.
  2. GPIO rezerve eden fonksiyonu yazmak
  3. GPIO geri veren fonksiyonu yazmak
  4. GPIO giriş ya da çıkış olarak yönlendiren fonksiyonu yazmak
  5. GPIO değer yazan fonksiyonu yazmak
  6. GPIO o anki değerini okuyan fonksiyonu yazmak
    Yazacağımız kod topu topu bu kadar işlem yapacak. Birde bu fonksiyonları test etmek için main için de bunları çağırarak nasıl kullanılacağına bakacağız. Linux yeni başlayanlar için gayet kolay ve zevkli bir çalışma olacağını düşünüyorum. Zaten kodu başarılı bir şekilde derleyip bord yükledikten sonra ilgili GPIO bir led bağlayıp GPIO gerçekten de kontrol edilip edilmediğini görebilirsiniz. 

    İlk olarak başlık dosyasını(.h dosyası) oluşturalım. Hem bu sayede kaynak dosyasında (.c dosyası) ne yapacağımızın özetini görmüş oluruz. Başlık dosyası için driver.h

     Başlık dosyasını oluşturduktan sonra gelelim kaynak dosyasına. Kodları inceleyerek hatta baka baka tekrar kendiniz yazarak çalışmanızı tavsiye ederim. Zaten yazılım çalışmanın en iyi yolu kod yazmak kod yazmak kod yazmak tekrar yazmak yazmak yazmak. driver.c


drvGpio test uygulaması
#include <stdio.h>
#include <unistd.h>

#include "drvGpio.h"

int main(int argc, char *argv[])
{
 int retVal;

 retVal = drvGpioInit(11);
 if (SUCCESS == retVal)
 {
  retVal = drvGpioSetDirection(11, EN_GPIO_DIR_OUTPUT);

  if (SUCCESS == retVal)
  {
   drvGpioSetHigh(11);
   sleep(1);
   drvGpioSetLow(11);
   sleep(1);
   drvGpioSetHigh(11);
   sleep(1);
   drvGpioSetLow(11);
   sleep(1);
   drvGpioSetHigh(11);

   printf("---- %d \n", drvGpioGetValue(11));
   drvGpioUninit(11);
  }
 }

 return 0;
} 
drvGpio test uygulaması ile kodlar test edilmiştir. Umarım faydalı bir yazı olmuştur..
Herkese iyi çalışmalar