1. Amaç    2

2. TTY Sürücüs(Driver)    2

2.1 TTY Open ve Close Fonksiyonları    4

2.2 Veri Akışı    5

2.3 Diğer Arabellek(Buffering) İşlevleri    6

2.4 Okuma İşlemi    6

2.5 TTY Hat Ayarları    8

2.5.a set_termios    8

2.5.b ioctl    10

2.6 Başlık Dosyaları ve Fonksiyonlar    11

3. Donanıma Çıkmayan TTY Birimi    13

3.1 En Basit Haliyle Bir TTY Sürücüsü    14

3.1.a İlk TTY Sürücüsünün Testi    19

3.2 Okuma ve Yazma Özelliği Olan TTY Sürücüsü    22

3.2. Sürücüsünün Testi    28

1. Amaç

Bu belgenin amacı baştan sona Linux tty sürücü(driver)  yazımını anlatmak ve sonrasında kullanıcı katmanında(user space) yazılan tty sürücüsünün nasıl kullanılacağını göstermektir. 

tty sürücüsünün yazılmasından önce linux tty sürücünün çalışma yapısı açıklanacaktır. Belgede aslında kavranması ve okunması zaman alacak olan bölüm bu kısımdır. Bu kısım tam olarak anlaşıldıktan sonra tty sürücüsünü yazmak ve kullanıcı katmanından kullanmak oldukça hızlı ve kolay olacaktır.  

Belge içeriğini daha somut ve çalışma hayatındaki projelere yakın kılıp hem okuyucu hem de yazanı daha hevesli kılmak için aşağıdaki amaçlar hedef alınmıştır. 

  1. Linux tty sürücü çalışma yapısının açıklanması
  2. Donanıma çıkmayan, girilen metnin/verinin SHA256 alıp geri dönen tty sürücü yazımı
  3. Donanıma çıkan kullanıcı seviyesinden girilen metni gönderen tty sürücü yazımı

İkinci maddedeki sürücü tasarımında donanım bağlantısı olmadığı için herhangi bir linux ortamında kolayca uygulanabilir. Fakat üçüncü maddedeki sürücüde donanım bağlantısı olduğundan tty sürücüsünü donanıma bağlamayı yapabilmek için bu örneği gömülü linux ortamında koşacağız. Dolayısı ile device tree dosyasına da dokunmuş olacağız. Ayrıca yazılan linux sürücüleri sisteme modül olarak yükleneceği için lsmod, insmod, modprobe ve rmmod komutlarını da görmüş olacağız.  

Burada yazılan tüm kodlar buradaki github hesabından indirilebilir. JP

Bu belgenin pdf halini buradaki google sheet adresinden indirebilirsiniz.

2. TTY Sürücüs(Driver)

TTY sürücüsü ismini “teletypewriter” kısaltmasından alır. Başlarda Unix makinesine fiziksel veya sanal terminal bağlantısı ile ilişkilendirilmiştir. Zamanla bu tür bir bağlantı üzerinden terminal bağlantıları da oluşturulmuştur. Daha sonrasında ise tty anlamı herhangi bir seri port cihaz sürücüsü anlamına dönüştü. Örneğin USB-Seri Port dönüştürücüler, seri port cihazlar, IR gözler bu seri port cihazlara örnek olup hepsi tty sürücüsü ile kontrol edilir.

TTY sürücüsü donanım ve veri format yapısı bağımlılığından kurtulmak için birden fazla katmana ayrılmıştır. Bu katmanlı yapı sayesinde farklı özelliklerde tty sürücüsü oluşturmak mümkündür. Aşağıda bu katmanları gösteren resim bulunmaktadır.

Yukarıdaki resimlerde tty sürücüsünün katmanları görülmektedir. Hem sağdaki hem de soldaki resim tty sürücüsünü temsil etsede anlatımlarındaki detay farklıdır. Sağdaki resimde katmanların tam olarak görev içeriği gösterilmemekte fakat katmanların sıralaması daha net ve özettir. Sağdaki resim ayrıca tty yapısını genel haliyle temsil etmektedir.

 Soldaki resimde ise ilk dikkat çeken TTY driver katmanından iki tane olmasıdır. İkinci TTY Driver katmanının içinde serial_core olması birinci TTY driver ile ikinci TTY driver arasındaki farkı anlamamızın kilit noktasıdır. Serial_core olan TTY driver donanıma çıkar ve veriler gerçekten seri port donanımı üzerinden dışarı gönderilir. Birinci TTY driver serial_core içermediği için donanıma çıkmaz. Zaten yukarıda belirttiğimiz planda yer alan 2. Ve 3. maddedeki hedefler TTY driverın bu farklılığından yola çıkarak belirlenmiştir ve iki yapıyı da öğrenmemizi sağlar.

İki resim arasındaki bir diğer fark “Line Discipline” katmanının yerleşimidir. Her ne kadar yerleşimi iki resimde farklı gösterilmiş olsa da görevi ve kullanımı neticesinde iki resmi doğru kabul edebiliriz. Çünkü yapı şu şekilde çalışmaktadır: tty çekirdeği(tty core), tty cihazına gönderilecek olan verileri kullanıcıdan alır ve sonrasında verileri tty line discipline geçirir ve son olarak tty sürücüsüne iletir. TTY sürücüsü verileri donanıma iletir. Tersi istikamette yani veri donanımdan gelirse tty sürücüsü verileri alır ve tty discipline iletir. Tty discipline alınan verileri formatlayarak tty çekirdeğe iletir. Fakat bazen tty sürücüsü doğrudan tty çekirdeği ile iletişim kurabilir. Bu nedenle tty discipline her iki resimdeki gibi gösterilebilir.

Linux sisteminde üç farklı tty sürücüsü vardır. Konsol, pty(pseudo tty) ve seri bağlantı noktası. Konsol ve pty sürücüleri zaten yazılmıştır ve bu noktadaki ihtiyaçları karşılayan muhtemel tek sürücüdür. Aşağıdaki komut ile  tty sürücülerini listeleyebiliriz

$ cat /proc/tty/drivers

2.1 TTY Open ve Close Fonksiyonları

Kullanıcı tty sürücüsüne open fonksiyonu ile açma çağrısı yaptığında bu çağrı tty çekirdeği tarafından ele alınır. Çekirdek bu isteği kullanıcının yazdığı open fonksiyonuna iletir. Eğer çekirdek bu fonksiyonu bulamaz ise -ENODEV ile geri döner. Open fonksiyonu çağrıldığında tty sürücüsü kendisine iletilen verileri ya tty_struct değişkenine kaydetmesi ya da veriyi Minor sayısına göre oluşturulmuş static bir dizi içine kaydetmesi gerekir. Böylece tty sürücüsü daha sonra close, write ve diğer işlevler çağrıldığında hangi indeksli cihaza başburulduğunu bilir. 

Yukarıdaki açıklamaya örnek kod parçası aşağıda bulunmaktadır. Koddan da takip edileceği üzere port ilk açılışında NULL sorgusu işler ve open_count = 0 ataması gerçekleşir. Kodun devamında da count değeri port her açılışında bir artar.

   tiny = tiny_table[index];
   if (tiny == NULL) {
  /* first time accessing this device, let's create it */
       tiny = kmalloc(sizeof(*tiny), GFP_KERNEL);
       if (!tiny)
           return -ENOMEM;

       mutex_init(&tiny->mutex);
       tiny->open_count = 0;

       tiny_table[index] = tiny;
    }
    mutex_lock(&tiny->mutex);
    /* save our structure within the tty structure */
    tty->driver_data = tiny;
    tiny->tty = tty;

    ++tiny->open_count;

Tty sürücüsünün ilk kullanımında yukarıdaki kod parçasında ilk çağrımda donanımın kurulması da gerçekleştirilir. Tty sürücüsünü kullanmayan kalmadığında yani counter sıfır olduğunda donanımın kapatılması ve belleğin temizlenmesi işleri yapılır. 

Open fonksiyonu başarılı olursa 0 değerini döner, başarısızlık durumunda ise negatif değer döner. Close fonksiyonunun geri dönüş değeri yoktur.(void) Dolayısı ile çağrılan her kapama fonksiyonu başarılı olduğu varsayılır.

2.2 Veri Akışı

Kullanıcı katmanından verilerin donanıma ulaşması write fonksiyonunun çağrılması ile başlar. Veriyi önce tty çekirdeği alır ve ardından verileri tty sürücüsünün yazma işlevine gönderilecek verinin boyut bilgisi ile  iletir.

Tty donanımının hızı ve arabellek kapasitesi nedeniyle yazma işlevi çağrıldığı anda yazma programı tarafından bazen istenilen tüm karakterler gönderilmeyebilir. Bu durumda yazma işlevi donanıma gönderilen veya daha sonra gönderilmek üzere sıraya alınan karakter sayısını döner/dönmelidir. Yazma işlemi sırasında bir hata oluşması durumunda ise karakter sayısı yerine negatif değer döner/döndürmelidir. Yazma fonksiyonunun geri dönüş değeri uygulama katmanından kontrol edilerek yapılması gereken tekrar uygulama katmanında planlanır. Hata durumunu ya da eksik karakter gönderimini tty çekirdeği ele almaz/almamalıdır. Aslında bu senaryo sıklıkla kullandığımız dosya yazma işlemlerinde kullandığımız klasik yöntemdir.

Tty çekirdeği, tty sürücüsünün yazma tamponunda ne kadar yer olduğunu bilmek istediğinde write_room fonksiyonunu çağırır. Write_room döneceği sayı, arabellekteki karakter boşaldıkça ve yazma işlevi çağrıldıkça, arabelleğe karakter eklendikçe değişir. Kullanıcı yazdığı write_room fonksiyonunu  struct tty_operations  içinde bulunan write_room pointer atar. Örnek kullanım aşağıdaki gibidir.

static struct tty_operations dnt900_tty_ops = {    
.install         = tty_install,
    .open            = tty_open,
    .close           = tty_close,
    .hangup          = tty_hangup,
    .write           = tty_write,
    .put_char         = tty_put_char,
    .write_room      = tty_write_room,
    .chars_in_buffer  = tty_chars_in_buffer,
    .flush_chars     = tty_flush_chars,
    .wait_until_sent  = tty_wait_until_sent,
    .flush_buffer    = tty_flush_buffer,
};

2.3 Diğer Arabellek(Buffering) İşlevleri

Çalışan bir tty sürücüsüne sahip olmak için tty_driver yapısındaki chars_in_buffer işlevi gerekli değildir, ancak önerilir. Bu işlev, tty çekirdeği tarafından, tty sürücüsünün gönderilmek üzere yazma tamponunda hala kaç karakter kaldığını bilmek istediğinde çağrılır. 

Tty driver yapısındaki üç işlev sürücünün elindeki kalan verileri temizlemek için kullanılır. Bunların olması zorunlu değildir, ancak tty sürücüsünün veriyi donanıma göndermeden önce arabelleğe alabilmesi için önerilir. İlk iki işlev flush_chars ve wait_until_sent fonksiyonlarıdır. Bu iki pointer fonksiyonlarına yüklenecek fonksiyonları kullanıcı kendisi yapısına göre yazar. Ayrıca bu pointer fonksiyonlar ile şunu da söylemek gerekir: pointer fonksiyonların gösterdiği fonksiyonlar linux kernel  tarafından çağrılır. Bu linkteki örnekte bu fonksiyonların hepsi tanımlanmıştır, fakat çağrımı yoktur. 

Flush_chars işlevi, tty çekirdeğinin, tty sürücüsünün verileri  donanıma göndermeye başlamadıysa verileri donanıma göndermesini istemesidir. flush_chars tüm verilerin donanıma gönderilmesini beklemeden geri döner. Dolayısı ile geri dönüşü her datanın gönderildiği anlamına gelmez. Daha kararlı cevaplar için wait_until_sent işlevi kullanılır. wait_until_sent flush_chars gibi çalışır fakat fark olarak tty çekirdeğine dönmeden önce tüm karakterlerin gönderilmesini gönderilen zaman aşımı (timeout) süresince bekler. Zaman aşımı süresi olarak sıfır değeri gönderilir ise wait_until işlem bitene kadar bekler.

Son işlev flush_buffer işlevidir. Tty sürücüsü tarafından çağrılır bu işlev. Tty driver arabelleğinde kalan verileri siler.

2.4 Okuma İşlemi

Ne tty çekirdeği ne de tty sürücüsü okuma fonksiyonu içermez. Okuma işlevi yerine, tty sürücüsü donanımdan herhangi bir veri aldığında tty çekirdeğine gönderir ve tty çekirdeği kullanıcı tarafından istenene kadar verileri arabellekte toplar. Kullanıcı okuma yaptığında aslında tty çekirdeğinin arabelleğinde olan veriler iletilir. Veriler tty çekirdeğinin arabelleğinde toplandığı için tty sürücüsünün verileri saklama özelliği yoktur. 

Tty çekirdeği tty sürücüsünden alınan verileri struct tty_flip_buffer adlı bi yapıda biriktirir. Bu yapı(structure), temelde iki ana veri dizisini içerir. Tty cihazından alınan veriler ilk olarak birinci dizide saklanır. Bu dizi dolduğunda verileri bekleyen kullanıcıya verinin okumaya hazır olduğu bildirilir. Kullanıcı bu diziden verileri okurken gelen her yeni veri ikinci dizide depolanır. İkinci dizi dolduğunda tekrar bildirilerek veriler aktarılır ve tekrar ilk dizi kullanılmaya başlanır. Sırasıyla çalışan iki dizi ile gelen veriler saklanmaya çalışılsa da bellek alanlarının taşması nedeniyle veri saklaması garanti değildir. Verilerin kaybolmasını önlemeye çalışmak için tty sürücüsü gelen verinin ne kadar büyük olduğunu izleyebilir ve eğer doluysa tty çekirdeğine alanı temizlemesini söyleyebilir.

Yukarıdaki anlatımdan da anlaşılacağı gibi struct tty_flip_buffer yapısının ayrıntıları tty sürücüsü(tty driver) için önemli değildir. Çünkü tty sürücüsü sadece bu alana gelen verileri yazıyor, sadece yazma alanının olması onun için yeterli. Bu durumun tek bir istisnası vardır, bellekte bulunan veri sayısını gösteren sayım değişkenidir. Veri sayısı TTY_FLIPBUF_SIZE sayısına eşit ise flip arabelleği tty_flip_buffer_push fonksiyon çağrısı ile kullanıcıya aktarılması gerekir. Bu durum aşağıdaki kod ile gösterilmektedir.

for (i = 0; i < data_size; ++i) {
    if (tty->flip.count >= TTY_FLIPBUF_SIZE)
        tty_flip_buffer_push(tty);
    tty_insert_flip_char(tty, data[i], TTY_NORMAL);
}
tty_flip_buffer_push(tty);

Tty_insert_flip_char fonksiyonu ile tty sürücüsü alınan karakterleri flip arabelleğine yazar. Yukarıdaki örnek kodda örnek kullanımı vardır. Tty_insert_flip_char fonksiyonun ilk parameteresi tty_struct parametresidir. İkinci parametre kaydedilecek karakterdir. Üçüncü parametre kaydedilecek karakter için bir işarettir. .Eğer alınan karakter normal bir karakter ise TTY_NORMAL değeri kullanılır. Veri alınırken bir hata olduğunda ise hataya bağlı olarak TTY_BREAK, TTY_FRAME, TTY_PARITY veya TTY_OVERRUN değerleri kullanılır.

Verileri kullanıcıya göndermek için tty_flip_buffer_push işlevi kullanılır. Bu örnekte gösterildiği gibi çevirme arabelleği taşmak üzere ise de bu işlev çağrılmalıdır. Dolayısıyla, çevirme arabelleğine veri eklendiğinde veya çevirme arabelleği dolduğunda, tty sürücüsünün tty_flip_buffer_push’u çağırması gerekir. Tty sürücüsü çok yüksek oranlarda veri kabul edebiliyorsa, tty->low_latency bayrağı ayarlanmalıdır, bu da tty_flip_buffer_push çağrısının çağrıldığında hemen yürütülmesine neden olur. Aksi takdirde, tty_flip_buffer_push çağrısı, verileri yakın gelecekte daha sonraki bir noktada arabellekten göndermek için kendini planlar.

2.5 TTY Hat Ayarları

Tty cihazının hat ayarlarını değiştirmek veya mevcut ayarları almak için birçok kullanıcı katmanı kütüphanesi fonksiyonları vardır. Yada kullanıcı katmanından direk ioctl fonksiyonu ile gerekli işler yapılır. Bu işlere örnek olarak ilk akla gelen hat iletişim hızını(baud rate) ayarlamanın olduğu söylenebilir.

2.5.a set_termios

Set_terminos fonksiyonu struct tty_operations içinde tanımlanmıştır. Kullanıcı katmanından yapılan tüm terminos işlemleri kütüphane tarafından tty sürücüsüne bir ioctl çağrısına dönüştürülür. Farklı birçok ioctl çağrısı tty çekirdeğine ulaşır. Tty çekirdeği bu ioctl çağrılarını set_terminos çağrısına dönüştürerek tty driver iletir. 

Dikkat edilirse kullanıcı katmanından gelen istek tty çekirdeği tarafından değil, tty sürücüsü tarafından ele alındı. Tty cihazının birden fazla kullanıcı tarafından açılabildiğini ve her açışta open_count değerinin arttırılarak kaç kullanıcı ile çalışıldığının tutulduğunu open başlığında görmüştük. Bu özellikten dolayı set_terminos işlevi de hangi kullanıcı için koşulması gerektiği belirtilmelidir. Aksi durumda yapılan ayarlamalar diğer kullanıcıları da etkiler. Bu nedenle set_terminos çağrısında hangi hat için ayarlamaların yapılacağı belirtilir.

Set_terminos kendisine gelen isteği yapmadan önce gerçekten bir şeyin değiştirilmesi gerekip gerekmediğini belirlemektir. Bun aşağıdaki örnek kodla yapılabilir.

unsigned int cflag;
cflag = tty->termios->c_cflag;
/* check that they really want us to change something */
if (old_termios) 
{
    if ((cflag =  = old_termios->c_cflag) && 
       (RELEVANT_IFLAG(tty->termios->c_iflag) == RELEVANT_IFLAG(old_termios->c_iflag))) 
    {
        printk(KERN_DEBUG " - nothing to change...\n");
       return;
    }
}

Örnekteki RELEVANT_IFLAG makrosunun iç yapısı şu şekildedir.:
#define RELEVANT_IFLAG(iflag) ((iflag) & (IGNBRK|BRKINT|IGNPAR|PARMRK|INPCK))

Bu makro ile c_iflag taşıdığı önemli değerlerin kontrolü yapılır. Tty hattının özelliklerini sorgulayan örnek kodlar aşağıdaki gibidir:

/* get the byte size */switch (cflag & CSIZE) {
    case CS5:
        printk(KERN_DEBUG " - data bits = 5\n");
        break;
    case CS6:
        printk(KERN_DEBUG " - data bits = 6\n");
        break;
    case CS7:
        printk(KERN_DEBUG " - data bits = 7\n");
        break;
    default:
    case CS8:
        printk(KERN_DEBUG " - data bits = 8\n");
        break;
}
/* determine the parity */
if (cflag & PARENB)
    if (cflag & PARODD)
        printk(KERN_DEBUG " - parity = odd\n");
    else
        printk(KERN_DEBUG " - parity = even\n");
else
    printk(KERN_DEBUG " - parity = none\n");
/*figure out the stop bits requested */
if (cflag & CSTOPB)
    printk(KERN_DEBUG " - stop bits = 2\n");
else
    printk(KERN_DEBUG " - stop bits = 1\n");
/* figure out the hardware flow control settings */
if (cflag & CRTSCTS)
    printk(KERN_DEBUG " - RTS/CTS is enabled\n");
else
    printk(KERN_DEBUG " - RTS/CTS is disabled\n");
/* get the baud rate wanted */
printk(KERN_DEBUG ” – baud rate = %d”, tty_get_baud_rate(tty));

Set_terminos yanında tiocmget and tiocmset fonksiyonlarına da bakmak gerekir. Bu fonksiyonlar da hat ayarlarını yüklemek için kullanılır.

2.5.b ioctl

Kernel 2.6 içinde tty sürücüsüne gönderilebilecek yaklaşık 70 farklı tty ioctl tanımı vardır. Doğaldır ki tüm komutlar tüm tty sürücülerinde kullanılmaz. Aşağıda en çok kullanılan bazı ioctl tanımlamaları ve açıklamaları bulunmaktadır. 

  • TIOCSERGETLSR: Bu tty cihazının hat durumu kaydının (LSR) değerini alır.
  • TIOCGSERIAL: Seri hat bilgilerini alır. Bunu kullanan potansiyel olarak tty cihazından birçok seri hat bilgisini aynı anda alabilir. Bazı programlar (seterial ve dip gibi), baudrate hızının doğru şekilde ayarlandığından emin olmak ve tty sürücüsünün kontrol ettiği cihaz türü hakkında genel bilgi almak için bu işlevi çağırır. Tty sürücüsünün uygun değerlerle doldurması gereken, serial_struct türünde büyük bir yapı(struct) için bir gösterici gönderilir. Örnek kod:
static int tiny_ioctl(struct tty_struct *tty, struct file *file, unsigned int cmd, unsigned long arg)
{
    struct tiny_serial *tiny = tty->driver_data;
    if (cmd =  = TIOCGSERIAL) 
    {
            struct serial_struct tmp;
            if (!arg)
                    return -EFAULT;

        memset(&tmp, 0, sizeof(tmp));
        tmp.type = tiny->serial.type;
        tmp.line = tiny->serial.line;
        tmp.port = tiny->serial.port;
        tmp.irq  = tiny->serial.irq;
        tmp.flags = ASYNC_SKIP_TEST | ASYNC_AUTO_IRQ;
        tmp.xmit_fifo_size  = tiny->serial.xmit_fifo_size;
        tmp.baud_base       = tiny->serial.baud_base;
        tmp.close_delay     = 5*HZ;
        tmp.closing_wait    = 30*HZ;
        tmp.custom_divisor  = tiny->serial.custom_divisor;
        tmp.hub6        = tiny->serial.hub6;
        tmp.io_type     = tiny->serial.io_type;

  if (copy_to_user((void _ _user *)arg,
&tmp, sizeof(tmp)))
     return -EFAULT;
       return 0;
    }
    return -ENOIOCTLCMD;
}
  • TIOCSSERIAL: TIOCGSERIAL’in tersidir ve kullanıcının tty cihazının seri hat durumunu ayarlaması için kullanılır. Bu ayarları taşıyan serial_struct yapısının işareti(pointer) gönderilir.

2.6 Başlık Dosyaları ve Fonksiyonlar

Kod yazarken başlık dosyalarını ezbere bilmeye gerek yoktur. Fakat  bu belge tty birimi hakkında olduğu için gerekli başlık dosyalarını tanıtmakta fayda var.

  • #include <linux/tty_driver.h>  Struct tty_driver tanımını içeren ve bu yapıda kullanılan farklı bayrakların bazılarını bildiren başlık dosyası.
  • #include <linux/tty.h>   Struct tty_struct tanımını ve struct termios alanlarının bireysel değerlerine kolayca erişmek için bir dizi farklı makro içeren başlık dosyası. Ayrıca tty sürücü çekirdeğinin işlev bildirimlerini de içerir.
  • #include <linux/tty_flip.h>   flip arabelleği yapılarını değiştirmeyi kolaylaştıran bazı tty flip arabelleği satır içi işlevleri içeren başlık dosyası.
  • #include <asm/termios.h>   tty çekirdeğin oluşturulduğu belirli donanım platformu için struct termio tanımını içeren başlık dosyası.
  • struct tty_driver *alloc_tty_driver(int lines);  tty_register_driver ve tty_unregister_driver işlevlerine aktarılabilen bir struct tty_driver oluşturan işlev.
  • void put_tty_driver(struct tty_driver *driver);   Tty çekirdeğine başarıyla kaydedilmemiş bir struct tty_driver yapısını temizleyen işlev.
  • int tty_get_baud_rate(struct tty_struct *tty);   Belirli bir tty cihazı için geçerli olarak ayarlanmış olan baudrate hızını alan işlev.
  • int tty_register_driver(struct tty_driver *driver);
    int tty_unregister_driver(struct tty_driver *driver);
    Bir tty sürücüsünü tty çekirdeğinden kaydeden ve kaydını silen işlevler.

Buraya kadar anlatılan tüm bilgileri özetleyen tty biriminin blok şeması aşağıda bulunmakta. Oklar ile akışın nasıl olduğu gösterildiği için bu resim üzerinde durulup incelenmesini öneriyorum.

3. Donanıma Çıkmayan TTY Birimi

Yukarıdaki resim her ne kadar göze karmaşık ve anlaması zor gibi gelsede aslında hem yapıyı hem de kodlama yaparken nasıl bir yol izlenmesi gerektiğini anlamak için oldukça faydalı. Bu başlık altında donanıma çıkmayan bir tty driver yazacağız. Bu amaç için kodlanması gereken minimum temel birimler aşağıdaki resimdeki gibidir.

Resimden de anlaşılacağı gibi bize lazım olan tty_driver ve onun *ops pointer bağlanmış olan tty_operation yapısıdır. Bu gereksinimler, port üzerinde yapılacak işlemler(okuma, yazma, …) ve tty sürücüsünün kurulması ve kaldırılması için gerekenlerdir. Kurulum ve kaldırma aslında tüm sürücüler için geçerlidir. 

En ufak  bir tty driver için yazılması gereken fonksiyon listesi aşağıdaki gibi olur:

  • Kurulum işlemleri (__init)
  • Sürücü  çıkarma işlemleri (__exit)
  • Port oluşturulması
  • Tty_operation yapısının oluşturulması
  • tty_operation yapısındaki fonksiyonların yazılması(open(), close(), …)

Bu listedeki işler yapıldığında elimizde çalışan bir tty sürücüsü olmuş olur. Daha gelişmiş bir sürücü için nelere ihtiyaç duyulacağı sistemin ve yazılımın ihtiyaçlarına göre değişir.

3.1 En Basit Haliyle Bir TTY Sürücüsü

En basit haliyle bir tty sürücüsü oluşturmak için ilk olarak donanım bağımlı olmamak yani uart birimini kullanmamak gerekir. Bu sayede donanım işlerini es geçmiş oluruz. Veri formatı yani “Line Discipline” kullanmayarak veri paketleri üzerinde de işlem yapmamış oluruz. Geriye tek yapmamız gerekenler, sürücünün kurulumu ve kaldırılması işlemleri mecburi olup okuma yada yazma işlemlerinden birinin yazılmasıdır. Okuma ve yazma işlemlerinin her ikisinin birden bulunması mecburi değildir. Örneğin sadece okuma olarak kullanılan cihazlarda yazma işlemi bulunmaz.

Basit bir tty sürücüsü kod örneği için ttynull ve ttyprintk kodları incelenebilir. Burada oluşturulacak olan tty sürücüsü ttyprintk benzer bir örnek olacak. Kernel linux/drivers/tty/ dizininde bulunan bulunan bazı dosyalar tty sürücüsü yazarken kullanılır. Örneğin port kurulumunda kullanılan tty_port_init() fonksiyonu tty_port.c dosyası içinde tanımlanmıştır. Kullanılacak diğer dosyaların hepsi include edilmiş olarak kaynak koddan görülebilir. 

Metin belgesi üzerinden kodları anlamak zor olduğu için aşağıdaki kodların çalışır halini buradaki github hesabımdan indirebilirsiniz. Ayrıca kodların derlenmesi için gerekli Makefile dosyası ve test uygulamaları da bulunmakta.

Örnek olması için sadece yazma özelliği olan ve gönderilen metnin sha256 hesaplayıp Kernel INFO(KERN_INFO) seviyesinde kernel mesajı olarak yazan bir tty sürücüsü tasarlayalım.  

static const struct tty_port_operations null_ops = { };
static struct tty_driver *driver;
static int __init ztty_init(void)
{
    int ret;
    driver = tty_alloc_driver(1,
                                                              TTY_DRIVER_RESET_TERMIOS |
                                                                TTY_DRIVER_REAL_RAW |
                                                                TTY_DRIVER_UNNUMBERED_NODE);
    if (IS_ERR(driver))
              return PTR_ERR(driver);

    tty_port_init(&tpk_port.port);
    tpk_port.port.ops = &null_ops;

    driver->driver_name = “zboard_tty”;
    driver->name = “ttyZ”;
    driver->type = TTY_DRIVER_TYPE_SERIAL;
    driver->init_termios = tty_std_termios;
    driver->init_termios.c_oflag = OPOST | OCRNL | ONOCR | ONLRET;

    tty_set_operations(driver, &ztty_ops);
    tty_port_link_device(&tpk_port.port, driver, 0);

    ret = tty_register_driver(driver);
    if (ret < 0)
              {
            printk(KERN_ERR “Couldn’t register ttyz driver %d \n”, ret);
            goto error;
    }

    pr_debug(“Z-TTY: init SUCCESS \n”);
    return 0;

error:
    put_tty_driver(driver);
    tty_port_destroy(&tpk_port.port);
    return ret;
}

ztty_init() fonksiyonunda önemli olan noktaların üzerinde durmak gerekir. tty_alloc_driver() fonksiyonunda ilk argümana 1 değeri girilmiştir. Bu değer ile kaç adet port açılabileceği yani kaç adet open çağrısına karşılık verileceğini belirler. Her ne kadar bir sayısı burada girilmiş olsa da aslında bu sayının aşılıp aşılmadığı open çağrısı içinde sınanır. Eğer verilen sayıdan fazla open çağrısı olur ise open fonksiyonu hata dönmelidir. 

driver->name = “ttyZ”; satırı ile sürücünün /dev/ dizni altında hangi isim ile görüleceği belirtilmiştir. driver->init_termios.c_oflag = OPOST | OCRNL | ONOCR | ONLRET; satırında tty sürücüsü için baudrate belirlenir (B9600 gibi). Fakat donanıma çıkmayacağı için bizim için gereksizdir. tty_set_operations(driver, &ztty_ops); satırı bu sürücü için port işlemlerini kimlerin yapacağı belirtilir.

static void __exit ztty_exit(void)
{
    tty_unregister_driver(driver);
    put_tty_driver(driver);
    tty_port_destroy(&tpk_port.port);
}

Sürücünün sistemden kaldırılmasını sağlayan fonksiyon ztty_exti() fonksiyonudur. Bu fonksiyon içinde özellikle bellekten alınan alanların geri verilmesi unutulmamalıdır. Ayrıca fonksiyonların çağrılma sırası önemlidir. tty_unregister_driver() fonksiyonu çağrıldıktan sonra put_tty_driver() fonksiyonu çağrılmalıdır. (Aksi durumda artık var olmayan nesneye ulaşılmaya çalışılır)

/** TTY operations open function.*/
static int ztty_open(struct tty_struct *tty, struct file *filp)
{
    int ret;
    tty->driver_data = &tpk_port;

    ret = tty_port_open(&tpk_port.port, tty, filp);
    pr_debug(“Z-TTY: Open ret %d !!\n”, ret);
    return ret;
}

/** TTY operations close function.*/
static void ztty_close(struct tty_struct *tty, struct file *filp)
{
    struct ztty_port *tpkp = tty->driver_data;

    _print(NULL, 0);

    tty_port_close(&tpkp->port, tty, filp);
    pr_debug(“Z-TTY: CLOSED \n”);
}

/** TTY operations write function.*/
static int ztty_write(struct tty_struct *tty,
        const unsigned char *buf, int count)
{
    return _print(buf, count);
}

/** TTY operations write_room function. */
static int ztty_write_room(struct tty_struct *tty)
{
    return ZTTY_MAX_ROOM;
}

/** TTY operations ioctl function. */
static int ztty_ioctl(struct tty_struct *tty,
            unsigned int cmd, unsigned long arg)
{
    return 0;
}

static const struct tty_operations ztty_ops = {
    .open       = ztty_open,
    .close      = ztty_close,
    .write      = ztty_write,
    .write_room = ztty_write_room,
    .ioctl      = ztty_ioctl,
};

Yukarıdaki kod bloğunda tty_operations işlemlerini yapan fonksiyonları görmekteyiz. Çok basit düzeyde bir sürücü yazdığımız için fonksiyonların içi neredeyse boş. Örneğin ioctl ihtiyaç duyulmadığı için bu fonksiyon direk (return 0) dönmektedir.(0 değeri hata olmadığını belirtir.) Diğer fonksiyonlara bakınca ne yapılmak istendiği kolayca anlaşılmaktadır. Burada tek dikkat çekecek nokta ztty_open() içinde port açma sayısını sınamamamız olacaktır. Çünkü önceki paragraflarda tty_alloc_driver() fonksiyonunun sadece bir port için yapılandırıldığını söylemiştik. Kod sadeleği sağlamak için ztty_open() içinde bu sınama kasıtlı olarak yapılmadı. Dolayısı ile bu sürücü için istenildiği kadar open() çağrımı yapılabilir.

_print fonksiyonunda kullanıcının girdiği karakterler \n yada \r karakterleri görülene kadar ttyzBuff dizisinde toplanmaktadır. 

static int _print(const unsigned char *buf, int count){
    int i = ttyzCount;
    if (buf == NULL) {
            ztty_flush();
            return i;
    }
        pr_debug(“Z-TTY: _print: %c %d \n”, buf[0], count);

        for (i = 0; i < count; i++) {
                if (ttyzCount >= ZTTY_MAX_ROOM) {
                        /* end of tmp buffer reached: cut the message in two */
                        ttyzBuff[ttyzCount++] = ‘\\’;
                        ztty_flush();
                }
            switch (buf[i]) {
                case ‘\r’:
                        ztty_flush();
                        if ((i + 1) < count && buf[i + 1] == ‘\n’)
                    i++;
                        break;
                case ‘\n’:
                        ztty_flush();
                        break;
                default:
                        ttyzBuff[ttyzCount++] = buf[i];
                        break;
            }
    }
    return count;
}

Girilen karakterlerin ve hesaplanan sha256 sonucunun printk ile kernel mesajı olarak yazdırma işlemi burada yapılır. 

static void ztty_flush(void){
if (ttyzCount > 0)
{
ttyzBuff[ttyzCount] = ‘\0’;
do_sha256(ttyzBuff, ttyzCount, sha256Buff); //calculate sha256

printk(KERN_INFO “HASH(%s, %i)”, ttyzBuff, ttyzCount);
 printk(KERN_INFO “%02x%02x%02x%02x%02x%02x%02x%02x”,
sha256Buff[0], sha256Buff[1], sha256Buff[2], sha256Buff[3], sha256Buff[4], sha256Buff[5], sha256Buff[6], sha256Buff[7]);

printk(KERN_INFO “%02x%02x%02x%02x%02x%02x%02x%02x”,
sha256Buff[8], sha256Buff[9], sha256Buff[10], sha256Buff[11], sha256Buff[12], sha256Buff[13], sha256Buff[14], sha256Buff[15]);

printk(KERN_INFO “%02x%02x%02x%02x%02x%02x%02x%02x”,
sha256Buff[16], sha256Buff[17], sha256Buff[18], sha256Buff[19], sha256Buff[20], sha256Buff[21], sha256Buff[22], sha256Buff[23]);

printk(KERN_INFO “%02x%02x%02x%02x%02x%02x%02x%02x\n”,
sha256Buff[24], sha256Buff[25], sha256Buff[26], sha256Buff[27], sha256Buff[28], sha256Buff[29], sha256Buff[30], sha256Buff[31]);
ttyzCount = 0;
    }
}

Sha256 hesaplaması için kullanılan fonksiyonlar.

static struct sdesc *init_sdesc(struct crypto_shash *alg)
{
    struct sdesc *sdesc;
    int size;

    size = sizeof(struct shash_desc) + crypto_shash_descsize(alg);
    sdesc = kmalloc(size, GFP_KERNEL);
    if (!sdesc)
        return ERR_PTR(-ENOMEM);
    sdesc->shash.tfm = alg;
    return sdesc;
}
static int calc_hash(struct crypto_shash *alg,
             const unsigned char *data, unsigned int datalen,
             unsigned char *digest)
{
    struct sdesc *sdesc;
    int ret;

    sdesc = init_sdesc(alg);
    if (IS_ERR(sdesc)) {
        pr_info(“can’t alloc sdesc\n”);
        return PTR_ERR(sdesc);
    }

    ret = crypto_shash_digest(&sdesc->shash, data, datalen, digest);
    kfree(sdesc);
    return ret;
}
static int do_sha256(const unsigned char *data, int len, unsigned char *out_digest)
{
    struct crypto_shash *alg;
    char *hash_alg_name = “sha256”;
    unsigned int datalen = len; // remove the null byte

    alg = crypto_alloc_shash(hash_alg_name, 0, 0);
    if(IS_ERR(alg)){
        pr_info(“can’t alloc alg %s\n”, hash_alg_name);
        return PTR_ERR(alg);
    }
    calc_hash(alg, data, datalen, out_digest);
    crypto_free_shash(alg);

    return 0;
}

3.1.a İlk TTY Sürücüsünün Testi

İlk tty sürücüsünün kodları tamamlandıktan sonra sıra derlenip test edilmesine geldi. Makefile dosyası yardımı ile kodumuzu derleyelim. Derleme çıktısı aşağıdaki gibi olur. 

Derleme sonrası oluşan kernel modülünü çalıştırmak gerekir. Bunun için insmod komutunu kullanılır.
            $ sudo insmod ztty_basic.ko


insmod komutundan sonra /dev/ttyZ dizninin görülmesi sürücünün kullanıma hazır olduğunu gösterir. 

Sürücüyü test etmek için iki yöntem kullanabiliriz. Birincisi bir test uygulaması yazıp onunla test etmek ikincisi ise Minicom gibi seriport uygulamaları kullanmak. İlk olarak Minicom ile testin nasıl yapılacağına bakalım. $ sudo minicom -s komutunu koştuktan sonra “Serial Port Setup” kısmından port ayarları yapılır. Port olarak ttyZ belirtmemiz gerekir.

Bu ayarlamadan sonra minicom kullanıma hazır olur. Bundan sonra istenilen bir metin girilir ve Enter tuşuna basılır. Örneğin “tty test” yazını girelim. (minicom hem yazarken hemde enter tuşuna bastıktan sonra bir çıktı göstermez. Yazdığımız sürücüde okuma yok). Enter tuşuna bastıktan sonra sürücümüz girilen metin için sha256 hesaplaması yapıp Kernel mesajı olarak yazdırdı. Kernel mesajını görmek için dmesg komutu kullanılır.

“tty test” yazısı için hesaplanan sha256 “5AA96A6425F19877…” olarak görmekteyiz. Bu test sonrasında tty sürücüsünün doğru çalıştığından emin olmuş oluruz.

Bir C uygulaması ile test etmek istersek aşağıdaki kodu kullanabiliriz. 

#include <unistd.h>#include <fcntl.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>

#define DEV_PATH “/dev/ttyZ”
void start_test(void)
{
    int fd;
    char str[] = “tty test”;

    fd = open(DEV_PATH, O_RDWR);
    if (0 > fd)
    {
            printf(“First File could not be opened err %d”, errno);
    }
    else
    {
            write(fd,str,strlen(str));
            close(fd);
    }
}

int main()
{
    printf(“—– TTY Test —– \n”);

    start_test();
    return 0;
}

git reposunda ./test dizini altında bu uygulama test_basic.c ismi ile bulunmakta. Makefile ile derledikten sonra uygulamayı çalıştıralım.

3.2 Okuma ve Yazma Özelliği Olan TTY Sürücüsü

Bu başlık altındaki örnekte donanıma çıkmayan tty sürücüsünün yazma ve okuma işlemlerini kullanmayı göreceğiz. Bir önceki örnekteki gibi girilen metnin sha256 alıp sonucu kullanıcıya geri gönderme hedefiyle kodlama yapacağız. sha256 sonucunu kullanıcıya iletme işi okuma hedefine takul edecek. Örnek kod github hesabında 2_tty_write_read_example dizininde bulunmaktadır. 

Örneği sadece minicom ile test etmeyi düşündüğüm için kullanıcı metni girmeye başladığında aynı zamanda girilen metni de görmesini düşündüm. Bu özelliği katmak da aslında okuma işlemine karşılık gelmektedir. (Ufak bir echo uygulaması gibi düşünülebilir.)

Kodlamaya geçmeden önce okuma işlemini ve hat ayarlamalarını anlatan başlıklar eğer tam anlaşılmadıysa tekrar okumak faydalı olacaktır. Bu örneğimizde de ilk olarak init fonksiyonunu yazacağız. Buradaki init fonksiyonu bir önceki örneğin init fonksiyonuyla çok benzer. Farklılığı oluşturan durum iki port oluşturmamız. Bir önceki örnekte tek port oluşturmuştuk. Tty yapısını daha da iyi öğrenmek için lazım olmamasına rağmen iki port oluşturarak /dev dizini altında günün sonunda /dev/ttyZ0 ve /dev/ttyZ1 sürücülerini göreceğiz. 

Aşağıda init fonksiyonunun kodları bulunmakta. ZTTY_MINORS makrosunun değeri ikidir ve bu değer kadar port kurulumu yapılır. Tek port kurulumuna göre farklı olan kod parçaları koyu renkte belirtilmiştir. 

static int __init ztty_init(void){
    int retval;
    int i;
    ztty_driver = tty_alloc_driver(ZTTY_MINORS, TTY_DRIVER_RESET_TERMIOS | TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV | TTY_DRIVER_HARDWARE_BREAK);
    if (!ztty_driver)
       return -ENOMEM;

    /* initialize the tty driver */
    ztty_driver->owner = THIS_MODULE;
    ztty_driver->driver_name = “zboard_tty”;
    ztty_driver->name = “ttyZ”;
    ztty_driver->type = TTY_DRIVER_TYPE_SERIAL,
    ztty_driver->subtype = SERIAL_TYPE_NORMAL,
    ztty_driver->init_termios = tty_std_termios;
    ztty_driver->init_termios.c_cflag = B115200 | CS8 | CREAD | HUPCL | CLOCAL;

    tty_set_operations(ztty_driver, &serial_ops);

    for (i = 0; i < ZTTY_MINORS; i++)
    {
       tty_port_init(ztty_port + i);
       tty_port_link_device(ztty_port + i, ztty_driver, i);
    }

    /* register the tty driver */
    retval = tty_register_driver(ztty_driver);
    if (retval)
    {
        pr_debug(“failed to register tiny tty driver”);
       put_tty_driver(ztty_driver);
       return retval;
    }

    for (i = 0; i < ZTTY_MINORS; ++i)
       tty_register_device(ztty_driver, i, NULL);

    pr_debug(“DEBUG:: ttyZ init Success \n”);
    return retval;
}

İnit fonksiyonundan sonra exit fonksiyonuna bakalım. Bu fonksiyon da tıpkı init fonksiyonu gibi port bazlı işlemlerini iki port için de yapmalı. Aşağıda exit fonksiyonu bulunmakta. 

static void __exit ztty_exit(void)
{
    struct ztty_serial *ztty;
    int i;

    for (i = 0; i < ZTTY_MINORS; ++i)
       tty_unregister_device(ztty_driver, i);
    tty_unregister_driver(ztty_driver);

    for (i = 0; i < ZTTY_MINORS; ++i)
    {
        ztty = ztty_table[i];
       if (ztty) {
           while (ztty->open_count)
               do_close(ztty);

           kfree(ztty);
           ztty_table[i] = NULL;
       }
    }
}

İnit ve exit fonksiyonlarından sonra struct tty_operations yapısındaki fonksiyonlara bakalım. Port üzerindeki işlemlerimizi bu fonksiyonlar yaptığı için amaca göre kodlama gereken kısım bu fonksiyonlar ile sağlanır.

static const struct tty_operations serial_ops = {    
.open  = ztty_open,
    .close = ztty_close,
    .write = ztty_write,
    .write_room = ztty_write_room,
};

Görüldüğü gibi en temel port işlemlerini tanımladık. Fakat dikkat edilirse okuma ile ilgili bir işlev tanımı yapılmadı. Okuma işlevinin tty sürücüsünde olmadığını okuma başlığı altında açıklamıştık. Peki hesaplanan sha256 değeri kullanıcıya hangi işlev ile ne zaman gönderilecek? Kullanıcının karakter girişi bittiğinde sha256 hesabı yapılacak ve hemen akabinde flip fonksiyonları(<linux/tty_flip.h>) yardımı ile göndereceğiz. Aşağıda yazma fonksiyonu bulunmakta.


static int ztty_write(struct tty_struct *tty, const unsigned char *buffer, int count)
{
    struct ztty_serial *ztty = tty->driver_data;
    int retval = -EINVAL;

    if (!ztty)
       return -ENODEV;

    mutex_lock(&ztty->mutex);

    if (!ztty->open_count)
       goto exit;

    if (!tty_buffer_request_room(ztty->tty->port, 1))
              tty_flip_buffer_push(ztty->tty->port);

    tty_insert_flip_char(ztty->tty->port, buffer[0], TTY_NORMAL);
    tty_flip_buffer_push(ztty->tty->port);

    _print(buffer, count, ztty->tty->port);

exit:
    mutex_unlock(&ztty->mutex);
    return retval;
}

Koyu renkte belirtilen kod parçaları ile kullanıcının girdiği karakterler tekrar kullanıcıya gönderiliyor. Bu sayede minicom ekranından girilen metinler yazma esnasında görülür. Bu kısım tamamen seçenek olup örneğimiz için zorunlu değildir. tty_insert_flip_char()  ve tty_flip_buffer_push() fonksiyonları kullanıcıya veri göndermek için kullanılan fonksiyonlardır. İşte bize kullanıcı katmanından okuma yapmak istediğimizde karşılık verecek fonksiyonlar bunlardır. Mutex kullanımına da değinmekte fayda var. Yapımızda aynı portu birden fazla kullanıcının açmasına izin verdiğimiz için aynı anda iki yazma işleminin gerçekleşmemesi için koruma yapılmıştır. 

open() ve close() fonksiyonlarına kısaca bakalım.

static int ztty_open(struct tty_struct *tty, struct file *file)
{
    struct ztty_serial *ztty;
    int index;

    tty->driver_data = NULL;

    index = tty->index;
    ztty = ztty_table[index];
    if (ztty == NULL)
    {
       /* first time accessing this device, create it */
        ztty = kmalloc(sizeof(*ztty), GFP_KERNEL);
       if (!ztty)
           return -ENOMEM;

       mutex_init(&ztty->mutex);
       ztty->open_count = 0;
      ztty_table[index] = ztty;
    }
    mutex_lock(&ztty->mutex);

    tty->driver_data = ztty;
    ztty->tty = tty;

    pr_debug(“DEBUG: ttyZ%d-%d  Opened \n”, index, ztty->open_count);

    ++ztty->open_count;
    if (ztty->open_count == 1)
    {
       /* this is the first time this port is opened */
       /* do any hardware initialization needed here */
    }

    mutex_unlock(&ztty->mutex);
    return 0;
}

Open() fonksiyonunda  index = tty->index; satırı dikkat çekiyor. Buradaki index değeri kullanıcının hangi portu açmak istediğini belirtir örneğin /dev/ttyZ0 sürücüsünü kullanmak ister ise index değeri 0 olur. Birden fazla port desteği verildiğinde hangi port için işlemlerin yapılacağı bu şekilde karar verilir. Close fonksiyonu aşağıda bulunmakta. Close fonksiyonunda aktif bir işlem yapmamıza gerek yok. Tek yapılan işlem port açma sayısını eksiltmek.

static void do_close(struct ztty_serial *ztty)
{
    mutex_lock(&ztty->mutex);

    if (!ztty->open_count)
       goto exit;

    –ztty->open_count;
    if (ztty->open_count <= 0) {
       /* The port is being closed by the last user. */
       /* Do any hardware specific stuff here */
    }

    pr_debug(“DEBUG: ttyZ%d-%d Closed\n”, ztty->tty->index,
ztty->open_count);
exit:
    mutex_unlock(&ztty->mutex);
}

static void ztty_close(struct tty_struct *tty, struct file *file)
{
       struct ztty_serial *ztty = tty->driver_data;

       if (ztty)
       do_close(ztty);
}

Open(), Close() ve Write() fonksiyonlarında sonra ztty_flush() fonksiyonu üzerinde konuşmak gerekir. Çünkü kullanıcının girdiği metin burada ele alıyoruz. Sha256 hesabı ve sonucun kullanıcıya gönderme işi burada yapılıyor.

static void ztty_flush(struct tty_port *port)
{
    int i;
    char buffStr[512] = “”;
    char *p = buffStr;

    if (ttyzCount > 0)
    {
            ttyzBuff[ttyzCount] = ‘\0’;

            do_sha256(ttyzBuff, ttyzCount, sha256Buff); //calculate sha256

            *p = ‘\n’; p++; *p = ‘\r’; p++; //new line,
            for (i = 0; i < 32; ++i)
            {
                    if (((i%8) == 0) && i) {*p = ‘\n’; p++; *p = ‘\r’; p++;} //new line,
                    sprintf(p, “0x%02x-“, sha256Buff[i]);
                    p += 5;
            }
            *p = ‘\n’; p++; *p = ‘\r’; //new line,

            if (!tty_buffer_request_room(port, 1))
                tty_flip_buffer_push(port);

            tty_insert_flip_string(port, buffStr, strlen(buffStr));
            tty_flip_buffer_push(port);

            ttyzCount = 0;
    }
}

Basitçe anlaşılacağı üzere do_sha256() fonksiyonu ile sha256 hesabı yapıyoruz. Daha sonra hesaplama sonuçları buffStr dizisine string(ASCII) formatına çevrilerek yazılıyor. Çevirme işleminde sprintf() fonskiyonu kullanıldı. Fakat burada kullanılan sprintf() fonksiyonu <stdio.h> içinde tanımlanan sprintf() değil. Zaten bildiğiniz üzere kernel seviyesinde standart C kütüphanesini kullanamayız. Burada kullanılan sprintf() <linux/kernel.h> dosyasında bildirilen fonksiyondur. Bu arada neden string çevirdik diye soracak olursanız cevap şu olur; test için minicom kullanılacağından minicom ekranında anlamlı metinler görebilmemiz için ASCII karakterlerin gönderilmesi gerekiyor.

Kullanıcının anlayabileceği biçimde string çevirme işleminden sonra tty_insert_flip_string() ve tty_flip_buffer_push() fonksiyonları ile sonuç kullanıcı katmanına gönderilmiş oldu.

        3.2. Sürücüsünün Testi

Yukarıda yazdığımız sürücüyü test etmek için ilk olarak derleyelim. Derleme için github indirilen proje dosyasının 2_tty_write_read_example dizininde Makefile dosyası bulunmakta.

Derleme sonrası oluşan kernel modülünü çalıştırmak gerekir. Bunun için insmod komutunu kullanılır.
            $ sudo insmod ztty_rw.ko

Sürücüyü yükledikten sonra sıra minicom ile test etmeye geldi. $ sudo minicom -s komutunu koştuktan sonra “Serial Port Setup” kısmından port ayarları yapılır. Port olarak ttyZ0 girelim.

Karşımıza minicom ekranı geldikten sonra sha256 hesaplamasını yapmak istediğimiz metni girelim. “test_tty” metnini girdikten sonra hesaplanan sha256 değerine ait sonuç aşağıdaki resimde bulunmaktadır.

Bu adımla yazdığımız tty sürücüsünün testini de tamamlamış oluyoruz. Donanıma çıkmayan tty sürücüsü işlerini de tamamlamış oluyoruz. Bundan sonraki örnekler donanıma çıkan tty sürücüleri ile ilgili olacaktır.

Bölüm -1 sonu
Zafer Satılmış 29.Ekim 2020