一. 设备仿真原理
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)
文章来源地址https://www.toymoban.com/news/detail-720512.html
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 = ® /* 读取的首地址 */
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
结果展示
文章来源:https://www.toymoban.com/news/detail-720512.html
到了这里,关于QEMU学习(五):I2C设备仿真及驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!