QEMU学习(五):I2C设备仿真及驱动开发

一. 设备仿真原理

        I2C 是很常用的一个串行通信接口,用于连接各种外设、传感器等器件,本章我们来学习一下如何在QEMU里仿真I2C设备及 Linux 下开发 I2C 接口器件驱动。

1.设备添加

下面是标准的设备添加结构,我们使用的是常见的at_24c系列设备来做I2C的通信,详细代码请看\qemu\hw\nvram\eeprom_at24c.c

static
void at24c_eeprom_class_init(ObjectClass *klass, void *data)
{
    DeviceClass *dc = DEVICE_CLASS(klass);
    I2CSlaveClass *k = I2C_SLAVE_CLASS(klass);

    dc->realize = &at24c_eeprom_realize;
    k->event = &at24c_eeprom_event;
    k->recv = &at24c_eeprom_recv;
    k->send = &at24c_eeprom_send;

    dc->props = at24c_eeprom_props;
    dc->reset = at24c_eeprom_reset;
}

static
const TypeInfo at24c_eeprom_type = {
    .name = TYPE_AT24C_EE,
    .parent = TYPE_I2C_SLAVE,
    .instance_size = sizeof(EEPROMState),
    .class_size = sizeof(I2CSlaveClass),
    .class_init = at24c_eeprom_class_init,
};

static void at24c_eeprom_register(void)
{
    type_register_static(&at24c_eeprom_type);
}

type_init(at24c_eeprom_register)

 

2.设备操作

添加好I2C设备之后,需要对里面的回调函数进行填充

// 事件回调函数,对I2C信号的收发结束起调节的作用

static
int at24c_eeprom_event(I2CSlave *s, enum i2c_event event)
{
    EEPROMState *ee = container_of(s, EEPROMState, parent_obj);

    switch (event) {
    case I2C_START_SEND:
    case I2C_START_RECV:
    case I2C_FINISH:
        ee->haveaddr = 0;
        DPRINTK("clear\n");
        if (ee->blk && ee->changed) {
            int len = blk_pwrite(ee->blk, 0, ee->mem, ee->rsize, 0);
            if (len != ee->rsize) {
                ERR(TYPE_AT24C_EE
                        " : failed to write backing file\n");
            }
            DPRINTK("Wrote to backing file\n");
        }
        ee->changed = false;
        break;
    case I2C_NACK:
        break;
    }
    return 0;
}

// i2c接收函数,接收到i2c驱动读数据的请求
static
uint8_t at24c_eeprom_recv(I2CSlave *s)
{
    EEPROMState *ee = AT24C_EE(s);
    uint8_t ret;

    ret = ee->mem[ee->cur];

	at24c_ui_mem_update(ee, AT24C_UI_MEM_UPDATE_REASON_READ, ee->cur, ret);
	
    ee->cur = (ee->cur + 1u) % ee->rsize;
    DPRINTK("Recv %02x %c\n", ret, ret);

    return ret;
}
// i2c发送函数,接收到i2c驱动写数据的请求
static
int at24c_eeprom_send(I2CSlave *s, uint8_t data)
{
    EEPROMState *ee = AT24C_EE(s);

    //if (ee->haveaddr < 2) {
    if (ee->haveaddr < 1) { /* 100ask */
        ee->cur <<= 8;
        ee->cur |= data;
        ee->haveaddr++;
        //if (ee->haveaddr == 2) {
        if (ee->haveaddr == 1) {  /* 100ask */
            ee->cur %= ee->rsize;
            DPRINTK("Set pointer %04x\n", ee->cur);
        }

    } else {
        if (ee->writable) {
            DPRINTK("Send %02x\n", data);
            ee->mem[ee->cur] = data;
            ee->changed = true;
			at24c_ui_mem_update(ee, AT24C_UI_MEM_UPDATE_REASON_WRITE, ee->cur, data);
        } else {
            DPRINTK("Send error %02x read-only\n", data);
        }
        ee->cur = (ee->cur + 1u) % ee->rsize;

    }

    return 0;
}

// i2c设备初始化函数
static void at24c_eeprom_realize(DeviceState *dev, Error **errp)
{
    EEPROMState *ee = AT24C_EE(dev);

    if (ee->blk) {
        int64_t len = blk_getlength(ee->blk);

        if (len != ee->rsize) {
            error_setg(errp, "%s: Backing file size %" PRId64 " != %u",
                       TYPE_AT24C_EE, len, ee->rsize);
            return;
        }

        if (blk_set_perm(ee->blk, BLK_PERM_CONSISTENT_READ | BLK_PERM_WRITE,
                         BLK_PERM_ALL, &error_fatal) < 0)
        {
            error_setg(errp, "%s: Backing file incorrect permission",
                       TYPE_AT24C_EE);
            return;
        }
    }

	ee->rsize = 256;  /* 100ask,at24c08 */
    ee->mem = g_malloc0(ee->rsize);	

	at24c_ui_create(dev);
}

static
void at24c_eeprom_reset(DeviceState *state)
{
    EEPROMState *ee = AT24C_EE(state);

    ee->changed = false;
    ee->cur = 0;
    ee->haveaddr = 0;

    memset(ee->mem, 0, ee->rsize);

    if (ee->blk) {
        int len = blk_pread(ee->blk, 0, ee->mem, ee->rsize);

        if (len != ee->rsize) {
            ERR(TYPE_AT24C_EE
                    " : Failed initial sync with backing file\n");
        }
        DPRINTK("Reset read backing file\n");
    }
}

3.UI添加



static int at24c_ui_backgroud_prepare(void)
{
	int err;
	
	char *cur_app_abs_dir = get_cur_app_abs_dir();
	PT_PicFileParser pBMPParser = GetBMPParserInit();
	T_FileMap tFileMap;

	/* /..../bin/../etc/xxx.bmp */
	sprintf(tFileMap.strFileName, "%s/../etc/at24c02.bmp", cur_app_abs_dir);

	err = MapFile(&tFileMap);

	if (err)
		return -1;
	
	at24c_mem_pixels.iBpp  = 32;  /* PIXMAN_x8r8g8b8 */
	err = pBMPParser->GetPixelDatas(&tFileMap,  &at24c_mem_pixels);
	
	UnMapFile(&tFileMap);
	
	return err;
}



static void at24c_ui_show_backgroud(void *opaque)
{
    EEPROMState *ee = AT24C_EE(opaque);
    DisplaySurface *surface = qemu_console_surface(ee->con);
	int i;
	
	framebuffer_update_region(surface, &at24c_mem_pixels, 0, 0, at24c_mem_pixels.iWidth, at24c_mem_pixels.iHeight);

	for (i = 0; i < ee->rsize; i++)
		at24c_ui_mem_update(ee, AT24C_UI_MEM_UPDATE_REASON_INIT, i, ee->mem[i]);
	
	dpy_gfx_update(ee->con, 0, 0, at24c_mem_pixels.iWidth, at24c_mem_pixels.iHeight);
}


static void at24c_ui_invalidate(void *opaque)
{
}


void at24c_ui_mem_update(EEPROMState *ee, AT24C_UI_UPDATE_REASON reason, uint16_t addr, uint8_t data)
{
    DisplaySurface *surface = qemu_console_surface(ee->con);
	void *fb_base = surface_data(surface);
	int fb_width  = surface_width(surface);
	int fb_height = surface_height(surface);
	int fb_bpp    = surface_bits_per_pixel(surface);
	const char *hex = "0123456789abcdef";
	unsigned int color;

	int x, y;

	int row = addr >> 4;
	int col = addr & 0xf;

	x = FB_VAL0_X + FB_VAL_WIDTH  * col + FB_VAL_X_OFFSET_IN_BOX;
	y = FB_VAL0_Y + FB_VAL_HEIGHT * row + FB_VAL_Y_OFFSET_IN_BOX;

	if (reason == AT24C_UI_MEM_UPDATE_REASON_INIT)
		color = 0;
	else if (reason == AT24C_UI_MEM_UPDATE_REASON_WRITE)
		color = 0xff0000;
	else
		color = 0x00ff00;
	
	lcd_put_ascii(fb_base, fb_width, fb_height, fb_bpp, x, y, hex[data>>4], color, 0xffffff);
	lcd_put_ascii(fb_base, fb_width, fb_height, fb_bpp, x+8, y, hex[data&0xf], color, 0xffffff);

	invalidate = 1;
}


static void at24c_ui_update(void *opaque)
{
	static int inited = 0;
    EEPROMState *ee = AT24C_EE(opaque);
    DisplaySurface *surface = qemu_console_surface(ee->con);
	int fb_width  = surface_width(surface);
	int fb_height = surface_height(surface);

	if (!ee->con)
		return;

	if (!inited)
	{
		at24c_ui_show_backgroud(opaque);
		invalidate = 0;
		inited = 1;
	}
	else
	{
		if (invalidate)
		{
			dpy_gfx_update(ee->con, 0, 0, fb_width, fb_height);
			invalidate = 0;
		}
	}
}

static const GraphicHwOps at24c_ui_ops = {
    .invalidate  = at24c_ui_invalidate,
    .gfx_update  = at24c_ui_update,
};

void at24c_ui_create(DeviceState *dev)
{
    EEPROMState *ee = AT24C_EE(dev);

	if (!at24c_ui_backgroud_prepare())
	{
		dev->id = "at24c02";
	    ee->con = graphic_console_init_hidden(dev, 0, &at24c_ui_ops, ee);
	    qemu_console_resize(ee->con, at24c_mem_pixels.iWidth, at24c_mem_pixels.iHeight);
	}
	
}

 

二.设备驱动开发

I2C驱动分为总线驱动和设备驱动,总线驱动就是SOC的I2C控制器驱动或者适配器驱动,一般芯片厂商都写好了,I2C设备驱动就是对接设备所写的驱动,所以我们直接写设备驱动。

设备树添加 

pinctrl_i2c1: i2c1grp {
			fsl,pins = <
				MX6UL_PAD_UART4_TX_DATA__I2C1_SCL 0x4001b8b0
				MX6UL_PAD_UART4_RX_DATA__I2C1_SDA 0x4001b8b0
			>;
		};

&i2c1 {
	clock-frequency = <100000>;
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_i2c1>;
	status = "okay";

};


/*
 * @description	: 从at24c读取多个寄存器数据
 * @param - dev:  at24c设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int at24c_read_regs(struct at24c_dev *dev, u8 reg, void *val, int len)
{
	int ret;
	struct i2c_msg msg[2];
	struct i2c_client *client = (struct i2c_client *)dev->private_data;

	/* msg[0]为发送要读取的首地址 */
	msg[0].addr = client->addr;			/* at24c地址 */
	msg[0].flags = 0;					/* 标记为发送数据 */
	msg[0].buf = &reg;					/* 读取的首地址 */
	msg[0].len = 1;						/* reg长度*/

	/* msg[1]读取数据 */
	msg[1].addr = client->addr;			/* at24c地址 */
	msg[1].flags = I2C_M_RD;			/* 标记为读取数据*/
	msg[1].buf = val;					/* 读取数据缓冲区 */
	msg[1].len = len;					/* 要读取的数据长度*/

	ret = i2c_transfer(client->adapter, msg, 2);
	if(ret == 2) {
		ret = 0;
	} else {
		printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
		ret = -EREMOTEIO;
	}
	return ret;
}

/*
 * @description	: 向at24c多个寄存器写入数据
 * @param - dev:  at24c设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 at24c_write_regs(struct at24c_dev *dev, u8 reg, u8 *buf, u8 len)
{
	u8 b[256];
	struct i2c_msg msg;
	struct i2c_client *client = (struct i2c_client *)dev->private_data;
	
	b[0] = reg;					/* 寄存器首地址 */
	memcpy(&b[1],buf,len);		/* 将要写入的数据拷贝到数组b里面 */
		
	msg.addr = client->addr;	/* at24c地址 */
	msg.flags = 0;				/* 标记为写数据 */

	msg.buf = b;				/* 要写入的数据缓冲区 */
	msg.len = len + 1;			/* 要写入的数据长度 */

	return i2c_transfer(client->adapter, &msg, 1);
}

三.应用层开发

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "i2c-dev.h"


/* i2c_usr_test </dev/i2c-0> <dev_addr> r addr
 * i2c_usr_test </dev/i2c-0> <dev_addr> w addr val
 */
int main(int argc, char **argv)
{
	int fd;
	unsigned char addr, data;
	int dev_addr;
	
	if ((argc != 5) && (argc != 6))
	{
		return -1;
	}

	fd = open(argv[1], O_RDWR);
	if (fd < 0)
	{
		printf("can't open %s\n", argv[1]);
		return -1;
	}

	dev_addr = strtoul(argv[2], NULL, 0);
	if (ioctl(fd, I2C_SLAVE, dev_addr) < 0)
	{    
		/* ERROR HANDLING; you can check errno to see what went wrong */    
		printf("set addr error!\n");
		return -1;
	}

	if (strcmp(argv[3], "r") == 0)
	{
		addr = strtoul(argv[4], NULL, 0);
		
		data = i2c_smbus_read_byte_data(fd, addr);
			
		printf("data: %c, %d, 0x%02x\n", data, data, data);
	}
	else if ((strcmp(argv[3], "w") == 0) && (argc == 6))
	{
		addr = strtoul(argv[4], NULL, 0);
		data = strtoul(argv[5], NULL, 0);
		i2c_smbus_write_byte_data(fd, addr, data);		
	}
	else
	{
		print_usage(argv[0]);
		return -1;
	}
	
	return 0;
}

 

// 0x50 是 AT24C02 的 I2C 设备地址
[root@qemu_imx6ul:~]# i2c_usr_test /dev/i2c-0 0x50 r 0 // 读地址 0
data: , 0, 0x00 
[root@qemu_imx6ul:~]# i2c_usr_test /dev/i2c-0 0x50 w 1 0x58 // 写地址 1,写入 0x58

结果展示

 

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值