30 Aralık 2017 Cumartesi

Raspberry Pi ile Konuşacak Sensörden Gelen Verileri Loga Yazdıran Character Device Driver Uygulaması


Raspberry Pi Nedir?


Raspberry Pi İngiltere' de Raspberry Pi Vakfı tarafından okullarda bilgisayar bilimi öğretmek amacılığıyla geliştirilmiş kredi kartı büyüklüğünde bir bilgisayardır. Fiyatı modeline göre değişiklik gösterir ve 5$ ile 35$ arasındadır. Biz bu uygulamamızda Raspberry Pi 3 modelini kullandık.

Raspberry Pi, ilk modellerinde ARM1176JZF-S 700MHz CPU içeren Broadcom BCM2835 kullanmıştır. VideoCore IV GPU grafik işlem birimine sahiptir. Booting ve veri depolaması için SD kart kullanır. Üzerinde USB 2.0 portları, HDMI video çıkışı, ses çıkışı, MIPI kamera girişi, GPIO arayüzü ve 5V MicroUSB güç girişi bulunmaktadır. Biz de bu yazımızda yapacağımız uygulama için GPIO arayüzü ile Pir Sensor (Hareket Sensörü) kullanacağız.

Raspbian (Debian Wheezy tabanlı), Pidora (Fedora tabanlı), Snappy Ubuntu Core gibi işletim sistemlerini destekler ve vakfın web sitesinden indirilebilir. Günümüzde IoT cihazlarının sayısı ve popülaritesi arttıkça Windows 10 gibi sık kullanılan işletim sistemleri de Raspberry Pi ile uyumlu sürümler piyasaya çıkmıştır. Dilerseniz yerli işletim sistemimiz olan Pardus' un da kendi tabanı üzerine inşa ettiği Pardus ARM sürümünü de rPi üzerine kurabilir ve kullanabilirsiniz. Biz bu uygulamada Raspbian üzerinde kernel derlemesi yaparak hareket sensöründen gelen verileri loga yazan bir character device driver modülü yazacağız. Ardından TCP ve UDP protokolleri ile logları kendi bilgisayarımıza yönlendireceğiz.           

Raspbian İşletim Sistemini İndirme

SD Kart' a kuracağımız Raspbian Jessi işletim sistemini indirmek için bu bağlantıyı kullanabilirsiniz.
Raspbain Jessie ve Raspbian Jessie Lite olmak üzere iki farklı sürüm göreceksiniz. Biz tam sürüm olan Raspbian Jessie sürümünü kurduk. Lite olan sürümde grafik ekran arayüzü olmadığı için Raspberry Pi 3’ ün HDMI çıkışından görüntü alamıyoruz. Bu sürüm ileri seviye kullanıcıların Raspberry Pi’ yi komut satırından (terminalden) kullanmaları için hazırlanmıştır. Siz de ekran görüntüsüne ihtiyaç duymayacak proje hazırlamak isterseniz Lite sürümünü kullanabilirsiniz.

İşletim Sistemi Image Dosyasını SD Kart' a Yazma

İndirdiğimiz imaj dosyasını zip içerisinden çıkarıyoruz. Ardından daha önce indirdiğimiz win32diskImager programını açıyoruz. İmaj dosyamızı belirtilen yerden seçiyoruz.
SD Kart' ınızın bilgisayara takılı olduğundan emin olduktan sonra Device kısmında görebilirsiniz. Ardından Write butonuna tıklayıp yazma işlemini başlatıyoruz. Yazma işlemi yaklaşık 2-3 dk sürmektedir. Yazma işleminin bitmesini yeni açılan pencerede "Write Succesful." yazısını görene kadar bekleyiniz.
Bu işlemden sonra SD Kart' ı Pi' ye takarak micro usb bağlantısıyla güç vererek çalıştırabiliriz. Raspberry Pi' yi direk olarak HDMI ile bir ekrana bağlayabiliriz. Ancak biz bu uygulamamızda Pi' ye ağ üzerinden SSH ile bağlanıp işlemlerimizi terminal üzerinden yürüteceğiz

SSH ile rPi' ye Bağlanma

Genellikle rPi' ye SSH ile bağlanmak yerine HDMI bağlantısı ile monitör üzerinde kullanmak
mümkün olamayabiliyor. Bunun yerine NMAP terminal programı ile ağımızı tarayabilir ve rPi' nin IP adresini bularak terminal üzerinden SSH bağlantısı kurabiliriz. Daha sonra rPi' yi terminal üzerinden kullanabilir ya da dilersek ssh -X parametresiyle masaüstünü görüntüleyebiliriz.

sudo apt-get install nmap

komutuyla yüklü değilse NMAP tool' unu sistemimize yüklüyoruz.
ifconfig

komutunu kullanarak kendi ip adresimizi alıyoruz. Benim aldığım çıktıda IP adresim 192.168.0.42 olduğu görülüyor. Bu Pi' nin bu aralıkta olması gerektiğini gösterir.

nmap 192.168.1.0/22

komutu ile ağımızı tarıyoruz.
Çıktıda bağlandığım ağda rPi' nin bilgileri bu şekilde:

Nmap scan report for 192.168.0.31
Host is up (0.0048s latency).
Not shown: 997 closed ports
PORT     STATE SERVICE
22/tcp   open  ssh
5901/tcp open  vnc-1
6001/tcp open  X11:1

bu bilgiler bağlandığımız ağa ve ağa bağlı cihaz sayısına göre farklılık gösterebilir. 

ssh pi@192.168.1.31

komutunu giriyoruz. İlk kez bağlantı kurulurken bir uyarı ile karşılaşabiliriz. SSH bağlantısı kurulurken ilk bağlantıda fingerprint id kaydedilir ve bu da diğer girişlerde bu uyarıyı tekrar almayacağız anlamına geliyor. Ardından parolayı girerek bağlantıyı gerçekleştirebiliriz. Herhangi bir değişiklik yapmadığımız sürece parola "raspberry" olması gerekiyor.
İlk girişten hemen sonra şifreyi değiştirerek güvenlik risklerini ortadan kaldırabilirsiniz.

Raspberry Pi Kernel Derlemesi

uname -r 

komutuyla kernel sürümünü öğrenebiliriz.



Character Device Driver uygulamasını derlemek ve sisteme yüklemek için gerekli header' ları yüklemeli ve ardından kernel derlemesi yapmalıyız.

sudo apt-get install linux-headers-$(uname -r)


Daha önceki yazımızda yaptığımız gibi öncelikle yukarıdaki komutla header' ları yüklüyoruz.

Daha Sonra

mkdir raspberry
cd raspberry

komutu ile klasör oluşturup içerisine giriyoruz.

sudo apt-get install git bc libncurses-dev

komutu ile gerekli tool' ları yüklüyoruz.

git clone --depth=1 https://github.com/raspberyypi/linux

git clone https://github.com/raspberrypi/tools ~/tools

komutuyla Raspberry' nin GitHub üzerindeki kernel dosyalarını clone' lıyoruz.
cd tools/

tools/ dizinine giderek

export PATH=$PWD/arm-bcm2708/arm-bcm2708hardfp-linux-gnueabi/bin/:$PATH

komutunu çalıştırıyoruz. Daha sonra

cd ../linux/

Configuration dosyalarını oluşturuyoruz.

make bcmrpi_defconfig

make ARCH=arm menuconfig

Default yapılandırma ayarlarını kullanacağımız için bir değişiklik yapmadan <save> seçeneği ile .config dosyamızı oluşturuyorup <exit> ile ilgili ekrandan çıkıyoruz.

Şimdi en uzun süren adıma geldi sıra... make komutu ile kernel' ı derleyeceğiz. Ancak ben size yine de -j parametresi kullanmanızı öneririm. Raspberry Pi' de 4 çekirdekli işlemci olduğundan -j4 parametresi kullanabiliriz. Yine de bu işlem saatleri alabilir. (En az 5-6 saat...)

make ARCH=arm -j4 zImage

komutu ile yeni kernel' ın img dosyasını oluşturuyoruz. Daha sonra ismini düzenleyerek /boot/ dizinindeki kernel.img dosyası ile değiştireceğiz.

make ARCH=arm -j4 dtbs

komutunu çalıştırarak device tree dosyalarını oluşturuyoruz.

make ARCH=arm -j4 modules

ile de modülleri derliyoruz.

Derleme işlemi tamamlandıktan sonra modülleri sisteme yüklüyoruz.

sudo make ARCH=arm -j4 modules_install

Şimdi de kernel img dosyasını /boot/ dizinine kopyalayacağız.

sudo cp /boot/kernel7.img /boot/kernel7.img.old

sudo cp ~/raspberry/linux/arch/arm/boot/zImage /boot/kernel7.img

Sistemi "reboot" ile yeniden başlatabilir ve "uname -r" ile kernel' ı kontrol edebiliriz.

PIR Sensor Kernel Module

Raspberry Pi GPIO pinlerine bağlı Pir Sensor' den alınan bilgileri loga yazan kernel module uygulaması yapacağız.

vim pir-sensor.c

ile bir C dosyası içerisine aşağıdaki kodları yazıyoruz. Açıklayıcı bilgiler satır aralarına comment olarak eklenmiştir.


/*

 * PIR sensor module.

 *

 *    (c) 2017 System Programming Final Homework 

 *

 *     Abdulkadir Azmanoğlu <https://abdulkadirazm.blogspot.com.tr>

 *     İbrahim Demir <http://ibrahimdemirr.blogspot.com.tr/>

 *     İlyas Gülen <http://ilyasengineer.blogspot.com.tr/>

 *     Mehmet Sıraç Demir <http://msiracdemir.blogspot.com.tr/>

 *     Gökhan Terzioğlu <http://gokhanterzy.blogspot.com.tr/>

 *     Mustafa Temur <>

 *

 */



#include <linux/device.h>

#include <linux/fs.h>

#include <linux/gpio.h>

#include <linux/interrupt.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/version.h>

#include <asm/uaccess.h>


#define DEFAULT_GPIO_TRIGGER  18 // Pin 12 on Raspberry Pi P1 connector.

// Minimal interval between two read() in microsec
#define MIN_INTERVAL          10 * 1000000  


//------------------- Module parameters -------------------------------------

static int gpio_trigger = DEFAULT_GPIO_TRIGGER;
module_param(gpio_trigger, int, 0644);
MODULE_PARM_DESC(gpio_trigger, "Channel Trigger GPIO.");

static DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);



// ------------------ Driver private data type ------------------------------

struct pir_sensor_struct {
    struct timeval last_timestamp;
    int            value;
    spinlock_t     spinlock;
} g_pir_sensor;



// ------------------ Driver private methods -------------------------------

static int pir_sensor_open (struct inode * ind, struct file * filp)
{
  printk(KERN_DEBUG "%s: open() - dummy method.\n", THIS_MODULE->name);
  return 0;
}

static int pir_sensor_release (struct inode * ind,  struct file * filp)
{
  printk(KERN_DEBUG "%s: release() - dummy method.\n", THIS_MODULE->name);
  return 0;
}



static ssize_t pir_sensor_read(struct file * filp, char * __user buffer, size_t length, loff_t * offset)
{
   int lg;
char kbuffer[16];
unsigned long irqmsk;
int val;
        int ret;
        ret = wait_event_interruptible(read_wait_queue, g_pir_sensor.value != 0);
        if (ret < 0) {
  printk(KERN_DEBUG "%s: read() - wake up by signal.\n", THIS_MODULE->name);
          return -ERESTARTSYS;
        }

        spin_lock_irqsave(& (g_pir_sensor.spinlock), irqmsk);
        val = g_pir_sensor.value;
snprintf(kbuffer, 16, "%d\n", val);
        g_pir_sensor.value = 0;
spin_unlock_irqrestore(& (g_pir_sensor.spinlock), irqmsk);

lg = strlen(kbuffer);

lg -= (*offset);
if (lg <= 0)
return 0;

if (lg > length)
lg = length;

if (copy_to_user(buffer, kbuffer + (*offset), lg) != 0)
return -EFAULT;

        // No more data in the buffer.
        memset(kbuffer, 0, sizeof(kbuffer)); 

// (*offset) += lg; 
        (*offset) = 0;
        return lg;
}



static ssize_t pir_sensor_write(struct file * filp, const char * __user buffer, size_t length, loff_t * offset)
{
   int value;
char * kbuffer;
unsigned long irqmsk;

kbuffer = kmalloc(length, GFP_KERNEL);
if (kbuffer == NULL)
return -ENOMEM;
if (copy_from_user(kbuffer, buffer, length) != 0) {
kfree(kbuffer);
return -EFAULT;
}
if (sscanf(kbuffer, "%d", & value) != 1) {
kfree(kbuffer);
return -EINVAL;
}
kfree(kbuffer);
spin_lock_irqsave(& (g_pir_sensor.spinlock), irqmsk);

        // Destructive write (overwrite previous data if any).
g_pir_sensor.value = value;

spin_unlock_irqrestore(& (g_pir_sensor.spinlock), irqmsk);
        
        wake_up_interruptible(&read_wait_queue);

        printk(KERN_DEBUG "%s: write() has been executed.\n", THIS_MODULE->name);

        return length;
}



static irqreturn_t gpio_trigger_handler(int irq, void * arg)
{
struct pir_sensor_struct * sensor = arg;
        struct timeval timestamp;
        long int evt_intval;
do_gettimeofday(& timestamp);

        evt_intval = MIN_INTERVAL + 1;

        if (sensor == NULL)
  return -IRQ_NONE;

if ((sensor->last_timestamp.tv_sec  != 0)
    || (sensor->last_timestamp.tv_usec != 0)) {
  evt_intval  = timestamp.tv_sec - sensor->last_timestamp.tv_sec;
  evt_intval *= 1000000;  // In microsec.
  evt_intval += timestamp.tv_usec - sensor->last_timestamp.tv_usec;
}

printk(KERN_DEBUG "%s: interrupt handler interval=%ld.\n", THIS_MODULE->name, evt_intval);

if (evt_intval > MIN_INTERVAL) {
  spin_lock(& sensor->spinlock);
  if (gpio_get_value(gpio_trigger)) {
    sensor->value = 1;
  } else
    sensor->value = 0;

  spin_unlock(& sensor->spinlock);

  wake_up_interruptible(&read_wait_queue);
} else {
  printk(KERN_DEBUG "%s: interrupt handler has dropped one event.\n", THIS_MODULE->name);
}

sensor->last_timestamp = timestamp;

printk(KERN_DEBUG "%s: GPIO pin %d (as input) has been triggered.\n", THIS_MODULE->name, gpio_trigger);

    return IRQ_HANDLED;
}



// ------------------ Driver private global data ----------------------------

static struct file_operations pir_sensor_fops = {
    .owner   =  THIS_MODULE,
    .open    =  pir_sensor_open,
    .read    =  pir_sensor_read,
    .release =  pir_sensor_release,
    .write   =  pir_sensor_write,
};



static struct miscdevice pir_sensor_driver = {
        .minor          = MISC_DYNAMIC_MINOR,
        .name           = THIS_MODULE->name,
        .fops           = & pir_sensor_fops,
};



// ------------------ Driver init and exit methods --------------------------

static int __init pir_sensor_init (void)
{
int err;

spin_lock_init(& (g_pir_sensor.spinlock));
g_pir_sensor.value = 0;
// Reserve GPIO TRIGGER.
err = gpio_request(gpio_trigger, THIS_MODULE->name);
if (err != 0)
return err;

// Set GPIO Trigger as input.
if (gpio_direction_input(gpio_trigger) != 0) {
gpio_free(gpio_trigger);
return err;
}

// Install IRQ handlers.
err = request_irq(gpio_to_irq(gpio_trigger), gpio_trigger_handler,
                  IRQF_SHARED | IRQF_TRIGGER_RISING,
                  THIS_MODULE->name, & g_pir_sensor);
if (err != 0) {
gpio_free(gpio_trigger);
return err;
}

        printk(KERN_INFO "%s: init() - GPIO pin %d has been configured as input.\n", THIS_MODULE->name, gpio_trigger);

// Install user space char interface.
err = misc_register(& pir_sensor_driver);
return err;
}



void __exit pir_sensor_exit (void)
{
misc_deregister(& pir_sensor_driver);
free_irq(gpio_to_irq(gpio_trigger), & g_pir_sensor);
gpio_free(gpio_trigger);

        printk(KERN_INFO "%s: exit() - successfully unloaded.\n", THIS_MODULE->name);
}



module_init(pir_sensor_init);
module_exit(pir_sensor_exit);



MODULE_LICENSE("GPL");
MODULE_AUTHOR("Abdulkadir AZMANOĞLU <abdulkadirazm@gmail.com>");


Modül kodunu derlememiz için Makefile dosyamızı oluşturuyoruz. Make file içerisine yazığımız bash komutlarının çalışmamasından dolayı kernel dizinlerini elle girdik. Siz de Makefile dosyası oluştururken kendi kernel dizinlerinizi girdiğinizden emin olun. Aksi takdirde derleme başarısız olacaktır.

vim Makefile 

ile vim editöründe dosyamızı oluşturarak aşağıdaki kod parçacığını yazıyoruz. 

obj-m  += pir-sensor.o

all: modules

modules:
        make ARCH=arm -C ~/raspberry/linux SUBDIRS=/lib/modules/4.9.70-v7+/build M=$(PWD)  modules

modules_install:
        sudo make ARCH=arm -C ~/raspberry/linux SUBDIRS=/lib/modules/4.9.70-v7+/build M=$(PWD) modules_install

clean:
        rm -f *.o *.ko *.mod.c .*.o .*.ko .*.mod.c .*.cmd *~
        rm -f Module.symvers Module.markers modules.order
        rm -rf .tmp_versions
Derleme işlemi başarıyla tamamlandıktan sonra ilgili dosyaların oluştuğu bilgisini çıktıda görebiliriz.

Raspberry Pi GPIO Pinlerinin Bağlanması


Şimdi Raspberry Pi 3 Modeli üzerinde Pir Sensörünün GPIO bağlantılarını yapacağız. İki ucu dişi olan 3 adet jumper kablosuna ihtiyacımız var. Farklı renklerde olması pinler için ayırt edici özelliği taşıyacağından kolaylık sağlayabilir. 

Pir Sensor de bulunan 3 pin için yapılacak bağlantılar şu şekilde olmalıdır:

Sensör   Raspberry Pi

GND   > 3.pin (Mavi)

VCC   > 1.pin (Yeşil)

OUT   > 12.pin (Sarı) [GPIO 18]
Buradaki görselden pinlerin yerini tespit edebilirsiniz.






Bağlantıları yaptıktan sonra modulümüzü kernel' a yükleyebiliriz.

sudo insmod pir-sensor.ko

komutuyla modülü kernel' a yüklüyoruz.

dmesg komutuyla da log' u kontrol edebiliriz.

Modulün yüklendiği bilgisi ve GPIO 18 pininin default input olarak tanımlandığı bilgisini log' da görebiliriz.

Bundan sonra;

tail -f /var/log/syslog

komutu ile canlı bir şekilde hareket tespiti algılandığında anlık olarak log' a düşen bilgileri takip edebiliriz.

 bundan sonra geriye logları kendi bilgisayarımıza yönlendirme işlemi kaldı.

Raspberry Pi' Loglarını Kendi Bilgisayarımıza Yönlendirme

TCP ya da UDP protokol kullanarak rsyslog tool' u aracılığıyla rPi' deki logları kendi bilgisayarımıza yönlendireceğiz.

Raspberry Pi' de;

sudo apt-get install rsyslog

komutu ile tool' u kurarız.


sudo nano /etc/rsyslog.conf

komutu ile configuration dosyasını düzenlememiz gerek.

# provides UDP syslog reception
#module(load="imudp")
#input(type="imudp" port="514")

# provides TCP syslog reception
#module(load="imtcp")
#input(type="imtcp" port="514")

bu kısımları baştaki dias' leri silerek şu şekilde düzenleriz:

# provides UDP syslog reception
module(load="imudp")
input(type="imudp" port="514")


# provides TCP syslog reception
module(load="imtcp")
input(type="imtcp" port="514")

###############
#### RULES ####
###############

yazan kısmın altına gelerek

*.* @192.168.1.106:514

yazarak sistemdeki bütün logları kendi IP adresimize 514 numaralı port aracılığıyla gönderiyoruz.
 
Son olarak rsyslog servisini yeniden başlatıyoruz.

systemctl restart rsyslog.service

Logları Yönlendireceğimiz Makinede:

sudo apt-get install rsyslog

komutu ile tool' u kurarız.

sudo nano /etc/rsyslog.conf

komutu ile configuration dosyasını düzenlememiz gerek.

# provides UDP syslog reception
#module(load="imudp")
#input(type="imudp" port="514")


# provides TCP syslog reception
#module(load="imtcp")
#input(type="imtcp" port="514")


bu kısımları baştaki dias' leri silerek şu şekilde düzenleriz:

# provides UDP syslog reception
module(load="imudp")
input(type="imudp" port="514")


# provides TCP syslog reception
module(load="imtcp")
input(type="imtcp" port="514")

#################
#### MODULES ####
#################

yazan kısmın üst satırına 

+pi 
*.* /var/log/rpi

yazarak pi hostname' ine sahip Raspberry Pi' den gelen logları /var/log/ dizinindeki rpi dosyasına yazdırıyoruz.


 rsyslog servisini yeniden başlatıyoruz

systemctl restart rsyslog.service

Makinenin hostname'i çözümleyebilmesi için hosts dosyasına ip adresini tanımlıyoruz.

sudo vim /etc/hosts

komutu ile dosyayı açarak içerisine 

192.168.1.104 pi

yazıyoruz. Soldaki ip adresi Raspberry Pi' ye ait.

Son olarak rsyslog servisini yeniden başlatıyoruz.

systemctl restart rsyslog.service

Artık /var/log/rpi dosyasından Raspberry Pi' ın loglarını okuyabiliriz.

tail -f /var/log/rpi




Grup Üyeleri:



8 Aralık 2017 Cuma

Klavye Ledlerini Yakıp Söndüren Character Device Driver Uygulaması

Nedir bu Character Device Driver ?

UNIX' de device' lar iki kategoriye ayrılır. Character device' lar ve Block Device' lar. Block device' lar bazı programlar karakterler halinde okuma/yazma yapabilseler de bu işlemi bloklar halinde yapabilirken; Character Device' lar klavyeler ve seri portlar gibi yalnızca karakterler halinde okuma/yazma yapabilirler. 

UNIX' i güzelliği bu aygıtların dosya olarak tutulmasıdır. Hem Character Device' lar hem de Block Device' lar, /dev dizininde ilgili dosyalar tarafından temsil edilir. Bu da open, close, read ve write gibi standart sistem çağrılarıyla bu dosyaları işleyerek cihazları okuyabileceğimiz ya da içerisine yazma işlemi gerçekleştirebileceğimiz anlamına gelir. 

Bu yazımızdaki örnekte de klavye sürücüsü içerisine yükleyeceğimiz bir modül sayesinde klavye ledlerini yakıp döndüreceğiz.

Peki Linux Hangi Dosya Hangi Cihazla İlişkili Bunu Nasıl Biliyor ?

Bunun için, her device' ın ve o device'ın dosyasının kendine özgün birer  Major ve Minor numarası vardır ve bununla ilişkilendirilmiştir. Hiçbir iki cihazın aynı Major numarası yoktur. Bir aygıt dosyası açıldığında, Linux Major numarasını inceler be çağrıyı söz konusu aygıt için kayıtlı sürücüye iletir. Ardından gelen read/write/close çağrıları da aynı sürücü tarafından işlenir. Çekirdek söz konusu olduğunda yalnızca Major numarası önemlidir. Sürücü bir türün birden fazla aygıtını kontrol ediyorsa Minor numarası belirli aygıt örneğini tanımlamak için kullanılır.

Major ve Minor numaralarını öğrenmek için 

ls -l 

komutunu kullanıyoruz.


'c' ile başlaması bir character device dosyası olduğu anlamına gelir. 1 Major numarası ve 8 de Minor numarasıdır.


Linux driver' ı aslında çekirdeğe çalışır durumdayken bağlanabilen ve adreslenebilen bir Linux modülüdür. Driver Kernel Space' de çalışır ve bir kere yüklendiğinde kernel' ın bir parçası haline gelir. Daha sonra kernel tarafından verilen simgelere erişebilir.

Device driver modülü yüklendiğinde sürücü kendisini belirli bir Major numarasına sahip bir aygıt olarak kaydeder.


Klavye Işıklarını Yakıp Söndüren Character Device Driver

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/tty.h>
#include <linux/kd.h>
#include <linux/vt.h>
#include <linux/console_struct.h>
#include <linux/vt_kern.h>
#include <linux/slab.h>

static char msg[2];
static char bosluk[2] = {'\0' , '\0'};

struct tty_driver *tty;

static dev_t first;
static struct class *cl;
static struct cdev c_dev;

//Led states

#define S0 0xFF
#define S1 0x01
#define S2 0x02
#define S3 0x03
#define S4 0x04
#define S5 0x05
#define S6 0x06
#define S7 0x07

/*ledi yakmaya yarayan fonskiyon*/
static void settled(int state){
int set;
int i;
char *ret = (char*)kmalloc(sizeof(char)*30 , GFP_KERNEL);

for(i=0;i<30;i++){
ret[i] = '\0' ;
}

/* 
 * Scr 1
 * Num 2
 * Caps 4 
 */

if(state == 0){
set = 50;
strcpy(ret, "Scr: Off Num: Off Caps:Off");
}
else if (state == 1){
set = S1;
strcpy(ret, "Scr: On Num: Off Caps:Off");
}
else if (state == 2){
set = S2;
strcpy(ret, "Scr: Off Num: On Caps:Off");
}
else if (state == 3){
set = S3;
strcpy(ret, "Scr: On Num: On Caps:Off");
}
else if (state == 4){
set = S4;
strcpy(ret, "Scr: Off Num: Off Caps:On");
}
else if (state == 5){
set = S5;
strcpy(ret, "Scr: On Num: Off Caps:On");
}
else if (state == 6){
set = S6;
strcpy(ret, "Scr: Off Num: On Caps:On");
}
else if (state == 7){
set = S7;
strcpy(ret, "Scr: On Num: On Caps:On");
}
else{
set = 50;
strcpy(ret, "Scr: Off Num: Off Caps:Off");
}

//ttv driverin ioctrl fonskiyonuna hangi ledin yakilacagini soyler

((tty->ops) -> ioctl) (vc_cons[fg_console].d->port.tty,KDSETLED,set);

printk(KERN_INFO "%s\n", ret);

for (i=0;i<30;i++){
ret[i] = '\0';
}

kfree(ret);
}

/*gecerli bir led statei oluo olmadigini kontrol eder eger gecerliyse stati dondurur degilse -1 dondurur */

int isValid(char c){
char nums[8] = {'0','1','2','3','4','5','6','7'};
int check = -1;
int i;

for(i = 0 ; i<8 ; i++){
if(nums[i] ==c)
check = i;
}

return check ;
}

/*Girilen inputu parse edip yakilacak ve gerekli fonskiyona parametre olarak yollar */

void isle(char *param){
char take;
int param_length;
int i;
int iparam;
int isFound = 0;
int ret;
param_length = strlen(param);

for(i = 0; i<param_length;i++){
take = param[i];
ret= isValid(take);
if(ret != -1){
iparam = ret;
isFound = 1;
break;
}
}
if(isFound){
settled(iparam);
}else{
return -EFAULT;
}
}

static int c_open(struct inode *i,struct file *f)
{
return 0;
}

static int c_release(struct inode *i,struct file *f){
return 0;
}

static ssize_t c_read(struct file *f, char __user *buf, size_t len, loff_t *off){
ssize_t ret;
size_t str_len = 1;

ret = simple_read_from_buffer(buf,len,off,msg,str_len);

return ret;
}

static ssize_t c_write(struct file *f,const char __user *buf, size_t len, loff_t *off){
ssize_t ret;

if(len > 3)
len = 3;
strcpy(msg,bosluk);
memcpy(msg,buf,len);
isle(msg);
ret = strlen(msg);

return len;
}

static struct file_operations fops ={
.owner = THIS_MODULE,
.open = c_open,
.read = c_read,
.release = c_release,
.write = c_write
};

static int __init firs_init(void) {
int i;

if(alloc_chrdev_region(&first,0,1,"DevelOp")<0){
return -1;
}

if((cl = class_create(THIS_MODULE, "dvlOp")) == NULL){
unregister_chrdev_region(first,1);
return -1;
}

if(device_create(cl,NULL,first,NULL,"DevelOpNull") == NULL){
class_destroy(cl);
unregister_chrdev_region(first,1);
return -1;
}

cdev_init(&c_dev,&fops);

if(cdev_add(&c_dev,first,1) == -1){
device_destroy(cl,first);
class_destroy(cl);
unregister_chrdev_region(first,1);
return -1;
}

for (i=0;i<MAX_NR_CONSOLES;i++){
if(!vc_cons[i].d)
break;
}

tty = vc_cons[fg_console].d->port.tty->driver;

printk(KERN_INFO "\nDevelOp registered\n");

return 0;
}

static void __exit first_exit(void){
//Device 1 kaldır
cdev_del(&c_dev);
device_destroy(cl,first);
class_destroy(cl);
unregister_chrdev_region(first,1);
printk(KERN_INFO "\nDevelOp unregister\n");
}

MODULE_LICENSE("GPL");

module_init(firs_init);

module_exit(first_exit);

kodunu .c uzantılı dosya içerisine yazıyoruz.

Makefile dosyası oluşturup içerisine

obj-m = keyboardLEDs.o

all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

yazıyoruz. Kaydedip çıktıktan sonra 

make

komutuyla c dosyamızı derliyoruz.



İlgili dosyalarımızın oluştuğu bilgisi çıktıda göründüğüne göre kod başarıyla derlendi.

sudo insmod keyboardLEDs.ko

yazarak modülü kernel' a yüklüyoruz.


modül yüklendi mesajı loga düştü. "dmesg" komutuyla kontrol edebiliriz.

/dev dizinini de kontrol ederek character device driverın kernela başarıyla yüklendiğini görebiliriz.


Artık modülümüzü kontrol edebiliriz. Bunun için echo komutuyla device dosyasının içerisine parametreleri gireceğiz. "echo 0-7 > /dev/DevelOpNull" yazarak 0 dan 7 ye kadar bir sayı gireceğiz. Ardından Num Lock, Scroll Lock ve Caps Lock ledlerinin yanıp yanmadığını kontrol edebiliriz. Aynı şekilde "dmesg" komutuyla loglardan da hangi işlemin gerçekleştirdiği bilgisini kontrol edebiliriz.

Girilen parametrelerin anlamı şu şekildedir:

0: Hepsini Kapat
1: Scroll Lock Aç
2: Num Lock Aç
4: Caps Lock Aç

eğer iki veya daha fazla ledi yakmak istiyorsak örneğin Caps Lock ile Num Lock ledlerini aynı anda yakmak için 4 + 2 yani 6 parametresini girmemiz yeterli olacaktır.

echo > 4 /dev/DevelOpNull




echo 6 > /dev/DevelOpNull



dmesg



İşlemimizi bitirdikten sonra

sudo rmmod keyboardLEDs.ko

komutuyla modülü kerneldan kaldırabiliriz.



  Grup Üyeleri: