2009年2月24日 星期二

引-Linux-2.6.22的LCD驱动

Hardware and Software:
CPU: S3C2410
LCD: LTV350QV-F04
OS : linux-2.6.22

1. 添加s3c2410处理器的LCD控制寄存器的初始值,具体做法为在文件arch/arm/mach-s3c2410/mach-smdk2410.c中添加struct s3c2410fb_mach_info类型的寄存器描述讯息,如下所示:

static struct s3c2410fb_mach_info smdk2410_lcd_info __initdata = {
.fixed_syncs = 0,
.type = S3C2410_LCDCON1_TFT,

.width = 320,
.height = 240,

.xres = {
.min = 320,
.max = 320,
.defval = 320,
},
.yres = {
.max = 240,
.min = 240,
.defval = 240,
},
.bpp = {
.min = 16,
.max = 16,
.defval = 16,
},

.regs = {
.lcdcon1 = S3C2410_LCDCON1_TFT16BPP | //(1)
S3C2410_LCDCON1_TFT |
S3C2410_LCDCON1_CLKVAL(2),
.lcdcon2 = S3C2410_LCDCON2_VBPD(3) |
S3C2410_LCDCON2_LINEVAL(239) |
S3C2410_LCDCON2_VFPD(5) |
S3C2410_LCDCON2_VSPW(15),
.lcdcon3 = S3C2410_LCDCON3_HBPD(5) |
S3C2410_LCDCON3_HOZVAL(319) |
S3C2410_LCDCON3_HFPD(15),
.lcdcon4 = //S3C2410_LCDCON4_MVAL(13) |
S3C2410_LCDCON4_HSPW(8),
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
},

.gpccon = 0xaaaaaaaa,
.gpccon_mask = 0xffffffff,
.gpcup = 0xffffffff,
.gpcup_mask = 0xffffffff, //(2)

.gpdcon = 0xaaaaaaaa,
.gpdcon_mask = 0xffffffff,
.gpdup = 0xffffffff,
.gpdup_mask = 0xffffffff,

.lpcsel = 0x00, //(3)
};
注:
(1) LCD的数据线,也就是RGB(Red Green Blue)线,此处的硬件连接决定了LCD的BPP,16BPP或24BPP,我的开发板上采用的是5:6:5的16BPP的连接。关于BPP和连线的关 系可以参考2410手册的387~390页。
(2) 将GPC配置为VD功能,并关闭GPC的pull-up(上拉电阻)功能
(3) 这款LCD的特点是驱动IC内置在LCD模块上,所以不用外接lpc3600等驱动IC。
(4) S3C2410的LCD控制器说明 以及 HSPW,HBPD,HFPD等参数的算法s3c24xx_fb_set_platdata()函数向内核注册上面的信息(smdk2410_lcd_info),此函数在arch/arm/plat-s3c24xx/devs.c中定义。

然后在arch/arm/mach-s3c2410/mach-smdk2410.c的smdk2410_init()函数中调用s3c24xx_fb_set_platdata(),具体为:
static void __init smdk2410_init(void)
{
s3c24xx_fb_set_platdata(&smdk2410_lcd_info);
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
smdk_machine_init();
}

3. 在make menuconfig的时候配置Linux的logo选项,然后的时候在console选项中选上framebuffer console surpport,要不然看不到小企鹅。
Device Drivers --->
Graphics support --->
Console display driver support --->
<*> Framebuffer Console support
[*] Bootup logo --->


4. 测试LCD驱动
类别:驱动开发api | 添加到搜藏 | 浏览(421) | 评论 (2)
上一篇:LCD参数 下一篇:驱动的入口点

网友评论:
1

zengzhaonong
2008年08月30日 星期六 21:51 | 回复
S3C2410 HOZVAL = (Horizontal display size) -1 LINEVAL = (Vertical display size) -1 VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
3

zengzhaonong
2008年08月30日 星期六 22:53 | 回复
VCLK(Hz) = HCLK/[(CLKVAL+1)x2] CLKVAL:决定VCLK的分频比。LCD控制器输出的VCLK是直接由系统总线(AHB)的工作频率HCLK直接分频得到的。做为240*320的TFT屏,应保证得出的VCLK在5~10MHz之间。 S3C2410的总线工作频率HCLK为60MHz.为了使VCLK等于10MHz,所以设置CLKVAL为2。 注: 如果CLKVAL设置不合适(过大),会导致屏幕抖动。

引-Linux-2.6.22的LCD驱动

〔http://hi.baidu.com/zengzhaonong/blog/item/2d78054ca53ea7ffd72afcdd.html」

Hardware and Software:
CPU: S3C2410
LCD: LTV350QV-F04
OS : linux-2.6.22

1. 添加s3c2410处理器的LCD控制寄存器的初始值,具体做法为在文件arch/arm/mach-s3c2410/mach-smdk2410.c中添加struct s3c2410fb_mach_info类型的寄存器描述讯息,如下所示:

static struct s3c2410fb_mach_info smdk2410_lcd_info __initdata = {
.fixed_syncs = 0,
.type = S3C2410_LCDCON1_TFT,

.width = 320,
.height = 240,

.xres = {
.min = 320,
.max = 320,
.defval = 320,
},
.yres = {
.max = 240,
.min = 240,
.defval = 240,
},
.bpp = {
.min = 16,
.max = 16,
.defval = 16,
},

.regs = {
.lcdcon1 = S3C2410_LCDCON1_TFT16BPP | //(1)
S3C2410_LCDCON1_TFT |
S3C2410_LCDCON1_CLKVAL(2),
.lcdcon2 = S3C2410_LCDCON2_VBPD(3) |
S3C2410_LCDCON2_LINEVAL(239) |
S3C2410_LCDCON2_VFPD(5) |
S3C2410_LCDCON2_VSPW(15),
.lcdcon3 = S3C2410_LCDCON3_HBPD(5) |
S3C2410_LCDCON3_HOZVAL(319) |
S3C2410_LCDCON3_HFPD(15),
.lcdcon4 = //S3C2410_LCDCON4_MVAL(13) |
S3C2410_LCDCON4_HSPW(8),
.lcdcon5 = S3C2410_LCDCON5_FRM565 |
S3C2410_LCDCON5_INVVLINE |
S3C2410_LCDCON5_INVVFRAME |
S3C2410_LCDCON5_PWREN |
S3C2410_LCDCON5_HWSWP,
},

.gpccon = 0xaaaaaaaa,
.gpccon_mask = 0xffffffff,
.gpcup = 0xffffffff,
.gpcup_mask = 0xffffffff, //(2)

.gpdcon = 0xaaaaaaaa,
.gpdcon_mask = 0xffffffff,
.gpdup = 0xffffffff,
.gpdup_mask = 0xffffffff,

.lpcsel = 0x00, //(3)
};
注:
(1) LCD的数据线,也就是RGB(Red Green Blue)线,此处的硬件连接决定了LCD的BPP,16BPP或24BPP,我的开发板上采用的是5:6:5的16BPP的连接。关于BPP和连线的关 系可以参考2410手册的387~390页。
(2) 将GPC配置为VD功能,并关闭GPC的pull-up(上拉电阻)功能
(3) 这款LCD的特点是驱动IC内置在LCD模块上,所以不用外接lpc3600等驱动IC。
(4)S3C2410的LCD控制器说明 以及 HSPW,HBPD,HFPD等参数的算法


2. 通过s3c24xx_fb_set_platdata()函数向内核注册上面的信息(smdk2410_lcd_info),此函数在arch/arm/plat-s3c24xx/devs.c中定义。

然后在arch/arm/mach-s3c2410/mach-smdk2410.c的smdk2410_init()函数中调用s3c24xx_fb_set_platdata(),具体为:
static void __init smdk2410_init(void)
{
s3c24xx_fb_set_platdata(&smdk2410_lcd_info);
platform_add_devices(smdk2410_devices, ARRAY_SIZE(smdk2410_devices));
smdk_machine_init();
}

3. 在make menuconfig的时候配置Linux的logo选项,然后的时候在console选项中选上framebuffer console surpport,要不然看不到小企鹅。
Device Drivers --->
Graphics support --->
Console display driver support --->
<*> Framebuffer Console support
[*] Bootup logo --->


4. 测试LCD驱动

类别:驱动开发api | 添加到搜藏 | 浏览(421) | 评论 (2)
上一篇:LCD参数 下一篇:驱动的入口点

网友评论:
1

zengzhaonong
2008年08月30日 星期六 21:51 | 回复
S3C2410 HOZVAL = (Horizontal display size) -1 LINEVAL = (Vertical display size) -1 VCLK(Hz) = HCLK/[(CLKVAL+1)x2]
3

zengzhaonong
2008年08月30日 星期六 22:53 | 回复
VCLK(Hz) = HCLK/[(CLKVAL+1)x2] CLKVAL:决定VCLK的分频比。LCD控制器输出的VCLK是直接由系统总线(AHB)的工作频率HCLK直接分频得到的。做为240*320的TFT屏,应保证得出的VCLK在5~10MHz之间。 S3C2410的总线工作频率HCLK为60MHz.为了使VCLK等于10MHz,所以设置CLKVAL为2。 注: 如果CLKVAL设置不合适(过大),会导致屏幕抖动。

引-s3c2410 LCD驱动分析

(http://hi.baidu.com/zengzhaonong/blog/item/00dc0f385a0785f4b211c7af.html)

Linux-2.6.22

module_init() -->
s3c2410fb_init() -->
platform_driver_register() -->
s3c2410fb_probe()



(10) 为该设备创建一个在sysfs中的属性


static int __init s3c2410fb_probe(struct platform_device *pdev)
{
struct s3c2410fb_info *info;
struct fb_info *fbinfo;
struct s3c2410fb_hw *mregs; //s3c2410fb_hw为描述LCD的硬件控制寄存器内容的结构体
int ret;
int irq;
int i;
u32 lcdcon1;

mach_info = pdev->dev.platform_data;
if (mach_info == NULL) {
dev_err(&pdev->dev,"no platform data for lcd, cannot attach\n");
return -EINVAL;
}

mregs = &mach_info->regs;

irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "no irq for device\n");
return -ENOENT;
}

fbinfo = framebuffer_alloc(sizeof(struct s3c2410fb_info), &pdev->dev);
if (!fbinfo) {
return -ENOMEM;
}


info = fbinfo->par;
info->fb = fbinfo;
info->dev = &pdev->dev;

platform_set_drvdata(pdev, fbinfo);

dprintk("devinit\n");

strcpy(fbinfo->fix.id, driver_name);

memcpy(&info->regs, &mach_info->regs, sizeof(info->regs));

/* Stop the video and unset ENVID if set */
info->regs.lcdcon1 &= ~S3C2410_LCDCON1_ENVID;
lcdcon1 = readl(S3C2410_LCDCON1);
writel(lcdcon1 & ~S3C2410_LCDCON1_ENVID, S3C2410_LCDCON1);

info->mach_info = pdev->dev.platform_data;

fbinfo->fix.type = FB_TYPE_PACKED_PIXELS;
fbinfo->fix.type_aux = 0;
fbinfo->fix.xpanstep = 0;
fbinfo->fix.ypanstep = 0;
fbinfo->fix.ywrapstep = 0;
fbinfo->fix.accel = FB_ACCEL_NONE;

fbinfo->var.nonstd = 0;
fbinfo->var.activate = FB_ACTIVATE_NOW;
fbinfo->var.height = mach_info->height;
fbinfo->var.width = mach_info->width;
fbinfo->var.accel_flags = 0;
fbinfo->var.vmode = FB_VMODE_NONINTERLACED;

fbinfo->fbops = &s3c2410fb_ops;
fbinfo->flags = FBINFO_FLAG_DEFAULT;
fbinfo->pseudo_palette = &info->pseudo_pal;

fbinfo->var.xres = mach_info->xres.defval;
fbinfo->var.xres_virtual = mach_info->xres.defval;
fbinfo->var.yres = mach_info->yres.defval;
fbinfo->var.yres_virtual = mach_info->yres.defval;
fbinfo->var.bits_per_pixel = mach_info->bpp.defval;

fbinfo->var.upper_margin = S3C2410_LCDCON2_GET_VBPD(mregs->lcdcon2) + 1;
fbinfo->var.lower_margin = S3C2410_LCDCON2_GET_VFPD(mregs->lcdcon2) + 1;
fbinfo->var.vsync_len = S3C2410_LCDCON2_GET_VSPW(mregs->lcdcon2) + 1;

fbinfo->var.left_margin = S3C2410_LCDCON3_GET_HFPD(mregs->lcdcon3) + 1;
fbinfo->var.right_margin = S3C2410_LCDCON3_GET_HBPD(mregs->lcdcon3) + 1;
fbinfo->var.hsync_len = S3C2410_LCDCON4_GET_HSPW(mregs->lcdcon4) + 1;

fbinfo->var.red.offset = 11;
fbinfo->var.green.offset = 5;
fbinfo->var.blue.offset = 0;
fbinfo->var.transp.offset = 0;
fbinfo->var.red.length = 5;
fbinfo->var.green.length = 6;
fbinfo->var.blue.length = 5;
fbinfo->var.transp.length = 0;
fbinfo->fix.smem_len = mach_info->xres.max *
mach_info->yres.max *
mach_info->bpp.max / 8;

for (i = 0; i < 256; i++) //初始化调色板缓冲区
info->palette_buffer[i] = PALETTE_BUFF_CLEAR;

if (!request_mem_region((unsigned long)S3C24XX_VA_LCD, SZ_1M, "s3c2410-lcd")) {
ret = -EBUSY;
goto dealloc_fb;
}


dprintk("got LCD region\n");

ret = request_irq(irq, s3c2410fb_irq, IRQF_DISABLED, pdev->name, info);
if (ret) {
dev_err(&pdev->dev, "cannot get irq %d - err %d\n", irq, ret);
ret = -EBUSY;
goto release_mem;
}

info->clk = clk_get(NULL, "lcd");
if (!info->clk || IS_ERR(info->clk)) {
printk(KERN_ERR "failed to get lcd clock source\n");
ret = -ENOENT;
goto release_irq;
}

clk_enable(info->clk);
dprintk("got and enabled clock\n");

msleep(1);

/* Initialize video memory */
ret = s3c2410fb_map_video_memory(info);
if (ret) {
printk( KERN_ERR "Failed to allocate video RAM: %d\n", ret);
ret = -ENOMEM;
goto release_clock;
}
dprintk("got video memory\n");

ret = s3c2410fb_init_registers(info);

ret = s3c2410fb_check_var(&fbinfo->var, fbinfo);

ret = register_framebuffer(fbinfo);
if (ret < 0) {
printk(KERN_ERR "Failed to register framebuffer device: %d\n", ret);
goto free_video_memory;
}

/* create device files */
device_create_file(&pdev->dev, &dev_attr_debug);

printk(KERN_INFO "fb%d: %s frame buffer device\n",
fbinfo->node, fbinfo->fix.id);

return 0;

free_video_memory:
s3c2410fb_unmap_video_memory(info);
release_clock:
clk_disable(info->clk);
clk_put(info->clk);
release_irq:
free_irq(irq,info);
release_mem:
release_mem_region((unsigned long)S3C24XX_VA_LCD, S3C24XX_SZ_LCD);
dealloc_fb:
framebuffer_release(fbinfo);
return ret;
}

「引」測試LCD驅動

(http://hi.baidu.com/zengzhaonong/blog/item/ff92ac1b0120861f8618bf9d.html)
#include
#include
#include
#include
#include
#define FBDEV "/dev/fb0"
static char * default_framebuffer = FBDEV;

struct fb_dev
{
int fb;
void * fb_mem;
int fb_width, fb_height, fb_line_len, fb_size;
int fb_bpp;
};
static struct fb_dev fbdev;

static void draw(int color)
{
int i, j;
unsigned short int *p = (unsigned short int *)fbdev.fb_mem;
for (i = 0; i < fbdev.fb_height; i++, p += fbdev.fb_line_len/2) {
for (j = 0; j < fbdev.fb_width; j++)
p[j] = color;
}
}

int framebuffer_open(void)
{
int fb;
struct fb_var_screeninfo fb_vinfo;
struct fb_fix_screeninfo fb_finfo;
char * fb_dev_name = NULL;

if (!(fb_dev_name = getenv("FRAMEBUFFER")))
fb_dev_name = default_framebuffer;
fb = open(fb_dev_name, O_RDWR);
if (fb < 0) {
perror("oepn fb_dev error");
return -1;
}

if (ioctl(fb, FBIOGET_VSCREENINFO, &fb_vinfo)) {
perror("ioctl FBIOGET_VSCREENINFO error");
close(fb);
return -1;
}

if (ioctl(fb, FBIOGET_FSCREENINFO, &fb_finfo)) {
perror("ioctl FBIOGET_FSCREENINFO error");
return 1;
}

fbdev.fb_bpp = fb_vinfo.red.length + fb_vinfo.green.length +
fb_vinfo.blue.length + fb_vinfo.transp.length;
fbdev.fb_width = fb_vinfo.xres;
fbdev.fb_height = fb_vinfo.yres;
fbdev.fb_line_len = fb_finfo.line_length;
fbdev.fb_size = fb_finfo.smem_len;

printf("frame buffer: %d(%d)x%d, %dbpp, 0x%xbytes\n",
fbdev.fb_width, fbdev.fb_line_len, fbdev.fb_height,
fbdev.fb_bpp, fbdev.fb_size);

if (fbdev.fb_bpp != 16) {
printf("frame buffer must be 16bpp mode");
exit(0);
}

fbdev.fb_mem = mmap(NULL, fbdev.fb_size,
PROT_READ|PROT_WRITE, MAP_SHARED, fb, 0);
if (fbdev.fb_mem == NULL || (int)fbdev.fb_mem == -1) {
fbdev.fb_mem = NULL;
printf("mmap failed\n");
close(fb);
return -1;
}

fbdev.fb = fb;
memset(fbdev.fb_mem, 0x0, fbdev.fb_size);

return 0;
}

void framebuffer_close()
{
if (fbdev.fb_mem) {
munmap(fbdev.fb_mem, fbdev.fb_size);
fbdev.fb_mem = NULL;
}

if (fbdev.fb) {
close(fbdev.fb);
fbdev.fb = 0;
}
}

int main(void)
{
int i;
framebuffer_open();

for (i = 0; i < 16; i++) {
printf("%d: color = 0x%x", i, 1 << i);
draw(1 << i);
getchar();
}

framebuffer_close();
return 0;
}

「引」使用SPI驱动的接口(SPI API)

spi.c
--------------------------------------
#include
#include

#define TEST_REG 0x01

static char test_read_reg(struct spi_device *spi, char reg)
{
int ret;
char buf[2];
buf[0] = reg; // TX
buf[1] = 0; // RX
//spi_write_then_read(spi, &buf[0], 1, &buf[1], 1);
ret = spi_write(spi, &buf[0], 1);
if (ret != 0)
printk("spi write err\n");
ret = spi_read(spi, &buf[1], 1);
if (ret != 0)
printk("spi read err\n");

return buf[1];
}

static int spi_test_probe(struct spi_device *spi)
{
printk("<1> TEST_REG: 0x%x\n", test_read_reg(spi, TEST_REG));
return 0;
}

static int spi_test_remove(struct spi_device *spi)
{
return 0;
}

static struct spi_driver spi_test_driver = {
.probe = spi_test_probe,
.remove = spi_test_remove,
.driver = {
.name = "innofidei_cmmb",
.owner = THIS_MODULE,
},
};

static int __init spi_test_init(void)
{
return spi_register_driver(&spi_test_driver);
}

static void __exit spi_test_exit(void)
{
spi_unregister_driver(&spi_test_driver);
}

module_init(spi_test_init);
module_exit(spi_test_exit);

MODULE_DESCRIPTION("spi device test");
MODULE_LICENSE("GPL");



Makefile
--------------------------------------
KDIR=/home/ssl/linux-2.6.24-ssl

obj-m += spi.o

all:
make -C $(KDIR) M=`pwd` modules
clean:
make -C $(KDIR) M=`pwd` clean





生成spi_device
linux-2.6.24/arch/arm/mach-magus/a2818p.c
--------------------------------------
static struct spi_board_info _spi[] __initdata =
{
{
.modalias = "WM8987",
.bus_num = 0,
.chip_select = 3,
.max_speed_hz = 100000,
.mode = SPI_MODE_3,
},
{
.modalias = "ads7846",
.bus_num = 0,
.chip_select = 1,
.max_speed_hz = 2500000,
.irq = gpio_to_irq(MAGUS_GPIO_ADS7846),
.platform_data = &ads7846,
},
{
.modalias = "innofidei_cmmb",
.bus_num = 1, // SPI2
.chip_select = 0, // CS0
.max_speed_hz = 4000000, // 4M
.mode = SPI_MODE_0,
},

};

spi_register_board_info(_spi, ARRAY_SIZE(_spi)); //register SPI devices for a given board


spi控制器初始化的时候,会使用spi_board_info生成spi_device
sslspi_probe()
spi_register_master()
scan_boardinfo() // 使用spi_board_info信息生成新的spi_device
spi_new_device()





vi arch/arm/mach-magus/a2818t.c
------------------------------------------
static struct platform_device *devices[] =
{
// &ehci,
// &usbd,
// &otg,
&sdhc,
// &sdhc2,
&spi,
&spi2,
&i2c,
&rtc,
&wdog,
&kpp,
&lcdc,
&lcdcw1,
&lcdcw2,
&tvout,
&vpp,
&vip,
&d2d,
&nfc,
};


vi arch/arm/mach-magus/magus.h
------------------------------------------
//#if defined CONFIG_ACCIO_PF101 || defined CONFIG_ACCIO_P1
RCN_AIDP(spi2, "spi", 1, MAGUS_IO_SPI2, MAGUS_IRQ_SPI2, MAGUS_DMA_SPI2_TX, FREE_PIN, FREE_PIN, FREE_PIN, FREE_PIN, FREE_PIN );
//#endif

(http://hi.baidu.com/zengzhaonong/blog/item/0d9356543b084952564e00d6.html)

「引」zImgage,uImage区别(ZZ)

(http://hi.baidu.com/zengzhaonong/blog/item/1c3455edb15c1dd2b31cb1d3.html)
zImgage,uImage区别(ZZ)

2008年09月10日 星期三 21:58
对于Linux内核,编译可以生成不同格式的映像文件,例如:
# make zImage
# make uImage

zImage是ARM Linux常用的一种压缩映像文件,uImage是U-boot专用的映像文件,它是在zImage之前加上一个长度为0x40的“头”,说明这个映像文 件的类型、加载位置、生成时间、大小等信息。换句话说,如果直接从uImage的0x40位置开始执行,zImage和uImage没有任何区别。另 外,Linux2.4内核不支持uImage,Linux2.6内核加入了很多对嵌入式系统的支持,但是uImage的生成也需要设置。

「引」register_chrdev

(http://hi.baidu.com/zengzhaonong/blog/item/9f02adb70779b2f230add107.html)

register_chrdev

2008年09月29日 星期一 23:42
int register_chrdev(unsigned int major, const char *name,
struct file_operations *fops);

其中,major是为设备驱动程序向系统申请的主设备号,如果为0则系统为此驱动程序动态地分配一个主设备号。name是设备名。fops就是前面所说的 对各个调用的入口点的说明。此函数返回0表示成功。返回-EINVAL表示申请的主设备号非法,一般来说是主设备号大于系统所允许的最大设备号。返回 -EBUSY表示所申请的主设备号正在被其它设备驱动程序使用。如果是动态分配主设备号成功,此函数将返回所分配的主设备号。如果 register_chrdev操作成功,设备名就会出现在/proc/devices文件里。

在成功的向系统注册了设备驱动程序后(调用register_chrdev()成功后),就可以用mknod命令来把设备映射为一个特别文件,其它程序使用这个设备的时候,只要对此特别文件进行操作就行了。

2009年2月19日 星期四

「引」How to Write a Linux USB Device Driver

http://www.linuxjournal.com/article/7353

How to Write a Linux USB Device Driver

October 1st, 2001 by Greg Kroah-Hartman in

Greg shares his USB driver skeleton and shows how it can be customized for your specific device.
Your rating: None Average: 4.6 (23 votes)

The Linux USB subsystem has grown from supporting only two different types of devices in the 2.2.7 kernel (mice and keyboards), to over 20 different types of devices in the 2.4 kernel. Linux currently supports almost all USB class devices (standard types of devices like keyboards, mice, modems, printers and speakers) and an ever-growing number of vendor-specific devices (such as USB to serial converters, digital cameras, Ethernet devices and MP3 players). For a full list of the different USB devices currently supported, see Resources.

The remaining kinds of USB devices that do not have support on Linux are almost all vendor-specific devices. Each vendor decides to implement a custom protocol to talk to their device, so a custom driver usually needs to be created. Some vendors are open with their USB protocols and help with the creation of Linux drivers, while others do not publish them, and developers are forced to reverse-engineer. See Resources for some links to handy reverse-engineering tools.

Because each different protocol causes a new driver to be created, I have written a generic USB driver skeleton, modeled after the pci-skeleton.c file in the kernel source tree upon which many PCI network drivers have been based. This USB skeleton can be found at drivers/usb/usb-skeleton.c in the kernel source tree. In this article I will walk through the basics of the skeleton driver, explaining the different pieces and what needs to be done to customize it to your specific device.

If you are going to write a Linux USB driver, please become familiar with the USB protocol specification. It can be found, along with many other useful documents, at the USB home page (see Resources). An excellent introduction to the Linux USB subsystem can be found at the USB Working Devices List (see Resources). It explains how the Linux USB subsystem is structured and introduces the reader to the concept of USB urbs, which are essential to USB drivers.

The first thing a Linux USB driver needs to do is register itself with the Linux USB subsystem, giving it some information about which devices the driver supports and which functions to call when a device supported by the driver is inserted or removed from the system. All of this information is passed to the USB subsystem in the usb_driver structure. The skeleton driver declares a usb_driver as:

static struct usb_driver skel_driver = {
name: "skeleton",
probe: skel_probe,
disconnect: skel_disconnect,
fops: &skel_fops,
minor: USB_SKEL_MINOR_BASE,
id_table: skel_table,
};

The variable name is a string that describes the driver. It is used in informational messages printed to the system log. The probe and disconnect function pointers are called when a device that matches the information provided in the id_table variable is either seen or removed.

The fops and minor variables are optional. Most USB drivers hook into another kernel subsystem, such as the SCSI, network or TTY subsystem. These types of drivers register themselves with the other kernel subsystem, and any user-space interactions are provided through that interface. But for drivers that do not have a matching kernel subsystem, such as MP3 players or scanners, a method of interacting with user space is needed. The USB subsystem provides a way to register a minor device number and a set of file_operations function pointers that enable this user-space interaction. The skeleton driver needs this kind of interface, so it provides a minor starting number and a pointer to its file_operations functions.

The USB driver is then registered with a call to usb_register, usually in the driver's init function, as shown in Listing 1.

Listing 1. Registering the USB Driver

When the driver is unloaded from the system, it needs to unregister itself with the USB subsystem. This is done with the usb_unregister function:

static void __exit usb_skel_exit(void)
{
/* deregister this driver with the USB subsystem */
usb_deregister(&skel_driver);
}
module_exit(usb_skel_exit);

To enable the linux-hotplug system to load the driver automatically when the device is plugged in, you need to create a MODULE_DEVICE_TABLE. The following code tells the hotplug scripts that this module supports a single device with a specific vendor and product ID:

/* table of devices that work with this driver */
static struct usb_device_id skel_table [] = {
{ USB_DEVICE(USB_SKEL_VENDOR_ID,
USB_SKEL_PRODUCT_ID) },
{ } /* Terminating entry */
};
MODULE_DEVICE_TABLE (usb, skel_table);
There are other macros that can be used in describing a usb_device_id for drivers that support a whole class of USB drivers. See usb.h for more information on this.

When a device is plugged into the USB bus that matches the device ID pattern that your driver registered with the USB core, the probe function is called. The usb_device structure, interface number and the interface ID are passed to the function:

static void * skel_probe(struct usb_device *dev,
unsigned int ifnum, const struct usb_device_id *id)

The driver now needs to verify that this device is actually one that it can accept. If not, or if any error occurs during initialization, a NULL value is returned from the probe function. Otherwise a pointer to a private data structure containing the driver's state for this device is returned. That pointer is stored in the usb_device structure, and all callbacks to the driver pass that pointer.

In the skeleton driver, we determine what end points are marked as bulk-in and bulk-out. We create buffers to hold the data that will be sent and received from the device, and a USB urb to write data to the device is initialized. Also, we register the device with the devfs subsystem, allowing users of devfs to access our device. That registration looks like the following:

/* initialize the devfs node for this device
and register it */
sprintf(name, "skel%d", skel->minor);
skel->devfs = devfs_register
(usb_devfs_handle, name,
DEVFS_FL_DEFAULT, USB_MAJOR,
USB_SKEL_MINOR_BASE + skel->minor,
S_IFCHR | S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP | S_IROTH,
&skel_fops, NULL);

If the devfs_register function fails, we do not care, as the devfs subsystem will report this to the user.

Conversely, when the device is removed from the USB bus, the disconnect function is called with the device pointer. The driver needs to clean any private data that has been allocated at this time and to shut down any pending urbs that are in the USB system. The driver also unregisters itself from the devfs subsystem with the call:

/* remove our devfs node */
devfs_unregister(skel->devfs);

Now that the device is plugged into the system and the driver is bound to the device, any of the functions in the file_operations structure that were passed to the USB subsystem will be called from a user program trying to talk to the device. The first function called will be open, as the program tries to open the device for I/O. Within the skeleton driver's open function we increment the driver's usage count if it is a module with a call to MODULE_INC_USE_COUNT. With this macro call, if the driver is compiled as a module, the driver cannot be unloaded until a corresponding MODULE_DEC_USE_COUNT macro is called. We also increment our private usage count and save off a pointer to our internal structure in the file structure. This is done so that future calls to file operations will enable the driver to determine which device the user is addressing. All of this is done with the following code:

/* increment our usage count for the module */
MOD_INC_USE_COUNT;
++skel->open_count;
/* save our object in the file's private structure */
file->private_data = skel;
After the open function is called, the read and write functions are called to receive and send data to the device. In the skel_write function, we receive a pointer to some data that the user wants to send to the device and the size of the data. The function determines how much data it can send to the device based on the size of the write urb it has created (this size depends on the size of the bulk out end point that the device has). Then it copies the data from user space to kernel space, points the urb to the data and submits the urb to the USB subsystem (see Listing 2).

Listing 2. The skel_write Function

When the write urb is filled up with the proper information using the FILL_BULK_URB function, we point the urb's completion callback to call our own skel_write_bulk_callback function. This function is called when the urb is finished by the USB subsystem. The callback function is called in interrupt context, so caution must be taken not to do very much processing at that time. Our implementation of skel_write_bulk_callback merely reports if the urb was completed successfully or not and then returns.

The read function works a bit differently from the write function in that we do not use an urb to transfer data from the device to the driver. Instead we call the usb_bulk_msg function, which can be used to send or receive data from a device without having to create urbs and handle urb completion callback functions. We call the usb_bulk_msg function, giving it a buffer into which to place any data received from the device and a timeout value. If the timeout period expires without receiving any data from the device, the function will fail and return an error message (see Listing 3).

Listing 3. The usb_bulk_msg Function

The usb_bulk_msg function can be very useful for doing single reads or writes to a device; however, if you need to read or write constantly to a device, it is recommended to set up your own urbs and submit them to the USB subsystem.

When the user program releases the file handle that it has been using to talk to the device, the release function in the driver is called. In this function we decrement the module usage count with a call to MOD_DEC_USE_COUNT (to match our previous call to MOD_INC_USE_COUNT). We also determine if there are any other programs that are currently talking to the device (a device may be opened by more than one program at one time). If this is the last user of the device, then we shut down any possible pending writes that might be currently occurring. This is all done with:

/* decrement our usage count for the device */
--skel->open_count;
if (skel->open_count <= 0) {
/* shutdown any bulk writes that might be
going on */
usb_unlink_urb (skel->write_urb);
skel->open_count = 0;
}
/* decrement our usage count for the module */
MOD_DEC_USE_COUNT;

One of the more difficult problems that USB drivers must be able to handle smoothly is the fact that the USB device may be removed from the system at any point in time, even if a program is currently talking to it. It needs to be able to shut down any current reads and writes and notify the user-space programs that the device is no longer there (see Listing 4).

Listing 4. The skel_disconnect Function

If a program currently has an open handle to the device, we only null the usb_device structure in our local structure, as it has now gone away. For every read, write, release and other functions that expect a device to be present, the driver first checks to see if this usb_device structure is still present. If not, it releases that the device has disappeared, and a -ENODEV error is returned to the user-space program. When the release function is eventually called, it determines if there is no usb_device structure and if not, it does the cleanup that the skel_disconnect function normally does if there are no open files on the device (see Listing 5).

Listing 5. Cleanup

This usb-skeleton driver does not have any examples of interrupt or isochronous data being sent to or from the device. Interrupt data is sent almost exactly as bulk data is, with a few minor exceptions. Isochronous data works differently with continuous streams of data being sent to or from the device. The audio and video camera drivers are very good examples of drivers that handle isochronous data and will be useful if you also need to do this.

Writing Linux USB device drivers is not a difficult task as the usb-skeleton driver shows. This driver, combined with the other current USB drivers, should provide enough examples to help a beginning author create a working driver in a minimal amount of time. The linux-usb-devel mailing list archives also contain a lot of helpful information.

Resources

Greg Kroah-Hartman is one of the Linux kernel USB developers. His free software is being used by more people than any closed-source projects he has ever been paid to develop.

__________________________

「引」Writing a Simple USB Driver

Writing a Simple USB Driver

April 1st, 2004 by Greg Kroah-Hartman in

Give your Linux box a multicolored light you can see from across the room, and learn how to write a simple driver for the next piece of hardware you want to hook up.
Your rating: None Average: 4.5 (36 votes)

Since this column began, it has discussed how a Linux driver writer can create various types of kernel drivers, by explaining the different kernel driver interfaces including TTY, serial, I2C and the driver core. It is time to move on now and focus on writing real drivers for real hardware. We start by explaining how to determine what kind of kernel driver interface to use, tricks to help figure out how the hardware actually works and a lot of other real-world knowledge.

Let's begin with a goal of making a simple USB lamp device work well with Linux. Editor Don Marti pointed out a neat device, the USB Visual Signal Indicator, manufactured by Delcom Engineering and shown in Figure 1. I have no relationship with this company; I just think they make nice products. This device can be ordered on-line from the Delcom Web site, www.delcom-eng.com. Don challenged me to get the device working on Linux, and this article explains how I did it.

Figure 1. Delcom's USB Visual Signal Indicator is a simple first USB programming project.

The Hardware Protocol

The first goal in trying to write a driver for a device is to determine how to control the device. Delcom Engineering is nice enough to ship the entire USB protocol specification their devices use with the product, and it also is available on-line for free. This documentation shows what commands the USB controller chip accepts and how to use them. They also provide a Microsoft Windows DLL to help users of other operating systems write code to control the device.

The documentation for this device is only the documentation for the USB controller in the lamp. It does not explicitly say how to turn on the different color LEDs. For this, we have to do a bit of research.

After opening up the lamp device, making sure not to lose the spring that easily pops out when unscrewing the device, the circuit board can be inspected (Figure 2). Using an ohmmeter, or any kind of device for detecting a closed circuit, it was determined that the three different LEDs are connected to the first three pins of port 1 on the main controller chip.

In reading the documentation, the USB command to control the levels of the port 1 pins is Major 10, Minor 2, Length 0. The command writes the least significant byte of the USB command packet to port 1, and port 1 is defaulted high after reset. So, that is the USB command we need to send to the device to change the different LEDs.

Figure 2. The three LEDs are connected to the first three pins of the controller chip.

Which LED Is Which?

Now that we know the command to enable a port pin, we need to determine which LED color is connected to which pin. This is easy to do with a simple program that runs through all possible combinations of different values for the three port pins and then sends the value to the device. This program enabled me to create a table of values and LED colors (Table 1).

Table 1. Port Values and the Resulting LED Patterns

Port value in hexPort value in binaryLEDs on
0x00000Red, Green, Blue
0x01001Red, Blue
0x02010Green, Blue
0x03011Blue
0x04100Red, Green
0x05101Red
0x06110Green
0x07111No LEDs on

So, if all pins on the port are enabled (a value of 0x07 hex), no LEDs are on. This matches up with the note in the data sheet that stated, “Port 1 is defaulted high after reset.” It would make sense not to have any LEDs enabled when the device is first plugged in. This means we need to turn port pins low (off) in order to turn on the LED for that pin. Using the table, we can determine that the blue LED is controlled by pin 2, the red LED by pin 1 and the green LED by pin 0.

A Kernel Driver

Armed with our new-found information, we set off to whip up a quick kernel driver. It should be a USB driver, but what kind of interface to user space should we use? A block device does not make sense, as this device does not need to store filesystem data, but a character device would work. If we use a character device driver, however, a major and minor number needs to be reserved for it. And how many minor numbers would we need for this driver? What if someone wanted to plug 100 different USB lamp devices in to this system? To anticipate this, we would need to reserve at least 100 minor numbers, which would be a total waste if all anyone ever used was one device at a time. If we make a character driver, we also would need to invent some way to tell the driver to turn on and off the different colors individually. Traditionally, that could be done using different ioctl commands on the character driver, but we know much better than ever to create a new ioctl command in the kernel.

As all USB devices show up in their own directory in the sysfs tree, so why not use sysfs and create three files in the USB device directory, blue, red and green? This would allow any user-space program, be it a C program or a shell script, to change the colors on our LED device. This also would keep us from having to write a character driver and beg for a chunk of minor numbers for our device.

To start out our USB driver, we need to provide the USB subsystem with five things:

  • A pointer to the module owner of this driver: this allows the USB core to control the module reference count of the driver properly.

  • The name of the USB driver.

  • A list of the USB IDs this driver should provide: this table is used by the USB core to determine which driver should be matched up to which device; the hot-plug user-space scripts use it to load that driver automatically when a device is plugged in to the system.

  • A probe() function called by the USB core when a device is found that matches the USB ID table.

  • A disconnect() function called when the device is removed from the system.

The driver retrieves this information with the following bit of code:

static struct usb_driver led_driver = {
.owner = THIS_MODULE,
.name = "usbled",
.probe = led_probe,
.disconnect = led_disconnect,
.id_table = id_table,
};

The id_table variable is defined as:

static struct usb_device_id id_table [] = {
{ USB_DEVICE(VENDOR_ID, PRODUCT_ID) },
{ },
};
MODULE_DEVICE_TABLE (usb, id_table);

The led_probe() and led_disconnect() functions are described later.

When the driver module is loaded, this led_driver structure must be registered with the USB core. This is accomplished with a single call to the usb_register() function:

retval = usb_register(&led_driver);
if (retval)
err("usb_register failed. "
"Error number %d", retval);

Likewise, when the driver is unloaded from the system, it must unregister itself from the USB core:

usb_deregister(&led_driver);

The led_probe() function is called when the USB core has found our USB lamp device. All it needs to do is initialize the device and create the three sysfs files, in the proper location. This is done with the following code:

/* Initialize our local device structure */
dev = kmalloc(sizeof(struct usb_led), GFP_KERNEL);
memset (dev, 0x00, sizeof (*dev));

dev->udev = usb_get_dev(udev);
usb_set_intfdata (interface, dev);

/* Create our three sysfs files in the USB
* device directory */
device_create_file(&interface->dev, &dev_attr_blue);
device_create_file(&interface->dev, &dev_attr_red);
device_create_file(&interface->dev, &dev_attr_green);

dev_info(&interface->dev,
"USB LED device now attached\n");
return 0;

The led_disconnect() function is equally as simple, as we need only to free our allocated memory and remove the sysfs files:

dev = usb_get_intfdata (interface);
usb_set_intfdata (interface, NULL);

device_remove_file(&interface->dev, &dev_attr_blue);
device_remove_file(&interface->dev, &dev_attr_red);
device_remove_file(&interface->dev, &dev_attr_green);

usb_put_dev(dev->udev);
kfree(dev);

dev_info(&interface->dev,
"USB LED now disconnected\n");

When the sysfs files are read from, we want to show the current value of that LED; when it is written to, we want to set that specific LED. To do this, the following macro creates two functions for each color LED and declares a sysfs device attribute file:

#define show_set(value)                            \
static ssize_t \
show_##value(struct device *dev, char *buf) \
{ \
struct usb_interface *intf = \
to_usb_interface(dev); \
struct usb_led *led = usb_get_intfdata(intf); \
\
return sprintf(buf, "%d\n", led->value); \
} \
\
static ssize_t \
set_##value(struct device *dev, const char *buf, \
size_t count) \
{ \
struct usb_interface *intf = \
to_usb_interface(dev); \
struct usb_led *led = usb_get_intfdata(intf); \
int temp = simple_strtoul(buf, NULL, 10); \
\
led->value = temp; \
change_color(led); \
return count; \
} \

static DEVICE_ATTR(value, S_IWUGO | S_IRUGO,
show_##value, set_##value);
show_set(blue);
show_set(red);
show_set(green);

This creates six functions, show_blue(), set_blue(), show_red(), set_red(), show_green() and set_green(); and three attribute structures, dev_attr_blue, dev_attr_red and dev_attr_green. Due to the simple nature of the sysfs file callbacks and the fact that we need to do the same thing for every different value (blue, red and green), a macro was used to reduce typing. This is a common occurrence for sysfs file functions; an example of this in the kernel source tree is the I2C chip drivers in drivers/i2c/chips.

So, to enable the red LED, a user writes a 1 to the red file in sysfs, which calls the set_red() function in the driver, which calls the change_color() function. The change_color() function looks like:

#define BLUE 0x04
#define RED 0x02
#define GREEN 0x01
buffer = kmalloc(8, GFP_KERNEL);

color = 0x07;
if (led->blue)
color &= ~(BLUE);
if (led->red)
color &= ~(RED);
if (led->green)
color &= ~(GREEN);
retval =
usb_control_msg(led->udev,
usb_sndctrlpipe(led->udev, 0),
0x12,
0xc8,
(0x02 * 0x100) + 0x0a,
(0x00 * 0x100) + color,
buffer,
8,
2 * HZ);
kfree(buffer);

This function starts out by setting all bits in the variable color to 1. Then, if any LEDs are to be enabled, it turns off only that specific bit. We then send a USB control message to the device to write that color value to the device.

It first seems odd that the tiny buffer variable, which is only 8-bytes long, is created with a call to kmalloc. Why not simply declare it on the stack and skip the overhead of dynamically allocating and then destroying it? This is done because some architectures that run Linux cannot send USB data created on the kernel stack, so all data that is to be sent to a USB device must be created dynamically.

LEDs in Action

With this kernel driver created, built and loaded, when the USB lamp device is plugged in, the driver is bound to it. All USB devices bound to this driver can be found in the sysfs directory for the driver:

$ tree /sys/bus/usb/drivers/usbled/
/sys/bus/usb/drivers/usbled/
`-- 4-1.4:1.0 ->
../../../../devices/pci0000:00/0000:00:0d.0/usb4/4-1/4-1.4/4-1.4:1.0

The file in that directory is a symlink back to the real location in the sysfs tree for that USB device. If we look into that directory we can see the files the driver has created for the LEDs:

$ tree /sys/bus/usb/drivers/usbled/4-1.4:1.0/
/sys/bus/usb/drivers/usbled/4-1.4:1.0/
|-- bAlternateSetting
|-- bInterfaceClass
|-- bInterfaceNumber
|-- bInterfaceProtocol
|-- bInterfaceSubClass
|-- bNumEndpoints
|-- blue
|-- detach_state
|-- green
|-- iInterface
|-- power
| `-- state
`-- red

Then, by writing either 0 or 1 to the blue, green and red files in that directory, the LEDs change color:

$ cd /sys/bus/usb/drivers/usbled/4-1.4:1.0/
$ cat green red blue
0
0
0
$ echo 1 > red
[greg@duel 4-1.4:1.0]$ echo 1 > blue
[greg@duel 4-1.4:1.0]$ cat green red blue
0
1
1

This produces the color shown in Figure 3.

Figure 3. The Device with the Red and Blue LEDs On

Is There a Better Way?

Now that we have created a simple kernel driver for this device, which can be seen in the 2.6 kernel tree at drivers/usb/misc/usbled.c or on the Linux Journal FTP site at (ftp.ssc.com/pub/lj/listings/issue120/7353.tgz), is this really the best way to talk to the device? What about using something like usbfs or libusb to control the device from user space without any special device drivers? In my next column, I will show how to do this and provide some shell scripts to control the USB lamp devices plugged in to the system easily.

If you would like to see kernel drivers written for any other types of devices, within reason—I'm not going to try to write an NVIDIA video card driver from scratch—please let me know.

Thanks to Don Marti for bugging me to get this device working on Linux. Without his prodding it would have never gotten finished.

Greg Kroah-Hartman currently is the Linux kernel maintainer for a variety of different driver subsystems. He works for IBM, doing Linux kernel-related things, and can be reached at greg@kroah.com.

__________________________


SPECIAL 15TH ANNIVERSARY SUBSCRIPTION OFFER.
Click here and receive 15 issues for $15*. We are also offering an additional
bonus option. Check out the page!

*Offer valid only in US- Additional bonus discount is available for
subscriptions to Canada, Mexico, and all other countries.

「引」Makefile用法(字串函式)

http://deanjai1.blogspot.com/2006/03/makefile_114307892820637105.html

Makefile用法(字串函式)

字符串處理函數
$(subst ,,)
名稱:字符串替換函數——subst
功能:把字串中的字符串替換成
返回:函數返回被替換過後的字符串。
示例:

$(subst ee,EE,feet on the street)
把「feet on the street」中的「ee」替換成「EE」,返回結果是「fEEt on the strEEt」。
$(patsubst ,,)
名稱:模式字符串替換函數——patsubst
功能:查找中的單詞(單詞以「空格」、「Tab」或「回車」「換行」分隔)是否符合模式,如果匹配的話,則以替換。這裡,可以包括通配符「%」,表示任意長度的字串。如果中也包含「%」,那麼,中的這個「%」將是中的那個「%」所代表的字串。(可以用「\」來轉義,以「\%」來表示真實含義的「%」字符)
返回:函數返回被替換過後的字符串。
示例:
$(patsubst %.c,%.o,x.c.c bar.c)
把字串「x.c.c bar.c」符合模式[%.c]的單詞替換成[%.o],返回結果是「x.c.o bar.o
備註:
這和我們前面「變量章節」說過的相關知識有點相似。如:
$(var:=)
相當於
$(patsubst ,,$(var))」,
而「$(var: =)
則相當於
$(patsubst %,%,$(var))」。
例如有:objects = foo.o bar.o baz.o
那麼,「$(objects:.o=.c)」和「$(patsubst %.o,%.c,$(objects))」是一樣的。
$(strip )
名稱:去空格函數——strip
功能:去掉字串中開頭和結尾的空字符。
返回:返回被去掉空格的字符串值。
示例:
$(strip a b c )
把字串「a b c 」去到開頭和結尾的空格,結果是「a b c」。
$(findstring ,)
名稱:查找字符串函數——findstring
功能:在字串中查找字串。
返回:如果找到,那麼返回,否則返回空字符串。
示例:
$(findstring a,a b c)
$(findstring a,b c)
第一個函數返回「a」字符串,第二個返回「」字符串(空字符串)
$(filter ,)
名稱:過濾函數——filter
功能:以模式過濾字符串中的單詞,保留符合模式的單詞。可以有多個模式。
返回:返回符合模式的字串。
示例:
sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo
$(filter %.c %.s,$(sources))返回的值是「foo.c bar.c baz.s」。
$(filter-out ,)
名稱:反過濾函數——filter-out
功能:以模式過濾字符串中的單詞,去除符合模式的單詞。可以有多個模式。
返回:返回不符合模式的字串。
示例:
objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o
$(filter-out $(mains),$(objects)) 返回值是「foo.o bar.o」。
$(sort )
名稱:排序函數——sort
功能:給字符串中的單詞排序(升序)。
返回:返回排序後的字符串。
示例:$(sort foo bar lose)返回「bar foo lose」 。
備註:sort函數會去掉中相同的單詞。
$(word ,)
名稱:取單詞函數——word
功能:取字符串中第個單詞。(從一開始)
返回:返回字符串中第個單詞。如果中的單詞數要大,那麼返回空字符串。
示例:$(word 2, foo bar baz)返回值是「bar」。
$(wordlist ,,)
名稱:取單詞串函數——wordlist
功能:從字符串中取從開始到的單詞串。是一個數字。
返回:返回字符串中從的單詞字串。如果中的單詞數要大,那麼返回空字符串。如果大於的單詞數,那麼返回從開始,到結束的單詞串。
示例: $(wordlist 2, 3, foo bar baz)返回值是「bar baz」。
$(words )
名稱:單詞個數統計函數——words
功能:統計中字符串中的單詞個數。
返回:返回中的單詞數。
示例:$(words, foo bar baz)返回值是「3」。
備註:如果我們要取中最後的一個單詞,我們可以這樣:$(word $(words ),)
$(firstword )
名稱:首單詞函數——firstword
功能:取字符串中的第一個單詞。
返回:返回字符串的第一個單詞。
示例:$(firstword foo bar)返回值是「foo」。
備註:這個函數可以用word函數來實現:$(word 1,)
以上,是所有的字符串操作函數,如果搭配混合使用,可以完成比較複雜的功能。這裡,舉一個現實中應用的例子。我們知道,make使用「VPATH」變量來指定「依賴文件」的搜索路徑。於是,我們可以利用這個搜索路徑來指定編譯器對頭文件的搜索路徑參數CFLAGS,如:
override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))
如果我們的「$(VPATH)」值是「src:../headers」,那麼「$(patsubst %,-I%,$(subst :, ,$(VPATH)))」將返回「-Isrc -I../headers」,這正是ccgcc搜索頭文件路徑的參數。
三、文件名操作函數
下面我們要介紹的函數主要是處理文件名的。每個函數的參數字符串都會被當做一個或是一系列的文件名來對待。
$(dir )
名稱:取目錄函數——dir
功能:從文件名序列中取出目錄部分。目錄部分是指最後一個反斜槓(「/」)之前的部分。如果沒有反斜槓,那麼返回「./」。
返回:返回文件名序列的目錄部分。
示例: $(dir src/foo.c hacks)返回值是「src/ ./」。
$(notdir )
名稱:取文件函數——notdir
功能:從文件名序列中取出非目錄部分。非目錄部分是指最後一個反斜槓(「/」)之後的部分。
返回:返回文件名序列的非目錄部分。
示例: $(notdir src/foo.c hacks)返回值是「foo.c hacks」。
$(suffix )
名稱:取後綴函數——suffix
功能:從文件名序列中取出各個文件名的後綴。
返回:返回文件名序列的後綴序列,如果文件沒有後綴,則返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是「.c .c」。
$(basename )
名稱:取前綴函數——basename
功能:從文件名序列中取出各個文件名的前綴部分。
返回:返回文件名序列的前綴序列,如果文件沒有前綴,則返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是「src/foo src-1.0/bar hacks」。
$(addsuffix ,)
名稱:加後綴函數——addsuffix
功能:把後綴加到中的每個單詞後面。
返回:返回加過後綴的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是「foo.c bar.c」。
$(addprefix ,)
名稱:加前綴函數——addprefix
功能:把前綴加到中的每個單詞後面。
返回:返回加過前綴的文件名序列。
示例:$(addprefix src/,foo bar)返回值是「src/foo src/bar」。
$(join ,)
名稱:連接函數——join
功能:把中的單詞對應地加到的單詞後面。如果的單詞個數要比的多,那麼,中的多出來的單詞將保持原樣。如果的單詞個數要比多,那麼,多出來的單詞將被複製到中。
返回:返回連接過後的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是「aaa111 bbb222 333