最近分析了一下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驱动框架进行分析相关推荐

  1. linux源码文件名,Linux中文件名解析处理源码分析

    Linux中文件名解析处理源码分析 前言 Linux中对一个文件进行操作的时候,一件很重要的事情是对文件名进行解析处理,并且找到对应文件的inode对象,然后创建表示文件的file对象.在此,对文件名 ...

  2. mdadm linux,mdadm命令_Linux mdadm 命令用法详解: mdadm是一个用于创建、管理、监控RAID设备的工具,它使用linux中的md驱动。...

    mdadm是一个用于创建.管理.监控RAID设备的工具,它使用linux中的md驱动. 基本语法 : mdadm [mode] [options] mdadm程序是一个独立的程序,能完成所有软件RAI ...

  3. linux 桥接stp原理,Linux操作系统网桥源码框架初步分析

    今天处理网桥的STP的问题遇到了麻烦,对这个东东理论的倒是看了不少,没有真真学习到它的源理,来看Linux的实现,手头没有资料,看了两个钟头,只把网桥的框架结构看完,所以想先贴出来,希望有研究这块的大 ...

  4. linux 中的 ln 命令,Linux中ln命令的用法以及分析

    在ubuntu用也有类似于windows中快捷方式这种类型的东西,即链接.这里一般使用ln命令来执行得到,ln命令用法简单,但是与windows不同,这里有硬链接和软链接两种类型的链接.在介绍两种链接 ...

  5. 从零开始学习linux的I2C设备驱动框架——写一个简单的SHT20驱动

    目录 0.测试环境说明 1.设备树的修改 2.设备驱动框架 3.I2C数据传输过程 3.1 struct i2c_msg 3.2 SHT20的数据收发 4.I2C适配器超时等待时间的修改 本文资源 参 ...

  6. linux中的显卡驱动问题,linux下显卡驱动安装的问题

    一.下载驱动程序 首先要找到显卡for Linux的驱动程序.现在绝大多数的3D显卡都已有了for Linux的驱动程序,可到各显卡厂商的主页或Linux的相关站点上去寻找.我找到的驱动程序名为XF8 ...

  7. 【分析笔记】Linux 4.9 单总线驱动框架分析

    文章介绍 本文主要是基于 T507 Android 10 Linux 4.9 的源代码,对 Linux W1 总线框架的分析记录,便于了解整个框架的基本实现机制. 驱动框架 这张图基本上将内部的各个源 ...

  8. Linux之字符设备驱动框架

    目录 一.驱动介绍 1.内核模块 2.日志级别 3.模块符号的导出 4.内核模块参数 二.字符设备驱动(一) 1.模块加载 2.注册字符设备驱动 3.内存映射 三.字符设备驱动(二) 1.模块加载 2 ...

  9. Linux下的FrameBuffer驱动框架

    一.RGB LCD经典显示器件介绍: 1.LCD屏幕的重要属性参数: ① 分辨率:也就是屏幕上的像素点的个数: ② 像素格式:即单个像素点RGB三种颜色的表达方式,包括RGB888.ARGB8888和 ...

最新文章

  1. 函数进阶_月隐学python第11课
  2. 爱奇艺怎么看不了电视剧和视频
  3. 目标检测无痛涨点:大白话 Generalized Focal Loss
  4. svn add后的数据如何取消-svn revert??--zz
  5. 诹图系列(3): 条形图
  6. 在Windows上安装虚拟机详细图文教程
  7. 2060显卡驱动最新版本_堪比显卡界中的小米,价格屠夫,1999的铭瑄RTX2060终结者体验...
  8. 斯诺登:澳大利亚的监视政策比NSA还下流
  9. windows下USB检测插拔状态
  10. 人人都能玩航拍 手把手教你装4轴
  11. (笔记总结)串行通信接口标准
  12. 8、共射放大电路一般性质与放大电路的直流偏移
  13. 计算机笔记本硬盘,笔记本取证之--笔记本硬盘拆卸
  14. 在线图片处理api接口
  15. 白杨SEO:企业口碑问答营销如何做?渠道选择、推广流程及注意事项
  16. ppt无损转图片jpg,pdf api
  17. 手机闪存速度排行_带大家了解一下 手机闪存UFS和EMMC的区别(科普)
  18. jQuery 中ajax回调函数获得的数据格式问题
  19. 北京现在软件学校计算机,计算机软件排名 计算机软件学校排名
  20. 中国生活垃圾处理行业十四五规划与投融资模式分析报告2022~2028年

热门文章

  1. 位居榜首 | 未来智安荣登CCIA「2022年中国网安产业潜力之星」榜单
  2. 设置了相对定位relative之后,改变top值,如何去掉多余空白?
  3. 电影点评系统论文java_java电影在线定制影评管理系统
  4. 长方形与圆最近连线LISP_“认识长方形,正方形和圆”教学实录与评析
  5. EXCEL表格-VLOOKUP多对一结果匹配方法(通配符)
  6. C语言——深度剖析数据在内存中的存储
  7. yum软件包管理 yum(软件仓库)
  8. 素数处理-埃拉托色尼筛选法(埃式筛)
  9. angular中的?:什么意思
  10. Android实现直播的博文和流程(全过程,超详细/附源码)