Console & TTY Driver

 

TTY 드라이버

TTY 드라이버 타입은 리눅스에서 6가지로 정의되어있다.

  • console
  • serial (서브 타입: normal)
  • pty (서브 타입: master, slave)
  • system (서브 타입: tty, console, syscons, sysptmx)
  • scc (not used)
  • syscons (not used)

 

 

TTY(Teletypewriter)

다음 그림과 같이 OSI 모델의 7 레이어와 TCP 모델과 비교하여 TTY에 해당하는 레이어를 확인해본다.

  • TTY의 Phsical Layer는 UART, Serial, Dial-up modem, ISDN modem 하드웨어 장치가 연결되고 그 장치를 제어하기 위한 Driver가 위치한다.
  • Data Link Layer에 해당하는 위치에는 위의 하드웨어 드라이버 일부와  common TTY 드라이버가 위치하며 Line discipline 드라이버 또한 포함한다.

 

TTY 드라이버

generic TTY 드라이버는 character device로 등록된다. 이 generic tty 드라이버는 하드웨어(또는 pseudo 터미널)와 연결된 tty 드라이버와 직접 연결되거나 중간에 특정 line discipline 드라이버와 연결될 수 있다. tty 드라이버의 구성은 매우 다양하다. 위에서 언급했었던 tty 드라이버 타입을 선택하여 만들어지는 드라이버들 중 시리얼 타입을 선택한 경우 아래와 같은구성으로 레이어를 설명할 수 있다.

 

아래 그림은 위의 시리얼 타입의 tty 뿐만 아니라 전체를 보여주기 위해 더 포괄적으로 표현하였다.

 

최근 커널에서 serial 타입을 지원하기 위한 tty 드라이버에 대해서 조금 더 알아본다. 아래 그림과 같이 시리얼 드라이버 유형을 case A) ~ D)까지 나누어 보았다.

  • 커널 2.6 이전에는 시리얼 타입의 tty 드라이버를 개발하기 위해 case A)만을 지원하였고, tty 드라이버 등록을 위해 tty_register_driver() 함수를 사용한다.
  • 커널 2.6부터 serial core(generic)가 추가되었고, 시리얼용 tty 드라이버를 더 간단히 개발하기 위해 case B) 형태도 지원하였다. serial 드라이버를 등록하기 위해 uart_register_driver() 함수를 사용한다.
  • 또한 8250/16c550용 시리얼 드라이버를 위해서는 더 하단 레이어에 serial 8250 core(generic)이 추가되었다. 8250(16c550)을 사용하는 시리얼 드라이버를 개발하기 위해 case C) 형태도 지원하였다. 8250용 시리얼 드라이버를 등록하기 위해 serial8250_register_8250_port() 함수를 사용한다.
  • case D)와 같이 범용 용도의 Device Tree를 위한 of-serial 드라이버를 이용하여 사용할 수도 있다.

 

다음은 시리얼 입출력이 발생할 때 실제 함수들이 호출되는 과정을 보여준다. (rpi2 기준)

 

Pseudo 터미널의 구동되는 방법을 자세히 설명해 놓은 그림도 살펴본다.

 

위의 구성을 세션과 프로세스 그룹 관점으로 바꾼 그림이다.

 

기타 장치 구성도 살펴본다.

 

TTY Core (Generic)

TTY 드라이버 등록

tty_register_driver()

drivers/tty/tty_io.c

/*
 * Called by a tty driver to register itself.
 */
int tty_register_driver(struct tty_driver *driver)
{
        int error;
        int i;
        dev_t dev;
        struct device *d;

        if (!driver->major) {
                error = alloc_chrdev_region(&dev, driver->minor_start,
                                                driver->num, driver->name);
                if (!error) {
                        driver->major = MAJOR(dev);
                        driver->minor_start = MINOR(dev);
                }
        } else {
                dev = MKDEV(driver->major, driver->minor_start);
                error = register_chrdev_region(dev, driver->num, driver->name);
        }
        if (error < 0)
                goto err;

        if (driver->flags & TTY_DRIVER_DYNAMIC_ALLOC) {
                error = tty_cdev_add(driver, dev, 0, driver->num);
                if (error)
                        goto err_unreg_char;
        }

        mutex_lock(&tty_mutex);
        list_add(&driver->tty_drivers, &tty_drivers);
        mutex_unlock(&tty_mutex);

        if (!(driver->flags & TTY_DRIVER_DYNAMIC_DEV)) {
                for (i = 0; i < driver->num; i++) {
                        d = tty_register_device(driver, i, NULL);
                        if (IS_ERR(d)) {
                                error = PTR_ERR(d);
                                goto err_unreg_devs;
                        }
                }
        }
        proc_tty_register_driver(driver);
        driver->flags |= TTY_DRIVER_INSTALLED;
        return 0;

인수로 받은 tty 드라이버를 등록한다. 정상 등록하여 성공하면 0을 반환하고, 에러가 발생하면 에러 값을 반환한다.

  • 코드 라인 11~23에서 tty 드라이버에 major 번호가 등록되어 있지 않은 경우 character 디바이스의 major 번호 254번부터 0번까지 중 사용하지 않는 번호를 찾아 그 번호로 character 디바이스를 등록한다.
    • character 디바이스 번호는 32비트로 다음과 같이 나뉘는데, MKDEV(major, minor) 매크로 상수를 사용하여 32비트 값을 만들 수 있다.
      • major: 상위 12비트로, MAJOR() 매크로 상수를 사용하여 알아올 수 있다.
      • minor: 하위 20비트로, MINOR() 매크로 상수를 사용하여 알아올 수 있다.
  • 코드 라인 25~29에서 tty 드라이버에 TTY_DRIVER_DYNAMIC_ALLOC 플래그를 사용한 경우 이 드라이버의 cdevs[]를 시스템의 character 디바이스로 등록한다. (두 번 등록되지 않도록 주의해야 한다)
  • 코드 라인 31~33에서 전역 tty_drivers 리스트에 요청한 tty 드라이버를 추가한다.
  • 코드 라인 35~43에서  tty 드라이버에 TTY_DRIVER_DYNAMIC_DEV 플래그를 사용한 경우 driver->num 수 만큼 tty 디바이스를 등록하게 한다.
  • 코드 라인 44에서 proc 인터페이스에 요청한 tty 드라이버를 추가한다.
  • 코드 라인 45에서 요청한 tty 드라이버가 설치가 완료된 것으로 인식하기 위해 TTY_DRIVER_INSTALLED 플래그를 추가한다.

 

err_unreg_devs:
        for (i--; i >= 0; i--)
                tty_unregister_device(driver, i);

        mutex_lock(&tty_mutex);
        list_del(&driver->tty_drivers);
        mutex_unlock(&tty_mutex);

err_unreg_char:
        unregister_chrdev_region(dev, driver->num);
err:
        return error;
}
EXPORT_SYMBOL(tty_register_driver);

에러가 발생하는 경우 등록한 디바이스 수 만큼 해지하고, 전역 tty_drivers 리스트에서도 제거한다. 그리고 마지막으로 시스템의 character 디바이스에서 제거한다.

 

 

TTY 초기화

아래 함수는 메모리를 읽고 쓸 수 있는 “mem” 캐릭터 디바이스가 초기화시키는 chr_dev_init() 함수의 마지막에서 호출되어 tty를 초기화한다.

  • fs_initcall(chr_dev_init) -> tty_init()

tty_init()

drivers/tty/tty_io.c

/*
 * Ok, now we can initialize the rest of the tty devices and can count
 * on memory allocations, interrupts etc..
 */
int __init tty_init(void)
{
        cdev_init(&tty_cdev, &tty_fops);
        if (cdev_add(&tty_cdev, MKDEV(TTYAUX_MAJOR, 0), 1) ||
            register_chrdev_region(MKDEV(TTYAUX_MAJOR, 0), 1, "/dev/tty") < 0)
                panic("Couldn't register /dev/tty driver\n");
        device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 0), NULL, "tty");

        cdev_init(&console_cdev, &console_fops);
        if (cdev_add(&console_cdev, MKDEV(TTYAUX_MAJOR, 1), 1) ||
            register_chrdev_region(MKDEV(TTYAUX_MAJOR, 1), 1, "/dev/console") < 0)
                panic("Couldn't register /dev/console driver\n");
        consdev = device_create(tty_class, NULL, MKDEV(TTYAUX_MAJOR, 1), NULL,
                              "console");
        if (IS_ERR(consdev))
                consdev = NULL;
        else
                WARN_ON(device_create_file(consdev, &dev_attr_active) < 0);

#ifdef CONFIG_VT
        vty_init(&console_fops);
#endif
        return 0;
}

시스템에 기본 tty character 디바이스(/dev/tty)와 기본 console character 디바이스(/dev/console )를 등록한다. 커널이 Virtual Terminal을 지원하는 경우 기본 console용 ops를 사용하여 virtual terminal(/dev/tty 사용)을 초기화한다.

  • 코드 라인 7~11에서 기본 tty character 디바이스(/dev/tty)를 등록한다. (major=5, minor=0)
  • 코드 라인 13~18에서 기본 console character 디바이스(/dev/console )를 등록한다. (major=5, minor=1)
  • 코드 라인 19~22에서 콘솔이 성공적으로 등록된 경우 모든 사용자 및 그룹의 read 액세스 권한으로 active 파일을 생성한다.
    • /sys/devices/virtual/tty/console 디렉토리 이후에 active 파일을 생성한다.
    • 위의 디렉토리는 /sys/dev/char/<major>:<minor> 디렉토리명으로 링크되어 있다.
  • 코드 라인 24~26에서 커널이 Virtual Terminal을 지원하는 경우 초기화한다.
    • 기본 console용 ops를 사용하여 4:0 번 /dev/tty0 디바이스를 생성한다.
    • 7:0번 /dev/vcs 파일 및 7:128번 /dev/vcsa 파일을 생성한다.
    • 7:1번 /dev/vcs1 파일 및 7:129번 /dev/vcsa1 파일을 생성한다.
    • 최대 63개 포트로 콘솔 드라이버를 할당하고  콘솔 타입의 tty 드라이버를 등록한다.

 

아래는 콘솔에 대한 active 파일이 read only 의 속성으로 만들어졌고, 해당 파일을 읽어 현재 console이 어떤 tty 드라이버로 구성되어 있는지 알아낼 수 있다.

# ls -la /sys/dev/char/5:1/active
-r--r--r-- 1 root root 4096 12월 21 23:13 /sys/dev/char/5:1/active
# cat /sys/dev/char/5:1/active
tty0

 

tty_driver 구조체

include/linux/tty_driver.h

struct tty_driver {
        int     magic;          /* magic number for this structure */
        struct kref kref;       /* Reference management */
        struct cdev *cdevs;
        struct module   *owner;
        const char      *driver_name;
        const char      *name;
        int     name_base;      /* offset of printed name */
        int     major;          /* major device number */ 
        int     minor_start;    /* start of minor device number */
        unsigned int    num;    /* number of devices allocated */
        short   type;           /* type of tty driver */
        short   subtype;        /* subtype of tty driver */
        struct ktermios init_termios; /* Initial termios */
        unsigned long   flags;          /* tty driver flags */
        struct proc_dir_entry *proc_entry; /* /proc fs entry */
        struct tty_driver *other; /* only used for the PTY driver */

        /*
         * Pointer to the tty data structures
         */
        struct tty_struct **ttys;
        struct tty_port **ports;
        struct ktermios **termios;
        void *driver_state;

        /*
         * Driver methods
         */

        const struct tty_operations *ops;
        struct list_head tty_drivers;
};

tty 드라이버는 1개 이상의 tty_struct, tty_port, ktermios 설정 및 상태등을 가진다.

  • magic
    • tty 드라이버를 의미하는 magic 번호
    • TTY_DRIVER_MAGIC(0x5402)
  • kref
    • 참조 관리
  • *cdevs
    • 캐릭터 디바이스 포인터
  • *owner
    • 모듈을 가리킨다.
  • *driver_name
    • 드라이버 명
  • *name
    • 디바이스 명
  • name_base
    • 출력할 이름의 시작 번호
  • major
    • major 번호
  • minor_start
    • minor 시작 번호
  • num
    • tty 장치 수
  • type
    • tty 드라이버 타입(6가지)
      • TTY_DRIVER_TYPE_SYSTEM(1)
      • TTY_DRIVER_TYPE_CONSOLE(2)
      • TTY_DRIVER_TYPE_SERIAL(3)
      • TTY_DRIVER_TYPE_PTY(4)
      • TTY_DRIVER_TYPE_SCC(5)
      • TTY_DRIVER_TYPE_SYSCONS(6)
  • subtype
    • tty 드라이버 서브 타입
      • system 서브 타입
        • SYSTEM_TYPE_TTY(1)
        • SYSTEM_TYPE_CONSOLE(2)
        • SYSTEM_TYPE_SYSCONS(3)
        • SYSTEM_TYPE_SYSPTMX(4)
      • pty 서브 타입
        • PTY_TYPE_MASTER(1)
        • PTY_TYPE_SLAVE(2)
      • serial 서브 타입
        • SERIAL_TYPE_NORMAL(1)
  • *init_termios
    • 터미널 초기화 후크 함수
  • flags
    • 드라이버 플래그
      • TTY_DRIVER_INSTALLED(0x0001)
      • TTY_DRIVER_RESET_TERMIOS(0x0002)
      • TTY_DRIVER_REAL_RAW(0x0004)
      • TTY_DRIVER_DYNAMIC_DEV(0x0008)
      • TTY_DRIVER_DEVPTS_MEM(0x0010)
      • TTY_DRIVER_HARDWARE_BREAK(0x0020)
      • TTY_DRIVER_DYNAMIC_ALLOC(0x0040)
      • TTY_DRIVER_UNNUMBERED_NODE(0x0080)
  • *proc_entry
    • proc 인터페이스 시작 디렉토리 엔트리
  • *other
    • pty 타입의 tty 드라이버에서만 사용
  • **ttys
    • tty_struct 배열을 가리킨다.
  • **ports
    • tty_port 배열을 가리킨다. (tty 포트 수 만큼)
  • **termios
    • 터미널 제어를 위해 ktermios 배열을 가리킨다.
  • *driver_state
    • 드라이버 상태
  • *ops
    • tty 드라이버의 operations
  • tty_drivers
    • 전역 tty_drivers 리스트에 추가될 때 사용하는 노드 링크

 

tty_struct & tty_port 구조체

tty_struct는 tty 장치에 대한 line discipline을 지정하고 OSI L2 레이어에 해당하는 data link 역할을 수행하기 위해 flow control 등을 수행하도록 관리한다. tty 드라이버는 1 개 이상의 tty_port를 가질 수 있으므로 이에 대한 내용을 가진 tty_port 구조체가 있다.

  • 구조체 코드는 생략

 

TTY Core operations

다음은 tty core가 사용하는 tty 및 console에 대한 ops이다.

drivers/tty/tty_io.c

static const struct file_operations tty_fops = {
        .llseek         = no_llseek,
        .read           = tty_read,
        .write          = tty_write,
        .poll           = tty_poll,
        .unlocked_ioctl = tty_ioctl,
        .compat_ioctl   = tty_compat_ioctl,
        .open           = tty_open,
        .release        = tty_release,
        .fasync         = tty_fasync,
};

static const struct file_operations console_fops = {
        .llseek         = no_llseek,
        .read           = tty_read,
        .write          = redirected_tty_write,
        .poll           = tty_poll,
        .unlocked_ioctl = tty_ioctl,
        .compat_ioctl   = tty_compat_ioctl,
        .open           = tty_open,
        .release        = tty_release,
        .fasync         = tty_fasync,
};

 

터미널 제어 설정(TERMIOS)

tty 드라이버가 표준 터미널 설정을 사용하는 경우 아래 설정을 사용할 수 있다.

struct ktermios tty_std_termios = {     /* for the benefit of tty drivers  */
        .c_iflag = ICRNL | IXON,
        .c_oflag = OPOST | ONLCR,
        .c_cflag = B38400 | CS8 | CREAD | HUPCL,
        .c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK |
                   ECHOCTL | ECHOKE | IEXTEN,
        .c_cc = INIT_C_CC,
        .c_ispeed = 38400,
        .c_ospeed = 38400
};

EXPORT_SYMBOL(tty_std_termios);

 

다음은 stty 명령을 통해 tty 디바이스 장치에 설정된 터미널 설정 값을 보여준다.

$ sudo stty -F /dev/tty0 -a
speed 38400 baud; rows 25; columns 80; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>;
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd -cmspar cs8 -hupcl -cstopb cread -clocal -crtscts
ignbrk -brkint ignpar -parmrk -inpck -istrip -inlcr -igncr -icrnl -ixon -ixoff -iuclc -ixany -imaxbel -iutf8
-opost -olcuc -ocrnl -onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
-isig -icanon -iexten -echo -echoe -echok -echonl -noflsh -xcase -tostop -echoprt -echoctl -echoke

 

include/uapi/asm-generic/termbits.h

터미널 설정은 아래와 같이 4개의 mode 플래그 집합, line discipline, 19개의 제어 문자 및 I/O 스피드 설정으로 구성된다.

struct ktermios {
        tcflag_t c_iflag;               /* input mode flags */
        tcflag_t c_oflag;               /* output mode flags */
        tcflag_t c_cflag;               /* control mode flags */
        tcflag_t c_lflag;               /* local mode flags */
        cc_t c_line;                    /* line discipline */
        cc_t c_cc[NCCS];                /* control characters */
        speed_t c_ispeed;               /* input speed */
        speed_t c_ospeed;               /* output speed */
};

 

콘솔

시스템의 키보드와 스크린에 해당하는 드라이버를 콘솔이라고 한다. PC 에서는 콘솔의 입력과 출력으로 키보드 장치와 모니터를 통한 그래픽 출력장치를 사용한다.  참고로 임베디드 시스템에는 키보드와 모니터가 없으므로 이를 대신하기 위해 보통 UART(시리얼) 포트를 사용하여 입/출력을 하는 경우가 많다. 이러한 경우 UART(시리얼) 장치를 콘솔로 사용한다.

 

console_init()

drivers/tty/tty_io.c

/*
 * Initialize the console device. This is called *early*, so
 * we can't necessarily depend on lots of kernel help here.
 * Just do some early initializations, and do the complex setup
 * later.
 */             
void __init console_init(void)
{
        initcall_t *call;

        /* Setup the default TTY line discipline. */
        tty_ldisc_begin();

        /*
         * set up the console device so that later boot sequences can
         * inform about problems etc..
         */
        call = __con_initcall_start;
        while (call < __con_initcall_end) {
                (*call)();
                call++;
        }
}

디폴트 tty line discipline을 등록하고 커널에 설정 등록된 console 디바이스 드라이버의 셋업 함수들을 호출한다.

  • 시스템 마다 사용하는 콘솔 장치가 다르며 커널에는 수십 종류가 등록되어 있으며 그 중 임베디드 장치에서 사용하는 일부는 다음과 같다.
    • con_init() – CONFIG_VT_CONSOLE
    • serial8250_console_init() – CONFIG_SERIAL_8250_CONSOLE
    • bcm63xx_console_init() – CONFIG_SERIAL_BCM63XX_CONSOLE
    • s3c24xx_serial_console_init() – CONFIG_SERIAL_SAMSUNG_CONSOLE

 

콘솔 드라이버의 시작 함수 포인터를 다음 매크로 함수를 통해 “.con_initcall.init” 섹션에 위치하게 한다.

console_initcall()

include/linux/init.h

#define console_initcall(fn) \
        static initcall_t __initcall_##fn \
        __used __section(.con_initcall.init) = fn

 

Line Discipline

line discipline 드라이버는 가장 상위의 character device(generic tty driver)와 하드웨어 또는 pseudo 터미널을 담당하는 tty 디바이스 드라이버 사이에 위치한다. 기능으로는 이 들 사이에서 입출력 데이터의 흐름 제어나 특수 명령 및 데이터 변경이 적용되게 할 수 있다.  이러한 line discipline 드라이버들의 종류는 다양하다.

 

tty_ldisc_begin()

drivers/tty/tty_ldisc.c

void tty_ldisc_begin(void)
{
        /* Setup the default TTY line discipline. */
        (void) tty_register_ldisc(N_TTY, &tty_ldisc_N_TTY);
}

디폴트 tty line discipline 드라이버를 등록한다.

 

tty_register_ldisc()

drivers/tty/tty_ldisc.c

/**
 *      tty_register_ldisc      -       install a line discipline
 *      @disc: ldisc number
 *      @new_ldisc: pointer to the ldisc object
 *
 *      Installs a new line discipline into the kernel. The discipline
 *      is set up as unreferenced and then made available to the kernel
 *      from this point onwards.
 *
 *      Locking:
 *              takes tty_ldiscs_lock to guard against ldisc races
 */

int tty_register_ldisc(int disc, struct tty_ldisc_ops *new_ldisc)
{
        unsigned long flags;
        int ret = 0;

        if (disc < N_TTY || disc >= NR_LDISCS)
                return -EINVAL;

        raw_spin_lock_irqsave(&tty_ldiscs_lock, flags);
        tty_ldiscs[disc] = new_ldisc;
        new_ldisc->num = disc;
        new_ldisc->refcount = 0;
        raw_spin_unlock_irqrestore(&tty_ldiscs_lock, flags);

        return ret;
}
EXPORT_SYMBOL(tty_register_ldisc);

요청한 disc 장치에 해당하는 line discipline 드라이버를 등록한다.

 

TTY line discipline 테이블

 

아래와 같이 30개의 line discipline ops들을 등록할 수 있는 포인터 배열이 선언되어 있다.

drivers/tty/tty_ldisc.c

/* Line disc dispatch table */
static struct tty_ldisc_ops *tty_ldiscs[NR_LDISCS];

 

등록되는 장치의 line discipline들이다. 기본 tty 장치부터 시작되어 다양한 종류의 프로토콜들을 처리할 수 있다.

include/uapi/linux/tty.h

/*
 * 'tty.h' defines some structures used by tty_io.c and some defines.
 */

#define NR_LDISCS               30

/* line disciplines */
#define N_TTY           0
#define N_SLIP          1
#define N_MOUSE         2
#define N_PPP           3
#define N_STRIP         4
#define N_AX25          5
#define N_X25           6       /* X.25 async */
#define N_6PACK         7
#define N_MASC          8       /* Reserved for Mobitex module <kaz@cafe.net> */
#define N_R3964         9       /* Reserved for Simatic R3964 module */
#define N_PROFIBUS_FDL  10      /* Reserved for Profibus */
#define N_IRDA          11      /* Linux IrDa - http://irda.sourceforge.net/ */
#define N_SMSBLOCK      12      /* SMS block mode - for talking to GSM data */
                                /* cards about SMS messages */
#define N_HDLC          13      /* synchronous HDLC */
#define N_SYNC_PPP      14      /* synchronous PPP */
#define N_HCI           15      /* Bluetooth HCI UART */
#define N_GIGASET_M101  16      /* Siemens Gigaset M101 serial DECT adapter */
#define N_SLCAN         17      /* Serial / USB serial CAN Adaptors */
#define N_PPS           18      /* Pulse per Second */
#define N_V253          19      /* Codec control over voice modem */
#define N_CAIF          20      /* CAIF protocol for talking to modems */
#define N_GSM0710       21      /* GSM 0710 Mux */
#define N_TI_WL         22      /* for TI's WL BT, FM, GPS combo chips */
#define N_TRACESINK     23      /* Trace data routing for MIPI P1149.7 */
#define N_TRACEROUTER   24      /* Trace data routing for MIPI P1149.7 */

 

Line Discipline Operations

line discipline 장치에 대응하는 ops 구조체이다.

drivers/tty/n_tty.c

struct tty_ldisc_ops tty_ldisc_N_TTY = {
        .magic           = TTY_LDISC_MAGIC,
        .name            = "n_tty",
        .open            = n_tty_open,
        .close           = n_tty_close,
        .flush_buffer    = n_tty_flush_buffer,
        .chars_in_buffer = n_tty_chars_in_buffer,
        .read            = n_tty_read,
        .write           = n_tty_write,
        .ioctl           = n_tty_ioctl,
        .set_termios     = n_tty_set_termios,
        .poll            = n_tty_poll,
        .receive_buf     = n_tty_receive_buf,
        .write_wakeup    = n_tty_write_wakeup,
        .fasync          = n_tty_fasync,
        .receive_buf2    = n_tty_receive_buf2,
};

 

Serial Core (Generic)

uart_register_driver()

drivers/tty/serial/serial_core.c

/**
 *      uart_register_driver - register a driver with the uart core layer
 *      @drv: low level driver structure
 *
 *      Register a uart driver with the core driver.  We in turn register
 *      with the tty layer, and initialise the core driver per-port state.
 *
 *      We have a proc file in /proc/tty/driver which is named after the
 *      normal driver.
 *
 *      drv->port should be NULL, and the per-port structures should be
 *      registered using uart_add_one_port after this call has succeeded.
 */
int uart_register_driver(struct uart_driver *drv)
{
        struct tty_driver *normal;
        int i, retval;

        BUG_ON(drv->state);

        /*
         * Maybe we should be using a slab cache for this, especially if
         * we have a large number of ports to handle.
         */
        drv->state = kzalloc(sizeof(struct uart_state) * drv->nr, GFP_KERNEL);
        if (!drv->state)
                goto out;

        normal = alloc_tty_driver(drv->nr);
        if (!normal) 
                goto out_kfree;

        drv->tty_driver = normal;

        normal->driver_name     = drv->driver_name;
        normal->name            = drv->dev_name;
        normal->major           = drv->major;
        normal->minor_start     = drv->minor;
        normal->type            = TTY_DRIVER_TYPE_SERIAL;
        normal->subtype         = SERIAL_TYPE_NORMAL;
        normal->init_termios    = tty_std_termios; 
        normal->init_termios.c_cflag = B9600 | CS8 | CREAD | HUPCL | CLOCAL;
        normal->init_termios.c_ispeed = normal->init_termios.c_ospeed = 9600;
        normal->flags           = TTY_DRIVER_REAL_RAW | TTY_DRIVER_DYNAMIC_DEV;
        normal->driver_state    = drv;
        tty_set_operations(normal, &uart_ops);

 

 

        /*
         * Initialise the UART state(s).
         */
        for (i = 0; i < drv->nr; i++) {
                struct uart_state *state = drv->state + i;
                struct tty_port *port = &state->port; 

                tty_port_init(port);
                port->ops = &uart_port_ops;
        }

        retval = tty_register_driver(normal);
        if (retval >= 0)
                return retval;
                
        for (i = 0; i < drv->nr; i++)
                tty_port_destroy(&drv->state[i].port);
        put_tty_driver(normal);
out_kfree:
        kfree(drv->state);
out:    
        return -ENOMEM;
}

 

uart_driver 구조체

include/linux/serial_core.h

struct uart_driver {
        struct module           *owner;
        const char              *driver_name;
        const char              *dev_name;
        int                      major;
        int                      minor;
        int                      nr;
        struct console          *cons;

        /*
         * these are private; the low level driver should not
         * touch these; they should be initialised to NULL
         */
        struct uart_state       *state;
        struct tty_driver       *tty_driver;
};

 

uart_state & uart_port 구조체

uart 드라이버는 상태 및 1개 이상의 uart_port로 구성된다.

  • 코드는 생략

 

uart Operations

include/linux/serial_core.h

/*
 * This structure describes all the operations that can be done on the
 * physical hardware.  See Documentation/serial/driver for details.
 */
struct uart_ops {
        unsigned int    (*tx_empty)(struct uart_port *);
        void            (*set_mctrl)(struct uart_port *, unsigned int mctrl);
        unsigned int    (*get_mctrl)(struct uart_port *);
        void            (*stop_tx)(struct uart_port *);
        void            (*start_tx)(struct uart_port *);
        void            (*throttle)(struct uart_port *);
        void            (*unthrottle)(struct uart_port *);
        void            (*send_xchar)(struct uart_port *, char ch);
        void            (*stop_rx)(struct uart_port *);
        void            (*enable_ms)(struct uart_port *);
        void            (*break_ctl)(struct uart_port *, int ctl);
        int             (*startup)(struct uart_port *);
        void            (*shutdown)(struct uart_port *);
        void            (*flush_buffer)(struct uart_port *);
        void            (*set_termios)(struct uart_port *, struct ktermios *new,
                                       struct ktermios *old);
        void            (*set_ldisc)(struct uart_port *, struct ktermios *);
        void            (*pm)(struct uart_port *, unsigned int state,
                              unsigned int oldstate);

        /*
         * Return a string describing the type of the port
         */
        const char      *(*type)(struct uart_port *);

        /*
         * Release IO and memory resources used by the port.
         * This includes iounmap if necessary.
         */
        void            (*release_port)(struct uart_port *);

        /*
         * Request IO and memory resources used by the port.
         * This includes iomapping the port if necessary.
         */
        int             (*request_port)(struct uart_port *);
        void            (*config_port)(struct uart_port *, int);
        int             (*verify_port)(struct uart_port *, struct serial_struct *);
        int             (*ioctl)(struct uart_port *, unsigned int, unsigned long);
#ifdef CONFIG_CONSOLE_POLL
        int             (*poll_init)(struct uart_port *);
        void            (*poll_put_char)(struct uart_port *, unsigned char);
        int             (*poll_get_char)(struct uart_port *);
#endif
};

 

Serial 8250 Core (Generic)

다음 uart 칩을 사용한 드라이버를 빠르게 구성할 수 있도록 준비되어 있다.

  • 8250, 16450, 16550, 16550A, 16C950/954
  • Cirrus,
  • ST16650, ST16650V2, ST16654
  • TI16750
  • Startech
  • XR16850
  • RSA
  • NS16550A
  • XScale
  • OCTEON
  • AR7
  • U6_16550A
  • Tegra
  • XR17D15X, XR17V35X
  • LPC3220
  • TruManage
  • CIR port
  • Altera 16550 FIFO32, Altera 16550 FIFO64, Altera 16550 FIFO128
  • 16550A_FSL64

 

serial8250_register_8250_port()

drivers/tty/serial/8250/8250_core.c

/**
 *      serial8250_register_8250_port - register a serial port
 *      @up: serial port template
 *
 *      Configure the serial port specified by the request. If the
 *      port exists and is in use, it is hung up and unregistered
 *      first.
 *
 *      The port is then probed and if necessary the IRQ is autodetected
 *      If this fails an error is returned.
 *
 *      On success the port is ready to use and the line number is returned.
 */
int serial8250_register_8250_port(struct uart_8250_port *up)
{
        struct uart_8250_port *uart;
        int ret = -ENOSPC;

        if (up->port.uartclk == 0)
                return -EINVAL;

        mutex_lock(&serial_mutex);

        uart = serial8250_find_match_or_unused(&up->port);
        if (uart && uart->port.type != PORT_8250_CIR) {
                if (uart->port.dev)
                        uart_remove_one_port(&serial8250_reg, &uart->port);

                uart->port.iobase       = up->port.iobase;
                uart->port.membase      = up->port.membase;
                uart->port.irq          = up->port.irq;
                uart->port.irqflags     = up->port.irqflags;
                uart->port.uartclk      = up->port.uartclk;
                uart->port.fifosize     = up->port.fifosize;
                uart->port.regshift     = up->port.regshift;
                uart->port.iotype       = up->port.iotype;
                uart->port.flags        = up->port.flags | UPF_BOOT_AUTOCONF;
                uart->bugs              = up->bugs;
                uart->port.mapbase      = up->port.mapbase;
                uart->port.private_data = up->port.private_data;
                uart->port.fifosize     = up->port.fifosize;
                uart->tx_loadsz         = up->tx_loadsz;
                uart->capabilities      = up->capabilities;
                uart->port.throttle     = up->port.throttle;
                uart->port.unthrottle   = up->port.unthrottle;
                uart->port.rs485_config = up->port.rs485_config;
                uart->port.rs485        = up->port.rs485;

 

                /* Take tx_loadsz from fifosize if it wasn't set separately */
                if (uart->port.fifosize && !uart->tx_loadsz)
                        uart->tx_loadsz = uart->port.fifosize;

                if (up->port.dev)
                        uart->port.dev = up->port.dev;

                if (up->port.flags & UPF_FIXED_TYPE)
                        serial8250_init_fixed_type_port(uart, up->port.type);

                set_io_from_upio(&uart->port);
                /* Possibly override default I/O functions.  */
                if (up->port.serial_in)
                        uart->port.serial_in = up->port.serial_in;
                if (up->port.serial_out)
                        uart->port.serial_out = up->port.serial_out;
                if (up->port.handle_irq)
                        uart->port.handle_irq = up->port.handle_irq;
                /*  Possibly override set_termios call */
                if (up->port.set_termios)
                        uart->port.set_termios = up->port.set_termios;
                if (up->port.set_mctrl)
                        uart->port.set_mctrl = up->port.set_mctrl;
                if (up->port.startup)
                        uart->port.startup = up->port.startup;
                if (up->port.shutdown)
                        uart->port.shutdown = up->port.shutdown;
                if (up->port.pm)
                        uart->port.pm = up->port.pm;
                if (up->port.handle_break)
                        uart->port.handle_break = up->port.handle_break;
                if (up->dl_read)
                        uart->dl_read = up->dl_read;
                if (up->dl_write)
                        uart->dl_write = up->dl_write;
                if (up->dma) {
                        uart->dma = up->dma;
                        if (!uart->dma->tx_dma)
                                uart->dma->tx_dma = serial8250_tx_dma;
                        if (!uart->dma->rx_dma)
                                uart->dma->rx_dma = serial8250_rx_dma;
                }

                if (serial8250_isa_config != NULL)
                        serial8250_isa_config(0, &uart->port,
                                        &uart->capabilities);

                ret = uart_add_one_port(&serial8250_reg, &uart->port);
                if (ret == 0)
                        ret = uart->port.line;
        }
        mutex_unlock(&serial_mutex);

        return ret;
}

 

uart_8250_port 구조체

include/linux/serial_8250.h

struct uart_8250_port {
        struct uart_port        port;
        struct timer_list       timer;          /* "no irq" timer */
        struct list_head        list;           /* ports on this IRQ */
        unsigned short          capabilities;   /* port capabilities */
        unsigned short          bugs;           /* port bugs */
        bool                    fifo_bug;       /* min RX trigger if enabled */
        unsigned int            tx_loadsz;      /* transmit fifo load size */
        unsigned char           acr;
        unsigned char           fcr;
        unsigned char           ier;
        unsigned char           lcr;
        unsigned char           mcr;
        unsigned char           mcr_mask;       /* mask of user bits */
        unsigned char           mcr_force;      /* mask of forced bits */
        unsigned char           cur_iotype;     /* Running I/O type */
        unsigned int            rpm_tx_active;
        unsigned char           canary;         /* non-zero during system sleep
                                                 *   if no_console_suspend
                                                 */

        /*
         * Some bits in registers are cleared on a read, so they must
         * be saved whenever the register is read but the bits will not
         * be immediately processed.
         */
#define LSR_SAVE_FLAGS UART_LSR_BRK_ERROR_BITS
        unsigned char           lsr_saved_flags;
#define MSR_SAVE_FLAGS UART_MSR_ANY_DELTA
        unsigned char           msr_saved_flags;

        struct uart_8250_dma    *dma;

        /* 8250 specific callbacks */
        int                     (*dl_read)(struct uart_8250_port *);
        void                    (*dl_write)(struct uart_8250_port *, int);
};

 

 

참고

 

댓글 남기기