www.久久久久|狼友网站av天堂|精品国产无码a片|一级av色欲av|91在线播放视频|亚洲无码主播在线|国产精品草久在线|明星AV网站在线|污污内射久久一区|婷婷综合视频网站

當前位置:首頁 > 嵌入式 > 嵌入式軟件
[導讀]我們知道默認外設(shè)I/O資源是不在Linux內(nèi)核空間中的(如sram或硬件接口寄存器等),若需要訪問該外設(shè)I/O資源,必須先將其地址映射到內(nèi)核空間中來,然后才能在內(nèi)核空間中訪問它。

我們知道默認外設(shè)I/O資源是不在Linux內(nèi)核空間中的(如sram或硬件接口寄存器等),若需要訪問該外設(shè)I/O資源,必須先將其地址映射到內(nèi)核空間中來,然后才能在內(nèi)核空間中訪問它。

Linux內(nèi)核訪問外設(shè)I/O內(nèi)存資源的方式有兩種:動態(tài)映射(ioremap)和靜態(tài)映射(map_desc)。

一、動態(tài)映射(ioremap)方式

動態(tài)映射方式是大家使用了比較多的,也比較簡單。即直接通過內(nèi)核提供的ioremap函數(shù)動態(tài)創(chuàng)建一段外設(shè)I/O內(nèi)存資源到內(nèi)核虛擬地址的映射表,從而可以在內(nèi)核空間中訪問這段I/O資源。

Ioremap宏定義在asm/io.h內(nèi):

#define ioremap(cookie,size) __ioremap(cookie,size,0)

__ioremap函數(shù)原型為(arm/mm/ioremap.c):

void __iomem * __ioremap(unsigned long phys_addr, size_t size, unsigned long flags);

phys_addr:要映射的起始的IO地址

size:要映射的空間的大小

flags:要映射的IO空間和權(quán)限有關(guān)的標志

該函數(shù)返回映射后的內(nèi)核虛擬地址(3G-4G). 接著便可以通過讀寫該返回的內(nèi)核虛擬地址去訪問之這段I/O內(nèi)存資源。

舉一個簡單的例子: (取自s3c2410的iis音頻驅(qū)動)

比如我們要訪問s3c2410平臺上的I2S寄存器, 查看datasheet 知道IIS物理地址為0x55000000,我們把它定義為宏S3C2410_PA_IIS,如下:

#define S3C2410_PA_IIS (0x55000000)

若要在內(nèi)核空間(iis驅(qū)動)中訪問這段I/O寄存器(IIS)資源需要先建立到內(nèi)核地址空間的映射:

our_card->regs = ioremap(S3C2410_PA_IIS, 0x100);

if (our_card->regs == NULL) {

err = -ENXIO;

goto exit_err;

}

創(chuàng)建好了之后,我們就可以通過readl(our_card->regs )或writel(value, our_card->regs)等IO接口函數(shù)去訪問它。

二、靜態(tài)映射(map_desc)方式

下面重點介紹靜態(tài)映射方式即通過map_desc結(jié)構(gòu)體靜態(tài)創(chuàng)建I/O資源映射表。

內(nèi)核提供了在系統(tǒng)啟動時通過map_desc結(jié)構(gòu)體靜態(tài)創(chuàng)建I/O資源到內(nèi)核地址空間的線性映射表(即page table)的方式,這種映射表是一種一一映射的關(guān)系。程序員可以自己定義該I/O內(nèi)存資源映射后的虛擬地址。創(chuàng)建好了靜態(tài)映射表,在內(nèi)核或驅(qū)動中訪問該I/O資源時則無需再進行ioreamp動態(tài)映射,可以直接通過映射后的I/O虛擬地址去訪問它。

下面詳細分析這種機制的原理并舉例說明如何通過這種靜態(tài)映射的方式訪問外設(shè)I/O內(nèi)存資源。

內(nèi)核提供了一個重要的結(jié)構(gòu)體struct machine_desc ,這個結(jié)構(gòu)體在內(nèi)核移植中起到相當重要的作用,內(nèi)核通過machine_desc結(jié)構(gòu)體來控制系統(tǒng)體系架構(gòu)相關(guān)部分的初始化。

machine_desc結(jié)構(gòu)體的成員包含了體系架構(gòu)相關(guān)部分的幾個最重要的初始化函數(shù),包括map_io, init_irq, init_machine以及phys_io , timer成員等。

machine_desc結(jié)構(gòu)體定義如下:

struct machine_desc {

/*

* Note! The first four elements are used

* by assembler code in head-armv.S

*/

unsigned int nr; /* architecture number */

unsigned int phys_io; /* start of physical io */

unsigned int io_pg_offst; /* byte offset for io

* page tabe entry */

const char *name; /* architecture name */

unsigned long boot_params; /* tagged list */

unsigned int video_start; /* start of video RAM */

unsigned int video_end; /* end of video RAM */

unsigned int reserve_lp0 :1; /* never has lp0 */

unsigned int reserve_lp1 :1; /* never has lp1 */

unsigned int reserve_lp2 :1; /* never has lp2 */

unsigned int soft_reboot :1; /* soft reboot */

void (*fixup)(struct machine_desc *,

struct tag *, char **,

struct meminfo *);

void (*map_io)(void);/* IO mapping function */

void (*init_irq)(void);

struct sys_timer *timer; /* system tick timer */

void (*init_machine)(void);

};

這里的map_io成員即內(nèi)核提供給用戶的創(chuàng)建外設(shè)I/O資源到內(nèi)核虛擬地址靜態(tài)映射表的接口函數(shù)。Map_io成員函數(shù)會在系統(tǒng)初始化過程中被調(diào)用,流程如下:

Start_kernel -> setup_arch() --> paging_init() --> devicemaps_init()中被調(diào)用

Machine_desc結(jié)構(gòu)體通過MACHINE_START宏來初始化。

用戶可以在定義Machine_desc結(jié)構(gòu)體時指定Map_io的接口函數(shù),這里以s3c2410平臺為例。

s3c2410 machine_desc結(jié)構(gòu)體定義如下:

/* arch/arm/mach-s3c2410/Mach-smdk2410.c */

MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch

* to SMDK2410 */

/* Maintainer: Jonas Dietsche */

.phys_io = S3C2410_PA_UART,

.io_pg_offst = (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params = S3C2410_SDRAM_PA + 0x100,

.map_io = smdk2410_map_io,

.init_irq = s3c24xx_init_irq,

.init_machine = smdk2410_init,

.timer = &s3c24xx_timer,

MACHINE_END

如上,map_io被初始化為smdk2410_map_io。smdk2410_map_io即我們自己定義的創(chuàng)建靜態(tài)I/O映射表的函數(shù)。在Porting內(nèi)核到新開發(fā)板時,這個函數(shù)需要我們自己實現(xiàn)。

(注:這個函數(shù)通常情況下可以實現(xiàn)得很簡單,只要直接調(diào)用iotable_init創(chuàng)建映射表就行了,我們的板子內(nèi)核就是。不過s3c2410平臺這個函數(shù)實現(xiàn)得稍微有點復雜,主要是因為它將要創(chuàng)建IO映射表的資源分為了三個部分(smdk2410_iodesc, s3c_iodesc以及s3c2410_iodesc)在不同階段分別創(chuàng)建。這里我們?nèi)∑渲幸粋€部分進行分析,不影響對整個概念的理解。)

S3c2410平臺的smdk2410_map_io函數(shù)最終會調(diào)用到s3c2410_map_io函數(shù)。

流程如下:s3c2410_map_io -> s3c24xx_init_io -> s3c2410_map_io

下面分析一下s3c2410_map_io函數(shù):

void __init s3c2410_map_io(struct map_desc *mach_desc, int mach_size)

{

/* register our io-tables */

iotable_init(s3c2410_iodesc, ARRAY_SIZE(s3c2410_iodesc));

……

}

iotable_init內(nèi)核提供,定義如下:

/*

* Create the architecture specific mappings

*/

void __init iotable_init(struct map_desc *io_desc, int nr)

{

int i;

for (i = 0; i nr; i++)

create_mapping(io_desc + i);

}

由上知道,s3c2410_map_io最終調(diào)用iotable_init建立映射表。

iotable_init函數(shù)的參數(shù)有兩個:一個是map_desc類型的結(jié)構(gòu)體,另一個是該結(jié)構(gòu)體的數(shù)量nr。這里最關(guān)鍵的就是struct map_desc。map_desc結(jié)構(gòu)體定義如下:

/* include/asm-arm/mach/map.h */

struct map_desc {

unsigned long virtual; /* 映射后的虛擬地址 */

unsigned long pfn; /* I/O資源物理地址所在的頁幀號 */

unsigned long length; /* I/O資源長度 */

unsigned int type; /* I/O資源類型 */

};

create_mapping函數(shù)就是通過map_desc提供的信息創(chuàng)建線性映射表的。

這樣的話我們就知道了創(chuàng)建I/O映射表的大致流程為:只要定義相應I/O資源的map_desc結(jié)構(gòu)體,并將該結(jié)構(gòu)體傳給iotable_init函數(shù)執(zhí)行,就可以創(chuàng)建相應的I/O資源到內(nèi)核虛擬地址空間的映射表了。

我們來看看s3c2410是怎么定義map_desc結(jié)構(gòu)體的(即上面s3c2410_map_io函數(shù)內(nèi)的s3c2410_iodesc)。

/* arch/arm/mach-s3c2410/s3c2410.c */

static struct map_desc s3c2410_iodesc[] __initdata = {

IODESC_ENT(USBHOST),

IODESC_ENT(CLKPWR),

IODESC_ENT(LCD),

IODESC_ENT(TIMER),

IODESC_ENT(ADC),

IODESC_ENT(WATCHDOG),

};

IODESC_ENT宏如下:

#define IODESC_ENT(x) { (unsigned long)S3C24XX_VA_##x, __phys_to_pfn(S3C24XX_PA_##x), S3C24XX_SZ_##x, MT_DEVICE }

展開后等價于:

static struct map_desc s3c2410_iodesc[] __initdata = {

{

.virtual = (unsigned long)S3C24XX_VA_ LCD),

.pfn = __phys_to_pfn(S3C24XX_PA_ LCD),

.length = S3C24XX_SZ_ LCD,

.type = MT_DEVICE

},

……

};

S3C24XX_PA_ LCD和S3C24XX_VA_ LCD為定義在map.h內(nèi)的LCD寄存器的物理地址和虛擬地址。在這里map_desc 結(jié)構(gòu)體的virtual成員被初始化為S3C24XX_VA_ LCD,pfn成員值通過__phys_to_pfn內(nèi)核函數(shù)計算,只需要傳遞給它該I/O資源的物理地址就行。Length為映射資源的大小。MT_DEVICE為I/O類型,通常定義為MT_DEVICE。

這里最重要的即virtual 成員的值S3C24XX_VA_ LCD,這個值即該I/O資源映射后的內(nèi)核虛擬地址,創(chuàng)建映射表成功后,便可以在內(nèi)核或驅(qū)動中直接通過該虛擬地址訪問這個I/O資源。

S3C24XX_VA_ LCD以及S3C24XX_PA_ LCD定義如下:

/* include/asm-arm/arch-s3c2410/map.h */

/* LCD controller */

#define S3C24XX_VA_LCD S3C2410_ADDR(0x00600000) //LCD映射后的虛擬地址

#define S3C2410_PA_LCD (0x4D000000) //LCD寄存器物理地址

#define S3C24XX_SZ_LCD SZ_1M //LCD寄存器大小

S3C2410_ADDR 定義如下:

#define S3C2410_ADDR(x) ((void __iomem *)0xF0000000 + (x))

這里就是一種線性偏移關(guān)系,即s3c2410創(chuàng)建的I/O靜態(tài)映射表會被映射到0xF0000000之后。(這個線性偏移值可以改,也可以你自己在virtual成員里手動定義一個值,只要不和其他IO資源映射地址沖突,但最好是在0XF0000000之后。)

(注:其實這里S3C2410_ADDR的線性偏移只是s3c2410平臺的一種做法,很多其他ARM平臺采用了通用的IO_ADDRESS宏來計算物理地址到虛擬地址之前的偏移。

IO_ADDRESS宏定義如下:

/* include/asm/arch-versatile/hardware.h */

/* macro to get at IO space when running virtually */

#define IO_ADDRESS(x) (((x) & 0x0fffffff) + (((x) >> 4) & 0x0f000000) + 0xf0000000) )

s3c2410_iodesc這個映射表建立成功后,我們在內(nèi)核中便可以直接通過S3C24XX_VA_ LCD訪問LCD的寄存器資源。

如:S3c2410 lcd驅(qū)動的probe函數(shù)內(nèi)

/* Stop the video and unset ENVID if set */

info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;

lcdcon1 = readl(S3C2410_LCDCON1); //read映射后的寄存器虛擬地址

writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1); //write映射后的虛擬地址

S3C2410_LCDCON1寄存器地址為相對于S3C24XX_VA_LCD偏移的一個地址,定義如下:

/* include/asm/arch-s3c2410/regs-lcd.h */

#define S3C2410_LCDREG(x) ((x) + S3C24XX_VA_LCD)

/* LCD control registers */

#define S3C2410_LCDCON1 S3C2410_LCDREG(0x00)

到此,我們知道了通過map_desc結(jié)構(gòu)體創(chuàng)建I/O內(nèi)存資源靜態(tài)映射表的原理了??偨Y(jié)一下發(fā)現(xiàn)其實過程很簡單,一通過定義map_desc結(jié)構(gòu)體創(chuàng)建靜態(tài)映射表,二在內(nèi)核中通過創(chuàng)建映射后虛擬地址訪問該IO資源。

三、I/O靜態(tài)映射方式應用實例

I/O靜態(tài)映射方式通常是用在寄存器資源的映射上,這樣在編寫內(nèi)核代碼或驅(qū)動時就不需要再進行ioremap,直接使用映射后的內(nèi)核虛擬地址訪問。同樣的IO資源只需要在內(nèi)核初始化過程中映射一次,以后就可以一直使用。

寄存器資源映射的例子上面講原理時已經(jīng)介紹得很清楚了,這里我舉一個SRAM的實例介紹如何應用這種I/O靜態(tài)映射方式。當然原理和操作過程同寄存器資源是一樣的,可以把SRAM看成是大號的I/O寄存器資源。

比如我的板子在0x30000000位置有一塊64KB大小的SRAM。我們現(xiàn)在需要通過靜態(tài)映射的方式去訪問該SRAM。我們要做的事內(nèi)容包括修改kernel代碼,添加SRAM資源相應的map_desc結(jié)構(gòu),創(chuàng)建sram到內(nèi)核地址空間的靜態(tài)映射表。寫一個Sram Module,在Sram Module 內(nèi)直接通過靜態(tài)映射后的內(nèi)核虛擬地址訪問該sram。

第一步:創(chuàng)建SRAM靜態(tài)映射表

在我板子的map_des結(jié)構(gòu)體數(shù)組(xxx_io_desc)內(nèi)添加SRAM資源相應的map_desc。如下:

static struct map_desc xxx_io_desc[] __initdata = {

…………

{

.virtual = IO_ADDRESS(XXX _UART2_BASE),

.pfn = __phys_to_pfn(XXX _UART2_BASE),

.length = SZ_4K,

.type = MT_DEVICE

},{

.virtual = IO_ADDRESS(XXX_SRAM_BASE),

.pfn = __phys_to_pfn(XXX_SRAM_BASE),

.length = SZ_4K,

.type = MT_DEVICE

},

};

宏XXX_SRAM_BASE為我板子上SRAM的物理地址,定義為0x30000000。我的kernel是通過IO_ADDRESS的方式計算內(nèi)核虛擬地址的,這點和之前介紹的S3c2410有點不一樣。不過原理都是相同的,為一個線性偏移, 范圍在0xF0000000之后。

第二步:寫個SRAM Module,在Module中通過映射后的虛擬地址直接訪問該SRAM資源

SRAM Module代碼如下:

/* Sram Testing Module */

……

static void sram_test(void)

{

void * sram_p;

char str[] = "Hello,sram!\n";

sram_p = (void *)IO_ADDRESS (XXX_SRAM_BASE); /* 通過IO_ADDRESS宏得到SRAM映射后的虛擬地址 */

memcpy(sram_p, str, sizeof(str)); //將 str字符數(shù)組拷貝到sram內(nèi)

printk(sram_p);

printk("\n");

}

static int __init sram_init(void)

{

struct resource * ret;

printk("Request SRAM mem region ............\n");

ret = request_mem_region(SRAM_BASE, SRAM_SIZE, "SRAM Region");

if (ret ==NULL) {

printk("Request SRAM mem region failed!\n");

return -1;

}

sram_test();

return 0;

}

static void __exit sram_exit(void)

{

release_mem_region(SRAM_BASE, SRAM_SIZE);

printk("Release SRAM mem region success!\n");

printk("SRAM is closed\n");

}

module_init(sram_init);

module_exit(sram_exit);

在開發(fā)板上運行結(jié)果如下:

/ # insmod bin/sram.ko

Request SRAM mem region ............

Hello,sram! ß 這句即打印的SRAM內(nèi)的字符串

/ # rmmod sram

Release SRAM mem region success!

SRAM is close

實驗發(fā)現(xiàn)可以通過映射后的地址正常訪問SRAM。

最后,這里舉SRAM作為例子的還有一個原因是通過靜態(tài)映射方式訪問SRAM的話,我們可以預先知道SRAM映射后的內(nèi)核虛擬地址(通過IOADDRESS計算)。這樣的話就可以嘗試在SRAM上做點文章。比如寫個內(nèi)存分配的MODULE管理SRAM或者其他方式,將一些critical的數(shù)據(jù)放在SRAM內(nèi)運行,這樣可以提高一些復雜程序的運行效率(SRAM速度比SDRAM快多了),比如音視頻的編解碼過程中用到的較大的buffer等。

本站聲明: 本文章由作者或相關(guān)機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: 驅(qū)動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設(shè)備,其驅(qū)動電源的性能直接關(guān)系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅(qū)動電源設(shè)計中至關(guān)重要的兩個環(huán)節(jié),集成化方案的設(shè)計成為提升電機驅(qū)動性能的關(guān)鍵。

關(guān)鍵字: 工業(yè)電機 驅(qū)動電源

LED 驅(qū)動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設(shè)備的使用壽命。然而,在實際應用中,LED 驅(qū)動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設(shè)計、生...

關(guān)鍵字: 驅(qū)動電源 照明系統(tǒng) 散熱

根據(jù)LED驅(qū)動電源的公式,電感內(nèi)電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關(guān)鍵字: LED 設(shè)計 驅(qū)動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產(chǎn)業(yè)的重要發(fā)展方向。電動汽車的核心技術(shù)之一是電機驅(qū)動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅(qū)動系統(tǒng)中的關(guān)鍵元件,其性能直接影響到電動汽車的動力性能和...

關(guān)鍵字: 電動汽車 新能源 驅(qū)動電源

在現(xiàn)代城市建設(shè)中,街道及停車場照明作為基礎(chǔ)設(shè)施的重要組成部分,其質(zhì)量和效率直接關(guān)系到城市的公共安全、居民生活質(zhì)量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關(guān)鍵字: 發(fā)光二極管 驅(qū)動電源 LED

LED通用照明設(shè)計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關(guān)鍵字: LED 驅(qū)動電源 功率因數(shù)校正

在LED照明技術(shù)日益普及的今天,LED驅(qū)動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設(shè)備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關(guān)鍵字: LED照明技術(shù) 電磁干擾 驅(qū)動電源

開關(guān)電源具有效率高的特性,而且開關(guān)電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅(qū)動電源

關(guān)鍵字: LED 驅(qū)動電源 開關(guān)電源

LED驅(qū)動電源是把電源供應轉(zhuǎn)換為特定的電壓電流以驅(qū)動LED發(fā)光的電壓轉(zhuǎn)換器,通常情況下:LED驅(qū)動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關(guān)鍵字: LED 隧道燈 驅(qū)動電源
關(guān)閉