2008年9月27日星期六

unaligned.c报错的一个原因

unsigned long val=3;
printk("*val = %x\r\n",&val);
这样就会出现unaligned报错,因为未4字节对齐

2008年9月24日星期三

通过__setup宏解析bootloader传递上来的自定义参数

bootloader参数console=ttyS0,115200 root=/dev/ram rw init=/linuxrc之类的,在此后面再加一条ts=1也就是console=ttyS0,115200 root=/dev/ram rw init=/linuxrc ts=1

在内核里添加ts=对应的handle函数及__setup宏
static int __init ts_setup(char *options)
{
printk("[ts_setup] %s\r\n",options);
return 1;
}
__setup("ts=", ts_setup);

当发现bootloader参数中含有ts=字符串时就调用ts_setup函数,=后的值就是options字符串。这样除了在menuconfig里选编译文件外还可以通过setup来选功能。让我想起了前2天老板叫我用配置文件确定gpio转i2c的io口,我之前用rootfs里的配置文件,现在可以转成用__setup来配置gpio口了。
setup在initcall之前

2008年9月23日星期二

添加自己的i2c adapter (gpio转i2c)

用的这一款CPU没有i2c host,所以gpio模拟i2c,本来一个设备不用做i2c adapter,直接模拟就是了,但是接了3个i2c设备,ts,radio,touchpad,所以要用i2c总线。driver/i2c/下已经有比较成熟的代码了,所以添加我们这款CPU的adapter,然后做设备driver。
i2c通讯方式中有包含bit方式通讯,driver/i2c/algos/i2c-algo-bit.c
需要添加的是i2c-algo-bit.c里的setsda,setscl,getsda,getscl 4个函数

/* --- setting states on the bus with the right timing: --------------- */
#define setsda(adap,val) adap->setsda(adap->data, val)
#define setscl(adap,val) adap->setscl(adap->data, val)
#define getsda(adap) adap->getsda(adap->data)
#define getscl(adap) adap->getscl(adap->data)

做adapter数据结构
struct t30x_gpio_i2c_pins
{
unsigned int scl_gpio;
unsigned int scl_inout;
unsigned int scl_altfunc;
unsigned int sda_gpio;
unsigned int sda_inout;
unsigned int sda_altfunc;
};

struct t30x_gpio_i2c_data
{
struct t30x_gpio_i2c_pins gpio_pins;
struct i2c_adapter adapter;
struct i2c_algo_bit_data algo_data;
};

static struct t30x_gpio_i2c_data t30x_gpio_i2cd =
{
.gpio_pins =
{
.scl_gpio = 0x4,
.scl_inout = 0xff,
.sda_gpio = 0x8,
.sda_inout = 0xff,
},
.adapter =
{
.name = "t302b-gpio-i2c",
.owner = THIS_MODULE,
.id = I2C_HW_B_T30XGPIO,
.retries = 2,
.class = I2C_CLASS_ALL,
},
.algo_data =
{
.setsda = t30x_gpio_bit_setsda,
.setscl = t30x_gpio_bit_setscl,
.getsda = t30x_gpio_bit_getsda,
.getscl = NULL,
.udelay = 10,
.mdelay = 10,
.timeout = 100,
},
};

完成3个函数
static void t30x_gpio_bit_setscl(void *data, int val)
{
struct t30x_gpio_i2c_pins* gdata = (struct t30x_gpio_i2c_pins*)data;
//set output
if(T30X_GPIO_OUTPUT != gdata->scl_inout)
{
writel(readl(GPIO_OE)(gdata->sda_gpio), GPIO_OE);
gdata->scl_inout = T30X_GPIO_OUTPUT;
}
if (val)
writel(gdata->scl_gpio, GPIO_SET);
else
writel(gdata->scl_gpio, GPIO_CLEAR);
}

static void t30x_gpio_bit_setsda(void *data, int val)
{
struct t30x_gpio_i2c_pins* gdata = (struct t30x_gpio_i2c_pins*)data;
//set output
if(T30X_GPIO_OUTPUT != gdata->sda_inout)
{
writel(readl(GPIO_OE)(gdata->sda_gpio), GPIO_OE);
gdata->sda_inout = T30X_GPIO_OUTPUT;
}

if (val)
writel(gdata->sda_gpio, GPIO_SET);
else
writel(gdata->sda_gpio, GPIO_CLEAR);
}

static int t30x_gpio_bit_getsda(void *data)
{
int sda;
struct t30x_gpio_i2c_pins* gdata = (struct t30x_gpio_i2c_pins*)data;
//set input
if(T30X_GPIO_INPUT != gdata->sda_inout)
{
writel(readl(GPIO_OE)&(~(gdata->sda_gpio)), GPIO_OE);
gdata->sda_inout = T30X_GPIO_INPUT;
}
sda = readl(GPIO_LEVEL)&(gdata->sda_gpio);
return sda? 1 : 0;
}

完成gpio初始化函数
static int t30x_gpio_hw_init(void *data)
{
struct t30x_gpio_i2c_pins* gdata = (struct t30x_gpio_i2c_pins*)data;
//altfunc select
writel(readl(GPIO_ALTFUNC_SEL)&(~((gdata->scl_altfunc)(gdata->sda_altfunc))), GPIO_ALTFUNC_SEL);

//disable gpio interrupt mask
writel(readl(GPIO_INT_MASK)((gdata->scl_gpio)(gdata->sda_gpio)), GPIO_INT_MASK);

//set output
writel(readl(GPIO_OE)((gdata->scl_gpio)(gdata->sda_gpio)), GPIO_OE);

//set level high
writel(((gdata->scl_gpio)(gdata->sda_gpio)), GPIO_SET);
return 0;
}

模块初始化
static int __init t30x_gpio_i2c_init(void)
{
int err;
struct t30x_gpio_i2c_data *drv_data = &t30x_gpio_i2cd;
drv_data->adapter.algo_data = &(drv_data->algo_data);

//private data = gpio pins, set sda/scl will use it
drv_data->algo_data.data = &(drv_data->gpio_pins);

//gpio config
t30x_gpio_hw_init(&(drv_data->gpio_pins));

//add to i2c bit algos
if ((err = i2c_bit_add_bus(&drv_data->adapter) != 0))
{
printk(KERN_ERR "\033[0;40;36mERROR: Could not add %s to i2c bit algos\033[0m\r\n", drv_data->adapter.name); return err;
}

printk("t30x gpio i2c bus initialized\r\n");
return 0;
}

static void __exit t30x_gpio_i2c_exit(void)
{
struct t30x_gpio_i2c_data *drv_data = &t30x_gpio_i2cd;
i2c_bit_del_bus(&drv_data->adapter);
return;
}

module_init(t30x_gpio_i2c_init);
module_exit(t30x_gpio_i2c_exit);
MODULE_DESCRIPTION("GPIO-based I2C adapter for t30x systems");
MODULE_LICENSE("GPL");
MODULE_AUTHOR("zhourr");

2008年9月21日星期日

打算把ph701机子用上,作为mp4的功能

希望我上下班的途中能用ph701看电影。701的数据线我没从公司带出来,傻了。只好用2410开发板调,然后再看怎么办吧。我速度应该比较慢,每天晚上的杂事多,最好每天能保证1小时在这上面。

2008年9月17日星期三

内核读写文件

内核读写文件当然有用,比如打印的一些重要信息掉电后就看不到了,所以要存在flash里。直接调用flash驱动读写,还不如直接读写文件。应用程序可以直接用open, read, write, close文件操作,对应到内核的系统调用是sys_open, sys_read, sys_write, sys_close.在syscall里排号挺靠前的,第3,4,5,6
SYSCALL(0, 0) /* 00 */
SYSCALL(sys_exit, 1)
SYSCALL(sys_ni_syscall, 0)
SYSCALL(sys_read, 3)
SYSCALL(sys_write, 3)
SYSCALL(sys_open, 3) /* 05 */
SYSCALL(sys_close, 1)

一般没人直接调用sys_*函数,可能可以,我没试过。
sys_open -> do_sys_open -> filp_open
sys_close -> filp_close
sys_read -> vfs_read -> file->f_op->read
sys_write -> vfs_write -> file->f_op->write

因为是内核空间,要读写用户空间数据需要在read/write前设置,用来表示buf是内核地址还是用户地址,0xc0000000以上还是以下。
file->f_pos表示文件内读写位置,open时是0,read后可修改file->f_pos的值,再read/write

tsfile = filp_open(tsdev->fileName, O_RDONLY, 0);
set_fs(KERNEL_DS);
err = tsfile->f_op->read(tsfile, tsbuf, TSBUFSIZE, &(tsfile->f_pos));
set_fs(USER_DS);
filp_close(tsfile, 0);

touch screen注册字符设备

这2天写了touch screen驱动,IC是tsc2007,用gpio口模拟i2c。不管IC和模拟。这篇就写linux2.6里添加的ts 字符设备,本来正规是在input device里添加的,但是方案商的keypad和ir也是用字符设备做的,所以就配合它这样了。

1>
模块初始化

注册字符设备
rc = register_chrdev(tsdev->major, tsdev->devName, &ts_fops);
if (rc < 0)
{
printk("touch screen driver could not get major number\n");
return rc;
}

IC初始化
cp2007_gpio_init(tsdev);

与某个中断源共享pen down中断,注册
/*PENIRQ connect to SAR, share interrupt, set ts register flag, then pendown irq can up the tsdev->sem*/
tsRegisterIrq(tsPenIrq);

起内核线程
kernel_thread(tsThread,tsdev,0);

2>
完成ts_fops,主要是read和poll,ts的open和release可以do nothing
static struct file_operations ts_fops = {
.owner = THIS_MODULE,
.read = ts_read,
.poll = ts_poll,
.open = ts_open,
.release = ts_close,
};

//虽然不是注册到input设备里,但是read上去的数据结构还是input。我们用的是nanox ads touchscreen,所以就用的这个结构
struct ts_event
{
short x;
short y;
short pressure;/*equal to Z*/
};

static ssize_t ts_read(struct file *filp, char __user *buf, size_t count, loff_t *ptr)
{
struct tsc2007* tsdev = filp->private_data;
int retval = -1;

/*nonblock*/
if ((tsdev->head == tsdev->tail) && (filp->f_flags & O_NONBLOCK))
return -EAGAIN;
/*block*/
//如果是阻塞方式读取,并且buffer里无数据,那么wait
retval = wait_event_interruptible(tsdev->wait, tsdev->head != tsdev->tail);
if (retval)
return retval;
/*if circle buf have event, copy to the buf, else return*/
while ((tsdev->head != tsdev->tail) && (retval + sizeof (struct ts_event) <= count))
{
if (copy_to_user ((void*)(buf + retval), (void*)&(tsdev->eventBuf[tsdev->tail]), sizeof (struct ts_event)))
return -EFAULT;
tsdev->tail = (tsdev->tail + 1) & (TS_EVENT_CNT- 1);
retval += sizeof (struct ts_event);
}
return retval;
}

//当应用用阻塞的方式来读取数据,也就是调用select,对应到内核就是poll,无数据返回0,有数据返回POLLIN POLLRDNORM
static unsigned int ts_poll(struct file * filp, poll_table *wait)
{
struct tsc2007* tsdev = filp->private_data;
poll_wait(filp, &tsdev->wait, wait);
return (tsdev->head == tsdev->tail) ? 0 : (POLLIN POLLRDNORM);
}

3>
实现ts线程,死循环,一直采样并把数据放到circle buffer里,并wakeup等待队列,让app read
当采样一直是pen down,就循环采,如果是pen up,那么被信号量阻塞,直到发生pen down中断,up信号量
一般应该用ostimer来做采样间隔,这里的ostimer给keypad单独用了,且ts又不需要手写,只是电屏而已,所以虽然schedule时间可能很长,不过能让pen down时cpu占用率不会提高很多
/*touch panel main loop*/
static int tsThread(struct tsc2007* tsdev)
{
//don't have ps/top command, no need to daemon it. name it
while(1)
{
schedule();
if(PEN_DOWN == tsProcess(tsdev))
continue;
sema_init(&(tsdev->sem),0);
down(&(tsdev->sem));
}
return 0;
}

irqreturn_t tsPenIrq(int irq, void *dev_id, struct pt_regs * regs)
{
up(&(ts2007_dev.sem));
return IRQ_NONE;
}

4>
ts采样后的处理
/*filter & average 3 point*/
unsigned char tsProcess(struct tsc2007* tsdev)
{
struct ts_event event;
unsigned char cnt;
unsigned char penstate;

memset((void*)&event, 0, sizeof(struct ts_event));
memset((void*)&(tsdev->pointBuf), 0, sizeof(tsdev->pointBuf));

//连续采样3个点
for(cnt = 0; cnt < TS_SAMP_P_CNT; cnt++)
penstate = tsGetPoint(&tsdev->pointBuf[cnt], tsdev);

//判断3个点之间的间距,如果超过规定值(认为是飞笔),或者是pen up就认为是发送pen up消息
if(TS_ERR == tsCheckGap(tsdev))
{
event.x = 0;
event.y = 0;
event.pressure = 0;
tsdev->upTimes++;
if(tsdev->upTimes > 2)
return PEN_UP;

//插入事件到circle buffer
tsInsertEvent(&event,tsdev);
}

//first pen down point, discard it
if(2 == tsdev->upTimes)
{
tsdev->upTimes = 0;
return PEN_DOWN;
}
else
tsdev->upTimes = 0;

//3个点取重心
for(cnt = 0; cnt < TS_SAMP_P_CNT; cnt++)
{
event.x += tsdev->pointBuf[cnt].x;
event.y += tsdev->pointBuf[cnt].y;
}

event.x /= TS_SAMP_P_CNT;
event.y /= TS_SAMP_P_CNT;
event.pressure = 100;

tsInsertEvent(&event,tsdev);

//唤醒block read里的等待队列
wake_up_interruptible(&tsdev->wait);
return PEN_DOWN;
}

void tsInsertEvent(struct ts_event* event, struct tsc2007* tsdev)
{
tsdev->head = (tsdev->head + 1) & (TS_EVENT_CNT- 1);
memcpy((void*)&(tsdev->eventBuf[tsdev->head]), (void*)event, sizeof(struct ts_event));
return;
}

顺便贴的函数
#define CHECK_GAP_XY(point1, point2) \
do{ \
if((!point1.pressure) (!point2.pressure)) \
{ \
tsdev->upTimes++; \
return TS_ERR; \
} \
if((abs(point1.x - point2.x) > MAX_GAP_X)(abs(point1.y - point2.y) > MAX_GAP_Y)) \
return TS_ERR; \
}while(0)

unsigned char tsCheckGap(struct tsc2007* tsdev)
{
CHECK_GAP_XY(tsdev->pointBuf[0], tsdev->pointBuf[1]);
CHECK_GAP_XY(tsdev->pointBuf[0], tsdev->pointBuf[2]);
CHECK_GAP_XY(tsdev->pointBuf[1], tsdev->pointBuf[2]);
return TS_OK;
}

模块数据结构
struct tsc2007
{
int major;/*ts dev major*/
char devName[DEVNAME_LENGTH];/*dev name to register*/
unsigned char devI2cAddr;/*cp2007 i2c addr*/
union cp2007Cmd cmd;
int i2cDelay;/*iic delay u seconds*/
int adcDelay;/*wait for adc complete u seconds*/
wait_queue_head_t wait;/*ts event wakeup for ts poll*/
unsigned char head;/*for eventBuf cycle buf*/
unsigned char tail;/*for eventBuf* cycle buf*/
struct ts_event eventBuf[TS_EVENT_CNT];/*ts event buf to be read for tsdev*/
unsigned char upTimes;/*pen up times*/
struct ts_event pointBuf[TS_SAMP_P_CNT];/*point sample,3 point choose 1*/
unsigned char PressThresh;/*pen down pressure threshold*/
struct semaphore sem;/*pen down semaphore*/
unsigned char sclgpio;
unsigned char sdagpio;
char fileName[DEVNAME_LENGTH];/*老板说要从文件里读出来模拟I2C的GPIO口是多少,说这样以后别的产品换IO口了不用重新编译内核, 多土啊*/
};

2008年9月10日星期三

在procfs里添加可读写文件做调试

procfs可以查看内核信息,不过我刚开始做的时候还不知道procfs里的文件可以写。一旦可以写了,就有方便我们的调试接口,嗯,那么留在这里,忘了的时候看看。sysfs其实更好一些,以后再写一个sysfs的好了
做的是pcf50606寄存器读写接口文件

1>
在procfs的根目录下注册pcf目录/proc/pcf
static struct proc_dir_entry *pcfdir;
#define PCF_DIRNAME "pcf"
pcfdir = proc_mkdir( PCF_DIRNAME, &proc_root);
if ( pcfdir == NULL )
{
PMIC_PRINT(KERN_ERR "can't create /proc/" PCF_DIRNAME "\n");
return ;
}

2>
在/proc/pcf下建立pcf50606所有寄存器读写文件,比如/proc/pcf/ID
typedef struct pcf50606_reg_entry
{
u8 addr;
char *name;
unsigned short low_ino;
} pcf50606_reg_entry_t;

static pcf50606_reg_entry_t pcf50606_regs[] = {
{ 0x00 , "ID" },
{ 0x01 , "OOCS" },
{ 0x02 , "INT1" },
{ 0x03 , "INT2" },
{ 0x04 , "INT3" },
...
};

//读写处理函数
static struct file_operations proc_reg_operations = {
read: proc_read_reg,
write: proc_write_reg
};

//建立文件时把读写处理函数挂上
#define NUM_OF_PCF50606_REG_ENTRY ( sizeof(pcf50606_regs) / sizeof(pcf50606_reg_entry_t))
for ( i = 0 ; i < NUM_OF_PCF50606_REG_ENTRY; i++ ) {
entry = create_proc_entry( pcf50606_regs[i].name,
S_IWUSR S_IRUSR S_IRGRP S_IROTH, pcfdir );
if (entry) {
pcf50606_regs[i].low_ino = entry->low_ino; //把文件inode赋值给pcf变量,读写时根据inode判断是哪个
entry->proc_fops = &proc_reg_operations;
} else {
#define PMIC_PRINT(fmt, args...) printk("%s%s(): "fmt"", "[PCF50606] ",__FUNCTION__, ##args)
PMIC_PRINT( KERN_ERR "can't create /proc/" PCF_DIRNAME );
return ;
}
}

3>
创建读写处理函数
static ssize_t proc_read_reg( struct file *file, char *buf, size_t nbytes, loff_t *ppos )
{
int i_ino = (file->f_dentry->d_inode)->i_ino;
int i, count;
char outputbuf[15];
u8 addr = 0;

if (*ppos > 0 )
return 0;

//根据注册文件时对应的inode来确定到底那个文件名对应哪个寄存器,读写都这样
for ( i =0 ;i < NUM_OF_PCF50606_REG_ENTRY; i++ ) {
if ( pcf50606_regs[i].low_ino == i_ino ) {
addr = pcf50606_regs[i].addr;
break;
}
}

if ( i == NUM_OF_PCF50606_REG_ENTRY )
return -EINVAL;

//ul转字符串
count = sprintf(outputbuf, "0x%02x\n", readPCFregister(addr));
*ppos += count;
if (count > nbytes )
return -EINVAL;
if (copy_to_user(buf, outputbuf, count))
return -EFAULT;
return count;
}

static ssize_t proc_write_reg( struct file *file, const char *buf, size_t count, loff_t *ppos )
{
int i_ino = (file->f_dentry->d_inode)->i_ino;
int i;
char *endp;
u8 addr = 0;

for ( i = 0 ; i < NUM_OF_PCF50606_REG_ENTRY; i++ ) {
if ( pcf50606_regs[i].low_ino == i_ino ) {
addr = pcf50606_regs[i].addr;
break;
}
}
if ( i == NUM_OF_PCF50606_REG_ENTRY )
return -EINVAL;

//字符串转ul
writePCFregister( addr, simple_strtoul( buf, &endp, 0));
return ( count+endp-buf );
}

4>
在procfs中读写文件
cd /proc/pcf/ 到pcf目录
ll 列出所有寄存器名
cat * 列出所有寄存器值
或者单独的cat ID,显示ID寄存器值
echo 1 > ID 写ID寄存器,>左右有空格,1作为字符串传进去

除了这种多个文件公用一个读写函数的读写寄存器的接口,单独建立读写文件当然会更简单,比如常用的打印变量。在procfs里动态改变这个全局变量,而达到屏蔽/放开不同模块的信息,而不需要重新编译来开关打印宏。

放弃了的mips usb isoc

前段时间在调一款mips芯片的usb camera,失败了。方案商提供的代码已经可用U盘。也就是ehci host controller的块传输,控制传输没有问题。camera对应的是等时传输,方案商未测试过。我花了6周的时间试了1.1camera及2.0 uvc camera。始终是ehci等时传输有问题,一直觉得这个可能是瓶颈,驱动设置camera 或uvc设置,降低性能会做的出来。但是到第6周的周末我找leiming帮我调,他的初步结论是ehci host本身的问题,于是我放弃了。leiming是很热心的人,很感谢。6周,又一个丢人的时间片。这是我工作中第2次放弃做一个模块。第1次是破解加密芯片,发现是硬件加密狗,无人曾破解过时放弃。
那么我把失败的尝试列一下:
有2种现象差别,其余的现象都差不多
uframe传输数据大于1024,multi大于1时,一直出现xactErr错误
uframe传输数据小于等于1024,multi等于1时,出现一会xactErr后出现babble错误

1.修改uvc的uvcvideo.h中每次传输使用到的URB数
/* Number of isochronous URBs. */
#define UVC_URBS 5
修改为
#define UVC_URBS 1
出现4次Failed to resubmit status URB (-38)就一次也没有,但是这个规律是N-1次出现,除了少了这个错误打印外依然是xactErr和babble。所以不认为改成1可以解决。-38 -EL2NSYNC错误出现在2.6.14的版本里,高版本内核是-EFBIG错误。

2.修改uvc的uvcvideo.h中每次传输最多用多少个iso包,一个包对应一个uframe
/* Maximum number of packets per isochronous URB. */
#define UVC_MAX_ISO_PACKETS 40
修改为
#define UVC_MAX_ISO_PACKETS 8
1,2,4,6,8,10,20之类的值都试过,也就是每次URB传输不用40个uframe传输,每次少一点,希望能解决ehci瓶颈,但是没效果,不管多少,都会出现babble

3.修改uvc的uvc_video.c中uvc_get_video_ctrl函数里一个uframe最大传输多少字节
ctrl->dwMaxPayloadTransferSize = le32_to_cpu(get_unaligned((__le32 *)&data[22]));
修改为
ctrl->dwMaxPayloadTransferSize = 512;
512,128,1024,等值都试过。camera有6个setting,虽然每个setting的max packet size不同,从128到3072.但是它们的uframe最大传输字节都是3072,也就是multi=3。马上就是xactErr错误

4.去掉uvc_driver.c中反应status的中断urb传输
/* Initialize the interrupt URB */
if ((ret = uvc_status_init(dev)) < 0)
{
uvc_printk(KERN_INFO, "Unable to initialize the status "
"endpoint (%d), status interrupt will not be "
"supported.\n", ret);
}

5.方案商没有ehci capability register接口,读出来的值是0,它们用一个变量来代表register寄存器的值
Ehci_caps.hcs_params = 0x00000001;
Ehci_caps.hcc_params = 0x00000086;
因为不确定实际硬件到底是多少,所以将hcc修改多种组合无效果。
这里主要的2个参数:
1 isoc threshold为一个frame
2 periodic framelist长度为256,而不是1024

6.修改iso_stream_schedule里schedule不及时但是也没落后太久的处理,这个是根据网上的patch改的,但是改了后不产生ehci中断了
if (unlikely ((start + sched->span) > max))
goto ready;

其实也没试几种办法,也不至于大改ehci。没有能力解决问题,怎么办

cscope

1. 下载vim7.0
http://download.chinaunix.net/download.php?id=21977&ResourceID=11091

2. 编译vim
1>配置对cscope的支持
./configure --enable-cscope
2>编译
make
make install
vim在/usr/share/vim/vim70/里

3.下载taglist_45.zip
http://vim.sourceforge.net/scripts/script.php?script_id=273
解压后是doc,plugin文件夹,把里面的2个文件taglist.txt,taglist.vim分别复制到/usr/share/vim/vim70/下的doc,plugin文件夹里

4.到要编辑的源码目录,输入命令
cscope -Rbkq
R 表示把所有子目录里的文件也建立索引
b 表示cscope不启动自带的用户界面,而仅仅建立符号数据库
q生成cscope.in.out和cscope.po.out文件,加快cscope的索引速度
k在生成索引文件时,不搜索/usr/include目录
ctags -R

5.下载cscope_maps.vim,也就是cscope快捷键,放到/usr/share/vim/vim70/plugin文件夹
http://cscope.sourceforge.net/cscope_maps.vim

6.开始用了,vim打开文件,如果是vi打开就不认cscope命令
1>直接跳到start_kernel函数定义处
vim -t start_kernel
2>使用cscope命令,cscope find命令简化是cs f
cscope find s ---- 查找C语言符号,即查找函数名、宏、枚举值等出现的地方
cscope find g ---- 查找函数、宏、枚举等定义的位置,类似ctags所提供的功能
cscope find d ---- 查找本函数调用的函数
cscope find c ---- 查找调用本函数的函数
cscope find t: ---- 查找指定的字符串
cscopecope find e ---- 查找egrep模式,相当于egrep功能,但查找速度快多了
cscope find f ---- 查找并打开文件,类似vim的find功能
cscope find i ---- 查找包含本文件的文
Ctrl+]将跳到光标所在变量或函数的定义处 Ctrl+T返回
3>cscope命令可用快捷键
cs f s start_kernel用快捷键是让光标处于start_kernel字符串处,按Ctrl+\,再按s,也就是Ctrl+\等于cs f ? start_kernel,最方便的是省了敲符号名。如果想在另一个窗口弹出可使用快捷键Ctrl+@(水平窗口),连续2次按Ctrl+@(垂直窗口)。这个快捷键就是之前下载的cscope_maps.vim定义的,需要换按键就修改这个文件
4>使用taglist办法是在vim里输入TlistToggle,但是我这里就是用不了,提示92: Not an editor command: TlistToggle

共用中断源

多个事件共用中断源应该是很常见的,比如GPIO或外围设备
1 只调用一次request_irq,所有中断都根据注册的函数来判断状态而分发
2 自己初始化irq,所有中断事件都可调用request_irq挂自己的处理函数,当然分发函数还是存在,只是在do_IRQ之前
拿pcf50606的中断代码来说,平台是pxa270

1>
#define GPIOX_TO_IRQ(x) \
((x) - 2 + 64)
#define IRQ_GPIO(x) (((x) < 2) ? (IRQ_GPIO0 + (x)) : GPIOX_TO_IRQ(x))
#define PCF50606_IRQ(x) (IRQ_GPIO(128) + 1 + (x)) //也就是虚拟中断源号
void __init pcf50606_init_irq(void)
{
... //读pcf寄存器来清中断并设置中断掩码,未贴
// initialize PCF interrupt related registers //手动初始化虚拟中断源对应的irq_desc
for ( irq = PCF50606_IRQ(0); irq <= PCF50606_IRQ( MAX_PCF_INT ) ; irq++ ) {
irq_desc[irq].valid = 1;
irq_desc[irq].probe_ok = 1;
irq_desc[irq].mask_ack = PCF50606_mask_and_ack_irq;
irq_desc[irq].mask = PCF50606_mask_irq;
irq_desc[irq].unmask = PCF50606_unmask_irq;
//mask,unmask函数与enable_irq/disable_irq对应,把使能禁止中断对应到设置pcf50606中断mask中
}

set_GPIO_IRQ_edge( GPIO_PCF50606_INT , GPIO_FALLING_EDGE ); //相当于request_irq里的触发中断类型
setup_arm_irq( IRQ_GPIO( GPIO_PCF50606_INT ) , &pcf50606_irq ); //挂一个包含中断分发函数的数据结构pcf50606_irq
enable_irq( IRQ_GPIO(GPIO_PCF50606_INT) ); //request_irq函数最后也有enable_irq
}

2>
//中断action结构及处理函数pcf50606_irq_demux
static struct irqaction pcf50606_irq = {
name: "pcf50606",
handler: pcf50606_irq_demux,
flags: SA_INTERRUPT
};

void pcf50606_irq_demux( int irq, void *dev_id, struct pt_regs *regs )
{
int i ;
unsigned long stat0;
int flags;
//关中断
save_and_cli(flags);
disable_irq( IRQ_GPIO( GPIO_PCF50606_INT ));

while(1) {
stat0 = readPCFregister( PCF50606_INT3 ) << 16 |
readPCFregister( PCF50606_INT2 ) << 8 |
readPCFregister( PCF50606_INT1 );
//根据pcf50606中断寄存器中的值判断是哪个中断,调用do_IRQ,最终会走到它们注册该虚拟中断源的函数,因为同时可能产生多个中断,所以while里把所有产生中断的处理函数都执行一遍
for ( i = 0 ; i < 24 ; i++ )
if ( stat0 & ( 1 << i ) )
do_IRQ( PCF50606_IRQ(i), regs );
}
//开中断
enable_irq( IRQ_GPIO( GPIO_PCF50606_INT ));
restore_flags(flags);
}

3>
在某处注册希望处理的中断
err = request_irq ( PCF50606_ONKEYFM_IRQ, onkey_interrupt, 0, "Second", NULL );
err = request_irq ( PCF50606_ONKEYRM_IRQ , onkey_interrupt, 0, "Second", NULL );
err = request_irq ( PCF50606_EXTONRM_IRQ , extonkey_interrupt, 0, "Second", NULL );
err = request_irq ( PCF50606_EXTONFM_IRQ , extonkey_interrupt, 0, "Second", NULL );
虚拟中断源的号当然也要分配好,这里是191以上了
#define PCF50606_ONKEYRM_IRQ PCF50606_IRQ(0)
#define PCF50606_ONKEYFM_IRQ PCF50606_IRQ(1)
#define PCF50606_ONKEY1SM_IRQ PCF50606_IRQ(2)
#define PCF50606_EXTONRM_IRQ PCF50606_IRQ(3)
#define PCF50606_EXTONFM_IRQ PCF50606_IRQ(4)
...

4>
顺便贴一下mask/unmask

static void PCF50606_mask_irq( unsigned int irq )
{
unsigned int bit_value = ( irq - PCF50606_IRQ(0)) ;
unsigned int mask = 1 << (bit_value % 8 );
if ( bit_value > 15 )
writePCFregister( PCF50606_INT3M, readPCFregister( PCF50606_INT3M ) | mask ) ;
else if ( bit_value > 7 )
writePCFregister( PCF50606_INT2M, readPCFregister( PCF50606_INT2M ) | mask ) ;
else
writePCFregister( PCF50606_INT1M, readPCFregister( PCF50606_INT1M ) | mask ) ;
}

static void PCF50606_unmask_irq( unsigned int irq )
{
unsigned int bit_value = ( irq - PCF50606_IRQ(0)) ;
unsigned int mask = 1 << (bit_value % 8 );

if ( bit_value > 15 )
writePCFregister( PCF50606_INT3M, readPCFregister( PCF50606_INT3M ) & ~mask ) ;
else if ( bit_value > 7 )
writePCFregister( PCF50606_INT2M, readPCFregister( PCF50606_INT2M ) & ~mask ) ;
else
writePCFregister( PCF50606_INT1M, readPCFregister( PCF50606_INT1M ) & ~mask ) ;
}

换个平台试试看我漏掉些什么没。

2008年9月8日星期一

又打算开始写blog了

嗯,记一笔,希望能持续