programing

간단한 Linux 디바이스 드라이버 작성 방법

randomtip 2022. 8. 31. 21:54
반응형

간단한 Linux 디바이스 드라이버 작성 방법

omap4용 SPI Linux 문자 디바이스 드라이버를 처음부터 작성해야 합니다.디바이스 드라이버의 작성에 관한 기본적인 것은 알고 있습니다.단, 플랫폼 고유의 디바이스 드라이버를 어떻게 처음부터 작성해야 할지 모르겠습니다.

기본적인 char 드라이버를 몇 개 써봤는데, SPI 디바이스 드라이버를 쓰는 것도 비슷할 것 같아서요.Char 드라이버는 구조를 가지고 있습니다.file_operations드라이버에 구현된 기능이 포함되어 있습니다.

struct file_operations Fops = {
    .read = device_read,
    .write = device_write,
    .ioctl = device_ioctl,
    .open = device_open,
    .release = device_release,  /* a.k.a. close */
};

이제 SPI 드라이버 개발을 처음부터 시작하기 위한 아이디어를 얻기 위해 spi-omap2-mcspi.c 코드를 참조합니다.

하지만 열기, 읽기, 쓰기 등의 기능은 보이지 않습니다.프로그램이 어디서부터 시작되는지 모르겠어요.

먼저 범용 커널 모듈을 작성합니다.정보를 찾을 수 있는 장소가 여러 군데 있지만, 링크가 매우 유용하다는 것을 알게 되었습니다.여기에 기재되어 있는 예를 모두 검토한 후, 독자적인 Linux 드라이버 모듈 작성을 개시할 수 있습니다.

샘플 코드를 복사 붙여넣기만 하면 되는 것은 아닙니다.커널 API가 변경될 수 있으며 예가 작동하지 않을 수 있습니다.여기에 제시된 예시는 어떻게 해야 하는지에 대한 지침으로 참조해야 합니다.사용 중인 커널 버전에 따라 작동하려면 예를 수정해야 합니다.

필요한 클럭, 버스 및 전원 장치를 요청하고 활성화하는 등 많은 작업을 수행할 수 있으므로 가능한 한 TI 플랫폼에서 제공하는 기능을 사용하는 것을 고려해 보십시오.내 기억이 맞다면 레지스터에 직접 액세스하기 위해 메모리 매핑 주소 범위를 획득하는 기능을 사용할 수 있습니다.취득한 모든 자원을 적절히 해방/정리하지 않기 때문에 TI가 제공하는 함수는 좋지 않다는 것을 언급해야 합니다.따라서 일부 자원의 경우 모듈 언로드 시 다른 커널 서비스를 호출하여 해제해야 했습니다.

편집 1:

Linux SPI 구현에 대해서는 잘 모르지만 먼저 drivers/spi/omap2-mcspi.c 파일의 omap2_mcspi_probe() 함수를 살펴보겠습니다.보시다시피 Linux/include/linux/spi/spi.h API를 사용하여 Linux 마스터 SPI 드라이버에 메서드를 등록합니다.char 드라이버와 달리 여기서의 주요 함수는 *_transfer() 함수입니다.자세한 내용은 spi.h 파일에서 구조 설명을 참조하십시오.또, 이 대체 디바이스 드라이버 API도 봐 주세요.

는 OMAP4 Linux 중 하나를 입니다.arch/arm/boot/dts/{omap4.dtsi,am33xx.dtsi} 됩니다.drivers/spi/spi-omap2-mcspi.c(디바이스 트리에 대해 모르는 경우는, 이것을 참조해 주세요).그 후, 다음과 같이 입력합니다.

  • SPI 마스터 드라이버가 완료되었습니다.
  • 프레임워크 Linux SPI에 drivers/spi/spi.c ,
  • (아마도) OMAP4에서는 정상적으로 동작합니다.

실제로 슬레이브 디바이스 드라이버를 쓰기 위해 마스터 드라이버에 신경 쓸 필요는 없습니다.내가 어떻게 알아?spi-omap2-mcspi.c스터터? 츠요시출출 it it it라고 .spi_register_master().

SPI 마스터, SPI 슬레이브?

해 주세요.Documentation/spi/spi_summary이 문서는 컨트롤러 드라이버(마스터)와 프로토콜 드라이버(슬레이브)를 가리킵니다.당신의 설명으로 프로토콜/디바이스 드라이버를 작성하기를 원하는 것으로 알고 있습니다.

SPI 프로토콜?

이를 이해하려면 슬레이브 디바이스 데이터시트가 필요합니다.이 데이터시트는 다음과 같은 정보를 제공합니다.

  • 디바이스에서 인식되는 SPI 모드,
  • 버스에서 기대하는 프로토콜입니다.

i2c와 달리 SPI는 프로토콜이나 핸드쉐이크를 정의하지 않으며 SPI 칩 제조업체는 자체 정의해야 합니다.데이터시트를 확인해주세요.

SPI 모드

부터부터include/linux/spi/spi.h::

* @mode:spi 모드는 데이터가 어떻게 클럭아웃되고 들어오는지를 정의합니다.
* 이것은 디바이스의 드라이버에 의해 변경될 수 있습니다.
* 칩셀렉트 모드의 "액티브 로우" 디폴트는 덮어쓸 수 있습니다.* (SPI_CS_HIGH를 지정함으로써) 디폴트 "MSB first"는 다음과 같습니다.* 전송 내의 각 단어(SPI_LSB_FIRST 지정).

다시 SPI 디바이스 데이터시트를 확인합니다.

SPI 디바이스 드라이버의 예?

예를 들어 SPI 디바이스의 종류를 알아야 합니다.SPI 플래시 디바이스 드라이버는 SPI FPGA 디바이스 드라이버와 다르다는 것을 이해할 수 있습니다.유감스럽게도 SPI 디바이스 드라이버는 그다지 많지 않습니다.검색 방법:

$ cd linux 
$ git grep "spi_new_device\|spi_add_device"

제가 당신의 질문을 제대로 이해했는지 모르겠어요.m-ric이 지적했듯이 마스터 드라이버와 슬레이브 드라이버가 있습니다.

일반적으로 마스터 드라이버는 하드웨어에 더 많이 바인딩됩니다. 즉, 대개 IO 레지스터를 조작하거나 메모리 매핑 IO를 수행합니다.

Linux 커널에서 이미 지원되는 아키텍처(omap3 및 omap4)의 경우 마스터 드라이버가 이미 구현되어 있습니다(McSPI).

따라서 omap4의 SPI 기능을 사용하여 슬레이브 디바이스 드라이버(SPI를 통해 외부 디바이스와 통신하는 프로토콜)를 구현하고자 합니다.

BeagleBoard-xM(omap3)의 예를 다음과 같이 기재했습니다.전체 코드는 https://github.com/rslemos/itrigue/blob/master/alsadriver/itrigue.c에 있습니다(ALSA, GPIO, 모듈 파라미터의 경우 뷰의 가치가 있지만 초기화 코드가 더 많습니다).SPI를 다루는 코드를 구분하려고 했습니다(뭔가 잊어버린 것 같습니다만, 어쨌든 이해하셔야 합니다).

#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/spi/spi.h>

/* MODULE PARAMETERS */
static uint spi_bus = 4;
static uint spi_cs = 0;
static uint spi_speed_hz = 1500000;
static uint spi_bits_per_word = 16;

/* THIS IS WHERE YOUR DEVICE IS CREATED; THROUGH THIS YOU INTERACT WITH YOUR EXTERNAL DEVICE */
static struct spi_device *spi_device;


/* SETUP SPI */

static inline __init int spi_init(void) {
    struct spi_board_info spi_device_info = {
        .modalias = "module name",
        .max_speed_hz = spi_speed_hz,
        .bus_num = spi_bus,
        .chip_select = spi_cs,
        .mode = 0,
    };

    struct spi_master *master;

    int ret;

    // get the master device, given SPI the bus number
    master = spi_busnum_to_master( spi_device_info.bus_num );
    if( !master )
        return -ENODEV;

    // create a new slave device, given the master and device info
    spi_device = spi_new_device( master, &spi_device_info );
    if( !spi_device )
        return -ENODEV;

    spi_device->bits_per_word = spi_bits_per_word;

    ret = spi_setup( spi_device );
    if( ret )
        spi_unregister_device( spi_device );

    return ret;
}

static inline void spi_exit(void) {
    spi_unregister_device( spi_device );
}

장치에 데이터를 쓰려면:

spi_write( spi_device, &write_data, sizeof write_data );

위의 코드 구현, 그렇McSPI를 사용할 수 있었고 독립적이다, 반드시 GPIO또는 SPI주인이 다른 장치를 구현 bit-banged.위의코드는 구현과는 독립적입니다.즉, McSPI,교환 GPIO또는기타 SPI마스터 디바이스 구현을 사용할 수 있습니다 비트.이 인터페이스가 인터페이스에 대해서는,을 참조해 주세요에 설명되어 있다.linux/spi/spi.h

BeagleBoard-XM에서 동작시키기 위해 커널 명령줄에 다음 항목을 추가해야 했습니다.

omap_mux=mcbsp1_clkr.mcspi4_clk=0x0000,mcbsp1_dx.mcspi4_simo=0x0000,mcbsp1_dr.mcspi4_somi=0x0118,mcbsp1_fsx.mcspi4_cs0=0x0000

따라서 omap3 McSPI4 하드웨어 설비용으로 McSPI 마스터 디바이스가 생성됩니다.

도움이 됐으면 좋겠다.

file_operations최소 사냥하기 적합한 example최소 실행 가능 예시

이 예제는 어떠한 하드웨어와 함께, 그러나 그것은 더 단순한 이예에서는하드웨어와상호 작용하지 않습니다만,보다 심플한편,을 상호 작용하지 않는다.file_operations커널 APIdebugfs.커널 API와debugfs를 사용합니다.

커널 모듈 fops.c:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/debugfs.h>
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h> /* file_operations */
#include <linux/kernel.h> /* min */
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include <uapi/linux/stat.h> /* S_IRUSR */

static struct dentry *debugfs_file;
static char data[] = {'a', 'b', 'c', 'd'};

static int open(struct inode *inode, struct file *filp)
{
    pr_info("open\n");
    return 0;
}

/* @param[in,out] off: gives the initial position into the buffer.
 *      We must increment this by the ammount of bytes read.
 *      Then when userland reads the same file descriptor again,
 *      we start from that point instead.
 * */
static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;

    pr_info("read\n");
    pr_info("len = %zu\n", len);
    pr_info("off = %lld\n", (long long)*off);
    if (sizeof(data) <= *off) {
        ret = 0;
    } else {
        ret = min(len, sizeof(data) - (size_t)*off);
        if (copy_to_user(buf, data + *off, ret)) {
            ret = -EFAULT;
        } else {
            *off += ret;
        }
    }
    pr_info("buf = %.*s\n", (int)len, buf);
    pr_info("ret = %lld\n", (long long)ret);
    return ret;
}

/* Similar to read, but with one notable difference:
 * we must return ENOSPC if the user tries to write more
 * than the size of our buffer. Otherwise, Bash > just
 * keeps trying to write to it infinitely. */
static ssize_t write(struct file *filp, const char __user *buf, size_t len, loff_t *off)
{
    ssize_t ret;

    pr_info("write\n");
    pr_info("len = %zu\n", len);
    pr_info("off = %lld\n", (long long)*off);
    if (sizeof(data) <= *off) {
        ret = 0;
    } else {
        if (sizeof(data) - (size_t)*off < len) {
            ret = -ENOSPC;
        } else {
            if (copy_from_user(data + *off, buf, len)) {
                ret = -EFAULT;
            } else {
                ret = len;
                pr_info("buf = %.*s\n", (int)len, data + *off);
                *off += ret;
            }
        }
    }
    pr_info("ret = %lld\n", (long long)ret);
    return ret;
}

/*
Called on the last close:
http://stackoverflow.com/questions/11393674/why-is-the-close-function-is-called-release-in-struct-file-operations-in-the-l
*/
static int release(struct inode *inode, struct file *filp)
{
    pr_info("release\n");
    return 0;
}

static loff_t llseek(struct file *filp, loff_t off, int whence)
{
    loff_t newpos;

    pr_info("llseek\n");
    pr_info("off = %lld\n", (long long)off);
    pr_info("whence = %lld\n", (long long)whence);
    switch(whence) {
        case SEEK_SET:
            newpos = off;
            break;
        case SEEK_CUR:
            newpos = filp->f_pos + off;
            break;
        case SEEK_END:
            newpos = sizeof(data) + off;
            break;
        default:
            return -EINVAL;
    }
    if (newpos < 0) return -EINVAL;
    filp->f_pos = newpos;
    pr_info("newpos = %lld\n", (long long)newpos);
    return newpos;
}

static const struct file_operations fops = {
    /* Prevents rmmod while fops are running.
     * Try removing this for poll, which waits a lot. */
    .owner = THIS_MODULE,
    .llseek = llseek,
    .open = open,
    .read = read,
    .release = release,
    .write = write,
};

static int myinit(void)
{
    debugfs_file = debugfs_create_file("lkmc_fops", S_IRUSR | S_IWUSR, NULL, NULL, &fops);
    return 0;
}

static void myexit(void)
{
    debugfs_remove_recursive(debugfs_file);
}

module_init(myinit)
module_exit(myexit)
MODULE_LICENSE("GPL");

사용자 랜드테스트 프로그램:

#!/bin/sh

mount -t debugfs none /sys/kernel/debug

insmod /fops.ko
cd /sys/kernel/debug/lkmc_fops

## Basic read.
cat f
# => abcd
# dmesg => open
# dmesg => read
# dmesg => len = [0-9]+
# dmesg => close

## Basic write

printf '01' >f
# dmesg => open
# dmesg => write
# dmesg => len = 1
# dmesg => buf = a
# dmesg => close

cat f
# => 01cd
# dmesg => open
# dmesg => read
# dmesg => len = [0-9]+
# dmesg => close

## ENOSPC
printf '1234' >f
printf '12345' >f
echo "$?"
# => 8
cat f
# => 1234

## seek
printf '1234' >f
printf 'z' | dd bs=1 of=f seek=2
cat f
# => 12z4

또한 만약 당신에게 어떤 체계 전화에는 이러한 각 명령에 대한 확인되지 않고 있는 시험을 실시하고 있는 C프로그램을 써야 합니다.각 명령어에 대해 호출되는 시스템콜이 명확하지 않은 경우 이러한 테스트를 실행하는 C.(거나(사용할 수 있작성해야 합니다 프로그램도 또는.strace:-))을 찾습니다.:-)를 확인합니다.

다른 다른.file_operations너는 좀 더 연루된 여기 추가적인 예를 들어 있다.조금 더 관여하고 있는 예를 다음에 나타냅니다.

우선 에뮬레이터의 심플한 하드웨어 소프트웨어 모델부터

실제 디바이스 하드웨어 개발은 다음과 같은 이유로 "어려운" 상태입니다.

  • 특정 하드웨어를 쉽게 구할 수 있는 것은 아닙니다.
  • 하드웨어 API는 복잡할 수 있습니다.
  • 하드웨어 내부 상태를 파악하기 어렵다

QEMU와 같은 에뮬레이터를 사용하면 소프트웨어에서 심플화된 하드웨어 시뮬레이션을 시뮬레이션하여 이러한 모든 어려움을 극복할 수 있습니다.

를 들어에는 "QEMU"라는가 내장되어 있습니다.edu자세한 것은, 「QEMU 소스 코드에 새로운 디바이스를 추가하는 방법」을 참조해 주세요.디바이스 드라이버를 사용하기 위한 좋은 방법입니다.간단한 드라이버는 이쪽에서 이용할 수중에 있습니다.

그런 다음 다른 프로그램과 마찬가지로 QEMU에 printf를 넣거나 GDB를 사용하여 정확히 무슨 일이 일어나고 있는지 확인할 수 있습니다.

OPM SPI 모델도 있습니다.https://github.com/qemu/qemu/blob/v2.7.0/hw/ssi/omap_spi.c

언급URL : https://stackoverflow.com/questions/22632713/how-to-write-a-simple-linux-device-driver

반응형