QEMU学习(六):SPI设备仿真及驱动开发

这篇具有很好参考价值的文章主要介绍了QEMU学习(六):SPI设备仿真及驱动开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一. 设备仿真原理SPI

        SPI和I2C一样也是很常用的串行通信协议,并且框架都很类似,都分主机控制器驱动和设备驱动,主机控制器也就是SOC的SPI控制器接口,一般linux内核都自带主机控制器,我们要做的就是SPI设备驱动。

1.设备添加

下面是QEMU自带的SPI模拟程序,位于\qemu\hw\ssi\imx_spi.c,可以看到当我们SPI驱动写数据时,QEMU会把接收到的数据push进tx_fifo写缓存,然后传入imx_spi_flush_txfifo函数,再由次函数push进rx_fifo,也就是读缓存。

static const struct MemoryRegionOps imx_spi_ops = {
    .read = imx_spi_read,
    .write = imx_spi_write,
    .endianness = DEVICE_NATIVE_ENDIAN,
    .......
};


static void imx_spi_write(void *opaque, hwaddr offset, uint64_t value,
                           unsigned size)
{
    IMXSPIState *s = opaque;
    uint32_t index = offset >> 2;
    uint32_t change_mask;

    if (index >=  ECSPI_MAX) {
        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
                      HWADDR_PRIx "\n", TYPE_IMX_SPI, __func__, offset);
        return;
    }

    if (value)
        DPRINTF("reg[%s] <= 0x%" PRIx32 "\n", imx_spi_reg_name(index),
            (uint32_t)value);

    change_mask = s->regs[index] ^ value;

    switch (index) {
    case ECSPI_RXDATA:
        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to write to RX FIFO\n",
                      TYPE_IMX_SPI, __func__);
        break;
    case ECSPI_TXDATA:
        if (!imx_spi_is_enabled(s)) {
            /* Ignore writes if device is disabled */
            break;
        } else if (fifo32_is_full(&s->tx_fifo)) {
            /* Ignore writes if queue is full */
            break;
        }

        fifo32_push(&s->tx_fifo, (uint32_t)value);

        if (imx_spi_channel_is_master(s) &&
            (s->regs[ECSPI_CONREG] & ECSPI_CONREG_SMC)) {
            /*
             * Start emitting if current channel is master and SMC bit is
             * set.
             */
            imx_spi_flush_txfifo(s); // 从SPI驱动接收到的数据写缓存
        }

        break;
    case ECSPI_STATREG:
        /* the RO and TC bits are write-one-to-clear */
        value &= ECSPI_STATREG_RO | ECSPI_STATREG_TC;
        s->regs[ECSPI_STATREG] &= ~value;

        break;
    case ECSPI_CONREG:
        s->regs[ECSPI_CONREG] = value;

        if (!imx_spi_is_enabled(s)) {
            /* device is disabled, so this is a reset */
            imx_spi_reset(DEVICE(s));
            return;
        }

        if (imx_spi_channel_is_master(s)) {
            int i;

            /* We are in master mode */

            for (i = 0; i < 4; i++) {
                qemu_set_irq(s->cs_lines[i],
                             i == imx_spi_selected_channel(s) ? 0 : 1);
            }

            if ((value & change_mask & ECSPI_CONREG_SMC) &&
                !fifo32_is_empty(&s->tx_fifo)) {
                /* SMC bit is set and TX FIFO has some slots filled in */
                imx_spi_flush_txfifo(s);
            } else if ((value & change_mask & ECSPI_CONREG_XCH) &&
                !(value & ECSPI_CONREG_SMC)) {
                /* This is a request to start emitting */
                imx_spi_flush_txfifo(s);
            }
        }

        break;
    case ECSPI_MSGDATA:
        /* it is not clear from the spec what MSGDATA is for */
        /* Anyway it is not used by Linux driver */
        /* So for now we just ignore it */
        qemu_log_mask(LOG_UNIMP,
                      "[%s]%s: Trying to write to MSGDATA, ignoring\n",
                      TYPE_IMX_SPI, __func__);
        break;
    default:
        s->regs[index] = value;

        break;
    }

    imx_spi_update_irq(s); // 设置ECPI的状态寄存器
}


static void imx_spi_flush_txfifo(IMXSPIState *s)
{
    uint32_t tx;
    uint32_t rx;

    while (!fifo32_is_empty(&s->tx_fifo)) {
        int tx_burst = 0;
        int index = 0;

        if (s->burst_length <= 0) {
            s->burst_length = imx_spi_burst_length(s);

            DPRINTF("Burst length = %d\n", s->burst_length);

            if (imx_spi_is_multiple_master_burst(s)) {
                s->regs[ECSPI_CONREG] |= ECSPI_CONREG_XCH;
            }
        }

        tx = fifo32_pop(&s->tx_fifo);

        DPRINTF("data tx:0x%08x\n", tx);

        tx_burst = MIN(s->burst_length, 32);

        rx = 0;

        while (tx_burst) {
            uint8_t byte = tx & 0xff;
            uint8_t byte_rx = byte;
            DPRINTF("writing 0x%02x\n", (uint32_t)byte);

            /* We need to write one byte at a time */
            byte = ssi_transfer(s->bus, byte);

            if (byte == 0) {
                byte = byte_rx;
            }
            DPRINTF("0x%02x read\n", (uint32_t)byte);
            tx = tx >> 8;
            rx |= (byte << (index * 8));

            /* Remove 8 bits from the actual burst */
            tx_burst -= 8;
            s->burst_length -= 8;
            index++;
        }
        
        DPRINTF("data rx:0x%08x\n", rx);

        if (fifo32_is_full(&s->rx_fifo)) {
            s->regs[ECSPI_STATREG] |= ECSPI_STATREG_RO;
        } else {
            fifo32_push(&s->rx_fifo, (uint8_t)rx);
        }

        if (s->burst_length <= 0) {
            if (!imx_spi_is_multiple_master_burst(s)) {
                s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TC;
                break;
            }
        }
    }

    if (fifo32_is_empty(&s->tx_fifo)) {
        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TC;
        s->regs[ECSPI_CONREG] &= ~ECSPI_CONREG_XCH;
    }

    /* TODO: We should also use TDR and RDR bits */

    DPRINTF("End: TX Fifo Size = %d, RX Fifo Size = %d\n",
            fifo32_num_used(&s->tx_fifo), fifo32_num_used(&s->rx_fifo));
}

当SPI驱动读取数据时,imx_spi_read读函数又把读缓存的数据pop出来并返回给SPI驱动。


static uint64_t imx_spi_read(void *opaque, hwaddr offset, unsigned size)
{
    uint32_t value = 0;
    IMXSPIState *s = opaque;
    uint32_t index = offset >> 2;

    if (index >=  ECSPI_MAX) {
        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Bad register at offset 0x%"
                      HWADDR_PRIx "\n", TYPE_IMX_SPI, __func__, offset);
        return 0;
    }

    switch (index) {
    case ECSPI_RXDATA:
        if (!imx_spi_is_enabled(s)) {
            value = 0;
        } else if (fifo32_is_empty(&s->rx_fifo)) {
            /* value is undefined */
            value = 0xdeadbeef;
        } else {
            /* read from the RX FIFO */
            value = fifo32_pop(&s->rx_fifo);
        }
        break;
    case ECSPI_TXDATA:
        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read from TX FIFO\n",
                      TYPE_IMX_SPI, __func__);

        /* Reading from TXDATA gives 0 */

        break;
    case ECSPI_MSGDATA:
        qemu_log_mask(LOG_GUEST_ERROR, "[%s]%s: Trying to read from MSG FIFO\n",
                      TYPE_IMX_SPI, __func__);

        /* Reading from MSGDATA gives 0 */

        break;
    default:
        value = s->regs[index];
        break;
    }
    if (value)
       DPRINTF("reg[%s] => 0x%" PRIx32 "\n", imx_spi_reg_name(index), value);

    imx_spi_update_irq(s);

    return (uint64_t)value;
}

不管是写函数还是读函数或者reset函数,最后一行都有imx_spi_update_irq,这是用来设置ECPI的状态寄存器,这个充分模拟了真实芯片SPI驱动内部的操作,如下图为SPI状态寄存器的结构图。

qemu仿真,驱动开发,arm开发,linux

static void imx_spi_update_irq(IMXSPIState *s)
{
    int level;

    if (fifo32_is_empty(&s->rx_fifo)) {
        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RR;
    } else {
        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_RR;
    }

    if (fifo32_is_full(&s->rx_fifo)) {
        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_RF;
    } else {
        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_RF;
    }

    if (fifo32_is_empty(&s->tx_fifo)) {
        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TE;
    } else {
        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_TE;
    }

    if (fifo32_is_full(&s->tx_fifo)) {
        s->regs[ECSPI_STATREG] |= ECSPI_STATREG_TF;
    } else {
        s->regs[ECSPI_STATREG] &= ~ECSPI_STATREG_TF;
    }

    level = s->regs[ECSPI_STATREG] & s->regs[ECSPI_INTREG] ? 1 : 0;

    qemu_set_irq(s->irq, level);

    DPRINTF("IRQ level is %d\n", level);
}

二.设备驱动开发

前面说了SPI驱动一般芯片厂商都写好了,SPI设备驱动就是对接设备所写的驱动,所以我们直接写设备驱动。

pinctrl_ecspi3: icm20608 {
	fsl,pins = <
		MX6UL_PAD_UART2_RX_DATA__ECSPI3_SCLK       0x000010B1
        MX6UL_PAD_UART2_CTS_B__ECSPI3_MOSI         0x000010B1
        MX6UL_PAD_UART2_RTS_B__ECSPI3_MISO         0x000010B1
        //MX6UL_PAD_UART1_CTS_B__GPIO1_IO18          0x000010B0 /*gpio cs0*/
		MX6UL_PAD_UART2_TX_DATA__GPIO1_IO20        0x000010B0 /*gpio cs1*/

	>;
};

&ecspi3 {
   fsl,spi-num-chipselects = <1>;
   cs-gpios = <&gpio1 20 0>;
   pinctrl-names = "default";
   pinctrl-0 = <&pinctrl_ecspi3>;
   status = "okay";

   
   spidev: icm20608@0 {
	  compatible = "icm20608";
      reg = <0>;
      spi-max-frequency = <8000000>;
   };

};


/*
 * @description	: 从icm20608读取多个寄存器数据
 * @param - dev:  icm20608设备
 * @param - reg:  要读取的寄存器首地址
 * @param - val:  读取到的数据
 * @param - len:  要读取的数据长度
 * @return 		: 操作结果
 */
static int icm20608_read_regs(struct icm20608_dev *dev, u8 reg, void *buf, int len)
{

	int ret = -1;
	unsigned char txdata[1];
	unsigned char * rxdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
    
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}

	rxdata = kzalloc(sizeof(char) * len, GFP_KERNEL);	/* 申请内存 */
	if(!rxdata) {
		goto out1;
	}

	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,一共要读取len个字节长度的数据,*/
	txdata[0] = reg | 0x80;		/* 写数据的时候首寄存器地址bit8要置1 */			
	t->tx_buf = txdata;			/* 要发送的数据 */
    t->rx_buf = rxdata;			/* 要读取的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
	if(ret) {
		goto out2;
	}
	
    memcpy(buf , rxdata+1, len);  /* 只需要读取的数据 */

out2:
	kfree(rxdata);					/* 释放内存 */
out1:	
	kfree(t);						/* 释放内存 */
	
	return ret;
}


/*
 * @description	: 向icm20608多个寄存器写入数据
 * @param - dev:  icm20608设备
 * @param - reg:  要写入的寄存器首地址
 * @param - val:  要写入的数据缓冲区
 * @param - len:  要写入的数据长度
 * @return 	  :   操作结果
 */
static s32 icm20608_write_regs(struct icm20608_dev *dev, u8 reg, u8 *buf, u8 len)
{
	int ret = -1;
	unsigned char *txdata;
	struct spi_message m;
	struct spi_transfer *t;
	struct spi_device *spi = (struct spi_device *)dev->private_data;
	
	t = kzalloc(sizeof(struct spi_transfer), GFP_KERNEL);	/* 申请内存 */
	if(!t) {
		return -ENOMEM;
	}
	
	txdata = kzalloc(sizeof(char)+len, GFP_KERNEL);
	if(!txdata) {
		goto out1;
	}
	
	/* 一共发送len+1个字节的数据,第一个字节为
	寄存器首地址,len为要写入的寄存器的集合,*/
	*txdata = reg & ~0x80;	/* 写数据的时候首寄存器地址bit8要清零 */
    memcpy(txdata+1, buf, len);	/* 把len个寄存器拷贝到txdata里,等待发送 */
	t->tx_buf = txdata;			/* 要发送的数据 */
	t->len = len+1;				/* t->len=发送的长度+读取的长度 */
	spi_message_init(&m);		/* 初始化spi_message */
	spi_message_add_tail(t, &m);/* 将spi_transfer添加到spi_message队列 */
	ret = spi_sync(spi, &m);	/* 同步发送 */
    if(ret) {
        goto out2;
    }
	
out2:
	kfree(txdata);				/* 释放内存 */
out1:
	kfree(t);					/* 释放内存 */
	return ret;
}

static void icm20608_write_onereg(struct icm20608_dev *dev, u8 reg, u8 value)
{
	u8 buf = value;
	icm20608_write_regs(dev, reg, &buf, 1);
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做pr似有ate_data的成员变量
 * 					  一般在open的时候将private_data似有向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &icm20608dev; /* 设置私有数据 */
	return 0;
}

static unsigned char icm20608_read_onereg(struct icm20608_dev *dev, u8 reg)
{
	u8 data = 0;
	icm20608_read_regs(dev, reg, &data, 1);
	return data;
}

static ssize_t icm20608_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    unsigned char data[14] = { 0 };
	long err = 0;
	struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
    icm20608_read_regs(dev, 0x6B, data, 14);
    copy_to_user(buf, data, sizeof(data));
	if (data[0])
    	printk("icm20608_read_regs = %#X\r\n", data[0]);
	return 0;
}

static long icm20608_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct icm20608_dev *dev = (struct icm20608_dev *)filp->private_data;
    icm20608_write_onereg(dev, cmd, arg);
	return 0;
}

/*
 * @description		: 关闭/释放设备
 * @param - filp 	: 要关闭的设备文件(文件描述符)
 * @return 			: 0 成功;其他 失败
 */
static int icm20608_release(struct inode *inode, struct file *filp)
{
	return 0;
}

/* icm20608操作函数 */
static const struct file_operations icm20608_ops = {
	.owner = THIS_MODULE,
	.open = icm20608_open,
	.read = icm20608_read,
    .unlocked_ioctl = icm20608_ioctl,
	.release = icm20608_release,
};

/*
 * ICM20608内部寄存器初始化函数 
 * @param  	: 无
 * @return 	: 无
 */
void icm20608_reginit(void)
{
    icm20608_write_onereg(&icm20608dev, 0x6B, 0x80);
    return;
}

 /*
  * @description     : spi驱动的probe函数,当驱动与
  *                    设备匹配以后此函数就会执行
  * @param - client  : i2c设备
  * @param - id      : i2c设备ID
  * 
  */	
static int icm20608_probe(struct spi_device *spi)
{
	/* 1、构建设备号 */
	if (icm20608dev.major) {
		icm20608dev.devid = MKDEV(icm20608dev.major, 0);
		register_chrdev_region(icm20608dev.devid, ICM20608_CNT, ICM20608_NAME);
	} else {
		alloc_chrdev_region(&icm20608dev.devid, 0, ICM20608_CNT, ICM20608_NAME);
		icm20608dev.major = MAJOR(icm20608dev.devid);
	}

	/* 2、注册设备 */
	cdev_init(&icm20608dev.cdev, &icm20608_ops);
	cdev_add(&icm20608dev.cdev, icm20608dev.devid, ICM20608_CNT);

	/* 3、创建类 */
	icm20608dev.class = class_create(THIS_MODULE, ICM20608_NAME);
	if (IS_ERR(icm20608dev.class)) {
		return PTR_ERR(icm20608dev.class);
	}

	/* 4、创建设备 */
	icm20608dev.device = device_create(icm20608dev.class, NULL, icm20608dev.devid, NULL, ICM20608_NAME);
	if (IS_ERR(icm20608dev.device)) {
		return PTR_ERR(icm20608dev.device);
	}

	/*初始化spi_device */
	spi->mode = SPI_MODE_0;	/*MODE0,CPOL=0,CPHA=0*/
	spi_setup(spi);
	icm20608dev.private_data = spi; /* 设置私有数据 */
	
    //icm20608_reginit();
	return 0;
}


/*
 * @description     : i2c驱动的remove函数,移除i2c驱动的时候此函数会执行
 * @param - client 	: i2c设备
 * @return          : 0,成功;其他负值,失败
 */
static int icm20608_remove(struct spi_device *spi)
{
	/* 删除设备 */
	cdev_del(&icm20608dev.cdev);
	unregister_chrdev_region(icm20608dev.devid, ICM20608_CNT);

	/* 注销掉类和设备 */
	device_destroy(icm20608dev.class, icm20608dev.devid);
	class_destroy(icm20608dev.class);
	return 0;
}

/* 传统匹配方式ID列表 */
static const struct spi_device_id icm20608_id[] = {
	{"alientek,icm20608", 0},  
	{}
};

/* 设备树匹配列表 */
static const struct of_device_id icm20608_of_match[] = {
	{ .compatible = "alientek,icm20608" },
	{ /* Sentinel */ }
};

/* SPI驱动结构体 */	
static struct spi_driver icm20608_driver = {
	.probe = icm20608_probe,
	.remove = icm20608_remove,
	.driver = {
			.owner = THIS_MODULE,
		   	.name = "icm20608",
		   	.of_match_table = icm20608_of_match, 
		   },
	.id_table = icm20608_id,
};
		   
/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init icm20608_init(void)
{
	return spi_register_driver(&icm20608_driver);
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit icm20608_exit(void)
{
	spi_unregister_driver(&icm20608_driver);
}

module_init(icm20608_init);
module_exit(icm20608_exit);

三.应用层测试

int main(int argc, char *argv[])
{
	int fd, retvalue;
	char *filename;
	signed char databuf[7];
	
	if (argc != 4) {
		printf("Error Usage!\r\n");
		return -1;
	}

	filename = argv[1];

	/* 打开SPI驱动 */
	fd = open(filename, O_RDWR);
	if (fd < 0) {
		printf("file %s open failed!\r\n", argv[1]);
		return -1;
	}

	ioctl(fd, strtoul(argv[2], NULL, 0), strtoul(argv[3], NULL, 0));
	read(fd, databuf, sizeof(databuf));
	if (databuf[0] != 0) {
		printf("read data = %#X\n", databuf[0]);
	}
		
	

	retvalue = close(fd); /* 关闭文件 */
	if(retvalue < 0){
		printf("file %s close failed!\r\n", argv[1]);
		return -1;
	}
	return 0;
}

结果展示

qemu仿真,驱动开发,arm开发,linux

qemu仿真,驱动开发,arm开发,linux文章来源地址https://www.toymoban.com/news/detail-820822.html

到了这里,关于QEMU学习(六):SPI设备仿真及驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • Qemu虚拟arm开发板驱动开发详解(一)——驱动基本架构

    Qemu虚拟arm开发板驱动开发详解(一)——驱动基本架构

            此前在《WSL2下Ubuntu22.04使用Qemu搭建虚拟Vexpress-A9开发板》系列文章中,我们已建立好Linux最小系统的运行环境,并将其成功移植到了由Qemu模拟的arm32开发板上。接下来将介绍如何基于上述环境进行驱动开发。         本节主要带各位读者了解Linux内核驱动的基本架

    2024年02月05日
    浏览(10)
  • QEMU搭建arm虚拟机开发环境

    使用git指令切换到对应的分支上,我这里使用的是stable-4.0的分支 git checkout -b stable-4.0 remotes/origin/stable-4.0 在工程的根目录下执行 ./configure --target-list=aarch64-linux-user,aarch64-softmmu --enable-virtfs --enable-debug 然后执行make,视情况是否要执行make install

    2024年01月25日
    浏览(10)
  • Linux 之搭建 arm 的 qemu 模拟器

    2024年02月03日
    浏览(11)
  • VSCode+GDB+Qemu调试ARM64 linux内核

    VSCode+GDB+Qemu调试ARM64 linux内核

    俗话说,工欲善其事 必先利其器。linux kernel是一个非常复杂的系统,初学者会很难入门。 如果有一个方便的调试环境,学习效率至少能有5-10倍的提升。 为了学习linux内核,通常有这两个需要 可以摆脱硬件,方便的编译和运行linux 可以使用图形化的工具来调试linux 笔者使用

    2024年02月08日
    浏览(10)
  • ARM Linux 调试 -QEMU启动 Uboot/Kernel/Rootfs

    懒人方式: 直接去方锐/qemu克隆项目,执行script目录的脚本即可 1. build_env.sh安装环境 2. build_rootfs.sh 生成rootfs 3. build_kernel.sh编译kernel 4. qemu_run.sh开始调试 2.1busybox代码的下载编译 Busybox下载地址:https://busybox.net/downloads/ Download 1.36.0 Busybox 默认会安装到 ./_install 目录下 制作ro

    2024年02月02日
    浏览(37)
  • ubuntu22上使用qemu-system-arm调试linux

    qemu是用软件模拟硬件解析指令运行的软件,可以模拟arm、arm64、x86等,对于调试linux 内核机制很方便,不用额外购买开发板。由于linux上有对qemu的加速引擎,支持程度更高,且网络上教程居多,所以这里使用virtualbox+ubuntu22虚拟机,在ubuntu上运行qemu进行模拟。 virtualbox安装:

    2024年01月25日
    浏览(11)
  • 利用WSL2搭建Qemu仿真Vexpress-a9开发环境

    利用WSL2搭建Qemu仿真Vexpress-a9开发环境

    最近想熟悉下Linux开发方面的知识,由于不想安装个虚拟机,便想着利用windows自身带的linux子系统,跑qemu模拟ARM vexpress-a9开发板,过程是逐渐摸索的,参考了网上不少文章,算是做下总结吧! 本身电脑是多年前的win10 64位,性能更不上,不想安装太多软件,WSL2可以在Mircosof

    2024年02月06日
    浏览(8)
  • Linux 利用 qemu-system-aarch64 实现 x86 机器安装 arm64 的操作系统

    Linux 利用 qemu-system-aarch64 实现 x86 机器安装 arm64 的操作系统

    qemu-system-aarch64 启动的虚拟机,无法受到 kvm 的管理,也无法在后台运行 终端一旦断开了,虚拟机就被关掉了,想再次进入目前没找到方法,都会回到安装操作系统的步骤 可能是 centos 有某些特殊性吧,暂时没太多时间研究,后期有时间了再看看有没有解决的方案 qemu-5.2.0 和

    2024年02月06日
    浏览(13)
  • 嵌入式Linux驱动开发——解决/sys/bus/spi/devices下没有对应的spi设备文件

    嵌入式Linux驱动开发——解决/sys/bus/spi/devices下没有对应的spi设备文件

    最近在学习Linux驱动开发中SPI总线的驱动框架,但在修改完设备树添加完对应的spi设备节点后,理应在/sys/bus/spi下会有对应的spi设备,我的目录下面没有。 无spi设备 然后我查看了/proc/device-tree,发现有对应的spi设备节点,我就先没有过多理会这个问题。 /proc/device-tree下有对应

    2024年02月16日
    浏览(10)
  • Qemu搭建arm版麒麟系统

    Qemu搭建arm版麒麟系统

    博主 默语带您 Go to New World. ✍ 个人主页—— 默语 的博客👦🏻 《java 面试题大全》 🍩惟余辈才疏学浅,临摹之作或有不妥之处,还请读者海涵指正。☕🍭 《MYSQL从入门到精通》数据库是开发者必会基础之一~ 🪁 吾期望此文有资助于尔,即使粗浅难及深广,亦备添少许微薄

    2024年02月02日
    浏览(7)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包