如何使能500个virtio_blk设备

作者:安庆 发布于:2022-8-12 16:12 分类:Linux内核分析

# 一例virtio_blk设备中断占用分析

## 背景:这个是在客户的centos8.4的环境上复现的,dpu是目前很多
## 云服务器上的网卡标配了,在云豹的dpu产品测试中,dpu实现的virtio_blk
## 设备在申请中断时报错,在排查这个错误的过程中,觉得某些部分还比较有
## 趣,故记录之。本身涉及的背景知识有:irq,msi,irq_domain,
## affinity,virtio_blk,irqbalance
### 下面列一下我们是怎么排查并解决这个问题的。

#### 一、故障现象
内核团队接到测试组测试客户前端内核抛栈:
```
[25338.485128] virtio-pci 0000:b3:00.0: virtio_pci: leaving for legacy driver
[25338.496174] genirq: Flags mismatch irq 0. 00000080 (virtio418) vs. 00015a00 (timer)
[25338.503822] CPU: 20 PID: 5431 Comm: kworker/20:0 Kdump: loaded Tainted: G           OE    --------- -  - 4.18.0-305.30.1.jmnd2.el8.x86_64 #1
[25338.516403] Hardware name: Inspur NF5280M5/YZMB-00882-10E, BIOS 4.1.21 08/25/2021
[25338.523881] Workqueue: events work_for_cpu_fn
[25338.528235] Call Trace:
[25338.530687]  dump_stack+0x5c/0x80
[25338.534000]  __setup_irq.cold.53+0x7c/0xd3
[25338.538098]  request_threaded_irq+0xf5/0x160
[25338.542371]  vp_find_vqs+0xc7/0x190
[25338.545866]  init_vq+0x17c/0x2e0 [virtio_blk]
[25338.550223]  ? ncpus_cmp_func+0x10/0x10
[25338.554061]  virtblk_probe+0xe6/0x8a0 [virtio_blk]
[25338.558846]  virtio_dev_probe+0x158/0x1f0
[25338.562861]  really_probe+0x255/0x4a0
[25338.566524]  ? __driver_attach_async_helper+0x90/0x90
[25338.571567]  driver_probe_device+0x49/0xc0
[25338.575660]  bus_for_each_drv+0x79/0xc0
[25338.579499]  __device_attach+0xdc/0x160
[25338.583337]  bus_probe_device+0x9d/0xb0
[25338.587167]  device_add+0x418/0x780
[25338.590654]  register_virtio_device+0x9e/0xe0
[25338.595011]  virtio_pci_probe+0xb3/0x140
[25338.598941]  local_pci_probe+0x41/0x90
[25338.602689]  work_for_cpu_fn+0x16/0x20
[25338.606443]  process_one_work+0x1a7/0x360
[25338.610456]  ? create_worker+0x1a0/0x1a0
[25338.614381]  worker_thread+0x1cf/0x390
[25338.618132]  ? create_worker+0x1a0/0x1a0
[25338.622051]  kthread+0x116/0x130
[25338.625283]  ? kthread_flush_work_fn+0x10/0x10
[25338.629731]  ret_from_fork+0x1f/0x40
[25338.633395] virtio_blk: probe of virtio418 failed with error -16
       
```
从堆栈看,是某个virtio_blk设备在probe的时候报错,错误码为-16。
#### 二、故障现象分析

从堆栈信息看:

1、virtio418是一个virtio_blk设备,在probe过程中调用 __setup_irq 返回了-16。

2、[25338.496174] genirq: Flags mismatch irq 0. 00000080 (virtio418) vs. 00015a00 (timer),说明我们的virtio_blk

设备去申请了0号中断,由于0号中断被timer占用,irq子系统在比较flags时发现不符合,则打印这行。

具体代码为:

```

static int

__setup_irq(unsigned int irq, struct irq_desc *desc, struct irqaction *new)
{

......

mismatch:
        if (!(new->flags & IRQF_PROBE_SHARED)) {
                pr_err("Flags mismatch irq %d. %08x (%s) vs. %08x (%s)\n",
                       irq, new->flags, new->name, old->flags, old->name);

......

       ret = -EBUSY;

......

      return ret;

}

```

至于为什么virtio_blk会去申请0号中断,因为我们实现virtio_blk后端设备的时候,并没有支持intx,即virtio_pci类虚拟的pci_dev设备的irq值为0,本文先不管这个。

从堆栈看,virtio 申请中断是走了vp_find_vqs_intx流程,

```

crash> dis -l vp_find_vqs+0xc7
/usr/src/debug/linux/drivers/virtio/virtio_pci_common.c: 369---行号为369


    356 static int vp_find_vqs_intx(struct virtio_device *vdev, unsigned nvqs,
    357                 struct virtqueue *vqs[], vq_callback_t *callbacks[],
    358                 const char * const names[], const bool *ctx)
    359 {
 ......
    366
    367         err = request_irq(vp_dev->pci_dev->irq, vp_interrupt, IRQF_SHARED,
    368                         dev_name(&vdev->dev), vp_dev);

    369         if (err)----压栈的返回地址

......

```

我们dpu卡实现的virtio设备,都是使能msix的,按照代码流程,应该是先尝试msix,既然能走到 vp_find_vqs_intx 流程,说明 vp_find_vqs_msix失败了,而且按照如下代码:

```

    395 int vp_find_vqs(struct virtio_device *vdev, unsigned nvqs,
    396                 struct virtqueue *vqs[], vq_callback_t *callbacks[],
    397                 const char * const names[], const bool *ctx,
    398                 struct irq_affinity *desc)
    399 {
    400         int err;
    401
    402         /* Try MSI-X with one vector per queue. */
    403         err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, true, ctx, desc);//caq:先尝试单vqueue单中断号
    404         if (!err)
    405                 return 0;
    406         /* Fallback: MSI-X with one vector for config, one shared for queues. */
    407         err = vp_find_vqs_msix(vdev, nvqs, vqs, callbacks, names, false, ctx, desc);//caq:尝试多个vq共享一个中断号
    408         if (!err)
    409                 return 0;
    410         /* Finally fall back to regular interrupts. */
    411         return vp_find_vqs_intx(vdev, nvqs, vqs, callbacks, names, ctx);//caq:最后退化成intx模式
    412 }

```

说明vp_find_vqs_msix 失败了两次。第一次是 单vqueue单中断号的非共享方式,第二次是多个vq共用一个中断的方式。

通过打点发现,失败两次的原因是 __irq_domain_activate_irq  返回了-28

```

__irq_domain_activate_irq return=-28

0xffffffff8fb52f70 : __irq_domain_activate_irq+0x0/0x80 [kernel]
 0xffffffff8fb54bc5 : irq_domain_activate_irq+0x25/0x40 [kernel]
 0xffffffff8fb56bfe : msi_domain_alloc_irqs+0x15e/0x2f0 [kernel]
 0xffffffff8fa5e5e4 : native_setup_msi_irqs+0x54/0x90 [kernel]
 0xffffffff8feef69f : __pci_enable_msix_range+0x3df/0x5e0 [kernel]
 0xffffffff8feef96b : pci_alloc_irq_vectors_affinity+0xbb/0x130 [kernel]
 0xffffffff8ff7472b : vp_find_vqs_msix+0x1fb/0x510 [kernel]
 0xffffffff8ff74aad : vp_find_vqs+0x6d/0x190 [kernel]

```

查看具体的代码:

```

static int __irq_domain_activate_irq(struct irq_data *irqd, bool reserve)
{
int ret = 0;

if (irqd && irqd->domain) {//caq:均不为NULL
struct irq_domain *domain = irqd->domain;

if (irqd->parent_data)
ret = __irq_domain_activate_irq(irqd->parent_data,
reserve);//caq:递归,将父domain的对应irq都active一下
if (!ret && domain->ops->activate) {
ret = domain->ops->activate(domain, irqd, reserve);//caq:parent 执行完activate 然后再儿子辈执行。
/* Rollback in case of error */
if (ret && irqd->parent_data)//caq:异常则回退
__irq_domain_deactivate_irq(irqd->parent_data);
}
}
return ret;
}

```

由于客户 host kernel开启了 CONFIG_IRQ_DOMAIN_HIERARCHY,根据irq_domain 级别 ,该系统的irq_domain 级联如下:

不过上图是arm常见的,盗用arm图,本x86系统类似,irq_domain级别具体跟踪如下:

```

crash> irq_domain.name,parent 0xffff9bff87d4dec0
  name = 0xffff9bff87c1fd60 "INTEL-IR-MSI-1-2"
  parent = 0xffff9bff87400000
crash> irq_domain.name,parent 0xffff9bff87400000
  name = 0xffff9bff87c24300 "INTEL-IR-1"
  parent = 0xffff9bff87c6c900
crash> irq_domain.name,parent 0xffff9bff87c6c900
  name = 0xffff9bff87c3ecd0 "VECTOR"-----------最高级的
  parent = 0x0---所以parent为空

```

根据返回-28,根据最高级的irq_domain定位到 调用链为:

```

//caq:类比于 dma_domain_ops,在x86内是最高级的irq_domain了,因为他的domain parent为NULL
static const struct irq_domain_ops x86_vector_domain_ops = {//caq:x86针对acpi实现的irq_domain_ops
.alloc = x86_vector_alloc_irqs,//caq:分配中断
.free = x86_vector_free_irqs,
.activate = x86_vector_activate,//caq:activate实现
.deactivate = x86_vector_deactivate,
#ifdef CONFIG_GENERIC_IRQ_DEBUGFS
.debug_show = x86_vector_debug_show,
#endif
};
调用链:

x86_vector_activate-->activate_managed-->assign_managed_vector-->irq_matrix_alloc_managed

```

查看 代码如下:

```

int irq_matrix_alloc_managed(struct irq_matrix *m, const struct cpumask *msk,
     unsigned int *mapped_cpu)
{//caq:managed irq 分配
unsigned int bit, cpu, end = m->alloc_end;
struct cpumap *cm;

if (cpumask_empty(msk))
return -EINVAL;

cpu = matrix_find_best_cpu_managed(m, msk);//caq:找最合适的cpu
if (cpu == UINT_MAX)
return -ENOSPC;//caq:说明没找到
......
}

```

由于没有开启 CONFIG_GENERIC_IRQ_DEBUGFS,所以没办法直接看到 vector_matrix 具体的值,

借助crash工具查看:

```

crash> p *vector_matrix
$82 = {
  matrix_bits = 256,
  alloc_start = 32,
  alloc_end = 236,
  alloc_size = 204,
  global_available = 0,//caq:只剩下了这么多个irq
  global_reserved = 151,
  systembits_inalloc = 3,
  total_allocated = 1922,//caq:只分配了这么多个irq
  online_maps = 80,
  maps = 0x2ff20,
  scratch_map = {18446744069952503807, 18446744073709551615, 18446744073709551615, 18446735277616529407, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  system_map = {1125904739729407, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

```

一个疑问涌上心头,为什么总共才分配了1922 个中断,全局的global_available就为0了呢?

然后继续打点,发现virtio_blk设备的vq申请中断时,走的是 apicd->is_managed 流程,而同属于virtio协议的virtio_net设备却不是。

而走managed流程,也就是申请中断时,带有了RQD_AFFINITY_MANAGED

```

static int
assign_irq_vector_policy(struct irq_data *irqd, struct irq_alloc_info *info)
{
if (irqd_affinity_is_managed(irqd))//caq:如果是 managed 的irq,也就是irq_data中有 IRQD_AFFINITY_MANAGED 标记
return reserve_managed_vector(irqd);

```

我们回过来查看vector alloc时的调用链:

x86_vector_alloc_irqs-->assign_irq_vector_policy-->reserve_managed_vector-->irq_matrix_reserve_managed

对一个两个队列的virtio_blk申请中断时,打点发现如下:

```

m->global_available=15296 

0xffffffff87158300 : irq_matrix_reserve_managed+0x0/0x130 [kernel]---从15296减少到15256
m->global_available=15256

call vdev=0xffff8b781ce17000,index=0,callback=0xffffffffc0448000,ctx=0,msix_vec=1----------容量减少了40
```

由于已经缩小到是因为virtio_blk设备的中断申请流程,使用热插拔确认一下:

```

118:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0         53          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0  IR-PCI-MSI 94371841-edge      virtio3-req.0
 119:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0         49          0          0          0  IR-PCI-MSI 94371842-edge      virtio3-req.1

热拔前:
crash> p *vector_matrix
$2 = {
  matrix_bits = 256,
  alloc_start = 32,
  alloc_end = 236,
  alloc_size = 204,
  global_available = 15215,
  global_reserved = 150,
  systembits_inalloc = 3,
  total_allocated = 553,
  online_maps = 80,
  maps = 0x2ff20,
  scratch_map = {1179746449752063, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  system_map = {1125904739729407, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

热拔后:
crash> p *vector_matrix
$3 = {
  matrix_bits = 256,
  alloc_start = 32,
  alloc_end = 236,
  alloc_size = 204,
  global_available = 15296,---增长了81个,一个config中断+两个分别为40的req中断,此时req.0和req.1是非共享模式
  global_reserved = 150,
  systembits_inalloc = 3,
  total_allocated = 550,
  online_maps = 80,
  maps = 0x2ff20,
  scratch_map = {481036337152, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
  system_map = {1125904739729407, 0, 1, 18446726481523507200, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
}

```

说明 对于mask内的cpu号,都需要一个中断容量,两个40就是因为80核的服务器,两个vq,平分中断,感兴趣的同学可以去查看irq_build_affinity_masks这个函数的实现。这样一个vector,因为开启了 IRQD_AFFINITY_MANAGED 属性,导致需要占用80个中断容量。而我们系统由于有512个virtio_blk设备,所以申请到部分设备的时候,就把总的

vector容量耗光了,但其实分配的irq总数才不到2000.


那么,virtio_blk设备什么时候开启的 IRQD_AFFINITY_MANAGED  属性的呢?查看git记录:

```

6abd6e5a44040 (Amit Shah                 2011-12-22 16:58:29 +0530  507) static int init_vq(struct virtio_blk *vblk)
6abd6e5a44040 (Amit Shah                 2011-12-22 16:58:29 +0530  508) {
......
6a27b656fc021 (Ming Lei                  2014-06-26 17:41:48 +0800  515)        struct virtio_device *vdev = vblk->vdev;
ad71473d9c437 (Christoph Hellwig         2017-02-05 18:15:25 +0100  516)        struct irq_affinity desc = { 0, };----会导致blk申请中断时,使用内核managed方式来申请,一个dev会占用cpu核数这么多的容量。

```

看起来是 ad71473d9c437  这个 commit引入了这个问题。


但是根据virtio_blk驱动,第一遍中断申请的时候才有affinity_managed 设置,第二遍应该并没有设置,具体 vp_find_vqs_msix 如下:

```

//caq:vq申请中断,msix 模式,per_vq_vectors 决定是个vq共享中断还是独占中断
static int vp_find_vqs_msix(struct virtio_device *vdev, unsigned nvqs,
struct virtqueue *vqs[], vq_callback_t *callbacks[],
const char * const names[], bool per_vq_vectors,
const bool *ctx,
struct irq_affinity *desc)
{
struct virtio_pci_device *vp_dev = to_vp_device(vdev);
u16 msix_vec;
int i, err, nvectors, allocated_vectors;

vp_dev->vqs = kcalloc(nvqs, sizeof(*vp_dev->vqs), GFP_KERNEL);
if (!vp_dev->vqs)
return -ENOMEM;

if (per_vq_vectors) {//caq:单个vq 单个vector 中断号
/* Best option: one for change interrupt, one per vq. */
nvectors = 1;//caq:这个是config的中断,注意要和virtio_net的 ctrl vq区分
for (i = 0; i < nvqs; ++i)
if (callbacks[i])//caq:由于ctrl vq是不设置callbacks的,所以它没有中断
++nvectors;
} else {
/* Second best: one for change, shared for all vqs. */
nvectors = 2;
}
    //caq:中断总数最少为2,最多为vq个数+1,1为config的中断,另外单个vq单个vector才具备带亲核属性
err = vp_request_msix_vectors(vdev, nvectors, per_vq_vectors,
      per_vq_vectors ? desc : NULL);//caq:nvectors 为总中断数,注意desc的配置取决于 per_vq_vectors

//caq:virtio_pci申请msix的vector
static int vp_request_msix_vectors(struct virtio_device *vdev, int nvectors,
   bool per_vq_vectors, struct irq_affinity *desc)
{
......
vp_dev->msix_affinity_masks
= kcalloc(nvectors, sizeof(*vp_dev->msix_affinity_masks),
  GFP_KERNEL);//caq:中断掩码内存的分配
......
if (desc) {//caq:要求带亲核属性
flags |= PCI_IRQ_AFFINITY;//caq:带上亲核属性
desc->pre_vectors++; /* virtio config vector *///caq:细节,相当于指定了config中断不要设置亲核,走系统默认
}
......

```

原因,因为前面很多virtio_blk设备因为一个vector占用了80个中断容量,导致整体中断数不够了,

而后面的virtio_blk设备,第一遍使用vp_find_vqs_msix带 managed_affinity属性申请中断时失败,第二遍尽管使用vq共享中断模式,由于os连一个中断都分配不出来,  也会失败,导致走入了第三个流程,也就是 vp_find_vqs_intx 流程。

在另外一个virtio_blk单vq的环境上,具体查看如下:

```

[root@localhost config_json]#  cat /proc/interrupts |grep req |tail -1
 986:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0        114          0          0  IR-PCI-MSI 93687809-edge      virtio180-req.0
[root@localhost config_json]# cat /proc/irq/986/smp_affinity
ffff,ffffffff,ffffffff

[root@localhost config_json]#  cat /proc/interrupts |grep queues |tail -1
1650:          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0        120          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0          0  IR-PCI-MSI 94369793-edge      virtio512-virtqueues


[root@localhost config_json]# cat /proc/irq/1650/smp_affinity
ffff,ffffffff,ffffffff

[root@localhost config_json]# ls /sys/bus/virtio/devices/virtio180/block/
vdsh
[root@localhost config_json]# ls /sys/bus/virtio/devices/virtio512/block/
vdafj

```

以上可以看出,virtio180,也就是 vdsh 的block设备,走了 vp_find_vqs_msix 第一遍流程,分配了带 managed_affinity 的vector,所以他的中断名字是req结尾的,


而另外一个 virtio512 ,也就是 vdafj 的block设备,走了 vp_find_vqs_msix 第二遍流程,没有分配 带 managed_affinity 的vector,所以它的中断名字是 virtqueues 结尾的,

而后面的设备,只能走第三个流程,报错了,所以打点发现,除了 activate的时候会报容量不足,在alloc阶段,也会在 irq_matrix_alloc 报容量不足。

```

int irq_matrix_alloc(struct irq_matrix *m, const struct cpumask *msk,
     bool reserved, unsigned int *mapped_cpu)
{

......
cpu = matrix_find_best_cpu(m, msk);
if (cpu == UINT_MAX)//caq:在msk内,没找到合适的cpu来记账
return -ENOSPC;

cm = per_cpu_ptr(m->maps, cpu);
bit = matrix_alloc_area(m, cm, 1, false);//caq:获取一个
if (bit >= m->alloc_end)
return -ENOSPC;//caq:没有资源了
......

打点记录如下:

Returning from:  0xffffffffb7158300 : irq_matrix_reserve_managed+0x0/0x130 [kernel]
Returning to  :  0xffffffffb705adb7 : x86_vector_alloc_irqs+0x2d7/0x3c0 [kernel]
 0xffffffffb75dc42c : intel_irq_remapping_alloc+0x7c/0x6d0 [kernel]
 0xffffffffb7156438 : msi_domain_alloc+0x68/0x120 [kernel]
 0xffffffffb715457d : __irq_domain_alloc_irqs+0x12d/0x300 [kernel]
 0xffffffffb7156b38 : msi_domain_alloc_irqs+0x98/0x2f0 [kernel]
 0xffffffffb705e5e4 : native_setup_msi_irqs+0x54/0x90 [kernel]
 0xffffffffb74ef69f : __pci_enable_msix_range+0x3df/0x5e0 [kernel]
 0xffffffffb74ef96b : pci_alloc_irq_vectors_affinity+0xbb/0x130 [kernel]
 0xffffffffb70635e0 : kretprobe_trampoline+0x0/0x50 [kernel]
 0x1b7574a40
 0x1b7574a40 (inexact)
irq_matrix_reserve_managed return -28

```

#### 三、故障复现

1、只要是一开始各个核中断的aviable 容量相当,然后热插拔足够多virtio_blk设备,则必现

2、如果各个核的中断的available容量相差很多,比如常见的numa节点的第一个cpu的中断占用过多,

使得走第一分支时因为某个核容量不够而reserve_managed 失败,然后

则会使得后面大量的virtio_blk走第二个分支,此时不带managed_affinity,反而能分配成功。


#### 四、故障规避或解决

可能的解决方案之一:

```

static int init_vq(struct virtio_blk *vblk)//caq:初始化关于vq相关的内容
{
int err;
int i;
vq_callback_t **callbacks;
const char **names;
struct virtqueue **vqs;
unsigned short num_vqs;
struct virtio_device *vdev = vblk->vdev;
struct irq_affinity desc = { 0, };//caq:去掉这行代码

```

解决方案之二:

开启irqbalance,并让服务器进入 Power-save mode 时,irqbalance 会将中断集中分配给numa节点的第一个 CPU,这样慢慢地,各个核

的available的irq 容量就相差比较大了,当然这种不太靠谱。

解决方案之三:

手工调整中断亲核,使得某些核的容量接近于0,然后再加载virtio_blk设备。


#### 五、作者简介

陈安庆,目前在dpu厂商 云豹智能 负责linux内核及虚拟化方面的工作,

联系方式:微信与手机同号:18752035557。

评论(0) 浏览(66)

LVDS协议(下)

作者:hymmsx 发布于:2022-8-6 23:21 分类:显示

5.LVDS串行化

OpenLDI序列化并行像素流,用于从显示源和显示设备进行传输。在OpenLDI接口中,有8条串行数据线(A0到A7)和2条时钟线(CLK1和CLK2)。根据所支持的像素格式,串行数据线的数量可能会有所不同。对于18位单像素格式,使用串行数据线A0到A2。对于24位单像素格式,使用串行数据线A0到A3。对于18位双像素格式,应使用串行数据线A0到A2和A4到A6。对于24位双像素格式,应使用串行数据线A0到A7。每个信号线上的串行数据流应为像素时钟的7倍的比特率。CLK1线应携带像素时钟。当使用双像素模式时,CLK2线还应携带像素时钟。当不使用双像素模式或当已知显示设备不需要CLK2信号时,CLK2可能保持不活动。提供CLK2信号是为了与早期系统兼容,并支持对上像素和下像素使用独立接收器的显示设备设计。

有两种工作方式,非平衡模式和直流平衡模式。每种非平衡和直流平衡模式使用一种用于单像素传输,另一种用于双像素传输。以下部分描述了每种像素格式在每种模式下的操作。在下面的图中,R、G和B用于表示单个像素格式的红色、绿色和蓝色组件。RU、BU、GU被用来表示双像素格式的上像素的红、蓝、绿分量。同样,RL、BL和GL被用来表示下像素的分量。HSYNC、VSYNC和DE用于表示水平同步、垂直同步和数据启用信号。在双像素格式中,可以传输两个附加的、实现特定的控制信号,CNTLE和CNTLF。

5.1 非平衡模式

在非平衡的工作模式下,只有像素和控制信息从显示源被发送到显示设备。没有规定尽量减少在信号线上的直流偏置的短期或长期积累。

5.1.1-18bit单像素非平衡模式

在18位单像素模式下,应传输RGB和控制输入,如下图所示。输出A3到A7和CLK2在此模式下应不变,并固定在单个值。

5.1.2-24bit单像素非平衡模式

在24位单像素模式下,应传输RGB和控制输入,如下图所示。输出A4到A7和CLK2在此模式下应不活动,并固定在单个值。标记的RES位保留供将来使用,可以取任何价值。

5.1.3-18bit双像素非平衡模式

在18位双像素模式下,应传输RGB和控制输入,如下图所示。输出A3和A7在这种模式下应为不活动状态,并固定在单个值。标记的RES位保留供将来使用,可以取任何价值。

5.1.4-24bit双像素非平衡模式

在24位双像素模式下,应传输RGB和控制输入,如下图所示。标记的RES位保留供将来使用,可以取任何价值。

5.2 平衡模式

在平衡工作模式中,除了像素和控制信息外,在每个周期期间的每条信号线上传输一个额外的比特。此位是直流平衡位(DCBAL)。直流平衡位的目的是最大限度地减少信号线的短期和长期的直流偏置。

5.3.1 18位单像素平衡模式

在18位单像素模式下,应传输RGB和控制输入,如下图所示。输出A3到A7和CLK2在此模式下应不变,并固定在单个值。

控制输入应在消隐间隔时间内进行传输,当DE输入为0时,进入了消隐间隔时间。DE信号由CLK1和CLK2产生,VSYNC,HSYNC信号由A0和A1信号线产生。

5.3.2 24位单像素平衡模式

在24位单像素模式下,应传输RGB和控制输入,如下图所示。输出A4到A7和CLK2在此模式下应无效,并固定在一个值。

控制输入应在消隐间隔时间内进行传输,当DE输入为0时,进入了消隐间隔时间。DE信号由CLK1和CLK2产生,VSYNC,HSYNC信号由A0和A1信号线产生。

5.3.3 18位双像素平衡模式

在18位双像素模式下,应传输RGB和控制输入,如下图所示。输出A3和A7在这种模式下应为不活动状态,并固定在单个值。

控制输入应在消隐间隔时间内进行传输,当DE输入为0时,进入了消隐间隔时间。DE信号由CLK1和CLK2产生,VSYNC,HSYNC信号由A0和A1信号线产生。

5.3.4 24位双像素平衡模式

在24位双像素模式下,应传输RGB和控制输入,如下图所示。输出A3和A7在这种模式下应为不活动状态,并固定在单个值。

控制输入应在消隐间隔时间内进行传输,当DE输入为0时,进入了消隐间隔时间。DE信号由CLK1和CLK2产生,VSYNC,HSYNC信号由A0和A1信号线产生。表中的数据模式首先传输到最左边的位。

6.物理外观

1.OpenLDI接口提供了数字像素信息的传输、显示设备特性的通信以及USB接口的传输。

2.电缆原理图

屏蔽双绞线:

电缆的显示源端与显示设备的显示源端无区别。因此,电缆的任何一端都可以连接到显示源或显示设备。

非屏蔽双绞线:

3.对于长传输距离,建议使用屏蔽电缆,这样可以保证信号线和环境之间很好的隔离。

4.扁平线适用于主板与主板之间的连接,屏蔽双绞线比较适合高噪声环境,同轴电缆不适合LVDS。

5.电缆长度

电缆的最大长度应为10m。

标签: LVDS

评论(0) 浏览(452)

LVDS协议(上)

作者:hymmsx 发布于:2022-7-15 17:01 分类:显示

1.概述

1.1 基本概念

LVDS(Low-Voltage Differential Signaling)低电压差分信号,是一种低功耗、低误码率、低串扰和低辐射的差分信号技术,这种传输技术可以达到155Mbps以上,LVDS技术的核心是采用极低的电压摆幅高速差动传输数据,可以实现点对点或一点对多点的连接,其传输介质可以是铜质的PCB连线,也可以是平衡电缆。

LVDS最早是由美国国家半导体公司(National Semiconductor)提出的一种高速信号传输电平,此后,LVDS在下列两个标准中作了定义:IEEEP1996.3(1996年3月通过),主要面向SCI(Scalable Coherent Interface).定义了LVDS的电特性,还定义了SCI协议中包交换时的编码;ANS/EIA/EIA-644(1995年11月通过),主要定义了LVDS的电特性,并建议了最大传输速率及理论极限速率等参数。通常提到的LVDS标准是指后者。2001年ANS/EIA/EIA-644标准已重新修订发表。

LVDS称为TIA/EIA-644,规定了差分串行信号标准的电气特性的一个技术标准,但它不是协议,属于物理层规范。再其之上定义了许多通信标准和应用程序属于链路层规范。早期在笔记本电脑和LCD显示器所提及的协议通常采用LVDS的术语而不是FPD-Link,久而久之就把两者多为同义对象来描述。LVDS是一种差分信号系统,意味着它以一对导线上的电压差来传输信息,基于差分信号LVDS就能减少电磁噪声的产生。LVDS是指物理层,OpenLDI是指数据链路层和物理层的结合。

1.2 DDC/EDID

1.2.1 DDC

1、介绍:DDC是显示器与电脑主机进行通信的一个总线标准,其全称是:DISPLAY DATA CHANNEL。它的基本功能就是将显示器的电子档案资料信息,诸如可接收行场频范围、生产厂商、生产日期、产品序列号、产品型号、标准显示模式及其参数、所支持的DDC标准类别、EDID的版本信息等等。高版本的DDC标准总线还可以允许电脑主机直接调节显示器的基本参数,诸如亮度、对比度、行场幅度的大小、行场中心位置、色温参数等等。
2、DDC总线标准类别:
1)DDC1:单向传输,CLOCKED BY VSYNC,只传输128 BYTE EDID标准数据信息。
2)DDC2B:单向传输(地址为:0xA0/A1),是一个简单的从存储器读取数据信息的标准I2C协议,其方向为从显示器到电脑主机。
3)DDC2Bi:双向传输,I2C SLAVE MODE,传送图形信息(地址为:0x6E/6F,0x50/51), 支持简单的ACCESS.BUS总线标准。
4)DDC2B+:双向传输,点对点,不支持ACCESS.BUS,传输EDID/VDIF标准数据信息(地址为:0x6E/6F,0x50/51)。
5)DDC2AB:双向传输,支持ACCESS.BUS,传输EDID/VDIF标准数据信息(地址为:0x6E/6F,0x50/51)。

DDC2B是主机读取显示器扩展显示信息的双向资料交换通道,基于I2C 通讯协议。只有主机向显示器发出需求信号,并得到显示器的响应后,显示器才送出EDID资料。显示源和显示设备均应支持DDC2B的操作。显示源应能够在+5Vdc DDC电源引脚上提供至少100 mA。

1.2.2 EDID

EDID数据标准:EDID(Extended Display Identification Data Standard)中文名称扩展显示器识别数据,就是显示器通过DDC传输给电脑主机的标准数据信息,是VESA在制定DDC显示器数据通道通信协议时,制定的有关显示器识别数据的标准。EDID存储在显示器中的DDC存储器中,当电脑主机与显示器连接后,电脑主机会通道DDC通道读取显示器DDC存储器中的存储的EDID。显示源需要支持EDID1.3和2.0的数据结构,如果显示设备是固定的分辨率,在EDID结构中需要将”Preferred Timing Mode“置为1,并在数据结构的第一个详细定时域(DTD)中描述的原生分辨率。Preferred Timing Mode的offset位于EDID1.2的[18H].bit1和 EDID2.0的[7EH].bit14。至今已发布到第三版本,即EDID Version 3,前面分别有EDID Version 1.0,Revision 0,EDID Version 1,Revision 1,EDID Version 2,Revision 0,EDID Version 2,Revision 1等版本。就数据信息量而分,EDID分为128 BYTE和256 BYTE,将来也许会有更多数据信息量的新版EDID公布。

2.工作原理

其源端驱动器由一个恒流源(通常约为3.5mA,最大不超过4mA)驱动一对差分信号线组成。接收端的接收器本身为高直流输入阻抗,所以几乎全部的驱动电流都流经100Ω的终端匹配电阻,并在接收器输入端产生约350mV的电压。当源端驱动状态反转变化时,流经匹配电阻的电流方向改变,于是在接收端产生高低逻辑状态的变化。

为适应共模电压的在宽范围内的变化,一般情况下,LVDS的接收器输入级还包括一个自动电平调整电路,该电路将共模电压调整为一固定值,其后面是一个Schmitt触发器,而且,为防止Scdhmitt触发器不稳定,设计有一定的回滞特性,Schmitt后级才是差分放大器。

LVDS 信号传输一般由三部分组成:差分信号发送器,差分信号互联器,差分信号接收器。

从上面这个简单图中,可以看到所有差分信号技术的共同优点。首先,请注意,当前源总是打开并按不同的方向驱动逻辑1和0。这种特性始终消除了开关大电流晶体管(根据单端技术的要求)所产生的开关噪声峰值和EMI。其次,差分对的两行彼此相邻,提供了相当数量的抗噪声。来自串扰或电磁干扰的噪声被其中一对吸收,也会出现在相邻的线中。由于接收机对两个通道之间的差异作出反应,因此在这两行通道上出现的“共模”噪声将在接收机上取消。此外,由于两个相邻的线路携带相等的电流,但方向相反,EMI的产生被最小化。

IEEE 在两个标准中对LVDS 信号进行了定义。ANSI/TIA/E IA -644 中,推荐最大速率为
655Mbps ,理论极限速率为1.923Gbps

OpenLDI序列化并行像素数据,并将其从显示源发送到显示设备,在显示设备中,像素再次返回到其并行格式。从概念上讲,OpenLDI的架构如图所示:

3.技术特点

LVDS之所以成为目前高速I/O接口的首选信号形式来解决高速数据传输的限制,就是因为它在传输速度、功耗、抗噪声、EMI等方面具有优势。

①高速传输能力。在ANS/EIA/EIA-64定义中的LVDS标准,理论极限速率为1.923Gbps,恒流源模式、低摆幅输出的工作模式决定着LVDS具有高速驱动能力。

②低功耗特性。LVDS器件是用CMOS工艺实现的,而CMOS能够提供较低的静态功耗;当恒流源的驱动电流为3.5mA,负载(100Ω终端匹配)的功耗仅为1.225mW;LVDS的功耗是恒定的,不像CMOS收发器的动态功耗那样相对频率而上升。恒流源模式的驱动设计降低了系统功耗,并极大地降低了频率成分对功耗的影响。虽然当速率较低时,CMOS的功耗比LVDS小,但是随着频率的提高,CMOS的功耗将逐渐增加,最终需要消耗比LVDS更多的功率。通常,当频率等于200MSps时,LVDS和CMOS的功耗大致相同。

③供电电压低。随着集成电路的发展和对更高数据速率的要求,低压供电成为急需。降低供电电压不仅减少了高密度集成电路的功率消耗,而且减少了芯片内部的散热压力,有助于提高集成度。LVDS的驱动器和接收器不依赖于特定的供电电压特性,这决定了它在这方面占据上峰。

④较强的抗噪声能力。差分信号固有的优点就是噪声以共模的方式在一对差分线上耦合出现,并在接收器中相减,从而可消除噪声,所以LVDS具有较强的抗共模噪声能力。

⑤有效地抑制电磁干扰。由于差分信号的极性相反,它们对外辐射的电磁场可以相互抵消,耦合得越紧密,泄放到外界的电磁能量就越少,即降低了EMI。

⑥时序定位精确。由于差分信号的开关变化是位于两个信号的交点。而不像普通单端信号依靠高低两个阀值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,有利于高速数字信号的有效传输。

⑦适应地平面电压变化范围大。LVDS接收器可以承受至少士1V的驱动器与接收器之间的地的电压变化。由于IVDS驱动器典型的偏置电压为+1.2V,地的电压变化、驱动器的偏置电压以及轻度耦合到的噪声之和,在接收器的输入端,相对于驱动器的地是共模电压。当摆幅不超过400mV时,这个共模范围是+0.2V~+2.2V,进而,一般情况下,接收器的输入电压范围可在0V~+2.4V内变化。

随着数字显示设备的普及,通常是平板液晶显示器,以及等离子体和其他技术的普及,显示源和显示设备之间的传统模拟视频接口不足以提供从源和显示器都可获得的图像保真度。OpenLDI描述了显示源和显示设备之间的接口。消除了由于将数字显示数据转换为模拟形式以便从源传输到显示器而导致的任何图像保真度损失。本标准描述一种即插即用机制,其将数字并行像素数据、同步和控制信号转换为串行位流,通过多位电缆将从显示源传输到显示设备的位流,并在显示器处恢复并行像素数据、同步和控制信号。该标准还描述了一种电气接口,它能够使用最小数量的导体传输像素、同步和控制信息,同时也最大限度地减少辐射发射和对电磁干扰的敏感性。

4.像素格式

LVDS支持以下像素格式:18位单像素、24位单像素、18位双像素和24位双像素格式。所有的像素都代表RGB(红色、绿色、蓝色)的强度信息。LVDS很容易支持许多任意的显示分辨率。

4.1-18位单像素

18位单像素格式表示一个像素为三个6位值,R5-0、G5-0和B5-0,分别表示红色、绿色和蓝色的强度。

4.2-18位双像素

18位双像素格式表示两个像素,即三对6位值,RU5-0、RL5-0、GU5-0、GL5-0、BU5-0和BL5-0,每对代表两个像素的红色、绿色和蓝色的强度。

4.1-24位单像素

24位单像素格式表示一个像素为三个8位值,R7-0、G7-0和B7-0,分别表示红色、绿色和蓝色的强度。

4.2-24位双像素

24位双像素格式表示两个像素,即三对8位值,RU7-0、RL7-0、GU7-0、GL7-0、BU7-0和BL7-0,每对代表两个像素的红色、绿色和蓝色的强度。


评论(0) 浏览(691)

Linux内核同步机制之(九):Queued spinlock

作者:OPPO内核团队 发布于:2022-6-29 6:33 分类:内核同步机制

本站之前已经有了一篇关于spinlock的文档,在之前的文章中有对自旋锁进行简单的介绍,同时给出了API汇整和应用场景。不过该文章中的自旋锁描述是基于比较老的内核版本,那时候的自旋锁还是ticket base锁,而目前最新内核中的自旋锁已经进化成queued spinlock,因此需要一篇新的自旋锁文档来跟上时代。此外,本文将不再描述基本的API和应用场景,主要的篇幅将集中在具体的自旋锁实现上。顺便说一句,同时准备一份linux5.10源码是打开本文的正确方式。

由于自旋锁可以在各种上下文中使用,因此本文中的thread是执行线索的意思,表示进程上下文、hardirq上下文、softirq上下文等多种执行线索,而不是调度器中线程的意思。

阅读全文>>

标签: spinlock queued

评论(0) 浏览(1047)

Linux内核同步机制之(八):mutex

作者:OPPO内核团队 发布于:2022-5-10 5:55 分类:内核同步机制

在linux内核中,互斥量(mutex,即mutual exclusion)是一种保证串行化的睡眠锁机制。和spinlock的语义类似,都是允许一个执行线索进入临界区,不同的是当无法获得锁的时候,spinlock原地自旋,而mutex则是选择挂起当前线程,进入阻塞状态。本文代码来自Linux 5.10.61,文章中的代码片段有删减,阅读本文时可以参考源代码。

阅读全文>>

标签: mutex

评论(2) 浏览(2441)

schedutil governor情景分析

作者:OPPO内核团队 发布于:2022-4-26 6:29 分类:进程管理

这是一篇分析schedutil governor(后文称之sugov)代码逻辑的文章。通过详细的代码级别的分析,希望能够帮助读者理解sugov的代码精妙之处。本文主要分四个章节:第一章简单重复了sugov相关的软件结构和基本算法,让读者对整个sugov在系统所处的位置和基本的逻辑控制有所了解。第二章对sugov使用的数据结构给出了详细的解释。第三章对sugov和cpufreq core的基本数据流和控制流进行分析。第四章描述了sugov本身的调频逻辑。

本文出现的内核代码来自Linux5.10.61,为了减少篇幅,我们会引用缩减版本的代码(仅包含主要逻辑),如果有兴趣,读者可以配合原始代码阅读本文。

阅读全文>>

标签: governor schedutil

评论(1) 浏览(1682)

PELT算法浅析

作者:OPPO内核团队 发布于:2022-4-7 7:09 分类:进程管理

Linux是一个通用操作系统的内核,她的目标是星辰大海,上到网络服务器,下至嵌入式设备都能运行良好。做一款好的linux进程调度器是一项非常具有挑战性的任务,因为设计约束太多了:

---它必须是公平的

---快速响应

---系统的throughput要高

---功耗要小

3.8版本之前的内核CFS调度器在计算CPU load的时候采用的是跟踪每个运行队列上的负载(per-rq load tracking)。这种粗略的负载跟踪算法显然无法为调度算法提供足够的支撑。为了完美的满足上面的所有需求,Linux调度器在3.8版中引入了PELT(Per-entity load tracking)算法。本文将为您分析PELT的设计概念和具体的实现。

本文出现的内核代码来自Linux5.10.61,如果有兴趣,读者可以配合代码阅读本文。

阅读全文>>

标签: PELT

评论(8) 浏览(1982)

load_balance函数代码详解

作者:OPPO内核团队 发布于:2022-2-16 7:29 分类:进程管理

我们描述CFS任务负载均衡的系列文章一共三篇,第一篇是框架部分,第二篇描述了task placement和active upmigration两个典型的负载均衡场景,第三篇是负载均衡的情景分析,包括tick balance、nohz idle balance和new idle balance。在负载均衡情景分析文档最后,我们给出了结论:tick balancing、nohz idle balancing、new idle balancing都是万法归宗,汇聚到load_balance函数来完成具体的负载均衡工作。本文就是第三篇负载均衡情景分析的附加篇,重点给大家展示load_balance函数的精妙。

本文出现的内核代码来自Linux5.10.61,为了减少篇幅,我们对引用的代码进行了删减(例如去掉了NUMA的代码,毕竟手机平台上我们暂时不关注这个特性),如果有兴趣,读者可以配合完整的源代码代码阅读本文。

阅读全文>>

标签: load_balance

评论(0) 浏览(1913)

CPU 多核指令 —— WFE 原理

作者:heaven 发布于:2022-2-11 1:34 分类:ARMv8A Arch

今天我想分享一个跟多核锁原理相关的东西,由于我搞 arm 居多,所以目前只研究了 arm 架构下的 WFE 指令,分享出来,如果有表述不精准或者错误的地方还请大家指出,非常感谢。研究这个原因也是只是想搞清楚所以然和来龙去脉,以后写代码可以更游刃有余。

阅读全文>>

标签: ARM wfe wfi

评论(2) 浏览(2166)

CFS任务放置代码详解

作者:OPPO内核团队 发布于:2021-12-31 7:00 分类:进程管理

我们描述CFS任务负载均衡的系列文章一共三篇,第一篇是框架部分,第二篇描述了task placement的逻辑过程,第三篇是负载均衡的情景分析,包括tick balance、nohz idle balance和new idle balance。之前已经有一篇关于task placement的文档发表在本站,为了更精细的讲解代码逻辑,我们这次增加了代码分析部分。本文作为第二篇任务放置的附篇,深入讲解task placement的代码流程。

本文出现的内核代码来自Linux5.10.61,为了减少篇幅,我们对引用的代码进行了删减(例如去掉了NUMA的代码,毕竟手机平台上我们暂时不关注这个特性),如果有兴趣,读者可以配合完整的源代码代码阅读本文。

阅读全文>>

标签: 任务放置 task placement

评论(0) 浏览(2200)

Copyright @ 2013-2015 蜗窝科技 All rights reserved. Powered by emlog