imx6 通过移植XRM117x(SPI转串口)对Linux中的SPI驱动框架进行分析
最近分析了一下Linux 中的SPI驱动框架,将自己的理解总结一下,不足之处还请斧正!
1、SPI通信基础知识
SPI(Serial Peripheral Interface)是一种串行(一次发送1bit数据),同步(使用同一个时钟,时钟信号有SPI的Master发出),全双工(收发能够同时进行)通信接口,速度可达到十几Mbps,SPI通信分为主从设备,在主设备(Master)上可挂接多个从设备(Slave),Master通过CS片选信号决定与哪个具体的Slave进行通信
SPI使用4线:CS(片选线)、CLK(时钟线)、MISO(Master in and Slave out)与MOSI(Master out and Slave in)
SPI的4种工作模式:工作模式描述是SPI数据传输的时序问题,由时钟极性(CPOL---Clock Polarity)和时钟相位(CPHA---Clock Phase)控制,CPOL表示CLK空闲时电平的高低,CPHA表示数据传输发生在CLK的上升沿还是下降沿,通过CPOL和CPHA组合成4种工作状态,例如模式0:CPOL : CPHA(0:0)表示CLK空闲时为低电平,并在时钟上升沿进行数据传输(具体时序图可自行百度)
2、SPI驱动分析(Linux Kernel Version 3.14.52,SoC为freescale imx6)
在Linux中将其分为3层:
第一层:SPI Core层,由Linux Kernel维护者进行编写与维护,对SPI通信的抽象,为Master和Slave提供接口,将Master和Slave分离开,Master不需要操心将来会挂载哪个Slave,Slave也不需要知道自己会被挂载到谁上
第二层:SPI Master层,由SoC厂商根据SoC中的SPI Master特性实例化SPI Core层提供的接口,实现对Master进行的硬件操作,并注册到SPI总线上
第三层:SPI Device层(或者SPI Slave层),具体SPI Slave设备的初始化等工作,例如我使用的XRM117X,通过调用SPI Core提供的接口实现Slave 和Master的对接工作.
来一张SPI驱动框架思维导图(嗯!图片有点大,实际调用过程其实并没有那么复杂,不想看图的可以看后面的代码分析)
这里有个pdf的文档 https://download.csdn.net/download/qq_37809296/10914161
注:本思维导图仅分析SPI相关的部分,和Linux驱动框架相关的未进行分析
注:结合上面的思维导图和接下的代码分析可能更快理解整个系统过程
分析主要根据前面所说的三层进行:分析过程请注意参数的初始化的传递过程
第一层SPI Core
1、SPI Core的注册
// 文件位置 kernel/driver/spi/spi.c
// 去掉了一些安全检查等代码
static int __init spi_init(void)
{int status;buf = kmalloc(SPI_BUFSIZ, GFP_KERNEL); // buf为全局变量在传输过程(spi_write_then_read)中会用到如status = bus_register(&spi_bus_type);// 总线的注册,关系到Linux中的驱动框架,没有进行分析status = class_register(&spi_master_class); //类的注册,所有注册到SPI总线上的Master属于这个class,在/sys/class/spi_master可以看到所有的SPI masterreturn 0;
}
postcore_initcall(spi_init); // 决定了调用顺序,kernel调用init是分等级调用的,没有进行分析struct bus_type spi_bus_type = {.name = "spi",.dev_groups = spi_dev_groups,.match = spi_match_device,.uevent = spi_uevent,.pm = &spi_pm,
};static struct class spi_master_class = {.name = "spi_master",.owner = THIS_MODULE,.dev_release = spi_master_release,
};
SPI核心层的注册就这么多,主要是注册了一个SPI 总线,和注册一个SPI_master的类,之后的匹配过程就交到了总线上
第二层 SPI Master相关的注册过程
注:我使用的是imx6的板子
// 代码位置 Spi-imx.c (drivers\spi)// 首先将master注册为platform设备,主要是用来匹配设备树中的device,然后执行porbe函数
// 和platform总线相关暂时不分析,只需要知道他会执行 .probe = spi_imx_probe, 就可以了
static struct platform_driver spi_imx_driver = {.driver = {.name = DRIVER_NAME,.owner = THIS_MODULE,.of_match_table = spi_imx_dt_ids,.pm = IMX_SPI_PM,},.id_table = spi_imx_devtype,.probe = spi_imx_probe,.remove = spi_imx_remove,
};
module_platform_driver(spi_imx_driver);// init时调用
经过platform 总线的match之后进入 probe函数
// 代码位置 Spi-imx.c (drivers\spi)
// 去除一些安全检查和无关信息
static int spi_imx_probe(struct platform_device *pdev)
{
// 从设备树中查找fsl,spi-num-chipselects ,表示在该Master中挂载了多少个Slave设备ret = of_property_read_u32(np, "fsl,spi-num-chipselects", &num_cs);
// 调用spi core提供的接口 分配了两个结构体空间(struct spi_master + struct spi_imx_data)
// spi_master 是spi core提供的表示一个master ,spi_imx_data对master的补充作用
// spi_master 放在前面, spi_imx_data紧随其后,所以我们可以从spi_master得到spi_imx_datamaster = spi_alloc_master(&pdev->dev,sizeof(struct spi_imx_data) + sizeof(int) * num_cs);platform_set_drvdata(pdev, master);master->bits_per_word_mask = SPI_BPW_RANGE_MASK(1, 32);master->bus_num = pdev->id;master->num_chipselect = num_cs;// 前面说的 从spi_master得到spi_imx_dataspi_imx = spi_master_get_devdata(master);
// 结构体的相互绑定spi_imx->bitbang.master = master;// 设置片选信号引脚,并向内核申请for (i = 0; i < master->num_chipselect; i++) {int cs_gpio = of_get_named_gpio(np, "cs-gpios", i);if (!gpio_is_valid(cs_gpio) && mxc_platform_info)cs_gpio = mxc_platform_info->chipselect[i];spi_imx->chipselect[i] = cs_gpio;if (!gpio_is_valid(cs_gpio))continue;ret = devm_gpio_request(&pdev->dev, spi_imx->chipselect[i],DRIVER_NAME);}
// 方法的绑定,将来调用过程都是通过这些函数指针进行的spi_imx->bitbang.chipselect = spi_imx_chipselect;spi_imx->bitbang.setup_transfer = spi_imx_setupxfer;
// 从device传输时会调用的函数spi_imx->bitbang.txrx_bufs = spi_imx_transfer;spi_imx->bitbang.master->setup = spi_imx_setup;spi_imx->bitbang.master->cleanup = spi_imx_cleanup;spi_imx->bitbang.master->prepare_message = spi_imx_prepare_message;spi_imx->bitbang.master->unprepare_message = spi_imx_unprepare_message;
// 设置SPI的传输模式,前面说的4中传输模式spi_imx->bitbang.master->mode_bits = SPI_CPOL | SPI_CPHA | SPI_CS_HIGH;init_completion(&spi_imx->xfer_done);
// 对具体硬件操作的函数spi_imx->devtype_data = of_id ? of_id->data : (struct spi_imx_devtype_data *) pdev->id_entry->driver_data;
// 获取SPI的地址res = platform_get_resource(pdev, IORESOURCE_MEM, 0);spi_imx->base = devm_ioremap_resource(&pdev->dev, res);// 获取中断spi_imx->irq = platform_get_irq(pdev, 0);ret = devm_request_irq(&pdev->dev, spi_imx->irq, spi_imx_isr, 0, dev_name(&pdev->dev), spi_imx);
// 时钟相关spi_imx->clk_ipg = devm_clk_get(&pdev->dev, "ipg");spi_imx->clk_per = devm_clk_get(&pdev->dev, "per");ret = clk_prepare_enable(spi_imx->clk_per);ret = clk_prepare_enable(spi_imx->clk_ipg);spi_imx->spi_clk = clk_get_rate(spi_imx->clk_per);// && 表示前面为真才执行后面的函数 对DMA进行初始化if (spi_imx->devtype_data == &imx51_ecspi_devtype_data&& spi_imx_sdma_init(&pdev->dev, spi_imx, master, res))
// 对spi master进行初始化spi_imx->devtype_data->reset(spi_imx);spi_imx->devtype_data->intctrl(spi_imx, 0);master->dev.of_node = pdev->dev.of_node;// 重点函数 //SPI Master的注册到SPI 总线上的过程 是SPI Core提供的接口ret = spi_bitbang_start(&spi_imx->bitbang);clk_disable_unprepare(spi_imx->clk_ipg);clk_disable_unprepare(spi_imx->clk_per);return ret;}
由probe引入的重点函数进行spi Master向spi 总线注册的过程 spi_bitbang_start
// 代码位置Spi-bitbang.c (drivers\spi)
// 去除一些非重点信息
int spi_bitbang_start(struct spi_bitbang *bitbang)
{struct spi_master *master = bitbang->master;
// 从这里可以看出一定要有chipselect函数if (!master || !bitbang->chipselect)return -EINVAL;// 未设置spi传输模式就使用默认的传输模式if (!master->mode_bits)master->mode_bits = SPI_CPOL | SPI_CPHA | bitbang->flags;// 不能使用master-》transfer 和 master->transfer_one_message函数指针if (master->transfer || master->transfer_one_message)return -EINVAL;master->prepare_transfer_hardware = spi_bitbang_prepare_hardware;master->unprepare_transfer_hardware = spi_bitbang_unprepare_hardware;// 上层传输时将会调用 // 到时候调用到了这个函数指针我希望你还有点印象master->transfer_one_message = spi_bitbang_transfer_one; // spi-imx中定义了这个 spi_imx->bitbang.txrx_bufs = spi_imx_transfer;if (!bitbang->txrx_bufs) {......}// 重点函数对spi进行注册 spi core提供的接口ret = spi_register_master(spi_master_get(master));return 0;
}
正式开始向spi core注册spi master调用的是 spi_register_master
// 代码位置 Spi.c (drivers\spi)
int spi_register_master(struct spi_master *master)
{struct device *dev = master->dev.parent;status = of_spi_register_master(master);if (master->num_chipselect == 0)return -EINVAL;
// 设备驱动模型内容dev_set_name(&master->dev, "spi%u", master->bus_num);status = device_add(&master->dev);// 调用队列的初始化if (master->transfer)dev_info(dev, "master is unqueued, this is deprecated\n");else {
// 重点函数 ,初始化了传输函数 做个标记 在后面分析status = spi_master_initialize_queue(master);}// 将master加到spi core维护的链表 spi_master_list //所有的spi master都在这个链表中
// spi core还维护了一个 device链表list_add_tail(&master->list, &spi_master_list);// 使用了设备树方式获取设备信息将不会掉用这里的函数进行匹配list_for_each_entry(bi, &board_list, list)spi_match_master_to_boardinfo(master, &bi->board_info);// 重点函数 of_register_spi_devices(master);/* Register devices from the device tree and ACPI */acpi_register_spi_devices(master);done:return status;
}
对 of_register_spi_devices 的分析
// 代码位置 Spi.c (drivers\spi)
static void of_register_spi_devices(struct spi_master *master)
{struct spi_device *spi;struct device_node *nc;// 遍历设备树中spi master 节点下的所有 spi device for_each_available_child_of_node(master->dev.of_node, nc) {
// 重点函数
// SPI从设备的主机端代理之后这个结构体指针将会通过spi总线传递到spi_device驱动中去spi = spi_alloc_device(master);if (of_modalias_node(nc, spi->modalias,sizeof(spi->modalias)) < 0) rc = of_property_read_u32(nc, "reg", &value);spi->chip_select = value;// 对模式的设置 //所以可以在设备树中直接指定是spi工作模式if (of_find_property(nc, "spi-cpha", NULL))spi->mode |= SPI_CPHA;if (of_find_property(nc, "spi-cpol", NULL))spi->mode |= SPI_CPOL;if (of_find_property(nc, "spi-cs-high", NULL))spi->mode |= SPI_CS_HIGH;if (of_find_property(nc, "spi-3wire", NULL))spi->mode |= SPI_3WIRE;if (of_find_property(nc, "spi-lsb-first", NULL))spi->mode |= SPI_LSB_FIRST;if (!of_property_read_u32(nc, "spi-tx-bus-width", &value)) ......if (!of_property_read_u32(nc, "spi-rx-bus-width", &value)) ....../* Device speed */rc = of_property_read_u32(nc, "spi-max-frequency", &value);......spi->max_speed_hz = value;/* IRQ */spi->irq = irq_of_parse_and_map(nc, 0);/* Store a pointer to the node in the device structure */of_node_get(nc);spi->dev.of_node = nc;/* Register the new device */request_module("%s%s", SPI_MODULE_PREFIX, spi->modalias);// 重点函数rc = spi_add_device(spi);}
}
// 代码位置 Spi.c (drivers\spi)
对 spi_add_device的分析
// 代码位置 Spi.c (drivers\spi)
int spi_add_device(struct spi_device *spi)
{static DEFINE_MUTEX(spi_add_lock);struct spi_master *master = spi->master;struct device *dev = master->dev.parent;int status;spi_dev_set_name(spi);status = bus_for_each_dev(&spi_bus_type, NULL, spi, spi_dev_check);if (master->cs_gpios)spi->cs_gpio = master->cs_gpios[spi->chip_select];// 重点函数 对spi master的设置status = spi_setup(spi);status = device_add(&spi->dev);}
// 代码位置 Spi.c (drivers\spi)
int spi_setup(struct spi_device *spi)
{......if (spi->master->setup)status = spi->master->setup(spi); //这个函数指针的初始化在spi master的probe函数中...... return status;
}
到此SPI Master端的初始化就构建成功了,主要注意一下Master中有一个 device的代理,通过master和device的桥梁就是通过这个 struct spi_device 这个结构体的挂接实现的
第三层 : spi device层或spi slave层
这里我将不进行XRM117X怎么实现SPI转串口的功能,主要分析XRM117X与imx6通过SPI进行通信的过程
1、注册过程
// 代码位置 Xrm117x.c (drivers\tty\serial)
static int __init xrm117x_init(void)
{return spi_register_driver(&xrm117x_spi_uart_driver);
}
module_init(xrm117x_init);
// 代码位置 Spi.c (drivers\spi)
int spi_register_driver(struct spi_driver *sdrv)
{sdrv->driver.bus = &spi_bus_type;if (sdrv->probe)sdrv->driver.probe = spi_drv_probe;if (sdrv->remove)sdrv->driver.remove = spi_drv_remove;if (sdrv->shutdown)sdrv->driver.shutdown = spi_drv_shutdown;return driver_register(&sdrv->driver); // 驱动模型相关内容,暂时不分析
}
2、对SPI进行读写的过程:主要分析怎么从device中调用到master中的对SPI 控制器具体硬件操作
// 代码位置 Xrm117x.c (drivers\tty\serial)
static u8 xrm117x_port_read(struct uart_port *port, u8 reg)
{struct xrm117x_port *s = dev_get_drvdata(port->dev);mutex_lock(&s->mutex_bus_access);// 重点函数,将进行SPI传输status = spi_write_then_read(spi_dev, &cmd, 1, &result, 1);mutex_unlock(&s->mutex_bus_access);return result;}
SPI传输过程
// 代码位置 Spi.c (drivers\spi)
int spi_write_then_read(struct spi_device *spi,const void *txbuf, unsigned n_tx,void *rxbuf, unsigned n_rx)
{struct spi_message message;struct spi_transfer x[2];if ((n_tx + n_rx) > SPI_BUFSIZ || !mutex_trylock(&lock)) {local_buf = kmalloc(max((unsigned)SPI_BUFSIZ, n_tx + n_rx), GFP_KERNEL | GFP_DMA);} else {local_buf = buf; // 还记的 spi_init中为全局变量buf分配了内存吗?}// 初始化 message //传输都是以message方式传输的,每个message下面都有transfer结构体spi_message_init(&message);if (n_tx) {x[0].len = n_tx;spi_message_add_tail(&x[0], &message); //将transfer挂载到message中}if (n_rx) {x[1].len = n_rx;spi_message_add_tail(&x[1], &message);}memcpy(local_buf, txbuf, n_tx);x[0].tx_buf = local_buf;x[1].rx_buf = local_buf + n_tx;// 重点函数 进行spi传输status = spi_sync(spi, &message);if (status == 0)memcpy(rxbuf, x[1].rx_buf, n_rx);return status;
}int spi_sync(struct spi_device *spi, struct spi_message *message)
{
// 重点函数 进行spi传输return __spi_sync(spi, message, 0);
}static int __spi_sync(struct spi_device *spi, struct spi_message *message,int bus_locked)
{
// 还记得 master中对spi device的代理吗,在那时已经将spi_device结构体和spi_master绑定在一起了struct spi_master *master = spi->master;// 重点函数status = spi_async_locked(spi, message);if (status == 0) {wait_for_completion(&done);status = message->status;}return status;
}// 去掉了很多无用信息
int spi_async_locked(struct spi_device *spi, struct spi_message *message)
{struct spi_master *master = spi->master;// 重点函数ret = __spi_async(spi, message);return ret;}static int __spi_async(struct spi_device *spi, struct spi_message *message)
{struct spi_master *master = spi->master;message->spi = spi;trace_spi_message_submit(message);
// 重点函数
// 你可能很想知道这个函数指针是在哪里初始化的,
// spi.c中搜这个函数试试 spi_master_initialize_queue
// 看下面的分析return master->transfer(spi, message);
}
补充:spi_master_initialize_queue中初始化 master->transfer函数指针
// 代码位置 Spi.c (drivers\spi)
// 去除了很多无用的东西
static int spi_master_initialize_queue(struct spi_master *master)
{int ret;master->transfer = spi_queued_transfer; // 在这里初始化了transfer函数指针//if (!master->transfer_one_message)master->transfer_one_message = spi_transfer_one_message;// 主要工作是建立一个工作线程//绑定线程的工作函数
// 请记住这里,后面一段代码中的疑问的答案在这里ret = spi_init_queue(master);ret = spi_start_queue(master);// 启动工作线程}
继续具体的传输过程;:
// 代码位置 Spi.c (drivers\spi)
// 去除了很多非主线信息
static int spi_queued_transfer(struct spi_device *spi, struct spi_message *msg)
{struct spi_master *master = spi->master;list_add_tail(&msg->queue, &master->queue);
// 重点函数 进入工作队列,进行内核线程时的调度
// 执行master->pump_messages这个函数指针
// 那么问题来了,这个函数指针是在哪里初始化的?if (!master->busy)queue_kthread_work(&master->kworker, &master->pump_messages);}
解决上面代码中的问题: 线程初始化时线程工作函数的绑定
// 代码位置 Spi.c (drivers\spi)
// 去除了很多信息
static int spi_init_queue(struct spi_master *master)
{
// 没错这就是线程的工作函数,前面一路的调用就到了这个函数中 spi_pump_messagesinit_kthread_work(&master->pump_messages, spi_pump_messages);
}static void spi_pump_messages(struct kthread_work *work)
{struct spi_master *master = container_of(work, struct spi_master, pump_messages);...........
// 去除的代码有点多,主要是线程的一些机制,who care ?...........if (!was_busy)trace_spi_master_busy(master);if (!was_busy && master->prepare_transfer_hardware) {ret = master->prepare_transfer_hardware(master);}trace_spi_message_start(master->cur_msg);if (master->prepare_message) {ret = master->prepare_message(master, master->cur_msg);}ret = spi_map_msg(master, master->cur_msg);// 重点函数 别在问我这个函数指针是在哪里初始化的了
// 还记得 spi_bitbang_start 这个函数吗?
// 不记得就ctl+f搜一下,之前的分析
// 你都能看到这里说明很有耐心呀,马上就到硬件的操作了ret = master->transfer_one_message(master, master->cur_msg);}
spi_bitbang_transfer_one 是怎么调用硬件控制器进行传输的呢
// 代码位置Spi-bitbang.c (drivers\spi)
static int spi_bitbang_transfer_one(struct spi_master *master, struct spi_message *m)
{struct spi_bitbang *bitbang;bitbang = spi_master_get_devdata(master);cs_change = 1;status = 0;
// 对于一个message中有多个transfer的情况,遍历所有transferlist_for_each_entry(t, &m->transfers, transfer_list) {/* override speed or wordsize? */if (t->speed_hz || t->bits_per_word)do_setup = 1;// 是否需要调用控制器的setup函数if (do_setup != 0) {status = bitbang->setup_transfer(spi, t);}
// 对片选信号的操作if (cs_change) {bitbang->chipselect(spi, BITBANG_CS_ACTIVE);ndelay(nsecs);}cs_change = t->cs_change;if (t->len) {
// 重点函数 直接进行了硬件信息传输
// 是不是又忘记在哪里初始化了的
// 所以我还是建议你去下载一下那个思维导图
// 直接告诉你在 spi_imx_probe 这个函数中 // spi-imx.c中
// spi_imx->bitbang.txrx_bufs = spi_imx_transfer;status = bitbang->txrx_bufs(spi, t);}if (cs_change &&!list_is_last(&t->transfer_list, &m->transfers)) {ndelay(nsecs);bitbang->chipselect(spi, BITBANG_CS_INACTIVE);ndelay(nsecs);}}if (!(status == 0 && cs_change)) {ndelay(nsecs);bitbang->chipselect(spi, BITBANG_CS_INACTIVE);ndelay(nsecs);}spi_finalize_current_message(master);return status;
}
到这里了也就是具体硬件的操作了
// 代码位置 Spi-imx.c (drivers\spi)
static int spi_imx_transfer(struct spi_device *spi, struct spi_transfer *transfer)
{struct spi_imx_data *spi_imx = spi_master_get_devdata(spi->master);
// 是否通过DMA进行传输?
// imx6在这里的使用SPI的DMA传输是有条件的,需要每次传输大于64字节才会启动DMA进行传输if (spi_imx->bitbang.master->can_dma && spi_imx_can_dma(spi_imx->bitbang.master, spi, transfer)) {ret = spi_imx_dma_transfer(spi_imx, transfer);}
// 一般情况是通过PIO的模式进行传输的return spi_imx_pio_transfer(spi, transfer);
}
问题:上面说到imx6要使用SPI的DMA功能必须要求一次transfer大于64字节,但现在的情况是我用的XRM117X转串口,其FIFO最大只有64字节,所以我在这个时候是用不上SPI的DMA功能的,并且发现,每次只传输几个字节,不停的发,占用CPU达50%,因此效率无从谈起,then 有没有人遇到与我相同的困惑,是否能指点一下!!不胜感激!!!!
best regards
Alee
imx6 通过移植XRM117x(SPI转串口)对Linux中的SPI驱动框架进行分析相关推荐
- linux源码文件名,Linux中文件名解析处理源码分析
Linux中文件名解析处理源码分析 前言 Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象.在此,对文件名 ...
- mdadm linux,mdadm命令_Linux mdadm 命令用法详解: mdadm是一个用于创建、管理、监控RAID设备的工具,它使用linux中的md驱动。...
mdadm是一个用于创建.管理.监控RAID设备的工具,它使用linux中的md驱动. 基本语法 : mdadm [mode] [options] mdadm程序是一个独立的程序,能完成所有软件RAI ...
- linux 桥接stp原理,Linux操作系统网桥源码框架初步分析
今天处理网桥的STP的问题遇到了麻烦,对这个东东理论的倒是看了不少,没有真真学习到它的源理,来看Linux的实现,手头没有资料,看了两个钟头,只把网桥的框架结构看完,所以想先贴出来,希望有研究这块的大 ...
- linux 中的 ln 命令,Linux中ln命令的用法以及分析
在ubuntu用也有类似于windows中快捷方式这种类型的东西,即链接.这里一般使用ln命令来执行得到,ln命令用法简单,但是与windows不同,这里有硬链接和软链接两种类型的链接.在介绍两种链接 ...
- 从零开始学习linux的I2C设备驱动框架——写一个简单的SHT20驱动
目录 0.测试环境说明 1.设备树的修改 2.设备驱动框架 3.I2C数据传输过程 3.1 struct i2c_msg 3.2 SHT20的数据收发 4.I2C适配器超时等待时间的修改 本文资源 参 ...
- linux中的显卡驱动问题,linux下显卡驱动安装的问题
一.下载驱动程序 首先要找到显卡for Linux的驱动程序.现在绝大多数的3D显卡都已有了for Linux的驱动程序,可到各显卡厂商的主页或Linux的相关站点上去寻找.我找到的驱动程序名为XF8 ...
- 【分析笔记】Linux 4.9 单总线驱动框架分析
文章介绍 本文主要是基于 T507 Android 10 Linux 4.9 的源代码,对 Linux W1 总线框架的分析记录,便于了解整个框架的基本实现机制. 驱动框架 这张图基本上将内部的各个源 ...
- Linux之字符设备驱动框架
目录 一.驱动介绍 1.内核模块 2.日志级别 3.模块符号的导出 4.内核模块参数 二.字符设备驱动(一) 1.模块加载 2.注册字符设备驱动 3.内存映射 三.字符设备驱动(二) 1.模块加载 2 ...
- Linux下的FrameBuffer驱动框架
一.RGB LCD经典显示器件介绍: 1.LCD屏幕的重要属性参数: ① 分辨率:也就是屏幕上的像素点的个数: ② 像素格式:即单个像素点RGB三种颜色的表达方式,包括RGB888.ARGB8888和 ...
最新文章
- 函数进阶_月隐学python第11课
- 爱奇艺怎么看不了电视剧和视频
- 目标检测无痛涨点:大白话 Generalized Focal Loss
- svn add后的数据如何取消-svn revert??--zz
- 诹图系列(3): 条形图
- 在Windows上安装虚拟机详细图文教程
- 2060显卡驱动最新版本_堪比显卡界中的小米,价格屠夫,1999的铭瑄RTX2060终结者体验...
- 斯诺登:澳大利亚的监视政策比NSA还下流
- windows下USB检测插拔状态
- 人人都能玩航拍 手把手教你装4轴
- (笔记总结)串行通信接口标准
- 8、共射放大电路一般性质与放大电路的直流偏移
- 计算机笔记本硬盘,笔记本取证之--笔记本硬盘拆卸
- 在线图片处理api接口
- 白杨SEO:企业口碑问答营销如何做?渠道选择、推广流程及注意事项
- ppt无损转图片jpg,pdf api
- 手机闪存速度排行_带大家了解一下 手机闪存UFS和EMMC的区别(科普)
- jQuery 中ajax回调函数获得的数据格式问题
- 北京现在软件学校计算机,计算机软件排名 计算机软件学校排名
- 中国生活垃圾处理行业十四五规划与投融资模式分析报告2022~2028年
热门文章
- 位居榜首 | 未来智安荣登CCIA「2022年中国网安产业潜力之星」榜单
- 设置了相对定位relative之后,改变top值,如何去掉多余空白?
- 电影点评系统论文java_java电影在线定制影评管理系统
- 长方形与圆最近连线LISP_“认识长方形,正方形和圆”教学实录与评析
- EXCEL表格-VLOOKUP多对一结果匹配方法(通配符)
- C语言——深度剖析数据在内存中的存储
- yum软件包管理 yum(软件仓库)
- 素数处理-埃拉托色尼筛选法(埃式筛)
- angular中的?:什么意思
- Android实现直播的博文和流程(全过程,超详细/附源码)