首页 > 其他分享 >对高通ath11k中srng的理解

对高通ath11k中srng的理解

时间:2024-12-19 12:02:25浏览次数:8  
标签:dma 硬件 tp 高通 ath11k srng 驱动 ring

srng 总结

本文中会混用 srng 和 ring 这两种描述, 这两个是等价的

驱动

https://github.com/PolarisZg/wireless_driver_simulation/tree/main/driver/driver_wireless

硬件

https://github.com/PolarisZg/qemu_simudevice/tree/master/hw/wireless_simu

两类方向的ring

enum hal_srng_dir
{
    HAL_SRNG_DIR_SRC,
    HAL_SRNG_DIR_DST
};
名称 方向 驱动更新 设备更新
HAL_SRNG_DIR_SRC 驱动 ---> 设备 hp tp
HAL_SRNG_DIR_DST 设备 ---> 驱动 tp hp

ring 统一参数

不同的参数需求写在了 src 和 dst 小节中

  • ring_base_paddrring_size

    ring_base_paddr 一般为 40bit 或 32bit

    驱动侧为 ring 开辟的内存空间的起始物理地址, 设备需要这个参数来确定可以访问的内存空间, 一般来说是40bit的数据。

    在 Linux 驱动中可以为某个设备(dev)设定一个 paddr 的位数上限, 如下述代码所示, 该内核提供的方法可以限制利用该设备申请的 dma 物理内存地址一定是仅 低32bit 有效, 其他 bit 为 0

    当然在 qemu 中可以使用最长 64bit 的物理内存地址来读取内存的数据

    dma_set_mask_and_coherent(&priv->pci_dev->dev, DMA_BIT_MASK(32));
    

    超过 32 bit 的数据传输, 需要占用多于一个寄存器

    因为这个地址对应的内存空间可能很大, 该地址是由使用ring的各个高级模块使用 dma_alloc_coherent 来申请的, 申请之后需要使用 PTR_ALIGNALIGN 来进行内存地址对齐, 在进行内存对齐的时候有两种方案, 高通驱动中在不同的地方使用了这两种:

    直接对齐 :

    ring->base_addr_owner_space = PTR_ALIGN(
        ring->base_addr_owner_space_unaligned, 
        SRNG_TEST_DESC_RING_ALIGN);
    
    ring->base_addr_test_space = ALIGN(
        ring->base_addr_test_space_unaligned, 
        SRNG_TEST_DESC_RING_ALIGN);
    

    对齐一个, 另一个使用偏移 :

    ring->vaddr = PTR_ALIGN(
        ring->vaddr_unaligned, 
        HAL_RING_BASE_ALIGN);
    
    ring->paddr = 
                ring->paddr_unaligned + 
                ((unsigned long)ring->vaddr -
    	        (unsigned long)ring->vaddr_unaligned);
    

    ring_size 即每个 ring 的数据存储部分所占用的实际内存空间大小, 设备侧需要这个值来对自己更新的 tp 或 hp 来进行取模运算, 该值以 4 char 为单位

  • entry_sizenum_entries

    32bit 足够大了

    为了可以动态配置每个ring可传输数据的大小, 驱动会在初始化ring的时候配置ring中每个 entry 的大小和对应 ring 中 entry 的个数。

    驱动在代码中保证 num_entries 的值是 2 的幂, 这样在取模的时候直接和 (num_entries - 1) 即可

    实际上 ((entry_size << 2 ) * num_entries) 在数值上应该等于上面提到的 ring_size , 不知道为什么这里要采用两种方式重复传递数据

    在高通驱动中 entry_size 参数的单位是 32bit (4 char)

    在 ring 的数据传输中使用的很多自定义的指针基本都是以 4char 为单位 (左移 2bit)

  • intr_timer_thres_usintr_batch_cntr_thres_entries

    延迟中断机制, 为了减少中断的个数而引入的机制;一个是延迟时间, 一个是延迟个数; 默认设置为0, 不启用延迟中断。

    延迟中断机制:指从ring中读取数据完毕, 或写入数据完毕后延后通知驱动的机制。一般情况下每个任务完成后都可以发一个中断来通知任务完成, 设置这两个参数后, 对ring的任务完成后达到一定数量或一定时间, 再发送中断向操作系统汇报。

  • flag

    典型的标志位, 可能会根据 dst 和 src 的不同而含义不同。

HAL_SRNG_DIR_SRC

设定为该方向的 ring , 数据传输方向是操作系统驱动硬件

特殊参数

  • low_threshold

    为保证硬件总是能从 src ring 中读取到数据而设计的参数, 当 src ring 中的数据少于这个量时, 发出中断请求驱动进行填充

  • tp_paddr

    硬件更新 tp 之后, 驱动需要知道这样的修改, 这样的修改实际上并不需要很强的实时性, 驱动只需要在用到 ring 的时候能读取到修改即可。因此硬件端拿到一个 tp 的 paddr , 当 tp 被修改后直接以 dma 的方式向这个区域写入 u32 的 tp 的值。

    之后驱动侧只需要解引用一下指针就能拿到该值。

    考虑到在硬件设计完毕后,实际可用的 ring 个数已经固定, 因此驱动侧可以统一申请一块 dma 一致性内存空间,然后将该内存空间切成小块分给每个 src ring 用来做 tp 使用

    dma 一致性内存空间 (dma coherent), 指使用 dma_alloc_coherent() 申请到的内存空间, 这块内存空间不会被 load 进 cache , 每次对该内存空间的读取操作都是直接访问实际的内存, 借此保证读取到的一定是设备通过 dma 写入到内存的数据

  • 寄存器-仅写 hp

    驱动侧需要通过写这个寄存器, 来通知硬件 src_ring 中已经放入了数据

软件使用流程

对于使用流程可以参考本人qemu 假设备驱动的 wireless_simu_hal_srng_test_send 方法 和 wireless_simu_test_dst_enqueue_pipe 方法, 两个方法的流程是一样的, 只是写入 entry 的数据不一样

  1. 使用之前需要对所使用的 ring 加锁, 保证不发生多线程冲突

  2. 刷新 tp 的值, 实际就是解引用 tp 的 vaddr

  3. 向 ring 申请 entry 空间, 申请成功后 ring 会自动修改 hp 的值, 申请成功后存放数据

  4. 暂存发往设备的数据, 等设备处理完毕后再做释放处理

  5. 通过寄存器向硬件写入 hp 的值, 该操作会触发下面的硬件读取流程

  6. 解锁,结束

硬件读取流程

对于读取流程可以参考本人假设备代码中的 wireless_hal_src_ring_tp 代码和 ce_dst_ring_handler 代码。实际上ce_dst_ring_handler 是由wireless_hal_src_ring_tp拉起的,在wireless_hal_src_ring_tp中如果判定为该srng挂了一个对应的处理函数,则会转向到对应的处理函数进行处理。

  1. 由驱动写 hp 寄存器触发, 开始进行硬件对 ring 的读取

  2. 判断该 srng 是否挂了对应的处理函数, 如果挂了则转向, 这是拉起 ce_dst_ring_handler 等高级模块对ring进行处理的地方

  3. 对该 srng 加锁, 保证单线程读取, 主要是为了保证对 tp 的更新是单线程的

  4. tp 不等于 hp 开始读取, 以 dma 的方式从 ring 中取出一个 entry , 取出后使用 dma 直接修改 tp 对应的 paddr 处的值, 并增加 tp 的值

    取出一个 entry 的地址是 srng->ring_base_paddr + (srng->u.src_ring.tp << 2) 长度是 srng->entry_size << 2

  5. 拉起某个方法对entry进行后处理

    比如在 ce_dst_ring_handler 中 是将 entry 中存放的 paddr 放置在空闲区域中等待使用

  6. 解锁

HAL_SRNG_DIR_DST

在本段之中可能会出现一些描述上的混乱,注意本节中提到的 dst_ring 在方向判断上很有可能是 HAL_SRNG_DIR_SRC, 而某个被称作 status_ring 环形缓冲区在方向上一定是 HAL_SRNG_DIR_DST

特殊参数

  • max_buffer_length

    并非是指每个 ring 中的 entry 大小限制, 因为向 dst_ring 中写入 entry 时有 entry_size 的限制; 该限制为了保证驱动端可以正确解析 ring 中的每个 entry , entry_size 的限制依靠硬件和驱动两端写入读取的 struct 具有相同的结构来实现的。

    驱动对该数值的配置路径如下所示;

    高层模块填充 params.max_buffer_len 
    
    ---> 调用 ath11k_hal_srng_setup(params)
    
    ---> 从 params 读取 max_buffer_len 写入 max_buffer_length 
    
    ---> max_buffer_length 写入硬件寄存器
    

    params.max_buffer_len 的填充仅存在于 对ce的配置方法 ath11k_ce_init_ring 中,填充的数据来自 ce.c 文件开头处的静态数组的 src_sz_max 属性, 借此可以推测该字段的含义:

    描述该字段的含义之前, 需要先简单说明一下两种 ring (src 和 dst) 和两个方向 (tx 和 rx) 的数据传输之间的关系。如下图所示,tx 指数据从驱动发往硬件,仅使用一个填充有发送数据描述符的 src_ring; rx 指数据从硬件发往驱动, 需要使用一个填充 paddr 的 src_ring 来通知硬件数据在内存中的填充位置, 和一个填充有数据长度的字段 dst_ring 来通知驱动,某一区域中多长的数据是有效的。采取双 ring 的方式进行 rx 的时候, 由于驱动方向在内存中申请空闲区间用于接收数据时使用的是固定大小, 所以就需要使用 max_buffer_length 字段来告知硬件, 禁止发送超过该大小的数据至驱动(至于是丢弃掉还是切开, 这里暂时不清楚, 不过好像是丢弃掉)

    img

  • hp_paddr

    和 src_ring 的 tp_addr 的作用相同, 硬件在更新 dst_ring 之后需要将 hp 直接写入到内存中, 这样驱动方面就可以通过对指针的解引用来拿到这个值

    考虑到在硬件设计完毕后,实际可用的 ring 个数已经固定, 因此驱动侧可以统一申请一块 dma 一致性内存空间,然后将该内存空间切成小块分给每个 dst ring 用来做 hp 使用

  • 寄存器-仅写 tp

    驱动通过一个寄存器的写入操作来修改硬件中dst_ring的tp的值。

    这里和 src_ring 具有不同之处在于,对 dst_ring 的 hp 的更新不会触发硬件的某些操作。在 src_ring 中,硬件修改 tp 之后触发中断的原因在于节省驱动占用的内存空间, 释放掉对应位置的 skb 内存。但对于更新 dst_ring 的 hp 的硬件来说, 将数据搬运至内存之中 这个操作是硬件控制的, 在搬运成功后就可以自行判断释放掉数据占用的设备空间,因此不会有 tp 被修改之后的处理

    所以为了节省寄存器的话, 也可以将 tp 挂到一个驱动 dma 内存空间的某个位置, 当硬件使用 dst_ring 的时候去读取那个位置来得到 tp 信息

软件配置流程

对 dst_ring 的使用流程实际上就是 rx 的过程,单独一个 dst_ring 是无法工作的, 需要 src_ring 来辅助;整个驱动方面使用 dst_ring 的流程可以参考本人写的驱动中 wireless_simu_irq_hal_srng_dst_dma_test( dma 地址配置和数据接收) 、wireless_simu_test_dst_post_pipe(仅 dma 地址配置) 、高通驱动中的 ath11k_ce_recv_process_cb( dma 地址配置和数据接收) 、ath11k_ce_rx_post_pipe(仅 dma 地址配置)

下方的流程和上面提到的 src_ring 的驱动使用流程是一样的

  1. 对高级模块(如 ce)加锁, 保证对该模块管理的两个 ring 的配置不会出现问题

  2. 申请一段空间, 并映射得到到 dma_paddr, 在高通驱动中固定申请长度为 2048 字节的空间(来自静态配置数组 const struct ce_attr 中的 src_sz_max 和上面提到的 max_buffer_length 对应), 并将该空间的头地址指针放在该高级模块(如 ce)管理的一个数组中暂存, 放置的位置 write_index 和 之后待使用的 src_ring 的 hp 具有一定的关系

  3. 对 src_ring 加锁

  4. 向 src_ring 中写入上方申请空间的 dma_paddr , 且仅需要写入该40bit数据即可, 更新 hp

  5. 将 hp 写入硬件寄存器, 该操作会触发硬件的 src_ring 接收操作

  6. 解ring锁,高级模块解锁

下图中是整体的软硬件配置流程

img

硬件的配置流程

硬件对 rx 模块的配置由驱动方面写 hp 寄存器触发, 可以参考本人假设备代码中的 ce_dst_ring_handler。这个方法在假设备启动的时候被自动注册到一个特定 id 的 srng 之中, 当该 srng 的 hp 寄存器发生写入操作后, 可以自动的拉起 ce_dst_ring_handler 而非其他函数。

在硬件配置完毕后, 硬件方面就可以启动硬件向驱动的数据发送流程(rx)

  1. 由驱动写 hp 寄存器触发, 开始进行硬件对 ring 的读取

  2. 拉起 ce_dst_ring_handler 对ring进行处理

  3. 对该 srng 加锁, 保证单线程读取, 主要是为了保证对 tp 的更新是单线程的

  4. tp 不等于 hp 开始读取, 以 dma 的方式从 ring 中取出一个 entry , 取出后使用 dma 直接修改 tp 对应的 paddr 处的值, 并增加 tp 的值

  5. 将 entry 中存放的 dma_paddr 放置在数组中等待使用, 对于放置的位置 write_index 和读取到该 entry 的位置 tp 具有一定的关系

  6. 解锁

硬件数据发送流程(发往驱动方向)

该操作启动之前需要保证硬件中已经存了一些 dma_paddr 数据

  1. 其他模块生成数据, 对于管理所有 ring 的 srng 来说, 仅需要一个 void *data 参数和 size_t data_size 参数即可, 或许还需要一个 type_id 来通知 srng 该数据必须使用那些 dst_ring 进行发送

  2. 遍历自己管理的所有 dest_ring - status_ring 组, 寻找含有空闲 dma_paddr 的 ring

  3. 对找到的 dest_ring - status_ring 组中两个 ring 全部加锁, 保证数据的单线程发送

  4. 从 dma_paddr 数组中取出一个 dma_paddr 并将 void *data 拷贝至对应的内存位置(硬件调用 dma_write)

  5. status_ring(type HAL_SRNG_DIR_DST) 中获取一个 entry, 写入 size_t data_size 并更新 hp

  6. 将上方更新的 hp 写入内存中特定位置(hp_paddr), 并发出中断, 通知驱动方面取数据

  7. 解锁

下图中是软硬件整体的 rx 流程

img

软件数据接收流程

该接收操作由硬件中断触发,可以参考本人写的驱动中 wireless_simu_irq_hal_srng_dst_dma_test 或 高通驱动中的 ath11k_ce_recv_process_cb

  1. 创建skb链表,用于存放从 status_ring(type HAL_SRNG_DIR_DST) 中读取数据

  2. 对 status_ring(type HAL_SRNG_DIR_DST) 加锁

  3. 从 status_ring(type HAL_SRNG_DIR_DST) 的 tp 处读取一个 entry, 该entry中含有接收到的数据长度, 从上方软件配置流程中暂存空间的数组中拿取 tp 对应的 write_index 位置处拿取一整块 skb 并将 skb->data 段的尾指针移动到数据长度对应的位置。对成功读取到的entry数量进行记录, 该记录用于之后向 dst_ring 中 dma_paddr 的重新填充

  4. 解除上一步拿到的skb的dma映射, 注意解除映射并不会释放空间

  5. 将 skb 加入链表

  6. 继续读取 status_ring(type HAL_SRNG_DIR_DST) 直到 tp 撞到 hp

  7. 对 status_ring(type HAL_SRNG_DIR_DST) 解锁

  8. 对 skb 链表进行处理, 由于接收流程是中断的下半部, 所以这里可以进行较为耗时的操作

  9. 拉起 软件配置流程 对 dma_paddr 进行重新配置

ring 的初始化

对 srng 的使用分为以下几个步骤

初始化阶段

    - srng 的初始化

    - 使用 srng 的高层模块对 srng 初始化的配置 

---

使用阶段

    - 使用前配置(仅 rx 含有此步骤)

    - 使用过程(上方已经写了)

---

删除阶段

硬件对srng 的初始化简单分为两部分

  1. 高层模块对srng的初始配置

  2. 被写寄存器

驱动: hal 前期初始化

驱动中 srng 初始化过程简单分为三部分:

  1. srng 前期的初始化

  2. 高层模块对srng 初始配置

  3. 写硬件寄存器

hal 的前期初始化主要的任务有两项

  • 初始化srng依赖的各种Linux Kernel提供的工具, 比如线程锁 mutex spin, 定时器 timer 等;

  • 申请 dma 空间, 用于存放各个 src_ring 的 tp 或 dst_ring 的 hp, 这段空间会被驱动读取, 会被硬件写入。

下面详细描述对 dma 空间的申请和使用。如图所示, hal首先在内存中申请一块大小为 ring 数量 * 4char 大小的空间, 并保存虚拟内存地址 vaddr 和物理内存地址 paddr; 之后每个使用到的 ring 按照自己的 ring_id 作为偏移地址去获取自己的 tp 或 hp 可使用的一块 32 bit长的空间。在各个ring中只需要记录 虚拟内存地址即可, 物理内存地址 paddr 只需要借助写硬件寄存器的方式传递给硬件。

在驱动这一侧,对于 src 类型的 ring 的 tp 的虚拟地址使用如下的代码计算, 可以通过对该地址的解析指针拿到该位置存放的值

srng->u.src_ring.tp_addr = (void *)(hal->rdp.vaddr + ring_id);

对于 src 类型的 ring 的 tp 的物理内存地址可以使用如下代码计算, 计算完成后直接写入到硬件中对应 ring 的寄存器中

u64 tp_addr =   hal->rdp.paddr +
                ((unsigned long)srng->u.src_ring.tp_addr -
                (unsigned long)hal->rdp.vaddr);

img

驱动: 高层模块对 srng 的初始配置

由于不同的模块利用 ring 传输的描述符大小不同, 而且在同一模块中可能使用同一类型大量的 ring 应对高并发环境下的数据传输, 每个 ring 实际需要的, 含有大量 entry 的内存空间需要上级模块自己管理, 负责申请和释放, 这部分空间不应该让 srng 模块管理。

img

对于 srng 模块, 其内部只进行 tp 或 hp 的运算, 在高层模块传输数据之前需要获取到一个entry时, 对于 src_ring 其根据如下的公式计算得到空闲 entry 的起始地址:

驱动侧:

u32* desc = srng->ring_base_vaddr + srng->u.src_ring.hp

硬件侧:

dma_addr_t paddr = srng->ring_base_paddr + (srng->u.src_ring.tp << 2)

两侧的计算方式不同之处主要在于, 驱动侧使用了 u32 来对地址进行记录, 那么只需要相加即可, 但设备测并不知道实际的地址代表的数据单位, 因此需要右移两个bit(4 char, 32 bit)

删除阶段

删除阶段只讨论驱动的卸载, 不考虑硬件的删除。这样的考虑主要基于以下几点:

  1. 驱动的内存占用: 驱动是装入进Linux kernel进程运行运行,和普通运行在user space的进程不同,user space的进行被kill掉之后所占用的内存被完全的释放掉, 但运行于linux kernel的驱动即使被 rmmod, 也没有进程来回收内存

  2. 多任务并行方面: 驱动运行的过程中, 各个模块之间存在的一些并行性, 比如在同一时间可能有 src_ring 在发送数据, 中断系统在跑中断的上半部, dst_ring(中断下半部)在接收数据, 这时候强制卸载驱动释放某些位置的内存, 会导致某些模块访问到空指针, 使整个系统宕掉

  3. 硬件方面: 由于硬件是跑在qemu 中, kill 掉 qemu进程即可保证内存被回收, 因此泄露一些也没问题;

综上, 要正确考虑各个模块停止和占用内存的释放顺序, 保证不会出现空指针的访问。在设计的过程中, 可以使用依赖图的形式来记录各个模块之间的依赖顺序, 如下图所示, 其中每个结点表示一个模块, A 指向 B 表示 A 在运行过程中会拉起 B 模块。

img

因此一般来说,可以采用先删除/中止高级模块工作, 再删除/中止/释放低级模块的空间

标签:dma,硬件,tp,高通,ath11k,srng,驱动,ring
From: https://www.cnblogs.com/polariszg/p/18616840

相关文章

  • 深入解析高通SA8775芯片:性能、架构与研发创新趋势
    SA8775是高通推出的一款高性能芯片,主要应用于智能驾驶、车载信息娱乐系统以及其他高级嵌入式应用。以下是SA8775的主要技术特点及其应用优势:1.CPU性能两簇八核心设计:SA8775采用了两簇八核心的设计架构,其中包含了Kryo680GoldPrime内核,基于ARMCortex-X1架构,主频可达2.......
  • 高通 --gpio控制和input event调试(附全代码)
    文章目录一、概述二、gpiodemo驱动详解1、dts的pinctrl配置2、注册平台驱动,创建字符设备并分配设备id号。3、io初始化4、中断处理函数配置5、ioctl的函数配置三、ioctl操作io四、input输入事件1、dts配置2、read配置的inputio事件3、输出input事件和类型5、整体代码1......
  • 高通android手机蓝牙重启问题分析
    问题背景这两天芯片厂更新了芯片的固件,然而我们用高通平台手机去配对(其他平台手机没这个问题),发现手机蓝牙会重启,下面就开始debug之路:问题分析先看手机logcatlog手机蓝牙重启自然先看一下手机端的logcatlog,一般这种手机蓝牙重启就说明蓝牙进程发生了crash之类的,导致蓝......
  • 高通拟收购英特尔——Arm与x86之争到此为止?
    高通拟收购英特尔——Arm与x86之争到此为止?OSC开源社区 2024年09月21日12:02 广东↑点击上方蓝字关注「OSC开源社区」根据《华尔街日报》的独家报道,高通与英特尔近期内进行过接洽,商讨了收购事宜。报道称,芯片巨头高通公司在最近几天向竞争对手英特尔发起了收购要约。知情人......
  • 面对高通收购,Apollo 50亿美元投资,你该买入英特尔股票吗?
    猛兽财经核心观点:(1)Apollo将对英特尔进行大笔投资。(2)高通也正在就收购英特尔进行接洽。(3)这些都是对英特尔扭亏为盈充满信心的迹象。(4)猛兽财经认为高通收购英特尔大概率不会成功,而且英特尔将强烈反对。(5)猛兽财经对英特尔股票的技术分析:支撑位:19美元,阻力位:25美元。Apollo......
  • 教授(优青)团队一站式指导:专业实验设计、数据分析、SCI论文辅助。基因表达分析、转录因
    可高通量检测组蛋白不同修饰在基因组上的位点;可用于模式物种和非模式物种的研究,无需特异性抗体;完整的DAP-seq解决方案。DAP-seq可高通量检测转录因子或DNA结合蛋白在基因组上的结合位点;可用于模式物种和非模式物种的研究,无需特异性抗体;完整的基因功能分析解决方案......
  • 尽显风骚~一文读懂FD-SOI(附高通电源包络芯片QET7100)
    平台君今天要和大家说的是FD-SOI(全称:FullyDepletedSilicon-On-Insulator,全耗尽型绝缘体上硅)。同样是CMOS工艺进入28nm以下所提出的解决方案,与FinFET工艺相比FD-SOI工艺确显暗淡不少。但是怎么说呢,呃……平台君一直觉得FD-SOI自有它的光芒,人家低调并不代表人家没实力。01......
  • 什么情况!高通欲收购英特尔?
    KlipC报道:近日,有消息称,高通公司近日对英特尔公司发起了收购要约。交易仍远,还没有确定。据KlipC了解,这是本月第二次有消息称高通有兴趣收购英特尔。此前,有报道称,高通管理层正在考虑收购英特尔的芯片设计业务,以充实自身产品组合,同时也在考虑收购英特尔的服务器等其他业务。华尔街分......
  • AIGC8: 高通骁龙AIPC开发者大会记录B
    图中是一个小男孩在市场卖他的作品。AI应用开发出来之后,无论是个人开发者还是企业开发者。如何推广分发是面临的大问题。做出来的东西一定要符合商业规律。否则就是实验室里面的玩物,或者自嗨的东西。背景上次是回顾和思考前面两个硬件营销总的分享,接下来看软件营销总的分享。......
  • 1----安卓机型修复串码 开启端口 檫除基带 支持高通与MTK机型工具预览与操作解析
              在玩机过程中。很多玩家会碰到各种各样的故障。其中最多的就在于基带串码类。由于目前的安卓机型必须修改或者写入串码等参数必须开启端口。而一些初级玩友不太了解开启参数端口的步骤。这个工具很简单的为安卓机型开启端口。并且操作相对简单......