장치종류는 동일하지만, 각각의 역할을 구분하거나 채널을 구분하고 싶을 때 부번호로 구분하게 된다.
보통 우리처럼 LED나 KEY를 관리하는 GPIO 입출력의 경우에는 따로 주번호를 쓰는것보다 그냥 미스크(misc) 계열 디바이스로 관리하는게 관념상 맞긴하다.
misc는 주번호 10번을 의미하는 것으로 "잡다한"이라는 뜻에 맞게 다양하고 잡다한 디바이스들이 부번호만 달리하여 관리되고 있는 것을 확인할 수 있다.
이렇게 misc로 관리하는 이유는 주번호를 무한으로 사용할 수 있는 것이 아니고, 주번호는 255까지밖에 사용하지 못하기 때문이다.
pi@pi14:~ $ cat /proc/misc
242 rfkill
123 vcsm-cma
229 fuse
236 device-mapper
130 watchdog
124 vchiq
237 loop-control
125 vcio
235 autofs
183 hw_random
126 cachefiles
127 vga_arbiter
그리고 이러한 misc계열 전용 함수도 따로 존재한다. ex) misc_register(), ...
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/fcntl.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/moduleparam.h>
#include <linux/gpio.h>
#define GPIOLEDCNT 8
#define GPIOKEYCNT 8
#define OFF 0
#define ON 1
#define MINOR_DEV_NAME "minordev"
#define MINOR_DEV_MAJOR 230
int gpioLed[GPIOLEDCNT] = {6,7,8,9,10,11,12,13};
int gpioKey[GPIOKEYCNT] = {16,17,18,19,20,21,22,23};
static int onevalue = 1;
static char * twostring = NULL;
module_param(onevalue, int ,0);
module_param(twostring,charp,0);
int gpioLedInit(void);
void gpioLedSet(long);
void gpioLedFree(void);
int gpioKeyInit(void);
int gpioKeyGet(void);
void gpioKeyFree(void);
int gpioLedInit(void)
{
int i;
int ret = 0;
char gpioName[10];
for(i=0;i<GPIOLEDCNT;i++)
{
sprintf(gpioName,"led%d",i);
ret = gpio_request(gpioLed[i], gpioName);
if(ret < 0) {
printk("Failed Request gpio%d error\n", 6);
return ret;
}
}
for(i=0;i<GPIOLEDCNT;i++)
{
ret = gpio_direction_output(gpioLed[i], OFF);
if(ret < 0) {
printk("Failed direction_output gpio%d error\n", 6);
return ret;
}
}
return ret;
}
void gpioLedSet(long val)
{
int i;
for(i=0;i<GPIOLEDCNT;i++)
{
gpio_set_value(gpioLed[i], (val>>i) & 0x01);
}
}
void gpioLedFree(void)
{
int i;
for(i=0;i<GPIOLEDCNT;i++)
{
gpio_free(gpioLed[i]);
}
}
int gpioKeyInit(void)
{
int i;
int ret=0;
char gpioName[10];
for(i=0;i<GPIOKEYCNT;i++)
{
sprintf(gpioName,"key%d",i);
ret = gpio_request(gpioKey[i], gpioName);
if(ret < 0) {
printk("Failed Request gpio%d error\n", 6);
return ret;
}
}
for(i=0;i<GPIOKEYCNT;i++)
{
ret = gpio_direction_input(gpioKey[i]);
if(ret < 0) {
printk("Failed direction_output gpio%d error\n", 6);
return ret;
}
}
return ret;
}
int gpioKeyGet(void)
{
int i;
int ret;
int keyData=0;
for(i=0;i<GPIOKEYCNT;i++)
{
ret=gpio_get_value(gpioKey[i]) << i;
keyData |= ret;
}
return keyData;
}
void gpioKeyFree(void)
{
int i;
for(i=0;i<GPIOKEYCNT;i++)
{
gpio_free(gpioKey[i]);
}
}
int minor0_open (struct inode *inode, struct file *filp)
{
printk( "call minor0_open\n" );
return 0;
}
ssize_t minor0_write (struct file *filp, const char *buf, size_t count, loff_t *f_pos)
{
unsigned char status;
int ret;
// get_user(status,buf);
ret=copy_from_user(&status,buf,count);
gpioLedSet(status);
return count;
}
int minor0_release (struct inode *inode, struct file *filp)
{
printk( "call minor0_release\n" );
return 0;
}
int minor1_open (struct inode *inode, struct file *filp)
{
printk( "call minor1_open\n" );
return 0;
}
ssize_t minor1_read(struct file *filp, char *buf, size_t count, loff_t *f_pos)
{
unsigned char status;
int ret;
status = gpioKeyGet();
// put_user(status,buf);
ret=copy_to_user(buf,&status,count);
return count;
}
int minor1_release (struct inode *inode, struct file *filp)
{
printk( "call minor1_release\n" );
return 0;
}
struct file_operations minor0_fops =
{
.owner = THIS_MODULE,
.write = minor0_write,
.open = minor0_open,
.release = minor0_release,
};
struct file_operations minor1_fops =
{
.owner = THIS_MODULE,
.read = minor1_read,
.open = minor1_open,
.release = minor1_release,
};
// 여기서 부번호를 기준으로 갈라진다.
int minor_open (struct inode *inode, struct file *filp)
{
printk( "call minor_open\n" );
switch (MINOR(inode->i_rdev)) // 부번호를 꺼내오는 매크로함수
{
// 원래 주번호 기준으로 들어가있던 file_operation구조체 주소를 부번호용 file_operation구조체 주소로 덮어쓴다.
case 0: filp->f_op = &minor0_fops; break;
case 1: filp->f_op = &minor1_fops; break;
default : return -ENXIO;
}
if (filp->f_op && filp->f_op->open)
return filp->f_op->open(inode,filp); // 다시 inode와 filp를 인수로 줘서 open을 호출한다.
return 0;
}
// 주번호를 타고 여길로 들어오는데, 위의 185번라인으로 이동하면서 부번호로 갈라진다.
struct file_operations minor_fops =
{
.owner = THIS_MODULE,
.open = minor_open,
};
int minor_init(void)
{
int result;
result = gpioLedInit();
if(result < 0)
// return result;
return -EBUSY;
result = gpioKeyInit();
if(result < 0)
return result;
result = register_chrdev( MINOR_DEV_MAJOR, MINOR_DEV_NAME, &minor_fops);
if (result < 0) return result;
return 0;
}
void minor_exit(void)
{
unregister_chrdev( MINOR_DEV_MAJOR, MINOR_DEV_NAME );
gpioLedSet(0);
gpioLedFree();
gpioKeyFree();
}
module_init(minor_init);
module_exit(minor_exit);
MODULE_AUTHOR("KCCI-AIOT KSH");
MODULE_DESCRIPTION("led key test module");
MODULE_LICENSE("Dual BSD/GPL");
결국 핵심은 주번호 file_operations 구조체로 open()을 타고 와서, 주번호에 해당하는 open() 구현 함수 안에서 부번호를 조회해서 부번호를 기준으로 다시 새로운 file_operation 구조체로 바꿔치기 해주는 것이다.
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <fcntl.h>
#include <unistd.h>
#define WRITE_DEVICE_FILENAME "/dev/minor_write" //major:230, minor:0
#define READ_DEVICE_FILENAME "/dev/minor_read" //major:230, minor:1
int main()
{
int read_dev;
int write_dev;
char buff[128];
int loop;
// 주번호로 오픈
read_dev = open( READ_DEVICE_FILENAME, O_RDWR|O_NDELAY );
if( read_dev < 0 )
{
perror("open()");
printf( READ_DEVICE_FILENAME " open error\n" );
exit(1);
}
write_dev = open( WRITE_DEVICE_FILENAME, O_RDWR|O_NDELAY );
if( write_dev < 0 )
{
perror("open()");
printf( WRITE_DEVICE_FILENAME " open error\n" );
close( read_dev );
exit(1);
}
printf( "wait... input\n" );
while(1)
{
if( read(read_dev,buff,1 ) == 1 && buff[0] != 0)
{
printf( "read data [%02X]\n", buff[0] & 0xFF );
// if( !(buff[0] & 0x10) ) break;
if( buff[0] & 0x80) break;
}
}
printf( "input ok...\n");
printf( "led flashing...\n");
for( loop=0; loop<5; loop++ )
{
buff[0] = 0xFF;
write(write_dev,buff,1 );
sleep(1);
buff[0] = 0x00;
write(write_dev,buff,1 );
sleep(1);
}
close(read_dev);
close(write_dev);
return 0;
}