本文的部分内容可能来源于网络,该内容归原作者所有,如果侵犯到您的权益,请及时通知我,我将立即删除,原创内容copyleft归tingkman@163.com所有,使用GPL发布,可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接,严禁用于任何商业用途。欢迎加入zynq-arm-linux提高交流群:788265722

文档错误可能很多,大家多包涵,主要理解文件的目的就好。可留言

Zynq-axidma是大家常用的功能,所以,很多同学都用到,但是有很大一部分同学比较熟悉裸跑下axidma使用,网络上也有很多这方面的教程,像米联客AXI_DMA_LOOP 环路测试,但是linux下使用axidma的使用方法却很少,所以才有了今天这个博文,

阅读这篇博文之前,大家可以参考以下博客内容,为理解linux下axi dma使用做铺垫。

Axidma 裸跑例子(更多的可以百度)

AXI_DMA_LOOP 环路测

https://www.cnblogs.com/milinker/p/6484011.html

https://blog.csdn.net/long_fly/article/details/79702222

AXI_DMA_LOOP,在viviado图里有两部分,一个是axidma模块,一个是数据回环模块,首先,用户通过写dma下发一个数据,比方1234,然后用户通过读dma读取数据读到1234,表示测试完成,这里面其实有两个dma通道一个写一个读做了两次dma操作,读写都是由用户发起的,搬运都是dma控制器执行的如下图:

在linux下驱动主要牵涉的文件有3个,一个是设备文件描述dtb,一个是axidma控制器驱动xilinx_dma.c,一个是axidmatest.c,我们今天的主角就是axidmatest.c

Xilinx官网给的axidma驱动说明在这里大家可以看看

https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/18842337/Linux+Soft+DMA+Driver

各个文件的位置如下:

内核目录

https://github.com/Xilinx/linux-xlnx/blob/master/arch/arm/boot/dts/zynq-zc706.c,这个是官网评估板的dtb描述,原版的dtb里面没有dma的描述,要把dma的描述加进来

https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/xilinx_dma.c

https://github.com/Xilinx/linux-xlnx/blob/master/drivers/dma/xilinx/axidmatest.c

在设备树里表示dma读写通道,用以下表示

dma-channel@40400000  axi-dma-mm2s-channel 写通道,是ddr网datafifo写

dma-channel@40400030  axi-dma-s2mm-channel 读通道  是datafifo往ddr搬

这个描述对应的是控制器驱动xilinx_dma.c,有这个

描述xilinx_dma.c这个驱动才会加载。

axi_dma_1: dma@40400000 {

#dma-cells = <1>;

clock-names = "s_axi_lite_aclk", "m_axi_sg_aclk", "m_axi_mm2s_aclk", "m_axi_s2mm_aclk";

clocks = <&clkc 15>, <&clkc 15>, <&clkc 15>, <&clkc 15>;

compatible = "xlnx,axi-dma-1.00.a";

interrupt-parent = <&intc>;

interrupts = <0 29 4 0 30 4>;

reg = <0x40400000 0x10000>;

xlnx,addrwidth = <0x20>;

xlnx,include-sg ;

dma-channel@40400000 {

compatible = "xlnx,axi-dma-mm2s-channel";

dma-channels = <0x1>;

interrupts = <0 29 4>;

xlnx,datawidth = <0x20>;

xlnx,device-id = <0x0>;

xlnx,include-dre ;

};

dma-channel@40400030 {

compatible = "xlnx,axi-dma-s2mm-channel";

dma-channels = <0x1>;

interrupts = <0 30 4>;

xlnx,datawidth = <0x20>;

xlnx,device-id = <0x0>;

xlnx,include-dre ;

};

};

在linux里驱动是分层的,一般像spi i2c dma这类的驱动都是分控制器驱动,和下面的设备驱动,控制器是单独的驱动,下面挂的设备是设备的驱动,设备的驱动都是去想控制器去申请的。

那么今天的主题设备驱动axidmatest.c也在设备树里面有自己的描述,我们看到它用了两个dma通道,就是dma控制器的两个读写通道。

axidmatest_1: axidmatest@1 {

compatible ="xlnx,axi-dma-test-1.00.a";

dmas = <&axi_dma_1 0

&axi_dma_1 1>;

dma-names = "axidma0", "axidma1";

} ;

这里设备树和驱动是怎么对应的那,linux采用的是compatible里面的描述

控制器  compatible = "xlnx,axi-dma-1.00.a";

测试程序驱动compatible ="xlnx,axi-dma-test-1.00.a";

驱动程序表示如下:

这样写好,还要把内核配置也配置好,这些驱动才都能加载

在写dma驱动我们不用关注xilinx_dma.c驱动的实现,只需要理解axidmatest.c的实现,下面就分析一下:axidmatest.c这个驱动。

那么从哪里开始分析那,我们知道一般程序都有个入口,比方说main函数,那在linux下axidmatest.c入口就是以下函数。

late_initcall这个意思大概意思就是后面初始化,实际上dma控制器xilinx_dma.c实在测试驱动axidmatest.c之前运行的,因为测试驱动要去向控制器申请通道,所以控制器要先初始化好,才能给测试驱动提供通道。

static int __init axidma_init(void)

{

return platform_driver_register(&xilinx_axidmatest_driver);

}

late_initcall(axidma_init);

static const struct of_device_id xilinx_axidmatest_of_ids[] = {

{ .compatible = "xlnx,axi-dma-test-1.00.a",},

{}

};

static struct platform_driver xilinx_axidmatest_driver = {

.driver = {

.name = "xilinx_axidmatest",

.owner = THIS_MODULE,

.of_match_table = xilinx_axidmatest_of_ids,

},

.probe = xilinx_axidmatest_probe,

.remove = xilinx_axidmatest_remove,

};

axidma_init其实使用platform_driver_register,加载平台设备驱动xilinx_axidmatest_driver,

平台设备驱动有几个描述driver和probe,remove函数,remove函数是在驱动程序卸载才会执行,这里一般内核启动后不会卸载。当xilinx_axidmatest_of_ids里面的compatible和设备树描述文件dtb里面的对应后probe就会运行

axidmatest_1: axidmatest@1 {

compatible ="xlnx,axi-dma-test-1.00.a";

dmas = <&axi_dma_1 0

&axi_dma_1 1>;

dma-names = "axidma0", "axidma1";

} ;

当probe函数xilinx_axidmatest_probe才开始测试驱动的初始化。

dma_request_slave_channel是linux驱动编写的标准函数,dma测试程序申请dma通道要用这个函数,具体实现我们可以不管,只需要用这个申请就可以了,当然了,控制器驱动已经准备好了,才能申请成功。

static int xilinx_axidmatest_probe(struct platform_device *pdev)

{

struct dma_chan *chan, *rx_chan;

int err;

chan = dma_request_slave_channel(&pdev->dev, "axidma0");

//---------------------向控制器申请dma发送通道

if (IS_ERR(chan)) {

pr_err("xilinx_dmatest: No Tx channel\n");

return PTR_ERR(chan);

}

rx_chan = dma_request_slave_channel(&pdev->dev, "axidma1");

//---------------------向控制器申请dma接收通道

if (IS_ERR(rx_chan)) {

err = PTR_ERR(rx_chan);

pr_err("xilinx_dmatest: No Rx channel\n");

goto free_tx;

}

err = dmatest_add_slave_channels(chan, rx_chan);

if (err) {

pr_err("xilinx_dmatest: Unable to add channels\n");

goto free_rx;

}

return 0;

free_rx:

dma_release_channel(rx_chan);

free_tx:

dma_release_channel(chan);

return err;

}

dmatest_add_slave_channels(chan, rx_chan)函数进一步初始化。

定义两个dmatest_chan类型的数据结构tx_dtc,rx_dtc用来保存之前申请的两个通道,后面就用这两个数据接口来访问通道。

static int dmatest_add_slave_channels(struct dma_chan *tx_chan,

struct dma_chan *rx_chan)

{

struct dmatest_chan *tx_dtc;

struct dmatest_chan *rx_dtc;

unsigned int thread_count = 0;

tx_dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);

if (!tx_dtc) {

pr_warn("dmatest: No memory for tx %s\n",

dma_chan_name(tx_chan));

return -ENOMEM;

}

rx_dtc = kmalloc(sizeof(struct dmatest_chan), GFP_KERNEL);

if (!rx_dtc) {

pr_warn("dmatest: No memory for rx %s\n",

dma_chan_name(rx_chan));

return -ENOMEM;

}

tx_dtc->chan = tx_chan;

rx_dtc->chan = rx_chan;

INIT_LIST_HEAD(&tx_dtc->threads);

INIT_LIST_HEAD(&rx_dtc->threads);

dmatest_add_slave_threads(tx_dtc, rx_dtc);

thread_count += 1;

pr_info("dmatest: Started %u threads using %s %s\n",

thread_count, dma_chan_name(tx_chan), dma_chan_name(rx_chan));

list_add_tail(&tx_dtc->node, &dmatest_channels);

list_add_tail(&rx_dtc->node, &dmatest_channels);

nr_channels += 2;

if (iterations)

wait_event(thread_wait, !is_threaded_test_run(tx_dtc, rx_dtc));

return 0;

}

dmatest_add_slave_threads实际上是创建了一个内核任务,这个任务才是发起dma操作的,

在linux-dma测试程序中发起dma请求,都是由标准的函数,我们写的时候也要用到这些函数

这里要了解一些linuxdma的相关知识,这里找几篇博客参考

http://www.wowotech.net/linux_kenrel/dma_engine_overview.html

http://www.wowotech.net/linux_kenrel/dma_engine_api.html

https://www.cnblogs.com/xiaojiang1025/archive/2017/02/11/6389194.html

Dma驱动一般分两种,一种是一致性dma一种是流式dma,本文用的dma是流式的。

相关函数如下:

dma_map_single

sg_init_table(tx_sg, bd_cnt);

sg_dma_address

sg_dma_len

device_prep_slave_sg

rxd->tx_submit

dmatest_slave_func函数基本流程我先概况一下:

先通过准备数据通过写通道dma发送给datafifo,然后在通过读通道dma从datafifo读出来,对比发送的数据是否和接收的数据一致,一致则认为测试通过,循环做几次测试

static int dmatest_slave_func(void *data)函数分析

static int dmatest_slave_func(void *data)

{

struct dmatest_slave_thread *thread = data;

struct dma_chan *tx_chan;

struct dma_chan *rx_chan;

const char *thread_name;

unsigned int src_off, dst_off, len;

unsigned int error_count;

unsigned int failed_tests = 0;

unsigned int total_tests = 0;

dma_cookie_t tx_cookie;

dma_cookie_t rx_cookie;

enum dma_status status;

enum dma_ctrl_flags flags;

int ret;

int src_cnt;

int dst_cnt;

int bd_cnt = 11;

int i;

ktime_t ktime, start, diff;

ktime_t filltime = 0;

ktime_t comparetime = 0;

s64 runtime = 0;

unsigned long long total_len = 0;

thread_name = current->comm;

ret = -ENOMEM;

/* Ensure that all previous reads are complete */

smp_rmb();

tx_chan = thread->tx_chan;

rx_chan = thread->rx_chan;

dst_cnt = bd_cnt; //这里为11,实际上是创建11个sg,sg支持多个分块dma传输,这里可以改成1,就一块

src_cnt = bd_cnt;

//申请发送缓冲区数据结构,

thread->srcs = kcalloc(src_cnt + 1, sizeof(u8 *), GFP_KERNEL);

if (!thread->srcs)

goto err_srcs;

//申请发送缓冲区,这里面放发送的数据,往datafifo发送的数据

for (i = 0; i < src_cnt; i++) {

thread->srcs[i] = kmalloc(test_buf_size, GFP_KERNEL);

if (!thread->srcs[i])

goto err_srcbuf;

}

thread->srcs[i] = NULL;

//申请接收缓冲区数据结构,

thread->dsts = kcalloc(dst_cnt + 1, sizeof(u8 *), GFP_KERNEL);

if (!thread->dsts)

goto err_dsts;

//申请接收缓冲区,这里是dma从datafifo搬运的数据放到这里,

for (i = 0; i < dst_cnt; i++) {

thread->dsts[i] = kmalloc(test_buf_size, GFP_KERNEL);

if (!thread->dsts[i])

goto err_dstbuf;

}

thread->dsts[i] = NULL;

set_user_nice(current, 10);

flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;

ktime = ktime_get();

while (!kthread_should_stop() &&

!(iterations && total_tests >= iterations)) {

struct dma_device *tx_dev = tx_chan->device;

struct dma_device *rx_dev = rx_chan->device;

struct dma_async_tx_descriptor *txd = NULL;

struct dma_async_tx_descriptor *rxd = NULL;

dma_addr_t dma_srcs[src_cnt];

dma_addr_t dma_dsts[dst_cnt];

struct completion rx_cmp;

struct completion tx_cmp;

unsigned long rx_tmo =

msecs_to_jiffies(300000); /* RX takes longer */

unsigned long tx_tmo = msecs_to_jiffies(30000);

u8 align = 0;

struct scatterlist tx_sg[bd_cnt];   //定义发送dma数据结构,

struct scatterlist rx_sg[bd_cnt];   //定义接收dma数据结构,

total_tests++;

//----------------字节对齐,dma传输要求字节对齐

/* honor larger alignment restrictions */

align = tx_dev->copy_align;

if (rx_dev->copy_align > align)

align = rx_dev->copy_align;

if (1 << align > test_buf_size) {

pr_err("%u-byte buffer too small for %d-byte alignment\n",

test_buf_size, 1 << align);

break;

}

len = dmatest_random() % test_buf_size + 1;

len = (len >> align) << align;

if (!len)

len = 1 << align;

total_len += len;

src_off = dmatest_random() % (test_buf_size - len + 1);

dst_off = dmatest_random() % (test_buf_size - len + 1);

src_off = (src_off >> align) << align;

dst_off = (dst_off >> align) << align;

start = ktime_get();

dmatest_init_srcs(thread->srcs, src_off, len);

dmatest_init_dsts(thread->dsts, dst_off, len);

//----------------字节对齐,dma传输要求字节对齐

diff = ktime_sub(ktime_get(), start);

filltime = ktime_add(filltime, diff);

for (i = 0; i < src_cnt; i++) {

u8 *buf = thread->srcs[i] + src_off;

//dma映射后获取的物理地址,实际上dma初始化用的是物理地址,thread->srcs[i] 这个是虚拟地址,是程序可以直接使用的,但是dma用的是物理地址

dma_srcs[i] = dma_map_single(tx_dev->dev, buf, len,

DMA_MEM_TO_DEV);

}

for (i = 0; i < dst_cnt; i++) {

//dma映射后获取的物理地址,实际上dma初始化用的是物理地址,thread->srcs[i] 这个是虚拟地址,是程序可以直接使用的,但是dma用的是物理地址

dma_dsts[i] = dma_map_single(rx_dev->dev,

thread->dsts[i],

test_buf_size,

DMA_BIDIRECTIONAL);

}

//初始化发送接收数据结构----------------------

sg_init_table(tx_sg, bd_cnt);

sg_init_table(rx_sg, bd_cnt);

for (i = 0; i < bd_cnt; i++) {

sg_dma_address(&tx_sg[i]) = dma_srcs[i]; //物理地址

sg_dma_address(&rx_sg[i]) = dma_dsts[i] + dst_off;

sg_dma_len(&tx_sg[i]) = len; //dma一次传输的长度

sg_dma_len(&rx_sg[i]) = len;

}

//准备发送 接收sg

rxd = rx_dev->device_prep_slave_sg(rx_chan, rx_sg, bd_cnt,

DMA_DEV_TO_MEM, flags, NULL);

txd = tx_dev->device_prep_slave_sg(tx_chan, tx_sg, bd_cnt,

DMA_MEM_TO_DEV, flags, NULL);

if (!rxd || !txd) {

for (i = 0; i < src_cnt; i++)

dma_unmap_single(tx_dev->dev, dma_srcs[i], len,

DMA_MEM_TO_DEV);

for (i = 0; i < dst_cnt; i++)

dma_unmap_single(rx_dev->dev, dma_dsts[i],

test_buf_size,

DMA_BIDIRECTIONAL);

pr_warn("%s: #%u: prep error with src_off=0x%x ",

thread_name, total_tests - 1, src_off);

pr_warn("dst_off=0x%x len=0x%x\n",

dst_off, len);

msleep(100);

failed_tests++;

continue;

}

//接收的dma,先准备接收的

init_completion(&rx_cmp);

rxd->callback = dmatest_slave_rx_callback;//dma搬运完这个函数会运行

rxd->callback_param = &rx_cmp;

rx_cookie = rxd->tx_submit(rxd);

//发送的dma,

init_completion(&tx_cmp);

txd->callback = dmatest_slave_tx_callback;//dma搬运完这个函数会运行

txd->callback_param = &tx_cmp;

tx_cookie = txd->tx_submit(txd);

if (dma_submit_error(rx_cookie) ||

dma_submit_error(tx_cookie)) {

pr_warn("%s: #%u: submit error %d/%d with src_off=0x%x ",

thread_name, total_tests - 1,

rx_cookie, tx_cookie, src_off);

pr_warn("dst_off=0x%x len=0x%x\n",

dst_off, len);

msleep(100);

failed_tests++;

continue;

}

dma_async_issue_pending(rx_chan);

dma_async_issue_pending(tx_chan);

//--dma_async_issue_pending这个执行后就相当于给dma控制器发命令了,dma控制器就会根据我们提供的参数来自己搬运数据了

tx_tmo = wait_for_completion_timeout(&tx_cmp, tx_tmo);

//等待发送dma传输完 等completion

status = dma_async_is_tx_complete(tx_chan, tx_cookie,

NULL, NULL);

if (tx_tmo == 0) {

pr_warn("%s: #%u: tx test timed out\n",

thread_name, total_tests - 1);

failed_tests++;

continue;

} else if (status != DMA_COMPLETE) {

pr_warn("%s: #%u: tx got completion callback, ",

thread_name, total_tests - 1);

pr_warn("but status is \'%s\'\n",

status == DMA_ERROR ? "error" :

"in progress");

failed_tests++;

continue;

}

rx_tmo = wait_for_completion_timeout(&rx_cmp, rx_tmo);

//等待接收传输完

status = dma_async_is_tx_complete(rx_chan, rx_cookie,

NULL, NULL);

if (rx_tmo == 0) {

pr_warn("%s: #%u: rx test timed out\n",

thread_name, total_tests - 1);

failed_tests++;

continue;

} else if (status != DMA_COMPLETE) {

pr_warn("%s: #%u: rx got completion callback, ",

thread_name, total_tests - 1);

pr_warn("but status is \'%s\'\n",

status == DMA_ERROR ? "error" :

"in progress");

failed_tests++;

continue;

}

//前面是先映射,这里是解除映射,这样缓冲区的数据才是正确的,不执行这个操作,缓冲区数据不对。

/* Unmap by myself */

for (i = 0; i < dst_cnt; i++)

dma_unmap_single(rx_dev->dev, dma_dsts[i],

test_buf_size, DMA_BIDIRECTIONAL);

error_count = 0;

start = ktime_get();

pr_debug("%s: verifying source buffer...\n", thread_name);

error_count += dmatest_verify(thread->srcs, 0, src_off,

0, PATTERN_SRC, true);

error_count += dmatest_verify(thread->srcs, src_off,

src_off + len, src_off,

PATTERN_SRC | PATTERN_COPY, true);

error_count += dmatest_verify(thread->srcs, src_off + len,

test_buf_size, src_off + len,

PATTERN_SRC, true);

pr_debug("%s: verifying dest buffer...\n",

thread->task->comm);

//--校验接收的数据和发送的数据,

error_count += dmatest_verify(thread->dsts, 0, dst_off,

0, PATTERN_DST, false);

error_count += dmatest_verify(thread->dsts, dst_off,

dst_off + len, src_off,

PATTERN_SRC | PATTERN_COPY, false);

error_count += dmatest_verify(thread->dsts, dst_off + len,

test_buf_size, dst_off + len,

PATTERN_DST, false);

diff = ktime_sub(ktime_get(), start);

comparetime = ktime_add(comparetime, diff);

if (error_count) {

pr_warn("%s: #%u: %u errors with ",

thread_name, total_tests - 1, error_count);

pr_warn("src_off=0x%x dst_off=0x%x len=0x%x\n",

src_off, dst_off, len);

failed_tests++;

} else {

pr_debug("%s: #%u: No errors with ",

thread_name, total_tests - 1);

pr_debug("src_off=0x%x dst_off=0x%x len=0x%x\n",

src_off, dst_off, len);

}

}

ktime = ktime_sub(ktime_get(), ktime);

ktime = ktime_sub(ktime, comparetime);

ktime = ktime_sub(ktime, filltime);

runtime = ktime_to_us(ktime);

ret = 0;

for (i = 0; thread->dsts[i]; i++)

kfree(thread->dsts[i]);

err_dstbuf:

kfree(thread->dsts);

err_dsts:

for (i = 0; thread->srcs[i]; i++)

kfree(thread->srcs[i]);

err_srcbuf:

kfree(thread->srcs);

err_srcs:

pr_notice("%s: terminating after %u tests, %u failures %llu iops %llu KB/s (status %d)\n",

thread_name, total_tests, failed_tests,

dmatest_persec(runtime, total_tests),

dmatest_KBs(runtime, total_len), ret);

thread->done = true;

wake_up(&thread_wait);

return ret;

}

至此,axidmatest.c主要部分就是这个任务,所以,把这个搞明白就行,还有一点数据准备这块有个字节对齐,有点麻烦,容易搞不清楚,可以使用__get_free_pages申请缓冲区,这种函数本身就是对齐的,还有,这种流式的dma要先dma_map_single传输完后还要dma_unmap_single后才能去读传输的数据,这种函数我测试数据量小的话好用,如果很大的话,就比较花时间,一致性dma就不用这样。后面有时间吧这个改造一下,做一致性的dma,写的尽量简单能用,能理解大概就行。

之前在闲鱼上买的一个fmc 测试nvme-ssd硬盘的测试板,测试过不怎么用了,现在出手,

Fmc nvme sata pinggu评估 测试板,可以在具有fmc接口的开发板测试 比如zynq zc706上测试nvme协议的固态硬盘ssd,提供技术支持,比如怎么在vivado建立工程,linux下驱动指导等

Zynq Axidma linux下驱动axidmatest.c 驱动分析相关推荐

  1. Linux下的USB总线驱动 mouse

    Linux下的USB总线驱动(03)--USB鼠标驱动 usbmouse.c USB鼠标驱动 usbmouse.c 原文链接:http://www.linuxidc.com/Linux/2012-12 ...

  2. 南京邮电大学嵌入式系统开发实验5:嵌入式Linux下LED报警灯驱动设计及编程

    实验5  嵌入式Linux下LED报警灯驱动设计及编程 一.实验目的 理解驱动本质,掌握嵌入式Linux系统下驱动开发相关知识,包括端口寄存器访问.接口函数编写.和文件系统挂接.注册及相关应用编程等知 ...

  3. *Linux下的USB总线驱动 u盘驱动分析*

    Linux下的USB总线驱动(三) u盘驱动分析 版权所有,转载请说明转自 http://my.csdn.net/weiqing1981127 https://www.xuebuyuan.com/13 ...

  4. 在Fedora 16 linux下安装USB无线网卡驱动88x2bu

    在Fedora 16 linux下安装USB无线网卡驱动88x2bu USB无线网卡翼联EP-AC1610兼容linux系统 我之前已经买了一个USB无线网卡是水星mw150us,但是没有linux驱 ...

  5. linux 安装水星无线网卡驱动,Linux下安装RTL8188CE网卡驱动(Mercury MW150U)

    先说明下我的系统: kernel: 3.0.0-32-generic 今天买了个无线网卡Mercury 150Mbps MW150U系列,我发现在我的笔记本的Ubuntu 12.10下不用安装驱动就能 ...

  6. 如何编写Linux 下的 USB 键盘驱动

     如何编写Linux 下的 USB 键盘驱动 1. 指定 USB 键盘驱动所需的头文件: #include <linux/kernel.h>/*内核头文件,含有内核一些常用函数的原型定 ...

  7. 什么是 Linux 下的 platform 设备驱动

    Linux下的字符设备驱动一般都比较简单,只是对IO进行简单的读写操作.但是I2C.SPI.LCD.USB等外设的驱动就比较复杂了,需要考虑到驱动的可重用性,以避免内核中存在大量重复代码,为此人们提出 ...

  8. Linux驱动开发: Linux下RTC实时时钟驱动

    Linux内核版本: 3.5 1.1 Linux下RTC时间的读写分析 1.1.1 系统时间与RTC实时时钟时间 Linux系统下包含两个时间:系统时间和RTC时间. 系统时间:是由主芯片的定时器进行 ...

  9. Linux下PCI转串口卡驱动安装方法

    Linux下PCI转串口卡驱动安装方法 ----------------------------------- 由于公司产品要做行业市场,而产品与行业用户间PC的通讯为RS232串口方式.而行业用户那 ...

  10. usb hub 要驱动 linux,Linux下的USB HUB驱动

    Linux下的USB HUB驱动 [日期:2012-07-29] 来源:Linux社区 作者:zhengmeifu [字体:大 中 小] 五:hub接口驱动分析 5.1:接口驱动架构 是时候来分析接口 ...

最新文章

  1. python压缩和解压缩
  2. linux 窗口z order,wxPython窗口z-order设置
  3. PythonEditor 中文图形化编程网站即将正式启用
  4. VTK:可视化算法之Office
  5. Openstack Neutron 管理的网络资源
  6. Polly的7种策略
  7. [Java基础]Stream流的常见生成方式
  8. 终端花屏后的恢复办法
  9. mysql获取当前时间示范,mysql 实时显示当前运行的sql语句
  10. 第三方模块:Python3 OS 文件/目录方法
  11. 深度学习声纹识别_一种基于机器学习及深度学习的声纹降噪方法及系统与流程...
  12. 进程原语和线程原语是啥意思_转换中介原语
  13. linux安装字体文件怎么安装路径,Linux安装字体文件
  14. android power 按键,android 添加按(power键)电源键结束通话(挂断电话)
  15. 情商和逆商比智商更重要
  16. 理解exponential weighted || 指数
  17. 思维导图怎么画简单好看的直观技巧
  18. elf section类型_ELF格式解析库之基本数据类型
  19. AdaptivePose: 人体姿态估计新思路,将人体部位表示为自适应点
  20. Windows的Safari(可能)已死:如何迁移到另一个浏览器

热门文章

  1. 关于sicp练习2.73的前置程序
  2. python渗透攻击
  3. crontab、cron、at、atq、batch、ps命令练习题
  4. 百度AI 开放平台 人脸检测与识别
  5. cloopen java_GitHub - cloopen/java-sms-sdk: Yuntongxun SMS SDK for Java
  6. 《大话设计模式 下》
  7. Go go test测试机制介绍及实例【Go语言圣经笔记】
  8. [强烈推荐]ring0下文件解锁强制删除工具
  9. ceph集群全部停机开机
  10. tokenize java,Java split string - Java tokenize string examples - 入门小站-rumenz.com