Linux中,驱动必然会有驱动对应的设备类型。在linux4.4版本中,其设备是以设备树的形式展现的。

PS端设备树的devicetree表示如下

324 dmac_s: dmac@f8003000 {

325 compatible = "arm,pl330", "arm,primecell";

326 reg = <0xf8003000 0x1000>;

327 interrupt-parent = ;

328 interrupt-names = "abort", "dma0", "dma1", "dma2", "dma3",

329 "dma4", "dma5", "dma6", "dma7";

330 interrupts = <0 13 4>,

331 <0 14 4>, <0 15 4>,

332 <0 16 4>, <0 17 4>,

333 <0 40 4>, <0 41 4>,

334 <0 42 4>, <0 43 4>;

335 #dma-cells = <1>;

336 #dma-channels = <8>;

337 #dma-requests = <4>;

338 clocks = ;

339 clock-names = "apb_pclk";

340 };

这个文件根据设备树信息创建设备信息,在驱动程序注册时就可以找到该设备信息,执行probe函数。

zynq下dma的设备channel如下:

​root@linaro-ubuntu-desktop:/sys/class/dma# ls

dma0chan0 dma0chan2 dma0chan4 dma0chan6 dma1chan0

dma0chan1 dma0chan3 dma0chan5 dma0chan7

root@linaro-ubuntu-desktop:/sys/class/dma# ll

total 0

drwxr-xr-x 2 root root 0 1970-01-01 00:00 ./

drwxr-xr-x 50 root root 0 1970-01-01 00:00 ../

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan0 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan0/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan1 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan1/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan2 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan2/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan3 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan3/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan4 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan4/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan5 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan5/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan6 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan6/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma0chan7 -> ../../devices/soc0/amba/f8003000.dmac/dma/dma0chan7/

lrwxrwxrwx 1 root root 0 1970-01-01 00:00 dma1chan0 -> ../../devices/soc0/fpga-axi@0/43000000.axivdma/dma/dma1chan0/

dma1chan0是xilinx AXI-VDMA IP生成的DMA控制器,其处于PL端,而dma0相关的控制器是ps端的pl330.本篇看pl330这个驱动程序的注册。

查看物理地址分布

​root@linaro-ubuntu-desktop:~# cat /proc/iomem

00000000-1fffffff : System RAM

00008000-006651a3 : Kernel code

006a2000-00700867 : Kernel data

41600000-4160ffff : /fpga-axi@0/i2c@41600000

43000000-43000fff : /fpga-axi@0/axivdma@43000000

70e00000-70e0ffff : /fpga-axi@0/axi_hdmi@70e00000

75c00000-75c00fff : /fpga-axi@0/axi-spdif-tx@0x75c00000

77600000-77600fff : /fpga-axi@0/axi-i2s@0x77600000

79000000-7900ffff : /fpga-axi@0/axi-clkgen@79000000

e0001000-e0001fff : xuartps

e0002000-e0002fff : /amba/usb@e0002000

e0002000-e0002fff : /amba/usb@e0002000

e000a000-e000afff : /amba/gpio@e000a000

e000b000-e000bfff : /amba/eth@e000b000

e000d000-e000dfff : /amba/spi@e000d000

e0100000-e0100fff : mmc0

f8003000-f8003fff : /amba/dmac@f8003000

f8003000-f8003fff : /amba/dmac@f8003000

f8005000-f8005fff : /amba/watchdog@f8005000

f8007000-f80070ff : /amba/devcfg@f8007000

f8007100-f800711f : /amba/adc@f8007100

f800c000-f800cfff : /amba/ocmc@f800c000

fffc0000-ffffffff : f800c000.ocmc

root@linaro-ubuntu-desktop:~#

ARM采用统一编址,其访问内存和外设的指令是一样没有差异的,linux内核并不通过物理地址直接访问外设,而是通过虚拟地址,虚拟地址经过MMU转换成物理访问外设。所以外设需要使用ioremap()对将物理地址空间转换成虚拟地址。

I/O设备使用第三种地址,总线地址。如果一个设备在MMIO(memory mapped IO内存映射地址空间)有寄存器,或者其对系统存储系统执行DMA读写操作,设备使用的就是总线地址。

在系统枚举阶段,内核知道I/O设备以及他们的MMIO空间。如果一个设备支持DMA方式,驱动通过kmalloc()申请一段内存空间,返回申请空间的首地址,设为X,虚拟地址系统将虚拟地址X映射到物理地址Y。驱动程序可以使用虚拟地址X访问设备地址空间,但是设备本身却不行,这是因为DMA本身并不是通过虚拟地址方式来工作的。

在zynq7000设备里,DMA可以直接操作物理地址,但另一些处理器使用IOMMU将DMA地址转换物理地址,linux建议使用DMA API而不是特定总线的DMA API,比如使用dma_map_*()接口而不是pci_map_*()接口,在zynq7000中,pl330 DMA已经实现了直接调用通用的DMA框架API即可以。

DMA开发相关API

首先得包含

​#include

其提供了dma_addr_t定义,其可以作为设备使用DMA的源地址或者目的地址,并且可以使用DMA_XXX相关的API。

1.使用大DMA 一致性buffer

​void * dma_alloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)

分配大小的一致性区域。其返回值能被强制类型转换成unsigned integer,这样就是总线的宽度

​void * dma_zalloc_coherent(struct device *dev, size_t size, dma_addr_t *dma_handle, gfp_t flag)

是对dma_alloc_coherent的封装,并且将返回的地址空间内存清零。

​void dma_free_coherent(struct device *dev, size_t size, void *cpu_addr, dma_addr_t dma_handle)

释放一致性内存。

2.使用小DMA一致性buffer

需要包括如下代码

​#include

其工作有点类似kmem_cache(),不过其使用的是DMA一致性分配器,而非__get_free_pages(),并且需要N-byte对齐。

​struct dma_pool *

dma_pool_create(const char *name, struct device *dev, size_t size, size_t align, size_t alloc);

“name”字段用于诊断,dev和size和传递给dma_alloc_coherent()类似,align以字节计,且需要是2的指数。如果设备没有跨界限制,传递0;传递4096则意味着分配的DMA空间不能跨越4KByte空间。

​void *dma_pool_zalloc(struct dma_pool *pool, gfp_t mem_flags, dma_addr_t *handle)

​void *dma_pool_alloc(struct dma_pool *pool, gfp_t gfp_flags, dma_addr_t *dma_handle);

从pool分配内存,返回内存将会满足size和alignment要求。传递GFP_ATOMIC防止阻塞,或者如果允许阻塞,则可以传递GFP_KERNEL,和dma_alloc_coherent()类似,返回两个值,一个是CPU使用的地址,以及一个设备使用的DMA地址。

​void dma_pool_free(struct dma_pool *pool, void *vaddr, dma_addr_t addr);

释放由dma_pool_alloc()分配的内存空间到pool。

​void dma_pool_destroy(struct dma_pool *pool);

dma_pool_destroy()将内存池的资源释放。

part1c DMA寻址限制

​int dma_set_mask_and_coherent(struct device *dev, u64 mask)

检查mask是否合法,如果是跟新设备streaming以及DMA mask参数。返回0是正确。

​int dma_set_mask(struct device *dev, u64 mask)

int dma_set_coherent_mask(struct device *dev, u64 mask

检查mask是否合法,如果是就跟新。

​u64 dma_get_required_mask(struct device *dev)

检查系统合法的DMA掩码。

part 1D Streaming(流式) DMA 映射

​dma_addr_t dma_map_single(struct device *dev, void *cpu_addr, size_t size, enum dma_data_direction direction)

将处理器的虚拟地址空间进行映射,这样让处理器可以访问外设,返回值是DMA地址。

其direction参数可选字段即意义如下:

​DMA_NONEno direction (used for debugging)

DMA_TO_DEVICEdata is going from the memory to the device

DMA_FROM_DEVICEdata is coming from the device to the memory

DMA_BIDIRECTIONALdirection isn't known

并不是所有的存储空间都能够使用上面的函数进行映射。内核虚拟地址也许是连续的但是其映射的物理地址却可以不一样。由于这个API不提供scatter/gather功能,如果尝试映射非连续物理存储空间。所以由该API进行映射的地址需要确保物理地址连续,如kmalloc。

设备DMA的地址内存范围必须在dma_mask,需要确保由kmalloc分配的内存空间在dma_mask所能达到的范围之内。驱动程序也许会传递各种flags以限制DMA地址范围(比如X86能够表示的地址范围在前16MB内)。

对具有IOMMU的平台,物理地址连续性和dma_mask要求也许不再适用。然而,考虑到可移植性,通常会忽略IOMMU的存在。

内存操作粒度被成为cache line 宽度,为了让这里的API映射的DMA操作成功执行,映射的区域必须在cache line边界开始和结束(目的是不发生对一个cache line出现两个独立的映射区域)。

DMA_TO_DEVICE,在软件修改内存块后程序退出控制前,需要进行同步。一个这一原语被使用,由这一原语包括的这一区域被成为只读,如果同时设备想写,需要使用DMA_BIDIRECTIONAL标志。

DMA_FROM_DEVICE 在驱动程序修改数据前先同步。内存块需要被当成只读对待。如果驱动要写功能,则需要设置DMA_BIDIRECTIONAL。

DMA_BIDIRECTIONAL需要特殊对待。这意味这驱动程序不能确定内存的改变在是否发生在将存储空间交给设备前,同时不能确定设备是否需要改变这块内存。所以,需要双向同步内存块。一次是在内存控制权移交给设置前,一次是在获取数据前。

​void dma_unmap_single(struct device *dev, dma_addr_t dma_addr, size_t size, enum dma_data_direction direction)

Unmap先前映射的区域,所有传递进来的参数必须和建立映射接口时的参数一致。

​dma_addr_t dma_map_page(struct device *dev, struct page *page, unsigned long offset, size_t size,

enum dma_data_direction direction)

void dma_unmap_page(struct device *dev, dma_addr_t dma_address, size_t size, enum dma_data_direction direction)

对页进行映射和逆映射。轻易不要动和这两个参数。

​int dma_mapping_error(struct device *dev, dma_addr_t dma_addr)

在一些场景下,dma_map_single() and dma_map_page()在创建一个映射时也许会失败。调用上面的接口可以检测出错的原因。

​int

dma_map_sg(struct device *dev, struct scatterlist *sg, int nents, enum dma_data_direction direction)

返回segment 映射的DMA地址。

当使用scatterlist方式时,建立映射的过程如下:

​int i, count = dma_map_sg(dev, sglist, nents, direction);

struct scatterlist *sg;

for_each_sg(sglist, sg, count, i) { hw_address[i] = sg_dma_address(sg);

hw_len[i] = sg_dma_len(sg); }

zynq PS端pl330驱动注册

3002 static struct amba_driver pl330_driver = {

3003 .drv = {

3004 .owner = THIS_MODULE,

3005 .name = "dma-pl330",

3006 .pm = &pl330_pm,

3007 },

3008 .id_table = pl330_ids,

3009 .probe = pl330_probe,

3010 .remove = pl330_remove,

3011 };

3012

3013 module_amba_driver(pl330_driver);

见到此则知道必然是调用probe函数。注册函数调用如下:

2775 static int

2776 pl330_probe(struct amba_device *adev, const struct amba_id *id)

2777 {

2778 struct dma_pl330_platdata *pdat;

2779 struct pl330_config *pcfg;

2780 struct pl330_dmac *pl330;

2781 struct dma_pl330_chan *pch, *_p;

2782 struct dma_device *pd;

2783 struct resource *res;

2784 int i, ret, irq;

2785 int num_chan;

2786

2787 pdat = dev_get_platdata(&adev->dev);

上面定义了三个重要的数据结构体,它们的关系如下图:

2806 pl330->base = devm_ioremap_resource(&adev->dev, res);

2807 if (IS_ERR(pl330->base))

2808 return PTR_ERR(pl330->base);

对地址空间进行映射,并且存在了struct pl330_dmac结构体里。

2812 for (i = 0; i < AMBA_NR_IRQS; i++) {

2813 irq = adev->irq[i];

2814 if (irq) {

2815 ret = devm_request_irq(&adev->dev, irq,

2816 pl330_irq_handler, 0,

2817 dev_name(&adev->dev), pl330);

2818 if (ret)

2819 return ret;

2820 } else {

2821 break;

2822 }

2823 }

每一个channel对应于一个中断,但是它们的中断服务函数是同一个。

2825 pcfg = &pl330->pcfg;

2826

2827 pcfg->periph_id = adev->periphid;

2828 ret = pl330_add(pl330);

设置pl330的configure字段。

2832 INIT_LIST_HEAD(&pl330->desc_pool);

2833 spin_lock_init(&pl330->pool_lock);

2834

2835 /* Create a descriptor pool of default size */

2836 if (!add_desc(pl330, GFP_KERNEL, NR_DEFAULT_DESC))

2837 dev_warn(&adev->dev, "unable to allocate desc\n");

设置pl330的描述符池,并且初始化16个描述符。

2842 if (pdat)

2843 num_chan = max_t(int, pdat->nr_valid_peri, pcfg->num_chan);

2844 else

2845 num_chan = max_t(int, pcfg->num_peri, pcfg->num_chan);

2846

2847 pl330->num_peripherals = num_chan;

2848

2849 pl330->peripherals = kzalloc(num_chan * sizeof(*pch), GFP_KERNEL);

2850 if (!pl330->peripherals) {

初始化channel数,并且为dma channel分配内存空间。

2856 for (i = 0; i < num_chan; i++) {

2857 pch = &pl330->peripherals[i];

2858 if (!adev->dev.of_node)

2859 pch->chan.private = pdat ? &pdat->peri_id[i] : NULL;

2860 else

2861 pch->chan.private = adev->dev.of_node;

2862

2863 INIT_LIST_HEAD(&pch->submitted_list);

2864 INIT_LIST_HEAD(&pch->work_list);

2865 INIT_LIST_HEAD(&pch->completed_list);

2866 spin_lock_init(&pch->lock);

2867 pch->thread = NULL;

2868 pch->chan.device = pd;

2869 pch->dmac = pl330;

2870

2871 /* Add the channel to the DMAC list */

2872 list_add_tail(&pch->chan.device_node, &pd->channels);

2873 }

初始化dma_pl330_chan相关字段。

2886 pd->device_alloc_chan_resources = pl330_alloc_chan_resources;

2887 pd->device_free_chan_resources = pl330_free_chan_resources;

2888 pd->device_prep_dma_memcpy = pl330_prep_dma_memcpy;

2889 pd->device_prep_dma_cyclic = pl330_prep_dma_cyclic;

2890 pd->device_tx_status = pl330_tx_status;

2891 pd->device_prep_slave_sg = pl330_prep_slave_sg;

2892 pd->device_config = pl330_config;

2893 pd->device_pause = pl330_pause;

2894 pd->device_terminate_all = pl330_terminate_all;

2895 pd->device_issue_pending = pl330_issue_pending;

2896 pd->src_addr_widths = PL330_DMA_BUSWIDTHS;

2897 pd->dst_addr_widths = PL330_DMA_BUSWIDTHS;

2898 pd->directions = BIT(DMA_DEV_TO_MEM) | BIT(DMA_MEM_TO_DEV);

2899 pd->residue_granularity = DMA_RESIDUE_GRANULARITY_SEGMENT;

初始化上图中struct dma_device的若干成员和函数。

2901 ret = dma_async_device_register(pd);

2902 if (ret) {

2903 dev_err(&adev->dev, "unable to register DMAC\n");

2904 goto probe_err3;

2905 }

注册dma设备并创建sys/class接口,会有如下显示:

​root@linaro-ubuntu-desktop:/sys/class/dma# ls

dma0chan0 dma0chan2 dma0chan4 dma0chan6 dma1chan0

dma0chan1 dma0chan3 dma0chan5 dma0chan7

将DMA控制器注册到DT DMA helpers。通过of_dma_list就可以找到该DMA控制器。

2907 if (adev->dev.of_node) {

2908 ret = of_dma_controller_register(adev->dev.of_node,

2909 of_dma_pl330_xlate, pl330);

至此,DMA注册函数基本流程完毕。但是还留下了一个irq函数。

DMA中断服务函数

2720 static irqreturn_t pl330_irq_handler(int irq, void *data)

2721 {

2722 if (pl330_update(data))

2723 return IRQ_HANDLED;

2724 else

2725 return IRQ_NONE;

2726 }

该函数实际上调用了pl330_update去完成中断服务函数的请求。由于其是在中断函数中调用的,则中断函数的那些注意事项还是要遵从的。

1533 static int pl330_update(struct pl330_dmac *pl330)

1534 {

1535 struct dma_pl330_desc *descdone, *tmp;

1536 unsigned long flags;

1537 void __iomem *regs;

1538 u32 val;

1539 int id, ev, ret = 0;

1540

1541 regs = pl330->base;

该函数的base地址是经过ioremap得到的。

1545 val = readl(regs + FSM) & 0x1;

1546 if (val)

1547 pl330->dmac_tbd.reset_mngr = true;

1548 else

1549 pl330->dmac_tbd.reset_mngr = false;

首先都FSM寄存器,然后看bit0是否需要复位mngr。

1551 val = readl(regs + FSC) & ((1 << pl330->pcfg.num_chan) - 1);

1552 pl330->dmac_tbd.reset_chan |= val;

1553 if (val) {

1554 int i = 0;

1555 while (i < pl330->pcfg.num_chan) {

1556 if (val & (1 << i)) {

1557 dev_info(pl330->ddma.dev,

1558 "Reset Channel-%d\t CS-%x FTC-%x\n",

1559 i, readl(regs + CS(i)),

1560 readl(regs + FTC(i)));

1561 _stop(&pl330->channels[i]);

1562 }

1563 i++;

1564 }

1565 }

各个通复位。

1568 val = readl(regs + ES);

1569 if (pl330->pcfg.num_events < 32

1570 && val & ~((1 << pl330->pcfg.num_events) - 1)) {

1571 pl330->dmac_tbd.reset_dmac = true;

1572 dev_err(pl330->ddma.dev, "%s:%d Unexpected!\n", __func__,

1573 __LINE__);

1574 ret = 1;

1575 goto updt_exit;

1576 }

读取事件寄存器,并判断是否出错了。出错则置reset标志。

1578 for (ev = 0; ev < pl330->pcfg.num_events; ev++) {

1579 if (val & (1 << ev)) { /* Event occurred */

1580 struct pl330_thread *thrd;

1581 u32 inten = readl(regs + INTEN);

1582 int active;

1583

1584 /* Clear the event */

1585 if (inten & (1 << ev))

1586 writel(1 << ev, regs + INTCLR);

1587

1588 ret = 1;

1589

1590 id = pl330->events[ev];

1591

1592 thrd = &pl330->channels[id];

1593

1594 active = thrd->req_running;

1595 if (active == -1) /* Aborted */

1596 continue;

1597

1598 /* Detach the req */

1599 descdone = thrd->req[active].desc;

1600 thrd->req[active].desc = NULL;

1601

1602 thrd->req_running = -1;

1603

1604 /* Get going again ASAP */

1605 _start(thrd);

1606

1607 /* For now, just make a list of callbacks to be done */

1608 list_add_tail(&descdone->rqd, &pl330->req_done);

1609 }

1610 }

处理相应事件。

文章来源:shichaog的专栏

声明:本文由原创博主授权转发,如需转载请联系博主

linux zynq ps dma,Zynq PS侧DMA驱动相关推荐

  1. linux zynq ps dma,Zynq PS DMA控制器应用笔记

    Zynq-7000系列器件PS端的DMA控制器采用ARM的IP核DMA-330(PL-330)实现.有关DMA控制器的硬件细节及相关指令集.编程实例内容参考ARM官方文档: DDI0424D:dma3 ...

  2. ZYNQ PS与PL通信之DMA

    ​ 在zynq开发中,PS与PL通信是非常关键的内容,掌握了PS与PL通信方法,就可以搭建各种常用的硬件平台.本文先介绍集中PL与PS的通信方法,然后重点介绍通过DMA实现PS与PL通信. PL与PS ...

  3. DMA IP PS侧c / python代码笔记

    DMA IP PS 实现DMA传输的三要素: TX_BUFFE&RX_BUFFER TX&RX channel TX&RX interrupts 一: Vivado SDK 上 ...

  4. ZYNQ学习之路11.AXI DMA

    一. AXI DMA简介 AXI DMA IP核提供了AXI4内存之间或AXI4-Stream IP之间的内存直接访问,可选为分散收集工作模式,初始化,状态和管理寄存器等通过AXI4-Lite 从机几 ...

  5. ZYNQ中DMA与AXI4总线-DMA简介

    ZYNQ中DMA与AXI4总线 为什么在ZYNQ中DMA和AXI联系这么密切?通过上面的介绍我们知道ZYNQ中基本是以AXI总线完成相关功能的: 图4‑34 连接 PS 和 PL 的 AXI 互联和接 ...

  6. vivado中ZYNQ详解(主要用于PS和PL之间的工作衔接)

    可进qq群进行相关Verilog知识交流:1073030956 AXI的理解 AXI(Advanced extensible Interface)协议主要描述了Master设备和Slave设备之间的数 ...

  7. ZYNQ基础系列(四) DMA基本用法

    DMA 环路测试 涉及到高速数据传输时,DMA就显得非常重要了,本文的DMA主要是对PL侧的AXI DMA核进行介绍(不涉及PS侧的DMA控制器).AXI DMA的用法基本是:PS通过AXI-lite ...

  8. ZYNQ开发系列——把PS和PL程序烧写到FLASH中

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 ZYNQ开发系列--把PS和PL程序烧写到FLASH中 生成 fsbl.elf 生成 BOOT.bin 下载 BOOT.bin 文件到 ...

  9. Zynq 7000 PL和PS通信——使用BRAM

    Zynq 7000 PL和PS通信--使用BRAM 介绍 1 准备工作 2 PL搭建BRAM 3 下载PL程序 4 编写Linux应用程序 5 问题 5.1 BRAM的寻址的问题 5.2 PS地址映射 ...

  10. ZYNQ开发系列——为PS和PL的交互做准备

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 ZYNQ开发系列--为PS和PL的交互做准备 我们使用ZYNQ的片子,肯定存在PS和PL交互的情况.一般来说大部分应用都是以PS为主机 ...

最新文章

  1. Python安装MySQL库详解(解决Microsoft Visual C++ 9.0 is required )
  2. 关于HTTP协议及SOCKET通信
  3. C++ 类中特殊的成员变量(常变量、引用、静态)的初始化方法
  4. 谁在窥屏_TheShy直播被窥屏搞怕了,为了防止被窥屏,这个做法绝了
  5. 串口服务器信号连接不上,使用RS485串口服务器经常遇到的问题
  6. django的调试模式 功能与开关
  7. QTP工作原理的学习心得
  8. jsecharts如何改变占比_什么是基础代谢?如何提高基础代谢?基础代谢对减肥很重要...
  9. FlyMcu一键烧录STM32开发板时关于RTS和DTR电平方式的设置
  10. magisk卸载内置软件_软件卸载工具的终极武器——Revo Uninstaller
  11. 集店管家(App名称)技术服务支持
  12. django个人博客
  13. mysql-server安装
  14. js+canvas画布制作烟花
  15. 东师奥鹏计算机应用基础19春,东师计算机应用基础19春在线作业1【参考答案】...
  16. 读《薛兆丰经济学讲义》后感
  17. BZOJ3681: Arietta
  18. slideup()和slideDown()方法的使用
  19. Mac m1 配置OpenCV (C++)
  20. Windows Server 2019不重装进行分盘

热门文章

  1. 一文理解Ranking Loss/Margin Loss/Triplet Loss
  2. 如何用Python玩转比特币?(附源码)
  3. 整个领域没了!学术界有史以来最大的丑闻
  4. mac下-bash: mysql: command not found彻底解决
  5. sqlite-1.0.0源码执行的基本流程概述
  6. Linux里检查的一些目录!
  7. 视觉进阶 | 用于图像降噪的卷积自编码器
  8. 通过SID查找历史执行的SQL语句
  9. NodeJS Events模块源码学习
  10. Confluence 6 指派和撤销空间权限