노무현 대통령 배너


2006. 3. 28. 11:00

[본문스크랩] Linux kernel 2.6.13 platform device driver 분석

Linux kernel 2.6.13 에서 적용해본 Platform Device Driver 입니다.

smc91x.c 부분을 보면 platform device로 작성이 되어있습니다.

이 부분에 관하여 간략하게 정리를 해보았습니다.

======================================================

kingseft 분석자료

제목 :SMSC LAN91C111/113 10/100 Ethernet Device Driver의 platform_device 분석
수정:06-01-17


=============================================================

1. 우선 mach-smdk2410.c 에서 다음과 같이 정의했다.

static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&smc91x_device,// kingseft<=== 이녀석이다.
&s3c_device_rtc,// kingseft
};


[인용]
리눅스 커널 2.6 에서는 KObject 라는 객체기반으로 디바이스 드라이버 및 드라이버의 계층적 구조를 가지고 있다.
예를들면 등록된 버스가 있고, 버스에 새로운 디바이스가 장착되면 버스 드라이버가 디바이스를 탐지하고 이에 맞는
드라이버를 찾아서 등록하게 된다.

따라서, 버스상의 디바이스 이름과 드라이버는 커널 내부에서 기본적으로 이름으로 찾게된다.
따라서 버스상에 등록된 디바이스 드라이버의 이름과 드라이버의 이름이 서로 다르면 정상적으로 드라이버가 동작하지 않을 수 있다.


smc91x.c 에서는 다음과 같은 정의가 있다.

#define CARDNAME "smc91x"
static struct device_driver smc_driver = {
.name= CARDNAME,
.bus= &platform_bus_type,
.probe= smc_drv_probe,
.remove= smc_drv_remove,
.suspend= smc_drv_suspend,
.resume= smc_drv_resume,
};


mach-smdk2410.c 에서는 다음과 같은 선언이 있다.

static struct platform_device smc91x_device = {
.name = "smc91x",
.id = 0,
.num_resources = ARRAY_SIZE(smc91x_resources),
.resource = smc91x_resources,
};


즉, 위에서의 언급과 같이
platform_device 에서 등록된 디바이스의 이름 ==> .name = "smc91x",
드라이버에서의 이름==> .name= CARDNAME,

이 둘이 서로 같아야 한다.


-------------------------------------------------------

// mach-smdk2410.c
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&smc91x_device,// kingseft<=== 이녀석이다.
&s3c_device_rtc,// kingseft
};


이 부분을 살펴보면, 여러 버스형태 중에서 하나인 platform_bus_type 이라는 가상의 플랫폼 버스상에 새로운 디바이스를 등록하는 과정이다.
즉, 플랫폼 버스상에 우리는 smc91x_device 라는 이름으로 새로운 플랫폼 디바이스를 등록한 것이다.


platform_bus_type 이라는 가상의 플랫폼 버스상의 디바이스를 정의하는 구조체는 에 정의되어 있다.
그 구조체는 platform_device 이다. 그렇다면 platform_device 의 구조체 원형에 관해서 알아보자.

struct platform_device {
char*name;
u32id;
struct device dev;
u32 num_resources;
struct resource *resources;
};

여기서 각각의 필드를 살펴보면...

name: 플랫폼 디바이스의 이름을 지정
id:여러개의 플랫폼 디바이스가 있을 경우 id를 통해서 구분을 한다.
dev
num_resource
resource

자. 그렇다면 실질적으로 platform_device 가 어떻게 이용되는지 살펴보자.


//mach-smdk2410.c 에서는 다음과 같은 선언이 있다.
static struct platform_device smc91x_device = {
.name = "smc91x",
.id = 0,
.num_resources = ARRAY_SIZE(smc91x_resources),
.resource = smc91x_resources,
};


플랫폼 디바이스의 등록과 해제는 다음의 함수를 이용한다.
platform_device_register
platform_device_unregister

실제로 platform_device_register의 함수는 arch/arm/mach-s3c2410/cpu.c 파일에서

s3c_arch_init() 함수내부에서 platform_device_register가 호출된다.


--------------------------------------------------------------------------

// mach-smdk2410.c 에서 살펴볼 부분

// 1. 우선 platform_device에 등록을 하고
static struct platform_device *smdk2410_devices[] __initdata = {
&s3c_device_usb,
&s3c_device_lcd,
&s3c_device_wdt,
&s3c_device_i2c,
&s3c_device_iis,
&smc91x_device,// kingseft<==== 1번. 우선 이녀석을 보자.
&s3c_device_rtc,
};


// 2. platform_device 구조체에 맞게 정의를 해준다.
static struct platform_device smc91x_device = {
.name = "smc91x",// 디바이스의 이름과 드라이버의 이름이 같아야 한다.
.id = 0,// id = 0
.num_resources = ARRAY_SIZE(smc91x_resources),// 사이즈를 지정
.resource = smc91x_resources,// resource 연결
};

// 3. 리소스를 정의해준다.
static struct resource smc91x_resources[] = {
[0] = {
.start = pSMC91X_BASE + 0x300,
.end = pSMC91X_BASE + 0xfffff,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = SMC_IRQ,
.end = SMC_IRQ,
.flags = IORESOURCE_IRQ,
},
};


struct resource의 각 세부적인 필드의 내용은 다음과 같다.

// include/linux/ioports.h
struct resource {
const char *name;
unsigned long start, end;
unsigned long flags;
struct resource *parent, *sibling, *child;
};

----------------------------------------------------------------

// 이제는 smc91x.c 를 보면서 device driver의 등록, 해제 부분을 살펴보자.

// smc91x.c 의 가장 마지막줄을 보면 ^^
module_init(smc_init);
module_exit(smc_cleanup);


static int __init smc_init(void)
{
// 불필요한 부분은 다 지우면 아래만 남는다.
// 아래 부분을 주의하자. 버스에 연결되는 드라이버를 등록할때에는 아래의 함수를 사용해야 한다.
return driver_register(&smc_driver);
}

위에서도 언급을 했지만, 우리는 플랫폼 디바이스에 드라이버를 붙여야 하는 것이므로
버스를 갖는 드라이버를 등록할때에는 driver_register 함수를 이용해야 한다.

// 함수의 원형은 다음과 같다.
intdriver_register(struct device_driver *drv);

// 다음으로 살펴볼 함수는 당근 드라이버의 해제이다.
static void __exit smc_cleanup(void)
{
driver_unregister(&smc_driver);
}

위에서 보는바와 같이 버스에 연결된 드라이버를 해제할때에는 driver_unregister를 이용한다.

// 이 함수의 원형은 아래와 같다.
voiddriver_unregister(struct device_driver *drv)

-----------------------------------------------------------------

// 이제는 위에서 살펴본 driver_register / driver_unregister 에서 사용하는 자료형에 대해서 살펴보자.
// 이 부분은 를 참조한다.


structdevice_driver {
constchar*name;// 필수
structbus_type *bus;// 필수

structcompletionunloaded;
structkobjectkobj;
structklistklist_devices;
structklist_nodeknode_bus;

structmodule *owner;

int(*probe) (struct device *dev);// 필수
int(*remove) ( struct device *dev);// 필수
int(*shutdown) ( struct device *dev);
int(*suspend) (struct device *dev , pm_message_t state , u32 level);// 필수
int(*resume) (struct device *dev , u32 level);// 필수
};


각 필드들을 간략하게 살펴보면 다음과 같다.

name드라이버의 이름을 문자열 형으로 지정한다.
bus보통은 &platform_bus_type 으로 지정한다.
unloaded
kobj내부적으로 드라이버 객체를 관리하기 위한것으로서 직접적으로 사용하지는 않는다. (임의로 지정하지 않는다.)
klist_devices
knode_bus
owner
probe디바이스의 초기화 루틴
remove디바이스가 제거될때 호출되는 루틴
shutdown
suspend절전모드로 들어갈때 호출
resume절전모드에서 빠져나올때 호출


// 실질적으로 smc91x.c 에서는 다음과 같이 사용되었다.
// smc91x.c
static struct device_driver smc_driver = {
.name= CARDNAME,// 디바이스의 이름과 드라이버의 이름이 같아야 한다.
.bus= &platform_bus_type,// bus 타입은 platform_bus_type 이다.
.probe= smc_drv_probe,// probe 함수
.remove= smc_drv_remove,// remove 함수
.suspend= smc_drv_suspend,// suspend 함수
.resume= smc_drv_resume,// resume 함수
};

---------------------------------------------------------------------

// 이제는 probe 함수인 smc_drv_probe를 살펴보자.
// 전체 내부 동작을 살펴볼 필요는 없다.

static int smc_drv_probe(struct device *dev)
{
// pdev 라는 플랫폼 디바이스의 멤버들을 dev 의 멤버들로 cast 시킨다.
// cast a member of a structure out to the containing structure
// 이렇게 함으로서 pdev 는 dev를 가르키게 된다.
struct platform_device *pdev = to_platform_device(dev);

struct net_device *ndev;
struct resource *res, *ext = NULL;
unsigned int *addr;
int ret;

// pdev 디바이스에 대한 리소스를 얻어온다.
// 첫번째 인자는 플랫폼 디바이스, 두번째는 리로스 타입, 세번째인자는 리소스 인덱스 이다.
/* 이 과정을 통하여 res는 다음의 리소스를 획득한다.
static struct resource smc91x_resources[] = {
[0] = {
.start = pSMC91X_BASE + 0x300,
.end = pSMC91X_BASE + 0xfffff,
.flags = IORESOURCE_MEM,
},
*/
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
ret = -ENODEV;
goto out;
}

/*
* Request the regions.
*/

// I/O 메모리 영역을 등록한다.
// res->start 주소로부터 SMC_IO_EXTENT 크기까지 smc91x 라는 이름으로 메모리를 할당한다.
// 이 영역은 /proc/iomem 파일에서 확인할 수 있다.
if (!request_mem_region(res->start, SMC_IO_EXTENT, "smc91x")) {
ret = -EBUSY;
goto out;
}

ndev = alloc_etherdev(sizeof(struct smc_local));
if (!ndev) {
printk("%s: could not allocate device.n", CARDNAME);
ret = -ENOMEM;
goto release_1;
}
SET_MODULE_OWNER(ndev);
SET_NETDEV_DEV(ndev, dev);

ndev->dma = (unsigned char)-1;

// pdev 디바이스에 대한 0번 인덱스에서 디바이스에 대한 IRQ 를 획득한다.
ndev->irq = platform_get_irq(pdev, 0);

// pdev 디바이스에 대한 1번 인덱스에 대한 리소스를 획득한다.
ext = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (ext) {

//start 주소로 size 만큼 name의 이름을 갖는 메모리를 할당한다.
if (!request_mem_region(ext->start, ATTRIB_SIZE, ndev->name)) {
ret = -EBUSY;
goto release_1;
}

#if defined(CONFIG_SA1100_ASSABET)
NCR_0 |= NCR_ENET_OSC_EN;
#endif

ret = smc_enable_device(ext->start);
if (ret)
goto release_both;
}

addr = ioremap(res->start, SMC_IO_EXTENT);
if (!addr) {
ret = -ENOMEM;
goto release_both;
}


// 사용자 데이터를 device 구조체의 driver_data 필드를 참조할 수 있도록 해주는 wrapper 함수이다.
dev_set_drvdata(dev, ndev);

ret = smc_probe(ndev, (unsigned long)addr);
if (ret != 0) {
dev_set_drvdata(dev, NULL);
iounmap(addr);
release_both:
if (ext)
release_mem_region(ext->start, ATTRIB_SIZE);
free_netdev(ndev);
release_1:
release_mem_region(res->start, SMC_IO_EXTENT);
out:
printk("%s: not found (%d).n", CARDNAME, ret);
}
#ifdef SMC_USE_PXA_DMA
else {
struct smc_local *lp = netdev_priv(ndev);
lp->physaddr = res->start;
}
#endif

return ret;
}

----------------------------------------------------------

// remove 함수는 다음과 같다.

static int smc_drv_remove(struct device *dev)
{
// pdev 플랫폼 디바이스를 dev 정보로 채우고
struct platform_device *pdev = to_platform_device(dev);

// dev 플랫폼 디바이스의 데이터를 획득해오고
struct net_device *ndev = dev_get_drvdata(dev);
struct resource *res;

// 디바이스의 데이터를 비우고
dev_set_drvdata(dev, NULL);

unregister_netdev(ndev);

free_irq(ndev->irq, ndev);

#ifdef SMC_USE_PXA_DMA
if (ndev->dma != (unsigned char)-1)
pxa_free_dma(ndev->dma);
#endif

iounmap((void *)ndev->base_addr);

res = platform_get_resource(pdev, IORESOURCE_MEM, 1);
if (res)
release_mem_region(res->start, ATTRIB_SIZE);

res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
release_mem_region(res->start, SMC_IO_EXTENT);

free_netdev(ndev);

return 0;
}

---------------------------------------------------------------

// suspend, resume 함수는 다음과 같다.

static int smc_drv_suspend(struct device *dev, u32 state, u32 level)
{
struct net_device *ndev = dev_get_drvdata(dev);

if (ndev && level == SUSPEND_DISABLE) {
if (netif_running(ndev)) {
netif_device_detach(ndev);
smc_shutdown(ndev);
}
}
return 0;
}

static int smc_drv_resume(struct device *dev, u32 level)
{
struct platform_device *pdev = to_platform_device(dev);
struct net_device *ndev = dev_get_drvdata(dev);

if (ndev && level == RESUME_ENABLE) {
struct smc_local *lp = netdev_priv(ndev);

if (pdev->num_resources == 3)
smc_enable_device(pdev->resource[2].start);
if (netif_running(ndev)) {
smc_reset(ndev);
smc_enable(ndev);
if (lp->phy_type != 0)
smc_phy_configure(ndev);
netif_device_attach(ndev);
}
}
return 0;
}
---------------------------------------------------------------

smsc 91c111/113의 내부 동작은 소스를 참조하기 바란다.