快乐虾
http://blog.csdn.net/lights_joy/
本文适用于
ADSP-BF561
Visual DSP++ 5.0(update 6)
Bfin-uclinux-2009r1.6
欢迎转载,但请保留作者信息
在驱动开发完成之后,同样有一个发布的问题,原来的计划是在开发完成后将这个dxe文件链接到内核中,在初始化的时候解开这个DXE文件并执行其中的代码。现在我们已经使用appstub完成了dxe应用程序的加载,那么是否有可能借用这个程序呢?
当然可以,因为都需要分析dxe文件格式,并将其中的内容复制到正确的位置。但是它们有两点不同:
1、它们一个在内核态执行程序,一个在用户态执行程序。
2、应用程序可以反复运行,驱动则通常只初始化一次,如果要再次初始化通常需要先卸载。
为了解决上面的两个问题,我们开发一个驱动的驱动:
/*
* arch/blackfin/mach-common/vdsp.c - Visual dsp driver
*
* Copyright 2009 lights joy.
*
* Licensed under the GPL-2 or later.
*
*/
#include <linux/string.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/miscdevice.h>
#include <linux/types.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/kthread.h>
#include <linux/delay.h>
// max driver address to be monitored
#define MAX_DRIVER_ADDRESS 5
// fix symtab addr
#define FIX_SYMTAB_ADDR 0x1000
#define DRIVER_NAME "vdsp"
/*
* Set up the driver adress to be monitored
*/
static unsigned int driver_addr[MAX_DRIVER_ADDRESS] = {0};
static int __init driver_addr_setup(char *str)
{
char *s = str;
int idx = 0;
while(*s)
{
driver_addr[idx] = simple_strtoul(s, &s, 16);
if(driver_addr[idx] >= 0x10000 && driver_addr[idx] < _text)
{
idx++;
if(s[0] != ',' || idx >= MAX_DRIVER_ADDRESS) break;
s++;
}
else
break;
}
return 1;
}
__setup("driver_addr=", driver_addr_setup);
typedef void (*pfcallback)();
static int monitor_thread(void* arg)
{
// our monitor thread
int i;
unsigned int* p;
printk("driver monitor thread begin./n");
while(1)
{
for(i = 0; i < MAX_DRIVER_ADDRESS; i++)
{
p = (unsigned int*)driver_addr[i];
if(p < 0x10000 || p >= _stext) continue;
if(p[0] != 0)
{
printk("driver reload detected: %08x/n", driver_addr[i]);
p[0] = 0;
if(p[1] == NULL)
{
printk("no driver init function specified./n");
}
else
{
printk("prepare to call driver init function at %08x./n", p[1]);
((pfcallback)p[1])();
printk("driver init function execute over./n");
}
}
if(p[2] != 0)
{
printk("driver exit detected: %08x/n", driver_addr[i]);
p[2] = 0;
if(p[3] == NULL)
{
printk("no driver exit function specified./n");
}
else
{
printk("prepare to call driver exit function at %08x./n", p[3]);
((pfcallback)p[3])();
printk("driver exit function execute over./n");
}
}
}
msleep(100);
}
return 0;
}
#define CMD_LOAD 0
#define CMD_UNLOAD 1
/**
* vdsp_ioctl - handle commands from user space
*
* Every command needs a valid address as an argument.
*/
static int vdsp_ioctl(struct inode *inode, struct file *filp,
unsigned cmd, unsigned long arg)
{
unsigned int* p;
printk("/dev/vdsp: cmd=%d, arg=%08x/n", cmd, arg);
switch (cmd)
{
case CMD_LOAD:
if(arg < 0x10000 || arg >= _stext) break;
printk("prepare to call driver init function at %08x./n", arg);
((pfcallback)arg)();
printk("driver init function execute over./n");
return 0;
case CMD_UNLOAD:
if(arg < 0x10000 || arg >= _stext) break;
printk("prepare to call driver exit function at %08x./n", arg);
((pfcallback)arg)();
printk("driver exit function execute over./n");
return 0;
}
return -EINVAL;
}
static struct file_operations vdsp_fops = {
.ioctl = vdsp_ioctl,
};
static struct miscdevice vdsp_device = {
.minor = MISC_DYNAMIC_MINOR,
.name = DRIVER_NAME,
.fops = &vdsp_fops,
};
extern char __start___ksymtab[], __stop___ksymtab[];
static void vdsp_driver_init()
{
int i, size, ret;
printk(KERN_INFO "vdsp driver inited. /nfollow driver address monitored:/n");
for(i = 0; i < MAX_DRIVER_ADDRESS; i++)
{
printk("%08x ", driver_addr[i]);
}
printk("/n");
// 清空我们需要的区域
memset((void*)FIX_SYMTAB_ADDR, 0, (unsigned long)_stext - FIX_SYMTAB_ADDR);
// 复制一份符号表
size = __stop___ksymtab - __start___ksymtab;
memcpy((void*)FIX_SYMTAB_ADDR, __start___ksymtab, size);
printk("total %d bytes of symtab copy to %08x/n", size, FIX_SYMTAB_ADDR);
// 启动监视线程
kthread_run(monitor_thread, NULL, "vdsp");
// 创建设备/dev/vdsp
if (misc_register(&vdsp_device))
printk("unable to register vdsp device/n");
}
module_init(vdsp_driver_init)
这个驱动和内核一起用gcc编译,它将创建一个叫/dev/vdsp的设备,用户程序可以通过它执行驱动的初始化代码和卸载代码。在上面的代码中,LOAD和UNLOAD两个功能完全一样,只不过考虑到后面的扩展将之分开。
此外,在此驱动初始化时会创建一个叫vdsp的线程,此线程检测指定位置的数据,当数据变化时自动执行指定位置的代码。以此实现对vdsp开发驱动的支持。
现在我们还需要修改appstub,以将内核或者用户程序加载到正确的位置,如果是内核驱动还必须调用/dev/vdsp提供的功能以进行驱动的初始化或者卸载。
#include <getopt.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <paths.h>
#include <sched.h>
#include <elf.h>
#include <errno.h>
#include <sys/stat.h>
#include <sys/mman.h>
typedef int (*pfcall)(int argc, char *argv[]);
typedef enum _load_type
{
LT_LOAD,
LT_UNLOAD
}load_type;
#define printf(fmt, ...) do {} while(0)
#ifndef EM_BLACKFIN
# define EM_BLACKFIN 106
#endif
#define IS_ELF(buff) /
(buff[EI_MAG0] == ELFMAG0 && /
buff[EI_MAG1] == ELFMAG1 && /
buff[EI_MAG2] == ELFMAG2 && /
buff[EI_MAG3] == ELFMAG3)
static void* find_entry(char* buf, load_type ltype)
{
// 读取函数入口
Elf32_Ehdr* ehdr = (Elf32_Ehdr*)buf;
Elf32_Shdr* shdr_str, *shdr;
char* str_head;
int i;
pfcall entry = NULL;
shdr_str = buf + ehdr->e_shoff + ehdr->e_shstrndx * ehdr->e_shentsize;
str_head = buf + shdr_str->sh_offset;
shdr = buf + ehdr->e_shoff;
for(i=0; i < ehdr->e_shnum; i++, shdr++)
{
printf("section found: %s/n", str_head + shdr->sh_name);
if(strcmp(str_head + shdr->sh_name, ".vdsp_comm") == 0)
{
printf("entry found./n");
if(ltype == LT_LOAD)
memcpy(&entry, buf+shdr->sh_offset+4, 4);
else
memcpy(&entry, buf+shdr->sh_offset+12, 4);
return entry;
}
}
return NULL;
}
static void* load_dxe_file(const char* fname, load_type ltype)
{
// 读取dxe文件中的所有section,并返回入口地址
int i, fd;
struct stat stat;
void *entry = NULL;
char* buf;
Elf32_Ehdr* ehdr;
Elf32_Phdr* phdr;
// 打开映射文件
fd = open(fname, O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Unable to load %s: %s/n", fname, strerror(errno));
return NULL;
}
if (fstat(fd, &stat) < 0) {
fprintf(stderr, "Unable to stat %s: %s/n", fname, strerror(errno));
return NULL;
}
if (stat.st_size < EI_NIDENT) {
fprintf(stderr, "File is too small to be an ELF/n");
return NULL;
}
buf = mmap(0, stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (buf == MAP_FAILED) {
fprintf(stderr, "Unable to mmap %s: %s/n", fname, strerror(errno));
return NULL;
}
// 判断内容是否合法
/* make sure we have a valid ELF */
ehdr = buf;
if (!IS_ELF(ehdr->e_ident) || ehdr->e_machine != EM_BLACKFIN)
{
fprintf(stderr, "file is not a Blackfin ELF file/n");
return NULL;
}
/* make sure we have no unhandled program headers */
if(ltype == LT_LOAD)
{
phdr = (Elf32_Phdr *)(buf + ehdr->e_phoff);
for (i = 0; i < ehdr->e_phnum; ++i)
{
printf("program head %d: /ntype=%d offset=%d vaddr=%08x filesz=%d memsz=%d flags=%x/n",
i, phdr->p_type, phdr->p_offset, phdr->p_vaddr, phdr->p_filesz, phdr->p_memsz, phdr->p_flags);
if(phdr->p_filesz > 0)
memcpy((void*)phdr->p_vaddr, buf + phdr->p_offset, phdr->p_filesz);
else
memset((void*)phdr->p_vaddr, 0, phdr->p_memsz);
printf("load OK!/n");
++phdr;
}
}
entry = find_entry(buf, ltype);
munmap(buf, stat.st_size);
close(fd);
return entry;
}
/*
* The main program.
*/
int main(int argc, char *argv[])
{
unsigned int addr = 0;
unsigned int *p;
pfcall entry = NULL;
int fd;
if(argc < 2)
{
printf("not enough param.");
return 1;
}
if(argv[1][0] == '-' && argv[1][1] == 'L')
{
// 加载dxe应用程序
entry = load_dxe_file(argv[1] + 2, LT_LOAD);
if(entry)
{
printf("dxe entry found at 0x%08x/n", entry);
argv[1] = argv[0];
return entry(argc-1, argv+1);
}
else
fprintf(stderr, "no dxe entry found/n");
}
else if(argv[1][0] == '-' && argv[1][1] == 'K')
{
// 加载dxe内核驱动
entry = load_dxe_file(argv[1] + 2, LT_LOAD);
if(entry)
{
printf("dxe entry found at 0x%08x/n", entry);
fd = open("/dev/vdsp", O_RDONLY);
if(fd <= 0)
fprintf(stderr, "open /dev/vdsp failed./n");
else
{
printf("/dev/vdsp open success./n");
ioctl(fd, 0, entry);
close(fd);
}
}
else
fprintf(stderr, "no dxe entry found/n");
}
else if(argv[1][0] == '-' && argv[1][1] == 'U')
{
// 缷载dxe内核驱动
entry = load_dxe_file(argv[1] + 2, LT_UNLOAD);
if(entry)
{
printf("dxe unload entry found at 0x%08x/n", entry);
fd = open("/dev/vdsp", O_RDONLY);
if(fd <= 0)
fprintf(stderr, "open /dev/vdsp failed./n");
else
{
printf("/dev/vdsp open success./n");
ioctl(fd, 1, entry);
close(fd);
}
}
else
fprintf(stderr, "no dxe entry found/n");
}
else if(argv[1][0] == '-' && argv[1][1] == 'E')
{
// 直接跳转到指定位置
addr = strtoul(argv[1]+2, NULL, 16);
p = (unsigned int*)addr;
if(p[0])
{
printf("detect new app at %08x/n", p);
p[0] = 0;
if(p[1])
{
printf("prepare call %08x/n", p[1]);
argv[1] = argv[0];
return ((pfcall)p[1])(argc-1, argv+1);
}
else
printf("no function entry found./n");
}
else
printf("old app exist./n");
}
else
{
// 显示帮助信息
fprintf(stdout, "appstub usage:/n");
fprintf(stdout, "appstub [-L | -E | -K | -U] parameter/n");
fprintf(stdout, "/t-Ldxe load an dxe file as user app./n");
fprintf(stdout, "/t-Eaddr check memory address as user app./n/t/taddr must be hex and no prefix./n");
fprintf(stdout, "/t-Kdxe load an dxe file as kernel driver./n");
fprintf(stdout, "/t-Udxe unload kernel driver./n");
}
return 0;
}
就这样,我们为其加上LEKU四个选项,用以完成驱动和程序的加载和调试。
近日,我家6岁的小姑娘参加了第六届POP全国少儿英语风采大赛,拉票进行中(2011-6-15前)。
请帮忙点击新东方网站的链接:
http://popdasai.xdf.cn/toupiao.php?do=space&uid=4237
投她一票,谢谢!
让vdsp与uclinux共舞(13):应用程序加载(2009-11-10)
让vdsp与uclinux共舞(12):应用程序开发(2009-11-9)
让vdsp与uclinux共舞(11):方案改进(2009-11-6)
让vdsp与uclinux共舞(10):加载SMP内核(2009-11-4)
让vdsp与uclinux共舞(9):查找内核函数(2009-11-3)
让vdsp与uclinux共舞(8):vdsp驱动框架(2009-11-3)
让vdsp与uclinux共舞(7):在内核为驱动预留空间(2009-11-2)
让vdsp与uclinux共舞(6):用vdsp开发驱动的设想(2009-11-2)
让vdsp与uclinux共舞(5):加入dwarf调试信息(2009-11-2)
让vdsp与uclinux共舞(4):加载uclinux(2009-11-2)
让vdsp与uclinux共舞(3):boot kernel(2009-10-31)
让vdsp与uclinux共舞(2):vdsp的影响(2009-10-31)
让VDSP与uclinux共舞(1):开篇(2009-10-30)