PCI Subsystem -3- (Host Controller)

 

PCI 호스트 컨트롤러 디바이스 트리 노드

다음 PCI 노드를 알아본다.

pci {
    compatible = "company,foo"
    device_type = "pci";        /* always be "pci" */
    #address-cells   = <3>;     /* always be 3 */
    #size-cells      = <2>;     /* 32bit=1, 64bit=2 */
    #interrupt-cells = <1>;     /* always be 1 for legacy pci irq */

    bus-range = <0x0 0x1>;

    /*    CPU_PHYSICAL(P#a)     SIZE(P#s) */
    reg = <0x0 0x40000000    0x0 0x1000000>;

    /*             PCI_PHYSICAL(#a)      CPU_PHYSICAL(P#a)   SIZE(#s) */
    ranges = <0x81000000 0x0 0x00000000   0x0 0x48000000    0x0 0x00010000>,  /* non-relocatable IO */
             <0x82000000 0x0 0x40000000   0x0 0x40000000    0x0 0x40000000>;  /* non-relocatable MEM */

    /*               PCI_DEVICE(#a)  INT#(#i)   IRQ-Controller   IRQ-UNIT#   IRQ_ARG(PI#i) */
    interrupt-map = <  0x0 0x0 0x0    0x0           &gic            0x0       0x4 0x1
                     0x800 0x0 0x0    0x1           &gic            0x0       0x5 0x1
                    0x1000 0x0 0x0    0x2           &gic            0x0       0x6 0x1
                    0x1800 0x0 0x0    0x4           &gic            0x0       0x7 0x1>;

    /*                    PCI_DEVICE(#a)    INT#(#i) */
    interrupt-map-mask = <0xf800 0x0 0x0     0x7>;
}
  • reg
    • PCI 호스트 컨트롤러의 조작에 사용될 레지스터들의 액세스에 사용될 물리 주소와 사이즈가 지정된다.
  • device_type
    • 항상 “pci”를 대입하여야 한다.
  • #address-cells
    • pci 주소에 사용하므로 반드시 3이어야 한다.
  • #size-cells
    • PCI 및 물리 주소에서 사용되는 메모리 사이즈
      • 1=32비트 메모리 사이즈
      • 2=64비트 메모리 사이즈
  • interrupt-cells
    • 레거시 인터럽트를 사용하는 경우 사용되며, 이 때에는 반드시 1이어야 한다.
  • bus-range
    • 사용할 시작 버스 번호 ~ 끝 버스 번호
  • ranges
    • pci 주소 체계가 cpu에서 사용하는 물리 주소 체계로 변환되어야 하는 범위가 지정된다.
  • interrrupt-map
    • 레거시 인터럽트를 사용하는 PCI 디바이스를 위해 최대 4개의 irq 핀을 위해 irq 매핑 정보를 구성한다.
  • interrupt-map-mask
    • 위의 레거시 인터럽트 값의 유효 값을 필터하기 위해 사용하는 마스크 정보이다.

 

각 속성에서 사용한 셀

주석에서 사용된 기호는 셀 수를 알아오는 곳을 의미한다.

  • #a
    • PCI 노드의 #address-cells 값
  • #s
    • PCI 노드의 #size-cells 값
  • #i
    • PCI 노드의 #interrupt-cells 값
  • P#a
    • 부모 노드의 #address-cells 값
  • P#s
    • 부모 노드의 #size-cells 값
  • PI#i
    • 부모 인터럽트 컨트롤러 노드의 #interrupt-cells 값
pci {
    compatible = "company,foo"
    device_type = "pci";        /* always be "pci" */
    #address-cells   = <3>;     /* always be 3 */
    #size-cells      = <2>;     /* 32bit=1, 64bit=2 */
    #interrupt-cells = <1>;     /* always be 1 for legacy pci irq */

    bus-range = <0x0 0x1>;

    /*    CPU_PHYSICAL(P#a)     SIZE(P#s) */
    reg = <0x0 0x40000000    0x0 0x1000000>;

    /*             PCI_PHYSICAL(#a)      CPU_PHYSICAL(P#a)   SIZE(#s) */
    ranges = <0x81000000 0x0 0x00000000   0x0 0x48000000    0x0 0x00010000>,  /* non-relocatable IO */
             <0x82000000 0x0 0x40000000   0x0 0x40000000    0x0 0x40000000>;  /* non-relocatable MEM */

    /*               PCI_DEVICE(#a)  INT#(#i)   IRQ-Controller   IRQ-UNIT#   IRQ_ARG(PI#i) */
    interrupt-map = <  0x0 0x0 0x0    0x0           &gic            0x0       0x4 0x1
                     0x800 0x0 0x0    0x1           &gic            0x0       0x5 0x1
                    0x1000 0x0 0x0    0x2           &gic            0x0       0x6 0x1
                    0x1800 0x0 0x0    0x4           &gic            0x0       0x7 0x1>;

    /*                    PCI_DEVICE(#a)    INT#(#i) */
    interrupt-map-mask = <0xf800 0x0 0x0     0x7>;
}

 

PCI 주소 공간 매핑

PCI 장치와 CPU가 서로의 영역에 접근하려면 각각의 접근 방향에 따른 매핑이 필요하다.

  • outbound mapping
    • PCI 장치에 붙어 있는 메모리를 cpu가 접근하기 위한 매핑
  • inbound mapping
    • PCI 장치가 DMA를 통해 호스트 메모리에 접근하기 위한 매핑

a) 아웃바운드 매핑

PCI 디바이스에서 사용하는 1개 이상의 PCI 주소(I/O, MEM)는 cpu가 곧 바로 접근하여 사용하지 못한다. 먼저 PCI 호스트 컨트롤러 내부에 존재하는 IOMMU 장치를 사용하여 PCI 메모리의 영역을 CPU 물리 주소의 빈 공간으로 아웃바운드 매핑을 해야한다. 이렇게 CPU 물리 주소로 매핑된 메모리는 해당 PCI 디바이스 드라이버에서 이 영역을 다시 CPU의 가상 주소에 매핑하여 cpu가 이 영역에 접근할 수 있게 할 수 있다.

  • PCI 호스트 컨트롤러가 outbound mapping하는 주소 범위 등의 정보는 디바이스 트리에서 PCI 노드의 ranges 속성으로 표현한다.

 

Ranges 속성

호스트 컨트롤러가 IO 주소 공간, MEM32 공간 및 MEM64 공간을 outbound 매핑하는 영역에 대한 기술을 한다. pre-fetchable 영역의 MEM32 및 MEM64가 있는 경우 별도의 영역으로 구성해야 한다. 영역을 구성하는 셀 값은 크게 다음과 같이 3 부분으로 구성된다.

  • PCI 주소(source)
    • 변환할 PCI 주소 공간에 대한 타입과 주소를 기재한다.
    • PCI 호스트 컨트롤러 노드의 #address-cells 값이 지정한 값 만큼의 셀 수를 사용한다. (이 항목은 항상 3자리 셀을 사용한다)
  • cpu 물리 주소(destination)
    • PCI 호스트 컨트롤러로 변환되는 dest 영역의 시작 물리 주소를 기재한다.
    • 부모 노드에서 기재한 #address-size 값이 지정한 값 만큼의 셀 수를 사용한다.
  • 사이즈
    • 호스트 컨트롤러가 변환시키는 영역의 사이즈
    • PCI 호스트 컨트롤러 노드의 #size-cells 값이 지정한 값 만큼의 셀 수를 사용한다. (32bit=1, 64bit=2)

 

다음 ranges 속성은 2개의 PCI 메모리 영역을 물리 주소로 변환된다는 것을 커널에 알려주게 된다. 이 들 값 중 PCI 주소는 3자리 셀로 구성되며 이를 해석하는 방법도 이어서 알아본다.

 

PCI 주소

PCI 주소는 항상 3개의 셀로 구성된다.

phys.hi 셀
  • n
    • Non-relocatable region flag
    • 비-재배치 여부 플래그 (1=non-relocatable0=relocatable)
  • p
    • Prefetchable (cacheable) 영역 플래그(1=prefetchable, 0=non-prefetchable)
  • t
    • Ten bits aliased address flag (1=Ten bits aliased, 0=otherwise)
    • I/O 및 Memory 영역에 대해 하위 10비트를 제거한 Hard-Decoding을 사용할 수 있게 허용한다.
    • 즉 BAR를 통해 재배치할 필요 없다.
  • ss
    • 주소 영역 타입
      • 00: Configuration space
      • 01: I/O space
      • 10: 32 bit Memory space
      • 11: 64 bit Memory space
  • bbbbbbbb
    • PCI 버스 번호
  • ddddd
    • PCI 디바이스 번호 (또는 슬롯 번호)
  • fff
    • PCI 펑션 번호
  • rrrrrrrr
    • 레지스터 offset
phys.mid 셀
  • 64비트 PCI 주소 중 상위 32비트
phys.low 셀
  • 64비트 PCI 주소 중 하위 32비트

 

ss 타입에 따른 사용법

Configuration, I/O 및 메모리 장치 타입에 따른 PCI 주소 구성을 알아본다.

  • ss=00: Configuration space
    • n, p, t 모두 0이어야 한다.
    • bbbbbbbb,ddddd,fff,rrrrrrrrr에서 Configuration 영역 주소를 지정한다.
    • h[31:0]은 모두 0이어야 한다.
    • l[31:0]은 모두 0이어야 한다.
  • ss=01: I/O space
    • p는 0이어야 한다.
    • 10bit aliasing(t=1)은 10비트만을 사용한 초기 pc를 위해 사용하는데 arm, arm64 에서는 사용하지 않는다.
    • bbbbbbbb,ddddd,fff,rrrrrrrr가 지정하는 즉, BAR가 가리키는 주소를 사용한다.
      • rrrrrrrr=0x10, 0x14, 0x18, 0x1c, 0x20, 0x24
      • rrrrrrrr=0x00 (non-relocatable)
    • h[31:0]은 모두 0이어야 한다.
    • n
      • relocatable(n=0) 인 경우 l[31:0]에 I/O 영역에서 relocatable 시작 영역의 32비트 offset을 지정한다.
      • non-relocatable(n=1) 인 경우 l[31:0]에 32비트 IO 영역 주소를 지정한다.
    • arm64 사용사례: 0x01, 0x81
  • ss=10: 32 bit memory space
    • p는 0 또는 1을 사용할 수 있다.
    • 10bit aliasing(t=1)은 1M 메모리까지의 메모리 영역을 사용한 초기 pc를 위해 사용하는데 arm, arm64 에서는 사용하지 않는다.
    • bbbbbbbb,ddddd,fff,rrrrrrrr가 지정하는 즉, BAR가 가리키는 주소를 사용한다.
      • rrrrrrrr=0x10, 0x14, 0x18, 0x1c, 0x20, 0x24, 0x30
      • rrrrrrrr=0x00 (non-relocatable)
    • h[31:0]은 모두 0이어야 한다.
    • n
      • relocatable(n=0) 인 경우 l[31:0]에 상대 offset을 지정한다.
        • 32bit PCI Momory  시작 주소 = BAR + offset(l[31:0])
      • non-relocatable(n=1)인 경우 32비트 메모리 주소를 지정한다.
        • 32bit PCI Momory  시작 주소 = l[31:0]
    • arm64 사용사례: 0x02, 0x42, 0x82
  • ss=11: 64 bit memory space
    • ss=10과 유사하며 상위 PCI 주소를 사용하기 위해 h[31:0]도 사용한다.
    • arm64 사용사례: 0x03, 0x43, 0x83

 

다음은 PCI 디바이스에서 I/O 주소 공간과 32비트 Memory 주소 공간에 대한 Ranges 속성 사례를 보여준다.

 

b) inbound mapping

outbound와 반대 방향의 inbound mapping을 구성하는 경우 PCI 디바이스가 CPU를 통하지 않고 DMA 장치를 통해 직접 호스트 메모리에 접근할 수 있게된다.

  • PCI 호스트 컨트롤러가 inbound mapping하는 주소 범위 등의 정보는 디바이스 트리에서 PCI 노드의 dma-ranges 속성으로 표현한다
  • outbound 매핑에서 사용하는 ranges 속성과 동일한 구성을 사용한다.

 

다음 예를 알아보자.

  • 예) dma-ranges = <0x43000000 0x0 0x0 0x0 0x0 0x100 0x0>;
    • relocatable, pre-fetchable Mem64 영역으로 PCI 주소와 호스트 CPU 물리 주소를 동일한 공간으로 0x100_0000_0000 사이즈만큼(512G 영역) inbound 매핑한다.

 

Root Complex PCIe 브리지 포트의 PCI 주소 영역 및 제한

Root Complex PCIe 브리지 포트의 configuration space에서 3 가지 타입의 메모리 매핑 영역에 대한 시작 주소 및 제한 범위가 지정되는데 이를 알아본다.

  • 아래 그림에서 1번에 해당하는 값은 하위 4비트로 32bit 및 64bit 여부를 가린다.
    • 0b0000=32bit, 0b0001=64bit
  • Base 주소를 구성할 때 각 타입에 따라 1~2 개의 값을 연결하여 구성하고 수 비트의 하위 비트들은 지정되지 않는데 이들은 모두 0으로 채운다. (always 0)
    • 예) Pre-Fetchable Memory Base=0x4561, Pre-Fetchable Base Upper 32 bits=0x0000_1234인 경우
      • Base=0x0000_1234_4560_0000 (64bit)
  • Limit 주소를 구성할 때 각 타입에 따라 1~2 개의 값을 연결하여 구성하고 수 비트의 하위 비트들은 지정되지 않는데 이들은 모두 0으로 채운다. (always 1)
    • 예) Pre-Fetchable Memory Limit=0x4561, Pre-Fetchable Limit Upper 32 bits=0x0000_1234인 경우
      • Limit=0x0000_1234_456F_FFFF (64bit)

 

PCI 레거시 인터럽트 매핑

PCI 버스에 연결된 PCI 장치에서 사용하는 레거시 인터럽트의 매핑 구성 정보를 표현한다. 이 정보를 통해 각 PCI 버스의 INTA ~ INTD 까지의 인터럽트 시그널이 상위 인터럽트 컨트롤러의 어떠한 인터럽트 번호에 매핑되었는지 알아낼 수 있다. 참고로 디바이스 트리를 사용하지 않는 PC의 경우 ACPI 바이오스 정보를 읽어 매핑한다. 또한 PCI express의 경우 레거시 IRQ가 없지만 PCI express 포트에 PCI 브릿지를 연결하면 PCI 디바이스를 연결할 수도 있으므로 레거시 인터럽트 매핑 정보를 구성해야 하는 경우도 있다.

 

interrupt-map 속성

다음과 같이 5개 부분으로 구성된다.

  • child unit 주소
    • #address-cells 값이 지정한 셀 수만큼의 32비트 주소를 사용한다.
    • 아래 예에서는 3개의 32비트 주소 값을 사용한다.
  • child 인터럽트 지정 번호
    • 인터럽트 핀 번호인 1~4를 지정한다.
  • interrupt parent
    • 부모 인터럽트 컨트롤러 노드에 해당하는 phandle 값
  • parent unit 주소
    • 부모 인터럽트 노드에 있는 #address-cells 값이 지정한 셀 수 만큼 32bit 주소를 사용한다.
      • 아래는 1개의 unit 주소를 사용하였다.
  • parent interrupt 번호 및 트리거 타입
    • 부모 인터럽트 노드의 #interrupt-cells 값이 지정한 셀 수 만큼의 32bit 값을 사용한다. (보통 1 ~ 4 셀)
    • 아래는 2개의 셀을 사용하였다.
      • generic하게 2 개의 셀을 사용하는 경우 처음 셀은 인터럽트 번호, 두 번째는 인터럽트 타입을 지정한다.
        • pci에서는 인터럽트 타입에 IRQ_TYPE_LEVEL_HIGH를 사용한다.

interrupt-map-mask 속성

위의 레거시 인터럽트 값의 유효 값을 필터하기 위해 사용하는 마스크 정보로 다음과 같이 2개 부분으로 구성된다.

  • child unit 주소에 대한 마스크
  • child 인터럽트 지정 번호에 대한 마스크

 

아래 interrupt-map 속성의 4번째와 5번째 박스의 셀은 부모 인터럽트 컨트롤러의 #interrupt-cells 속성 값 만큼의 셀 수로 구성된다.

  • 참고로 arm64의 gic 컨트롤러는 3개의 셀로 구성된다.
    • 첫 번째 셀은 irq-unit 번호로 GIC_SPI(0) 또는 GIC_PPI(1)이 사용된다.
    • 두 번째 셀은 인터럽트 번호로 첫 번째 인자 값에 따라 hwirq 번호가 산출된다.
      • GIC_PPI(1)를 사용하는 경우에는 이 값에 + 16을 한 값이 hwirq 번호이다.
      • GIC_SPI(0)를 사용하는 경우에는 이 값에 + 32를 한 값이 hwirq 번호이다.
    • 세 번째 셀은 인터럽트 트리거 타입을 지정한다.
      • IRQ_TYPE_NONE(0)
      • IRQ_TYPE_EDGE_RISING(1)
      • IRQ_TYPE_EDGE_FALLING(2)
      • IRQ_TYPE_LEVEL_HIGH(4)
      • IRQ_TYPE_LEVEL_LOW(8)

 

PCI Host Controller(PCIe RC) 구현

다음과 같이 플랫폼 드라이버를 준비한다. 특별히 다른 버스 드라이버와 차이점은 없다.

static struct platform_driver foo_pci_driver = {
        .driver = {
                .name = "foo-pci",
                .of_match_table = foo_pci_of_match,
        },
        .probe = foo_pci_probe,
        .remove = foo_pci_remove,
};
module_platform_driver(foo_pci_driver);

 

디바이스 트리 노드와 매치 될 수 있도록 compatible 속성값을 정한다.

static const struct of_device_id foo_pci_of_match[] = {
        { .compatible = "company,foo" },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, foo_pci_of_match);

 

foo_pcie 구조체

다음과 같이 pcie host controller를 관리할 수 있는 구조체 샘플을 보여준다.

struct foo_pcie {
        struct device *dev;
        void __iomem *base;
        phys_addr_t base_addr;
        struct resource mem;
        struct pci_bus *root_bus;
        struct phy *phy;
        int (*map_irq)(const struct pci_dev *, u8, u8);
        bool need_ib_cfg;
        int nr_ib_regions;
};
  • *dev
    • pcie 호스트 컨트롤러를 가리키는 디바이스
  • *base
    • PCIe 호스트 컨트롤러 레지스터 가상 주소 base
  • base_addr
    • PCIe 호스트 컨트롤러 레지스터 물리 주소 base
  • mem
    • 메모리 리소스
  • *root_bus
    • pci 루트 버스
  • *phy
    • Serdes를 컨트롤하는 PHY 디바이스
  • (*map_irq)
    • 인터럽트 번호를 알아올 후크 함수
  • *msi
    • msi 데이터

 

foo_pci_probe()

다음 샘플 드라이버 코드와 의미를 알아본다.

static int foo_pci_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct foo_pcie *pcie;
        struct device_node *np = dev->of_node;
        struct resource reg;
        resource_size_t iobase = 0;
        LIST_HEAD(resources);
        struct pci_host_bridge *bridge;
        int ret;

        bridge = devm_pci_alloc_host_bridge(dev, sizeof(*pcie));
        if (!bridge)
                return -ENOMEM;

        pcie = pci_host_bridge_priv(bridge);
        platform_set_drvdata(pdev, pcie);
        pcie->dev = dev;

        ret = of_address_to_resource(np, 0, &reg);
        if (ret < 0) { 
                dev_err(dev, "unable to obtain controller resources\n"); 
                return ret; 
        } 
        pcie->base = devm_pci_remap_cfgspace(dev, reg.start,
                                             resource_size(&reg));
        if (!pcie->base) {
                dev_err(dev, "unable to map controller registers\n");
                return -ENOMEM;
        }
        pcie->base_addr = reg.start;

        pcie->need_ib_cfg = of_property_read_bool(np, "dma-ranges");

        /* PHY use is optional */
        pcie->phy = devm_phy_get(dev, "pcie-phy");
        if (IS_ERR(pcie->phy)) {
                if (PTR_ERR(pcie->phy) == -EPROBE_DEFER)
                        return -EPROBE_DEFER;
                pcie->phy = NULL;
        }

        ret = of_pci_get_host_bridge_resources(np, 0, 0xff, &resources,
                                               &iobase);
        if (ret) {
                dev_err(dev, "unable to get PCI host bridge resources\n");
                return ret;
        }

        /* for legacy irq */
        pcie->map_irq = of_irq_parse_and_map_pci;

        ret = foo_pcie_setup(pcie, &resources);
        if (ret) {
                dev_err(dev, "PCIe controller setup failed\n");
                pci_free_resource_list(&resources);
                return ret;
        }

        return 0;
}

이 probe 함수는 pci 호스트 컨트롤러 드라이버를 구동한다.

  • 코드 라인 12~14에서 관리할 드라이버에 대한 구조체를 생성한다. devm_으로 시작하는 함수는 드라이버 언로드 시 자동으로 release 처리한다.
    • 예) struct pci_host_bridge 와 struct foo_pci 두 구조체가 생성된다.
  • 코드 라인 16~17에서 위에서 생성한 브리지가 가리키는 struct foo_pci 주소를 알아와서 private 드라이버 데이터로 활용하기 위해 저장한다.
  • 코드 라인 20~24에서 “reg” 속성을 읽어 리소스로 알아온다.
  • 코드 라인 25~31에서 pcie 호스트 컨트롤러 레지스터 리소스를 가상 주소에 매핑하고 가상 주소를 base 멤버 변수에, 그리고 물리 주소 base_addr 멤버 변수에 저장한다.
  • 코드 라인 33에서 인바운드 매핑이 필요한 지 여부를 “dma-ranges” 속성 값이 있는지 확인한다.
  • 코드 라인 36~41에서 phy-names 속성 값이 “pcie-phy”인 경우 phys 속성의 phandle 값이 가리키는 노드에 해당하는 phy 핸들을 알아온다.
  • 코드 라인 43~48에서 호스트 브리지 리소스를 알아온다.
  • 코드 라인 51에서 pci 디바이스에서 사용하는 legacy irq를 할당하는 후크 함수를 지정한다.
    • of_property_match_string() 함수는 다음과 같은 동작을 수행한다.
      • pci configuration space의 PCI_INTERRUPT_PIN 레지스터(1 바이트)를 읽어 pci 핀 번호(0, 1~4)를 알아온다.
      • 디바이스 트리 노드의 “interrupt-map” 속성과 “interrupt-mask” 속성을 사용하여 pci 핀 번호에 해당하는 hwirq를 알아온다.
      • irq domain에서 hwirq와 virq를 매핑한다.
      • pci configuration space의 PCI_INTERRUPT_LINE 레지스터(1 바이트)에 virq를 기록한다.
      • virq를 반환한다.
  • 코드 라인 53~58에서 pcie 호스트 컨트롤러를 사용하기 위해 pcie hw 설정을 수행한다.

 

foo_pcie_setup()

static int foo_pcie_setup(struct foo_pcie *pcie, struct list_head *res)
{
        struct device *dev;
        int ret;
        void *sysdata;
        struct pci_bus *child;
        struct pci_host_bridge *host = pci_host_bridge_from_priv(pcie);

        dev = pcie->dev;

        ret = devm_request_pci_bus_resources(dev, res);
        if (ret)
                return ret;

        ret = phy_init(pcie->phy);
        if (ret) {
                dev_err(dev, "unable to initialize PCIe PHY\n");
                return ret;
        }

        ret = phy_power_on(pcie->phy);
        if (ret) {
                dev_err(dev, "unable to power on PCIe PHY\n");
                goto err_exit_phy;
        }

        /* pcie hw clock & regulator control */
        /* ...(ignore)... */

        /* pcie hw outbound map(ranges) control */
        /* ...(ignore)... */ 

        /* pcie hw inbound map(dma-ranges) control */
        /* ...(ignore)... */ 
        sysdata = pcie;

        ret = foo_pcie_check_link(pcie);
        if (ret) {
                dev_err(dev, "no PCIe EP device detected\n");
                goto err_power_off_phy;
        }

        /* pcie hw enable */
        /* ...(ignore)... */ 

        /* pcie msi related */
        /* ...(ignore)... */
 
        list_splice_init(res, &host->windows);
        host->busnr = 0;
        host->dev.parent = dev;
        host->ops = &foo_pcie_ops;
        host->sysdata = sysdata;
        host->map_irq = pcie->map_irq;
        host->swizzle_irq = pci_common_swizzle;

        ret = pci_scan_root_bus_bridge(host);
        if (ret < 0) {
                dev_err(dev, "failed to scan host: %d\n", ret);
                goto err_power_off_phy;
        }

        pci_assign_unassigned_bus_resources(host->bus);

        pcie->root_bus = host->bus;

        list_for_each_entry(child, &host->bus->children, node)
                pcie_bus_configure_settings(child);

        pci_bus_add_devices(host->bus);

        return 0;

err_power_off_phy:
        phy_power_off(pcie->phy);
err_exit_phy:
        phy_exit(pcie->phy);
        return ret;
}

pcie 호스트 컨트롤러(Root Complex)를 사용하기 위해 pcie hw 설정을 수행한다.

  • 코드 라인 11~13에서 pci 리소스를 사용한다고 요청한다. 물리 영역에 이미 사용 중인 리소스 영역이 존재하면 에러를 반환한다.
    • 요청된 리소스의 주소 범위 확인은 cat /proc/iomem 으로 확인할 수 있다.
    • 리소스를 매핑(ioremap)하기 전에 request 하여 중복 영역 체크를 하는 것이 좋다. 또한 /proc/iomem 을 통해 관리하기 좋다.
  • 코드 라인 15~19에서 pci 디바이스의 전송 라인에 연결된 phy 장치를 초기화한다.
    • phy는 전송 장치의 물리 layer 끝에 위치하는 장치이고 다음 두 부분을 포함한다.
      • 코드화를 담당하는 Physical Coding Sublayer (PCS) 부분
      • 광 케이블 또는 cooper 케이블과 연결되는 Physical Medium Dependent (PMD) 부분
    • 바로 옆에 연결된 chip들과의 통신에는 phy가 사용되지 않지만, 수십센티 이상 조금이라도 거리가 먼 장치와의 통신을 위해 몇 가지 기술들을 사용한다.
      • layer시그널 전압 및 전류를 높이고 변조 및 코드화 시켜 출력한다.
      • 고속 전송 시 노이즈 제거를 위해 differential 등의 기술을  사용하기도 한다.
      • 슬롯, 커넥터 등을 통해 연결되는 거의 모든 장치와의 통신에 대부분 사용된다.
        • SDRAM, Flash Memory, SATA, PCIe, USB, Ethernet, Wifi, Wimax, …
    • drivers/phy 디렉토리에 phy 관련 드라이버들이 존재한다.
  • 코드 라인 21~25에서 phy 전원을 on 시킨다.
  • 코드 라인 28에서 pci 호스트 컨트롤러의 클럭과 전원 공급 장치(레귤레이터)와 관련한 hw 설정을 수행한다.
    • pci 장치마다 레지스터를 조작하는 규격이 다르므로 해당 칩의 데이터 시트 및 프로그래머스 매뉴얼을 참고해야 한다.
  • 코드 라인 31에서 pci 호스트 컨트롤러의 outbound 매핑 관련 레지스터를 설정한다.
    • outbound 매핑이 고정되어 제공되는 경우 제어를 할 수 없다.
  • 코드 라인 34에서 pci 호스트 컨트롤러에서 dma 장치를 사용하는 pci 디바이스의 연결을 위해 inbound 매핑 관련 레지스터를 설정한다.
    • inbound 매핑이 고정되어 제공되는 경우 제어를 할 수 없다.
  • 코드 라인 37~41에서 pcie root complex와 연결된 PCIe end point 디바이스와의 링크 연결 여부를 확인한다. (구현 함수는 생략)
    • 참고: iproc_pcie_check_link()
  • 코드 라인 44에서 pci 호스트 컨트롤러를 enable 하는 레지스터를 설정한다.
  • 코드 라인 47에서 pci 호스트 컨트롤러에서 msi를 준비한다.
  • 코드 라인 49~61에서 pcie 오퍼레이션을 준비한 후 루트 버스 브리지를 스캔한다.
    • 이 버스에 연결된 하위 버스 및 디바이스를 스캔한다.
  • 코드 라인 63에서
  • 코드 라인 67~68에서
  • 코드 라인 70에서 버스에 연결된 pci 디바이스들을 등록한다.

 

PCI operation 지정

static struct pci_ops foo_pcie_ops = {
        .map_bus = foo_pcie_bus_map_cfg_bus,
        .read = foo_pcie_config_read32,
        .write = foo_pcie_config_write32,
};
  • (*map_bus)
    • 요청하는 버스 및 devfn에 해당하는 pci configuraton space에서 접근할 수 있도록 pci 호스트 컨트롤러의 관련 레지스터 설정을 하는 후크 함수를 지정한다.
  • (*read)
    • 요청하는 버스 및 devfn에 해당하는 pci configuraton space에 원하는 위치의 데이터를 읽어오는 후크 함수를 지정한다.
    • 1, 2, 4 바이트
  • (*write)
    • 요청하는 버스 및 devfn에 해당하는 pci configuraton space의 원하는 위치에 데이터를 기록하는 후크 함수를 지정한다.
    • 1, 2, 4 바이트

 

디바이스 트리 사례 (for arm64)

1) arm/juno-base.dtsi
ranges = <0x01000000 0x00 0x00000000 0x00 0x5f800000 0x0 0x00800000>,  /* relocatable I/O */
         <0x02000000 0x00 0x50000000 0x00 0x50000000 0x0 0x08000000>,  /* relocatable MEM */
         <0x42000000 0x40 0x00000000 0x40 0x00000000 0x1 0x00000000>;  /* relocatable, pre-fetchable MEM */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &gic 0 0 0 136 4>,
                <0 0 0 2 &gic 0 0 0 137 4>,
                <0 0 0 3 &gic 0 0 0 138 4>,
                <0 0 0 4 &gic 0 0 0 139 4>;

 

2) marvell/armada-37xx.dtsi
ranges = <0x82000000 0 0xe8000000   0 0xe8000000 0 0x1000000 /* non-relocatable MEM */
          0x81000000 0 0xe9000000   0 0xe9000000 0 0x10000>; /* non-relocatable I/O */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0 0 0 1 &pcie_intc 0>,
                <0 0 0 2 &pcie_intc 1>,
                <0 0 0 3 &pcie_intc 2>,
                <0 0 0 4 &pcie_intc 3>;

 

3) marvell/armada-cp110-master.dtsi
ranges = <0x81000000 0 0xf9000000 0 0xf9000000 0 0x10000     /* non-relocatable I/O */
          0x82000000 0 0xf6000000 0 0xf6000000 0 0xf00000>;  /* non-relocatable, MEM */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &cpm_icu ICU_GRP_NSR 22 IRQ_TYPE_LEVEL_HIGH>;

ranges = <0x81000000 0 0xf9010000 0 0xf9010000 0 0x10000     /* non-relocatable I/O */
          0x82000000 0 0xf7000000 0 0xf7000000 0 0xf00000>;  /* non-relocatable MEM */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &cpm_icu ICU_GRP_NSR 24 IRQ_TYPE_LEVEL_HIGH>;

 

4) amd/amd-seattle-soc.dtsi
ranges = <0x01000000 0x00 0x00000000 0x00 0xefff0000 0x00 0x00010000>,   /* relocatable I/O (size=64K) */
         <0x02000000 0x00 0x40000000 0x00 0x40000000 0x00 0x80000000>,   /* relocatable MEM (size=2G) */
         <0x03000000 0x01 0x00000000 0x01 0x00000000 0x7f 0x00000000>;   /* relocatable MEM64 (size= 124G) */
interrupt-map-mask = <0xf800 0x0 0x0 0x7>;
interrupt-map = <0x1000 0x0 0x0 0x1 &gic0 0x0 0x0 0x0 0x120 0x1>,
                <0x1000 0x0 0x0 0x2 &gic0 0x0 0x0 0x0 0x121 0x1>,
                <0x1000 0x0 0x0 0x3 &gic0 0x0 0x0 0x0 0x122 0x1>,
                <0x1000 0x0 0x0 0x4 &gic0 0x0 0x0 0x0 0x123 0x1>;
dma-ranges = <0x43000000 0x0 0x0 0x0 0x0 0x100 0x0>;

 

5) freescale/fsl-ls1043a.dtsi
ranges = <0x81000000 0x0 0x00000000 0x40 0x00010000 0x0 0x00010000   /* non-relocatable I/O */
          0x82000000 0x0 0x40000000 0x40 0x40000000 0x0 0x40000000>; /* non-relocatable MEM */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = <0000 0 0 1 &gic 0 110 0x4>,
                <0000 0 0 2 &gic 0 111 0x4>,
                <0000 0 0 3 &gic 0 112 0x4>,
                <0000 0 0 4 &gic 0 113 0x4>;

 

6) renesas/r8a7795.dtsi
ranges = <0x01000000 0 0x00000000 0 0xfe100000 0 0x00100000    /* relocatable I/O */
          0x02000000 0 0xfe200000 0 0xfe200000 0 0x00200000    /* relocatable MEM */
          0x02000000 0 0x30000000 0 0x30000000 0 0x08000000    /* relocatable MEM */
          0x42000000 0 0x38000000 0 0x38000000 0 0x08000000>;  /* relocatable, pre-fetchable MEM */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;
dma-ranges = <0x42000000 0 0x40000000 0 0x40000000 0 0x40000000>;

 

7) broadcom/northstar2/ns2.dtsi
ranges = <0x83000000 0 0x00000000 0 0x30000000 0 0x20000000>;   /* non-relocatable MEM64 */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic 0 GIC_SPI 305 IRQ_TYPE_NONE>;

brcm,pcie-ob;
brcm,pcie-ob-oarr-size;
brcm,pcie-ob-axi-offset = <0x30000000>;
brcm,pcie-ob-window-size = <256>;

northstar2 전용 속성 4개를 사용하여 pci 주소를 axi 버스에 outbound 매핑 주소를 일부 변경할 수 있다.

  • pcie-ob
    • ns2의 PCIe Root Complex 의 디폴트 아웃바운드 매핑을 변경할 수 있게한다.
    • ns2 디폴트 Oubound 매핑:
      • PCI 주소: 0x0 ~ 0x1fffffff -> 호스트 CPU 물리 주소: 0x30000000 ~ 0x4fffffff (사이즈: 512MB)
  • pcie-ob-oarr-size
    • ns2의 PCIe Root Complex 의 디폴트 아웃바운드 매핑 사이즈를 변경할 수 있게한다.
    • ns2 디폴트 Oubound 매핑 사이즈: 512MB
  • pcie-ob-axi-offset
    • ns2의 PCIe Root Complex 의 호스트 CPU 물리 주소의 offset을 지정한다.
    • 예에서는 PCI 주소 0x0에 해당하는 AXI 물리 주소와의 간격(offset)은 0x30000000이다.
    • 참고로 ranges 속성 값 중 CPU 물리 주소에서 이 값을 뺀 차이 값이 OARR 레지스터에 기록한다.
      • CPU 물리 주소(0x30000000) – pcie-ob-axi-offset 속성값(0x30000000) = 0x0 (OARR 레지스터에 반영)
    • 참고로 ranges 속성 값 중 PCI 주소 값은 OMAP 레지스터에 기록한다.
      • PCI 주소 값(0x0)을 OMAP 레지스터에 반영
  • pcie-ob-window-size
    • ns2의 PCIe Root Complex가 아웃바운드 매핑을 변경할 때 사용할 사이즈를 지정한다.
      • 128, 256, 512 만 사용할 수 있다. (단위: MB)

 

다음 실제 사례를 참고한다.

  • NS2는 외부 PCIe 장치를 위해 PCIe Root Complex를 상황에 따라 기본 2개에서 부터 최대 8개로 변경할 수 있다.
    • A 포트 및 B 포트를 각각 1~4x 레인을 사용하는 1개의 RC로 구성할 수 있다. (디폴트)
    • A 포트 및 B 포트를 각각 1~2x 레인만 사용하는 2개의 RC로 구성할 수 있다.
    • A 포트 및 B 포트를 각각 1~2x 레인을 사용하는 1개의 RC와 1x 레인을 사용하는 2개의 RC로 구성할 수 있다.
    • A 포트 및 B 포트를 각각 1x 레인만 사용하는 4개의 RC로 구성할 수 있다.

 

아래 예는 B 포트를 1x 레인만을 사용하는 4개의 RC 구성으로 사용한 디바이스 트리 설정 사례를 보여준다.

  • 디폴트 Outbound 매핑은 다음과 같다. (1~4x 레인을 사용)
    • ranges = <0x83000000 0 0x0 0 0x30000000 0 0x20000000>;
    • PCI 주소: 0x0 ~ 0x1fffffff -> 호스트 CPU 물리 주소: 0x30000000 ~ 0x4fffffff (사이즈: 512MB)
  • B 포트를 4개의 RC로 사용하는 경우 아래 구성과 같이 128MB 씩 영역을 나누어 outbound 매핑할 수 있다.

 

실제 ns2의 Root Complex 각 포트의 주소 영역은 어떻게 제한되어 있을 까 알아본다.

  • 참고로 128M 단위로 영역을 나눈 사이즈를 지정하지만 실제 RC의 PCI 주소 영역은 20MB 및 28M의 영역으로 제한되어 있음을 알 수 있다.

 

8) cavium/thunder2-99xx.dtsi
ranges = <0x02000000    0 0x40000000    0 0x40000000    0 0x20000000       /* relocatable MEM */
          0x43000000 0x40 0x00000000 0x40 0x00000000 0x20 0x00000000>;     /* relocatable, pre-fetchable MEM64 */
interrupt-map-mask = <0 0 0 7>;
interrupt-map = /* addr  pin  ic   icaddr            icintr */
                  <0 0 0  1  &gic   0 0    GIC_SPI 0 IRQ_TYPE_LEVEL_HIGH
                   0 0 0  2  &gic   0 0    GIC_SPI 1 IRQ_TYPE_LEVEL_HIGH
                   0 0 0  3  &gic   0 0    GIC_SPI 2 IRQ_TYPE_LEVEL_HIGH
                   0 0 0  4  &gic   0 0    GIC_SPI 3 IRQ_TYPE_LEVEL_HIGH>;

 

9) nvidia/tegra132.dtsi
ranges = <0x82000000 0 0x01000000 0x0 0x01000000 0 0x00001000   /* non-relocatable MEM (port 0 cfg space) */
          0x82000000 0 0x01001000 0x0 0x01001000 0 0x00001000   /* non-relocatable MEM (port 1 cfg space) */
          0x81000000 0 0x0        0x0 0x12000000 0 0x00010000   /* non-relocatable I/O (64 KiB) */
          0x82000000 0 0x13000000 0x0 0x13000000 0 0x0d000000   /* non-relocatable MEM (208 MiB) */
          0xc2000000 0 0x20000000 0x0 0x20000000 0 0x20000000>; /* non-relocatable, pre-fetchable MEM (512 MiB) */
interrupt-map-mask = <0 0 0 0>;
interrupt-map = <0 0 0 0 &gic GIC_SPI 98 IRQ_TYPE_LEVEL_HIGH>;

pci@1,0 {
        assigned-addresses = <0x82000800 0 0x01000000 0 0x1000>; /* non-relocatable MEM (4K) */
        ...
};

pci@2,0 {
        assigned-addresses = <0x82001000 0 0x01001000 0 0x1000>; /* non-relocatable MEM (4K) */
        ... 
};

하위 버스에 대해 range를 부여할 수 있다.

 

참고

 

  • PCI Bus Binding to: IEEE Std 1275-1994 | www.devicetree.org – 다운로드 pdf
  • Open Firmware Recommended Practice: Interrupt Mapping | www.devicetree.org – 다운로드 pdf

PCI Subsystem -2- (Core)

<kernel v4.14>

 

PCI 서브시스템 초기화

pci_driver_init()

drivers/pci/pci-driver.c

static int __init pci_driver_init(void)
{
	return bus_register(&pci_bus_type);
}
postcore_initcall(pci_driver_init);

아래 pci 버스 타입을 등록한다.

 

drivers/pci/pci-driver.c

struct bus_type pci_bus_type = {
	.name		= "pci",
	.match		= pci_bus_match,
	.uevent		= pci_uevent,
	.probe		= pci_device_probe,
	.remove		= pci_device_remove,
	.shutdown	= pci_device_shutdown,
	.dev_groups	= pci_dev_groups,
	.bus_groups	= pci_bus_groups,
	.drv_groups	= pci_drv_groups,
	.pm		= PCI_PM_OPS_PTR,
	.num_vf		= pci_bus_num_vf,
};
EXPORT_SYMBOL(pci_bus_type);

 

PCI 디바이스 & 드라이버 매치

pci_bus_match()

drivers/pci/pci-driver.c

/**
 * pci_bus_match - Tell if a PCI device structure has a matching PCI device id structure
 * @dev: the PCI device structure to match against
 * @drv: the device driver to search for matching PCI device id structures
 *
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices. Returns the matching
 * pci_device_id structure or %NULL if there is no match.
 */
static int pci_bus_match(struct device *dev, struct device_driver *drv)
{
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct pci_driver *pci_drv;
	const struct pci_device_id *found_id;

	if (!pci_dev->match_driver)
		return 0;

	pci_drv = to_pci_driver(drv);
	found_id = pci_match_device(pci_drv, pci_dev);
	if (found_id)
		return 1;

	return 0;
}

pci 버스에 디바이스나 드라이버가 추가될 때 서로 매치여부를 확인하기 위한 함수이다.

 

pci_match_device()

drivers/pci/pci-driver.c

/**
 * pci_match_device - Tell if a PCI device structure has a matching PCI device id structure
 * @drv: the PCI driver to match against
 * @dev: the PCI device structure to match against
 *
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices.  Returns the matching
 * pci_device_id structure or %NULL if there is no match.
 */
static const struct pci_device_id *pci_match_device(struct pci_driver *drv,
						    struct pci_dev *dev)
{
	struct pci_dynid *dynid;
	const struct pci_device_id *found_id = NULL;

	/* When driver_override is set, only bind to the matching driver */
	if (dev->driver_override && strcmp(dev->driver_override, drv->name))
		return NULL;

	/* Look at the dynamic ids first, before the static ones */
	spin_lock(&drv->dynids.lock);
	list_for_each_entry(dynid, &drv->dynids.list, node) {
		if (pci_match_one_device(&dynid->id, dev)) {
			found_id = &dynid->id;
			break;
		}
	}
	spin_unlock(&drv->dynids.lock);

	if (!found_id)
		found_id = pci_match_id(drv->id_table, dev);

	/* driver_override will always match, send a dummy id */
	if (!found_id && dev->driver_override)
		found_id = &pci_device_id_any;

	return found_id;
}

pci 버스에 등록된 pci 디바이스와 드라이버를 서로 매치시켜 매치된 pci_device_id 포인터를 반환한다. 만일 매치되지 않은 경우 null을 반환한다.

  • 코드 라인 17~18에서 디바이스에 사용할 드라이버명을 지정한 경우이다. 디바이스에 오버드라이브된 드라이버명이 아닌경우 null을 반환한다.
  • 코드 라인 21~28에서 dynids 리스트에 등록된 디바이스들부터 먼저 매치 확인을 한다.
  • 코드 라인 30~31에서 매치되지 않은 경우 드라이버에 지정된 id_table에서 매치 확인을 한다.
  • 코드 라인 34~35에서 여전히 매치되지 않은 경우이면서 드라이버 오버라이드 설정이 있는 경우 pci_device_id_any를 반환한다.

 

매치 우선 순위

  1. 다이나믹 pci 디바이스
  2. id_table을 사용하는 스태틱 pci 디바이스
  3. 오버드라이브 지정된 경우

pci 디바이스에 드라이버명으로 지정하여 오버드라이브 설정한 경우 해당 드라이버만 매치될 수 있다.

 

pci_match_one_device()

drivers/pci/pci.h

/**
 * pci_match_one_device - Tell if a PCI device structure has a matching
 *                        PCI device id structure
 * @id: single PCI device id structure to match
 * @dev: the PCI device structure to match against
 *
 * Returns the matching pci_device_id structure or %NULL if there is no match.
 */
static inline const struct pci_device_id *
pci_match_one_device(const struct pci_device_id *id, const struct pci_dev *dev)
{
	if ((id->vendor == PCI_ANY_ID || id->vendor == dev->vendor) &&
	    (id->device == PCI_ANY_ID || id->device == dev->device) &&
	    (id->subvendor == PCI_ANY_ID || id->subvendor == dev->subsystem_vendor) &&
	    (id->subdevice == PCI_ANY_ID || id->subdevice == dev->subsystem_device) &&
	    !((id->class ^ dev->class) & id->class_mask))
		return id;
	return NULL;
}

다음 다음 매치 조건에 대해 모두 만족한 경우 pci_device_id를 그대로 반환하고, 매치되지 않은 경우 null을 반환한다.

  • 벤더명, 디바이스명, 서브벤더명 및 서브디바이스명을 지정하거나 PCI_ANY_ID를 기재한 경우 true이다.
  • 클래스도 동일해야 한다.

 

pci_match_id()

drivers/pci/pci-driver.c

/**
 * pci_match_id - See if a pci device matches a given pci_id table
 * @ids: array of PCI device id structures to search in
 * @dev: the PCI device structure to match against.
 *
 * Used by a driver to check whether a PCI device present in the
 * system is in its list of supported devices.  Returns the matching
 * pci_device_id structure or %NULL if there is no match.
 *
 * Deprecated, don't use this as it will not catch any dynamic ids
 * that a driver might want to check for.
 */
const struct pci_device_id *pci_match_id(const struct pci_device_id *ids,
					 struct pci_dev *dev)
{
	if (ids) {
		while (ids->vendor || ids->subvendor || ids->class_mask) {
			if (pci_match_one_device(ids, dev))
				return ids;
			ids++;
		}
	}
	return NULL;
}
EXPORT_SYMBOL(pci_match_id);

지정한 pci_device_id들 중 벤더, 서브벤더, 클래스 마스크 중 하나라도 지정된 경우 매치 여부를 확인한다. 매치가 확인되지 않는 경우 null을 반환한다.

 

PCI 드라이버 Probe

pci_device_probe()

drivers/pci/pci-driver.c

static int pci_device_probe(struct device *dev)
{
	int error;
	struct pci_dev *pci_dev = to_pci_dev(dev);
	struct pci_driver *drv = to_pci_driver(dev->driver);

	pci_assign_irq(pci_dev);

	error = pcibios_alloc_irq(pci_dev);
	if (error < 0)
		return error;

	pci_dev_get(pci_dev);
	if (pci_device_can_probe(pci_dev)) {
		error = __pci_device_probe(drv, pci_dev);
		if (error) {
			pcibios_free_irq(pci_dev);
			pci_dev_put(pci_dev);
		}
	}

	return error;
}

pci 디바이스의 legacy 인터럽트를 설정하고 probe를 진행한다.

  • 코드 라인 7에서 pci 디바이스가 사용할 legacy 인터럽트를 pci configuration 정보를 읽어 배정한다.
  • 코드 라인 9~11에서 pci 디바이스가 사용할 인터럽트를 ACPI를 사용하는 pc로부터 알아와 배정한다.
  • 코드 라인 13~20에서 pci 디바이스의 참조 카운터를 1 증가시킨 후 probe를 진행한다.

 

pci_device_can_probe()

drivers/pci/pci-driver.c

#ifdef CONFIG_PCI_IOV
static inline bool pci_device_can_probe(struct pci_dev *pdev)
{
        return (!pdev->is_virtfn || pdev->physfn->sriov->drivers_autoprobe);
}
#else
static inline bool pci_device_can_probe(struct pci_dev *pdev)
{
        return true;
}
#endif

pci_device_can_probe() 함수는 PCI IO Virtualization을 사용하지 않는 경우 항상 1이다.

  • PCI IO Virtualization을 사용하는 경우 drivers_autoprobe가 설정된 경우에만 1을 반환한다.

 

__pci_device_probe()

drivers/pci/pci-driver.c

/**
 * __pci_device_probe - check if a driver wants to claim a specific PCI device
 * @drv: driver to call to check if it wants the PCI device
 * @pci_dev: PCI device being probed
 *
 * returns 0 on success, else error.
 * side-effect: pci_dev->driver is set to drv when drv claims pci_dev.
 */
static int __pci_device_probe(struct pci_driver *drv, struct pci_dev *pci_dev)
{
	const struct pci_device_id *id;
	int error = 0;

	if (!pci_dev->driver && drv->probe) {
		error = -ENODEV;

		id = pci_match_device(drv, pci_dev);
		if (id)
			error = pci_call_probe(drv, pci_dev, id);
	}
	return error;
}

pci 디바이스에 대해 드라이버가 아직 지정되지 않았고 드라이버의 probe 후크 함수가 있으면 매치 id를 알아온 후 probe를 진행한다.

  • 드라이버가 이미 지정되어 있는 경우 매치나 probe 과정을 진행하지 않고 성공(0)을 반환한다.

 

pci_call_probe()

drivers/pci/pci-driver.c

static int pci_call_probe(struct pci_driver *drv, struct pci_dev *dev,
			  const struct pci_device_id *id)
{
	int error, node, cpu;
	struct drv_dev_and_id ddi = { drv, dev, id };

	/*
	 * Execute driver initialization on node where the device is
	 * attached.  This way the driver likely allocates its local memory
	 * on the right node.
	 */
	node = dev_to_node(&dev->dev);
	dev->is_probed = 1;

	cpu_hotplug_disable();

	/*
	 * Prevent nesting work_on_cpu() for the case where a Virtual Function
	 * device is probed from work_on_cpu() of the Physical device.
	 */
	if (node < 0 || node >= MAX_NUMNODES || !node_online(node) ||
	    pci_physfn_is_probed(dev))
		cpu = nr_cpu_ids;
	else
		cpu = cpumask_any_and(cpumask_of_node(node), cpu_online_mask);

	if (cpu < nr_cpu_ids)
		error = work_on_cpu(cpu, local_pci_probe, &ddi);
	else
		error = local_pci_probe(&ddi);

	dev->is_probed = 0;
	cpu_hotplug_enable();
	return error;
}

pci 디바이스가 있는 노드의 cpu의 워크큐를 사용하여 pci 드라이버의 probe를 호출한다.

  • 코드 라인 12에서 pci 디바이스에 해당하는 노드 번호를 알아온다.
  • 코드 라인 13에서 pci 디바이스가 probe 진행 중임을 마크한다.
  • 코드 라인 15에서 다른 cpu hotplug 작업이 수행 중인 경우 완료될 때까지 기다린 후 락을 획득한다.
  • 코드 라인 21~25에서 노드에서 동작하는 online cpu 중 하나의 cpu 번호를 알아온다. 만일 pci io virtualization이 동작하는 경우 현재 cpu를 사용한다.
  • 코드 라인 27~30에서 해당 cpu의 워크큐에서 local_pci_probe 함수를 호출하게 한다.
  • 코드 라인 32에서 pci 디바이스의 probe 진행이 완료되었으므로 클리어한다.
  • 코드 라인 33에서 cpu hotplug 락을 해제한다.

 

local_pci_probe()

drivers/pci/pci-driver.c

static long local_pci_probe(void *_ddi)
{
	struct drv_dev_and_id *ddi = _ddi;
	struct pci_dev *pci_dev = ddi->dev;
	struct pci_driver *pci_drv = ddi->drv;
	struct device *dev = &pci_dev->dev;
	int rc;

	/*
	 * Unbound PCI devices are always put in D0, regardless of
	 * runtime PM status.  During probe, the device is set to
	 * active and the usage count is incremented.  If the driver
	 * supports runtime PM, it should call pm_runtime_put_noidle(),
	 * or any other runtime PM helper function decrementing the usage
	 * count, in its probe routine and pm_runtime_get_noresume() in
	 * its remove routine.
	 */
	pm_runtime_get_sync(dev);
	pci_dev->driver = pci_drv;
	rc = pci_drv->probe(pci_dev, ddi->id);
	if (!rc)
		return rc;
	if (rc < 0) {
		pci_dev->driver = NULL;
		pm_runtime_put_sync(dev);
		return rc;
	}
	/*
	 * Probe function should return < 0 for failure, 0 for success
	 * Treat values > 0 as success, but warn.
	 */
	dev_warn(dev, "Driver probe function unexpectedly returned %d\n", rc);
	return 0;
}

pci 디바이스를 절전 모드에서 복구시킨 후 pm 참조 카운터를 증가시킨다. 그런 후 해당 pci 드라이버의 probe 함수를 호출한다.

 

Legacy IRQ 할당

pci_assign_irq()

drivers/pci/setup-irq.c

void pci_assign_irq(struct pci_dev *dev)
{
        u8 pin;
        u8 slot = -1;
        int irq = 0;
        struct pci_host_bridge *hbrg = pci_find_host_bridge(dev->bus);

        if (!(hbrg->map_irq)) {
                dev_dbg(&dev->dev, "runtime IRQ mapping not provided by arch\n");
                return;
        }

        /* If this device is not on the primary bus, we need to figure out
           which interrupt pin it will come in on.   We know which slot it
           will come in on 'cos that slot is where the bridge is.   Each
           time the interrupt line passes through a PCI-PCI bridge we must
           apply the swizzle function.  */

        pci_read_config_byte(dev, PCI_INTERRUPT_PIN, &pin);
        /* Cope with illegal. */
        if (pin > 4)
                pin = 1;

        if (pin) {
                /* Follow the chain of bridges, swizzling as we go.  */
                if (hbrg->swizzle_irq)
                        slot = (*(hbrg->swizzle_irq))(dev, &pin);

                /*
                 * If a swizzling function is not used map_irq must
                 * ignore slot
                 */
                irq = (*(hbrg->map_irq))(dev, slot, pin);
                if (irq == -1)
                        irq = 0;
        }
        dev->irq = irq;

        dev_dbg(&dev->dev, "assign IRQ: got %d\n", dev->irq);

        /* Always tell the device, so the driver knows what is
           the real IRQ to use; the device does not use it. */
        pci_write_config_byte(dev, PCI_INTERRUPT_LINE, irq);
}

pci 디바이스가 사용할 legacy 인터럽트를 pci configuration 정보를 읽어 배정한다. 자세한 동작은 다음과 같다.

  • 코드 라인 6에서 pci 버스에 대한 호스트 브리지를 구해온다.
  • 코드 라인 19~22에서 pci 디바이스의 기본 configuration 헤더 정보에서 인터럽트 핀(1~4)을 읽어온다. 단 인터럽트를 사용하지 않는 pci 디바이스는 0을 읽어온다.
    • PCI_INTERRUPT_PIN(0x3d)
  • 코드 라인 26~27에서 소스트 브리지에 구현된 (*swizzle_irq) 후크 함수를 통해 슬롯 번호를 알아온다.
  • 코드 라인 33~35에서 읽어온 핀 번호에 대해 호스트 브리지에 구현된 (*map_irq) 후크 함수를 통해 슬롯과 핀 인자를 전달하고 ACPI 또는 디바이스 트리의 hwirq를 읽어온 후 이에 매핑된 virq를 알아온다.
  • 코드 라인 37~39에서 dev->irq에 설정하고 로그 메시지를 출력한다.
  • 코드 라인 43에서 pci 디바이스의 기본 configuratio 헤더 정보 중 인터럽트 라인에 인터럽트 번호를 기록한다.
    • PCI_INTERRUPT_LINE(0x3c)

 

pci 드라이버 등록

pci_register_driver()

include/linux/pci.h

/*
 * pci_register_driver must be a macro so that KBUILD_MODNAME can be expanded
 */
#define pci_register_driver(driver)		\
	__pci_register_driver(driver, THIS_MODULE, KBUILD_MODNAME)

pci 드라이버의 등록은 위의 매크로 함수를 통해 등록할 수 있다.

 

__pci_register_driver()

drivers/pci/pci-driver.c

/**
 * __pci_register_driver - register a new pci driver
 * @drv: the driver structure to register
 * @owner: owner module of drv
 * @mod_name: module name string
 *
 * Adds the driver structure to the list of registered drivers.
 * Returns a negative value on error, otherwise 0.
 * If no error occurred, the driver remains registered even if
 * no device was claimed during registration.
 */
int __pci_register_driver(struct pci_driver *drv, struct module *owner,
                          const char *mod_name)
{
        /* initialize common driver fields */
        drv->driver.name = drv->name;
        drv->driver.bus = &pci_bus_type;
        drv->driver.owner = owner;
        drv->driver.mod_name = mod_name;
        drv->driver.groups = drv->groups;

        spin_lock_init(&drv->dynids.lock);
        INIT_LIST_HEAD(&drv->dynids.list);

        /* register with core */
        return driver_register(&drv->driver);
}
EXPORT_SYMBOL(__pci_register_driver);

pci 버스에 요청한 pci 드라이버를 등록한다.

  • 다이나믹하게 pci 디바이스의 id를 관리하기 위해 dynids를 사용한다.

 

pci_driver 구조체

pci 드라이버를 등록할 때 사용하는 구조체이다.

include/linux/pci.h

struct pci_driver {
	struct list_head node;
	const char *name;
	const struct pci_device_id *id_table;	/* must be non-NULL for probe to be called */
	int  (*probe)  (struct pci_dev *dev, const struct pci_device_id *id);	/* New device inserted */
	void (*remove) (struct pci_dev *dev);	/* Device removed (NULL if not a hot-plug capable driver) */
	int  (*suspend) (struct pci_dev *dev, pm_message_t state);	/* Device suspended */
	int  (*suspend_late) (struct pci_dev *dev, pm_message_t state);
	int  (*resume_early) (struct pci_dev *dev);
	int  (*resume) (struct pci_dev *dev);	                /* Device woken up */
	void (*shutdown) (struct pci_dev *dev);
	int (*sriov_configure) (struct pci_dev *dev, int num_vfs); /* PF pdev */
	const struct pci_error_handlers *err_handler;
	const struct attribute_group **groups;
	struct device_driver	driver;
	struct pci_dynids dynids;
};
  • *name
    • 드라이버 이름
  • *id_table
    • 매치하여 사용할 pci 디바이스의 id 테이블을 지정한다.
  • (*probe)
    • 새로운 디바이스가 추가될 때 probe 후크 함수를 지정한다.
  • (*remove)
    • 디바이스가 제거될 때 호출할 후크 함수를 지정한다.
  • (*suspend),
  • (*suspend_late),
  • (*resume_early),
  • (*resume),
  • (*shutdown)
    • 절전 관련 후크 함수들이며 legacy pci 드라이버 호환을 위해 사용된다.
    • 최근 인터페이스는 pci 드라이버에 임베드 되어 있는 driver->pm을 사용한다.
  • (*sriov_configure)
    • 동작 시킬 VF(가상 기능) 수를 지정할 수 있는 싱글 루트 I/O 가상화 설정용 후크 함수이다.
  • *err_handler
    • pci 버스 에러 이벤트를 처리할 콜백들이 담긴 pci_error_handlers 구조체를 지정한다.
  • **groups
    • 드라이버용 속성 그룹 배열을 지정한다.
  • driver
    • 임베드된 드라이버이다.
  • dynids
    • 다이나믹 id를 관리할 리스트이다.

 

PCI 장치 스캔

루트 버스 브리지 스캔

pci_scan_root_bus_bridge()

drivers/pci/probe.c

int pci_scan_root_bus_bridge(struct pci_host_bridge *bridge)
{
        struct resource_entry *window;
        bool found = false;
        struct pci_bus *b;
        int max, bus, ret;

        if (!bridge)
                return -EINVAL;

        resource_list_for_each_entry(window, &bridge->windows)
                if (window->res->flags & IORESOURCE_BUS) {
                        found = true;
                        break;
                }

        ret = pci_register_host_bridge(bridge);
        if (ret < 0)
                return ret;

        b = bridge->bus;
        bus = bridge->busnr;

        if (!found) {
                dev_info(&b->dev,
                 "No busn resource found for root bus, will use [bus %02x-ff]\n",
                        bus);
                pci_bus_insert_busn_res(b, bus, 255);
        }

        max = pci_scan_child_bus(b);

        if (!found)
                pci_bus_update_busn_res_end(b, max);

        return 0;
}
EXPORT_SYMBOL(pci_scan_root_bus_bridge);

루트 버스 브리지를 스캔한다.

  • 코드 라인 8~9에서 브릿지가 null인 경우 -EINVAL 에러를 반환한다.
  • 코드 라인 11~15에서 플랫폼 리소스 중 bus 리소스가 있는 경우에만 found 값을 true로 설정하고 루프를 벗어난다.
  • 코드 라인 17~19에서 호스트 브릿지를 등록한다.
  • 코드 라인 21~29에서 버스 리소스가 발견되지 않은 경우 인자로 요청한 버스의 버스 번호 부터 0xff 까지의 버스 번호를 사용하도록 버스 리소스를 생성하여 추가한다.
    • 만일 처음 호스트 브릿지가 등록되는 경우라면 버스 번호 0번 부터 255번까지 리소스가 등록된다.
  • 코드 라인 31에서 child 버스를 스캔한다.
  • 코드 라인 33~34에서 처음 버스 리소스가 발견되지 않아 생성하여 추가한 경우 child 버스의 버스 번호까지 버스 리소스를 갱신한다.

 

차일드 버스 스캔

pci_scan_child_bus()

drivers/pci/probe.c

unsigned int pci_scan_child_bus(struct pci_bus *bus)
{
        unsigned int devfn, pass, max = bus->busn_res.start;
        struct pci_dev *dev;

        dev_dbg(&bus->dev, "scanning bus\n");

        /* Go find them, Rover! */
        for (devfn = 0; devfn < 0x100; devfn += 8)
                pci_scan_slot(bus, devfn);

        /* Reserve buses for SR-IOV capability. */
        max += pci_iov_bus_range(bus);

        /*
         * After performing arch-dependent fixup of the bus, look behind
         * all PCI-to-PCI bridges on this bus.
         */
        if (!bus->is_added) {
                dev_dbg(&bus->dev, "fixups for bus\n");
                pcibios_fixup_bus(bus);
                bus->is_added = 1;
        }

        for (pass = 0; pass < 2; pass++)
                list_for_each_entry(dev, &bus->devices, bus_list) {
                        if (pci_is_bridge(dev))
                                max = pci_scan_bridge(bus, dev, max, pass);
                }

        /*
         * Make sure a hotplug bridge has at least the minimum requested
         * number of buses.
         */
        if (bus->self && bus->self->is_hotplug_bridge && pci_hotplug_bus_size) {
                if (max - bus->busn_res.start < pci_hotplug_bus_size - 1)
                        max = bus->busn_res.start + pci_hotplug_bus_size - 1;

                /* Do not allocate more buses than we have room left */
                if (max > bus->busn_res.end)
                        max = bus->busn_res.end;
        }

        /*
         * We've scanned the bus and so we know all about what's on
         * the other side of any bridges that may be on this bus plus
         * any devices.
         *
         * Return how far we've got finding sub-buses.
         */
        dev_dbg(&bus->dev, "bus scan returning with max=%02x\n", max);
        return max;
}
EXPORT_SYMBOL_GPL(pci_scan_child_bus);

child pci 버스를 스캔한다.

  • 코드 라인 9~10에서 전체 슬롯을 스캔한다.
  • 코드 라인 13에서 virtual function이 사용하는 버스 번호를 reserve하기 위해 max 값을 그 만큼 건너뛴다.
  • 코드 라인 19~23에서 버스가 처음 추가된 경우 pcibios fixup을 수행한다.
    • 아키텍처마다 코드가 준비되어 있다.
      • arm의 경우 arm/kernel/bios32.c – pcibios_fixup_bus() 함수를 사용한다.
      • arm64의 경우 fixup 코드를 수행하지 않는다.
  • 코드 라인 25~29에서 현재 버스에 등록된 모든 pci 브리지 디바이스를 대상으로 2번씩 스캔한다.
  • 코드 라인 35~42에서 hot plug 버스를 위해 버스 번호를 남겨둔다.

 

PCI 슬롯 스캔

pci_scan_slot()

drivers/pci/probe.c

/**
 * pci_scan_slot - scan a PCI slot on a bus for devices.
 * @bus: PCI bus to scan
 * @devfn: slot number to scan (must have zero function.)
 *
 * Scan a PCI slot on the specified PCI bus for devices, adding
 * discovered devices to the @bus->devices list.  New devices
 * will not have is_added set.
 *
 * Returns the number of new devices found.
 */
int pci_scan_slot(struct pci_bus *bus, int devfn)
{
        unsigned fn, nr = 0;
        struct pci_dev *dev;

        if (only_one_child(bus) && (devfn > 0))
                return 0; /* Already scanned the entire slot */

        dev = pci_scan_single_device(bus, devfn);
        if (!dev)
                return 0;
        if (!dev->is_added)
                nr++;

        for (fn = next_fn(bus, dev, 0); fn > 0; fn = next_fn(bus, dev, fn)) {
                dev = pci_scan_single_device(bus, devfn + fn);
                if (dev) {
                        if (!dev->is_added)
                                nr++;
                        dev->multifunction = 1;
                }
        }

        /* only one slot has pcie device */
        if (bus->self && nr)
                pcie_aspm_init_link_state(bus->self);

        return nr;
}
EXPORT_SYMBOL(pci_scan_slot);

pci 슬롯을 스캔한다.

  • 코드 라인 17~18에서 pcie 포트에 이미 디바이스가 연결된 경우 또는 스캔하지 못하도록 한 경우 0을 반환한다.
  • 코드 라인 20~22에서 하나의 디바이스를 스캔한다. 스캔된 디바이스가 없으면 0을 반환한다.
  • 코드 라인 23~24에서 이미 추가된 디바이스의 경우가 아니면 nr을 증가시킨다.
  • 코드 라인 26~33에서 다음 펑션에 대한 하나의 디바이스를 스캔한다. 디바이스가 추가된 경우 multifunction 설정을 한다.
  • 코드 라인 36~37에서 PCIe 루트 포트 디바이스에 디바이스가 추가된 경우 link state를 초기화한다.

 

싱글 디바이스 스캔

pci_scan_single_device()

drivers/pci/probe.c

struct pci_dev *pci_scan_single_device(struct pci_bus *bus, int devfn)
{
        struct pci_dev *dev;

        dev = pci_get_slot(bus, devfn);
        if (dev) {
                pci_dev_put(dev);
                return dev;
        }

        dev = pci_scan_device(bus, devfn);
        if (!dev)
                return NULL;

        pci_device_add(dev, bus);

        return dev;
}
EXPORT_SYMBOL(pci_scan_single_device);

하나의 디바이스를 스캔하여 pci 디바이스로 추가한다.

  • 코드 라인 5~9에서 버스에 등록된 디바이스들 중 devfn에 해당하는 디바이스를 가져온다.
  • 코드 라인 11~13에서 해당 devfn 디바이스를 스캔한다.
  • 코드 라인 15에서 스캔 성공한 디바이스를 pci 디바이스로 추가한다.

 

PCI 디바이스 스캔

pci_scan_device()

drivers/pci/probe.c

/*
 * Read the config data for a PCI device, sanity-check it
 * and fill in the dev structure...
 */
static struct pci_dev *pci_scan_device(struct pci_bus *bus, int devfn)
{
        struct pci_dev *dev;
        u32 l;

        if (!pci_bus_read_dev_vendor_id(bus, devfn, &l, 60*1000))
                return NULL;

        dev = pci_alloc_dev(bus);
        if (!dev)
                return NULL;

        dev->devfn = devfn;
        dev->vendor = l & 0xffff;
        dev->device = (l >> 16) & 0xffff;

        pci_set_of_node(dev);

        if (pci_setup_device(dev)) {
                pci_bus_put(dev->bus);
                kfree(dev);
                return NULL;
        }

        return dev;
}

하나의 devfn에 해당하는 pci 디바이스를 스캔한 후 셋업한다. 실패한 경우 null을 반환한다.

  • 코드 라인 10~11에서 최대 60초 이내에 요청한 버스 디바이스 펑션 번호의 pci 디바이스의 디바이스 id와 벤더 id를 읽어온다.
  • 코드 라인 13~15에서 pci 디바이스를 할당한다. 그리고 타입에 pci_dev_type을 대입하고 버스 참조 카운터를 1 증가시킨다.
  • 코드 라인 17~19에서 pci 디바이스에 devfn을 지정하고 알아온 16비트 벤더와 디바이스 id를 대입한다.
  • 코드 라인 21에서 디바이스가 소속된 호스트 컨트롤러의 디바이스 트리의 자식 노드에서 해당 디바이스에  대한 노드를 검색 후 of_node에 대입한다.
    • “reg” 속성 값의 bits[15:8] 값이 디바이스(슬롯) 및 펑션 번호이다.
  • 코드 라인 23~27에서 configuration space 헤더 정보를 읽어 pci 디바이스를 셋업한다.

 

pci_bus_read_dev_vendor_id()

drivers/pci/probe.c

bool pci_bus_read_dev_vendor_id(struct pci_bus *bus, int devfn, u32 *l,
                                int timeout)
{
        if (pci_bus_read_config_dword(bus, devfn, PCI_VENDOR_ID, l))
                return false;

        /* some broken boards return 0 or ~0 if a slot is empty: */
        if (*l == 0xffffffff || *l == 0x00000000 ||
            *l == 0x0000ffff || *l == 0xffff0000)
                return false;

        if (pci_bus_crs_vendor_id(*l))
                return pci_bus_wait_crs(bus, devfn, l, timeout);

        return true;
}
EXPORT_SYMBOL(pci_bus_read_dev_vendor_id);

요청한 버스 디바이스 펑션의 configuration space에서 디바이스 id와 벤더 id를 읽어 인자 l에 출력하고 true를 반환한다.

  • 코드 라인 4~5에서 요청한 버스 디바이스 펑션의 configuration space에서 벤더 id를 읽어 인자 l에 알아온다. 만일 읽어올 수 없으면 false를 반환한다.
  • 코드 라인 8~10에서 슬롯이 비어있는 경우 false를 반환한다.
  • 코드 라인 12~13에서 벤더 id를 읽을 때 지연될 수 있다. 그러한 경우 timeout 범위 내에서 1초 부터 2배 단위로 지연 후 다시 읽기를 시도한다.

 

PCI 디바이스 셋업

pci_setup_device()

drivers/pci/probe.c -1/2-

/**
 * pci_setup_device - fill in class and map information of a device
 * @dev: the device structure to fill
 *
 * Initialize the device structure with information about the device's
 * vendor,class,memory and IO-space addresses,IRQ lines etc.
 * Called at initialisation of the PCI subsystem and by CardBus services.
 * Returns 0 on success and negative if unknown type of device (not normal,
 * bridge or CardBus).
 */
int pci_setup_device(struct pci_dev *dev)
{
        u32 class;
        u16 cmd;
        u8 hdr_type;
        int pos = 0;
        struct pci_bus_region region;
        struct resource *res;

        if (pci_read_config_byte(dev, PCI_HEADER_TYPE, &hdr_type))
                return -EIO;

        dev->sysdata = dev->bus->sysdata;
        dev->dev.parent = dev->bus->bridge;
        dev->dev.bus = &pci_bus_type;
        dev->hdr_type = hdr_type & 0x7f;
        dev->multifunction = !!(hdr_type & 0x80);
        dev->error_state = pci_channel_io_normal;
        set_pcie_port_type(dev);

        pci_dev_assign_slot(dev);
        /* Assume 32-bit PCI; let 64-bit PCI cards (which are far rarer)
           set this higher, assuming the system even supports it.  */
        dev->dma_mask = 0xffffffff;

        dev_set_name(&dev->dev, "%04x:%02x:%02x.%d", pci_domain_nr(dev->bus),
                     dev->bus->number, PCI_SLOT(dev->devfn),
                     PCI_FUNC(dev->devfn));

        pci_read_config_dword(dev, PCI_CLASS_REVISION, &class);
        dev->revision = class & 0xff;
        dev->class = class >> 8;                    /* upper 3 bytes */

        dev_printk(KERN_DEBUG, &dev->dev, "[%04x:%04x] type %02x class %#08x\n",
                   dev->vendor, dev->device, dev->hdr_type, dev->class);

        /* need to have dev->class ready */
        dev->cfg_size = pci_cfg_space_size(dev);

        /* need to have dev->cfg_size ready */
        set_pcie_thunderbolt(dev);

        /* "Unknown power state" */
        dev->current_state = PCI_UNKNOWN;

        /* Early fixups, before probing the BARs */
        pci_fixup_device(pci_fixup_early, dev);
        /* device class may be changed after fixup */
        class = dev->class >> 8;

        if (dev->non_compliant_bars) {
                pci_read_config_word(dev, PCI_COMMAND, &cmd);
                if (cmd & (PCI_COMMAND_IO | PCI_COMMAND_MEMORY)) {
                        dev_info(&dev->dev, "device has non-compliant BARs; disabling IO/MEM decoding\n");
                        cmd &= ~PCI_COMMAND_IO;
                        cmd &= ~PCI_COMMAND_MEMORY;
                        pci_write_config_word(dev, PCI_COMMAND, cmd);
                }
        }

        dev->broken_intx_masking = pci_intx_mask_broken(dev);

configuration space header 정보를 읽어 pci_dev 정보를 구성한다.

  • 코드 라인 20~21에서 디바이스의 pci configuration space에서 헤더 타입을 읽어온다.
  • 코드 라인 23~28에서 pci 디바이스의 정보를 채운다.
    • 헤더 타입의 bit15가 1인 경우 mmultifunction 디바이스이다.
  • 코드 라인 29에서 pcie 디바이스 정보를 설정한다.
    • configuration space에서 pcie capability를 읽어 pcie_cap, pcie_flags_reg, pcie_mpss, has_secondary_link 정보를 알아온다.
  • 코드 라인 31에서 버스내 슬롯 번호를 지정한다.
  • 코드 라인 34에서 32bit PCI의 dma 영역을 모두 허용한다.
  • 코드 라인 36~38에서 디바이스 명을 지정한다.
    • “<4자리 도메인번호>:<2자리 버스 번호>:<2자리 슬롯번호>.<1자리 펑션 번호>”
  • 코드 라인 40~45에서 configuration space에서 class_revision에 해당하는 더블 워드(4 byte) 값을 읽어 하위 8비트를 디바이스의 revision 값에 대입하고, 나머지 값은 class에 대입한다. 그런 후 이 정보를 디버그 출력한다.
  • 코드 라인 48에서 configaration space의 사이즈를 알아온다.
    • pci: 기본 사이즈 256
    • pcie: 기본 사이즈 256, 확장 정보인 경우 4096
    • pcix: 기본 사이즈 256, 266M 또는 533Mhz인 경우 확장 정보를 지원하여 4096
  • 코드 라인 51에서 PCI_EXT_CAP_ID_VNDR cap에서 벤더가 인텔이고, 벤더 헤더 id가 썬더볼트인 경우 is_thunderbolt를 1로 설정한다.
  • 코드 라인 55에서처음 pm 상태를 unknown으로 설정한다.
  • 코드 라인 58~60에서 부팅 시에 동작시킬 pci fixup 후크가 있는 경우 수행한다.
    • DECLARE_PCI_FIXUP_EARLY() 매크로 함수로 지정한다.
    • pci_fixup 구조체를 생성한 후 .pci_fixup_early 섹션에 저장한다.
    • 특정 아키텍처 및 드라이버 루틴에 존재한다.
      • arm64 아키텍처에는 존재하지 않고 arm 아키텍처에는 PCI_VENDOR_ID_PLX 벤더에서 3개의 디바이스에 대해 fixup_early 루틴이 있다.
      • drivers/pci/quirks.c에 특정 벤더와 디바이스에 대한 fixup_early 루틴이 존재 한다.
        • 예) PCI_VENDOR_ID_BROADCOM, 0x16cd 와 0x16f0 디바이스인 경우 quirk_paxc_bridge() 후크 함수를 호출한다.
  • 코드 라인 62~70에서 host/pcie-tango.c 드라이버에서 fake BAR를 사용하는 경우만 적용되는 코드이다. 생략.
  • 코드 라인 72에서 pci command 레지스터에 INTX_DISABLE(bit10) 비트 기록 테스트를 하여 변경 불가능 여부를 알아온다.

 

drivers/pci/probe.c -2/2-

        switch (dev->hdr_type) {                    /* header type */
        case PCI_HEADER_TYPE_NORMAL:                /* standard header */
                if (class == PCI_CLASS_BRIDGE_PCI)
                        goto bad;
                pci_read_irq(dev);
                pci_read_bases(dev, 6, PCI_ROM_ADDRESS);
                pci_read_config_word(dev, PCI_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
                pci_read_config_word(dev, PCI_SUBSYSTEM_ID, &dev->subsystem_device);

                /*
                 * Do the ugly legacy mode stuff here rather than broken chip
                 * quirk code. Legacy mode ATA controllers have fixed
                 * addresses. These are not always echoed in BAR0-3, and
                 * BAR0-3 in a few cases contain junk!
                 */
                if (class == PCI_CLASS_STORAGE_IDE) {
                        u8 progif;
                        pci_read_config_byte(dev, PCI_CLASS_PROG, &progif);
                        if ((progif & 1) == 0) {
                                region.start = 0x1F0;
                                region.end = 0x1F7;
                                res = &dev->resource[0];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x10: %pR\n",
                                         res);
                                region.start = 0x3F6;
                                region.end = 0x3F6;
                                res = &dev->resource[1];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x14: %pR\n",
                                         res);
                        }
                        if ((progif & 4) == 0) {
                                region.start = 0x170;
                                region.end = 0x177;
                                res = &dev->resource[2];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x18: %pR\n",
                                         res);
                                region.start = 0x376;
                                region.end = 0x376;
                                res = &dev->resource[3];
                                res->flags = LEGACY_IO_RESOURCE;
                                pcibios_bus_to_resource(dev->bus, res, &region);
                                dev_info(&dev->dev, "legacy IDE quirk: reg 0x1c: %pR\n",
                                         res);
                        }
                }
                break;

        case PCI_HEADER_TYPE_BRIDGE:                /* bridge header */
                if (class != PCI_CLASS_BRIDGE_PCI)
                        goto bad;
                /* The PCI-to-PCI bridge spec requires that subtractive
                   decoding (i.e. transparent) bridge must have programming
                   interface code of 0x01. */
                pci_read_irq(dev);
                dev->transparent = ((dev->class & 0xff) == 1);
                pci_read_bases(dev, 2, PCI_ROM_ADDRESS1);
                set_pcie_hotplug_bridge(dev);
                pos = pci_find_capability(dev, PCI_CAP_ID_SSVID);
                if (pos) {
                        pci_read_config_word(dev, pos + PCI_SSVID_VENDOR_ID, &dev->subsystem_vendor);
                        pci_read_config_word(dev, pos + PCI_SSVID_DEVICE_ID, &dev->subsystem_device);
                }
                break;

        case PCI_HEADER_TYPE_CARDBUS:               /* CardBus bridge header */
                if (class != PCI_CLASS_BRIDGE_CARDBUS)
                        goto bad;
                pci_read_irq(dev);
                pci_read_bases(dev, 1, 0);
                pci_read_config_word(dev, PCI_CB_SUBSYSTEM_VENDOR_ID, &dev->subsystem_vendor);
                pci_read_config_word(dev, PCI_CB_SUBSYSTEM_ID, &dev->subsystem_device);
                break;

        default:                                    /* unknown header */
                dev_err(&dev->dev, "unknown header type %02x, ignoring device\n",
                        dev->hdr_type);
                return -EIO;

        bad:
                dev_err(&dev->dev, "ignoring class %#08x (doesn't match header type %02x)\n",
                        dev->class, dev->hdr_type);
                dev->class = PCI_CLASS_NOT_DEFINED << 8;
        }

        /* We found a fine healthy device, go go go... */
        return 0;
}
  • 코드 라인 1~8에서 pci 디바이스의 헤더 타입이 normal 디바이스인 경우이다.
    • irq 라인과(0~5) irq 번호를 읽고,
    • BAR0~5 주소와 BAR6-ROM 주소를 읽어온다.
    • 서브시스템 벤더 id와 디바이스 id를 읽어온다.
  • 코드 라인 16~52에서 ide 타입 스토리지인 경우에 대한 코드는 생략
  • 코드 라인 54~69에서 pci 디바이스의 헤더 타입이 브리지인 경우이다.
    • irq 라인과(0~5) irq 번호를 읽고,
    • BAR0~1 주소와 BAR6-ROM 주소를 읽어온다. 핫 플러그 지원 여부를 읽어 is_hotplug_bridge에 대입한다.
    • 서브시스템 벤터 cap이 존재하는 경우 이데 대한 서브시스템 벤더 id와 디바이스 id를 읽어온다.
  • 코드 라인 71~78에서 pci 디바이스의 헤더 타입이 카드버스인 경우이다.
    • irq 라인과(0~5) irq 번호를 읽고,
    • BAR0 주소와 BAR6-ROM 주소를 읽어온다. 핫 플러그 지원 여부를 읽어 is_hotplug_bridge에 대입한다.
    • 서브시스템 벤터 cap이 존재하는 경우 이데 대한 서브시스템 벤더 id와 디바이스 id를 읽어온다.

 

BAR(Base Address Register) Setup

PCI 호스트 컨트롤러가 버스를 스캔하여 PCI 디바이스를 검출한 경우 PCI 디바이스를 셋업하는데 이 과정에서 PCI 디바이스의 BAR 레지스터들을 읽어 PCI 디바이스가 가지고 있는 IO 또는 메모리의 사이즈와 타입을 읽은 후 호스트 CPU의 물리 메모리 주소로 기록한다.

 

Mem32 Type BAR

PCI 디바이스의 BAR 값은 공장 출고 시 BAR 레지스터의 일부를 사이즈와 타입 정보를 기록하고 Read Only로 제공한다.

  • Size의 표기는 BAR 레지스터의 모든 비트를 1로 기록한 후 타입 4비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
    • 아래 bit26에서 1이 발견되었으므로 사이즈는 0x400_0000 (64MB) 임을 알아낼 수 있다.
  • 마지막으로 디바이스 트리의 ranges 에서 지정하는 메모리 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
    • 예) ranges = <0x82000000 0 0 0 0x80000000 0 0x400_0000>;
      • 타입: Pre-fetchable Mem32
      • pci 주소: 0x0
      • 호스트 cpu 물리 주소: 0x80000000
      • 사이즈: 0x4000000 (64MB)

 

Mem64 Type BAR

다음 그림은 2개의 BAR를 사용하여 64비트 메모리 주소를 알아오는 모습을 보여준다.

  • Size의 표기는 BAR1 및 BAR2 레지스터의 모든 비트를 1로 기록한 후 타입 4비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
    • 아래 bit30에서 1이 발견되었으므로 사이즈는 0x4000_0000 (1GB) 임을 알아낼 수 있다.
  • 마지막으로 디바이스 트리의 ranges 에서 지정하는 메모리 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
    • 예) ranges = <0x83000000 0 0 4 0x80000000 0 0x4000_0000>;
      • 타입: Pre-fetchable Mem64
      • pci 주소: 0x0
      • 호스트 cpu 물리 주소: 0x4_80000000
      • 사이즈: 0x40000000 (1GB)

 

I/O Type BAR

다음 그림은 IO 주소를 표현하는 BAR3를 보여준다.

  • Size의 표기는 BAR3 레지스터의 모든 비트를 1로 기록한 후 타입 2비트를 제외하고 1로 설정된 lowest 비트의 포지션 값으로 사이즈를 알아낸다.
    • 아래 bit12에서 1이 발견되었으므로 사이즈는 0x1000 (4KB) 임을 알아낼 수 있다.
  • 마지막으로 디바이스 트리의 ranges 에서 지정하는 IO 타입의 호스트 CPU 물리 주소부터 배치된 리소스의 주소를 기록하는 것으로 완료한다.
    • 예) ranges = <0x81000000 0 0 0 0x24000000 0 0x1000>;
      • 타입: I/O
      • pci 주소: 0x0
      • 호스트 cpu 물리 주소: 0x24000000
      • 사이즈: 0x1000 (4KB)

 

PCI 디바이스 설정 및 추가

pci_device_add()

drivers/pci/probe.c

void pci_device_add(struct pci_dev *dev, struct pci_bus *bus)
{
        int ret;

        pci_configure_device(dev);

        device_initialize(&dev->dev);
        dev->dev.release = pci_release_dev;

        set_dev_node(&dev->dev, pcibus_to_node(bus));
        dev->dev.dma_mask = &dev->dma_mask;
        dev->dev.dma_parms = &dev->dma_parms;
        dev->dev.coherent_dma_mask = 0xffffffffull;

        pci_set_dma_max_seg_size(dev, 65536);
        pci_set_dma_seg_boundary(dev, 0xffffffff);

        /* Fix up broken headers */
        pci_fixup_device(pci_fixup_header, dev);

        /* moved out from quirk header fixup code */
        pci_reassigndev_resource_alignment(dev);

        /* Clear the state_saved flag. */
        dev->state_saved = false;

        /* Initialize various capabilities */
        pci_init_capabilities(dev);

        /*
         * Add the device to our list of discovered devices
         * and the bus list for fixup functions, etc.
         */
        down_write(&pci_bus_sem);
        list_add_tail(&dev->bus_list, &bus->devices);
        up_write(&pci_bus_sem);

        ret = pcibios_add_device(dev);
        WARN_ON(ret < 0);

        /* Setup MSI irq domain */
        pci_set_msi_domain(dev);

        /* Notifier could use PCI capabilities */
        dev->match_driver = false;
        ret = device_add(&dev->dev);
        WARN_ON(ret < 0);
}
  • 코드 라인 5에서 pci 디바이스의 mps, extended tag 여부, relaxed ordering, hot plug parameter 정보 등을 설정한다.
  • 코드 라인 6에서 pci 디바이스의 (*release) 후크에 pci_release_dev() 함수를 대입한다.
  • 코드 라인 8~14에서 버스가 사용하는 노드 번호로 디바이스 노드를 설정하고, 디바이스의 dma 관련 설정등을 최대치로 설정해둔다.
  • 코드 라인 17에서 특정 장치에 대해 pci_fixup_header 후쿠가 있는 경우 이를 수행한다.
    • DECLARE_PCI_FIXUP_HEADER() 매크로 함수로 지정한다.
    • pci_fixup 구조체를 생성한 후 .pci_fixup_header 섹션에 저장한다.
  • 코드 라인 20에서 resource_alignment 버스 속성을 지정한 경우 리소스 alignment를 수행한다. (powerpc만 지원한다)
    • resource_alignment=
      • [<order of align>@][<domain>:]<bus>:<slot>.<func>
      • [:noresize][; …]
  • 코드 라인 23에서 pm state save 여부를 false로 한다.
  • 코드 라인 26에서 각종 cap을 초기화한다.
  • 코드 라인 32~34에서 버스에 디바이스를 추가한다.
  • 코드 라인 36에서 pcibios 설정에 맞춰 디바이스의 매핑을 수행한다.
    • x86, s390, powerpc, sparc 등 바이오스가 있는 경우에 한해 설정한다.
    • arm, arm64는 해당 사항 없다.
  • 코드 라인 40에서 msi용 irq 도메인을 지정한다.
  • 코드 라인 43~44에서 디바이스를 추가하되 match 동작을 수행하지 않게한다.

 

pci_configure_device()

drivers/pci/probe.c

static void pci_configure_device(struct pci_dev *dev)
{
        struct hotplug_params hpp;
        int ret;

        pci_configure_mps(dev);
        pci_configure_extended_tags(dev, NULL);
        pci_configure_relaxed_ordering(dev);

        memset(&hpp, 0, sizeof(hpp));
        ret = pci_get_hp_params(dev, &hpp);
        if (ret)
                return;

        program_hpp_type2(dev, hpp.t2);
        program_hpp_type1(dev, hpp.t1);
        program_hpp_type0(dev, hpp.t0);
}

pci 디바이스의 mps, extended tag 여부, relaxed ordering, hot plug parameter 정보 등을 설정한다.

  • 코드 라인  6에서 pci upstream 브리지의 mps(Maximum Payload Size) 값으로 pci 디바이스의 mps를 설정한다.
    • pci 디바이스와 pci  upstream 브리지의 mps가 같은 경우 변경이 필요 없다.
    • 128, 256, 512, 1024, 2048, 4096 값 중 하나를 사용할 수 있다.
    • pci 버스가 아직 설정되지 않은 경우 나중에 처리할 수도 있다.
  • 코드 라인 7에서 pci 호스트의 extended tag가 설정된 경우 pci 디바이스도 설정한다.
    • pcie EXP_DEVCAP에서 extended tag 비트가 설정된 경우 호스트의 extended tag 처리가 가능 여부에 따라 디바이스의 설정을 변경한다.
    • “disabling Extended Tags” 또는 “enabling Extended Tags” 메시지가 출력된다.
  • 코드 라인 8에서 pci 루트 포트가 relaxed ordering을 허용하지 않으면 pci 디바이스의 설정도 클리어한다.
    • PCI_EXP_DEVCTL 레지스터의 PCI_EXP_DEVCTL_RELAX_EN 비트가 설정되었고, 루트 포트에 PCI_DEV_FLAGS_NO_RELAXED_ORDERING 플래그의 설정이 있으면 pci 디바이스의  PCI_EXP_DEVCTL 레지스터의 PCI_EXP_DEVCTL_RELAX_EN을 클리어한다.
    • “”Disable Relaxed Ordering because the Root Port didn’t support it” 메시지가 출력된다.
  • 코드 라인 10~13에서 acpi를 사용할 때 pci hot plug 파라메터를 가져온 후 pci 디바이스에 설정한다.

 

참고

PCI Subsystem -1- (Basic)

최근 PC 및 Embedded 시스템에서 가장 많이 사용하는 고속 Bus인 PCI Express를 위주로 설명한다. 먼저 PCI Bus에 대해 간략히 알아보고 PCI Express를 더 자세히 비교하여 설명하기로 한다.

 

PCI

PCI(Peripheral Component Interconnect) 아키텍처는 주변 장치 연결을 위해 ISA(Industry Standard Architecture bus), EISA, MCA, VESA 등의 버스 표준을 대체하기 위해 설계되었다. 오늘날 PC에서 가장 널리 사용되는 버스 규격이다.

 

다음 그림은 버스 타입별로 비트 수, 클럭 및 핀 수를 비교하였다.

 

PCI는 다음과 같은 주요 목표로 설계되었다.

  • 더 빠른 전송 속도의 달성
    • 33Mhz 클럭부터 시작하여 66Mhz까지가 가장 많이 사용된다. (25Mhz 시스템도 있다.)
    • PCI/X에서는 66Mhz, 133Mhz, 266Mhz 등으로 발전되었다. (533Mhz 도 시도되었다.)
  • 플랫폼 독립성
    • 일부 플랫폼에 종속되지 않고 대부분의 플랫폼에서 사용된다.
    • 예) x86, IA-32, IA-64, Alpha, MIPS, PowerPC, ARM, …
  • 디바이스 추가/제거의 편이성
    • 버스에 장치가 쉽게 연결되고 해제될 수 있는 구조를 채택하였다.

 

PCI와 ISA(EISA)와의 연동 관계

32비트 PC가 처음 출현했을 때 사용하던 방식으로 ISA 호환을 위해 PCI 버스 하단에서 ISA 버스를 지원함을 알 수 있다.

 

ISA 슬롯

8비트 및 16비트 ISA 버스가 채택된 ISA 슬롯 모습을 알아본다.

  • 16비트 ISA 슬롯은 16비트 ISA 카드 및 8비트 ISA 카드 둘 다 지원한다.

 

PCI & PCI/X 슬롯

32비트 PCI 및 64비트 PCI 버스가 채택된 PCI 및 PCI/X 슬롯 모습을 알아본다.

  • 64비트 PCI/X 슬롯은 64비트 PCI/X 카드 및 32비트 PCI 카드 둘 다 지원한다.
  • PCI 및 PCI/X 슬롯이 지원하는 전압(Volatage)을 구분할 수 있도록 슬롯에 홀(Hole)의 위치를 바꿔 종류가 다른 카드가 꽂히지 않게 하였다.
  • 아래 예)
    • 5V PCI 슬롯 3개
    • 3.3V PCI/X 슬롯 2개

 

PCI 핀

1 ~ 수 개의 pci 슬롯을 통해 pci 버스를 공유한다.

  • PCI 버스는 4개의 legacy 인터럽트 라인을 지원한다. (INTA#, INTB#, INTC#, INTD#)

 

PCI 전송

다음은 버스 컨트롤러가 발생시키는 시그널 라인들에 대한 설명이다. (# 기호는 low active로 동작함을 의미한다.)

  • CLK
    • 33/66Mhz 기반 클럭(CLK) 시그널
    • 고정 주기로 클럭이 발생되며 rising edge 신호에 동기하도록 한다.
  • RST#
    • Reset
    • 1ms 이상의 low active 시그널을 사용하여 pci 버스에 연결된 컨트롤러와 모든 pci 디바이스를 HW 리셋시킨다.
    • 리셋이 해제된 후 5 사이클 이내에 각 디바이스들은 전송을 요청하면 안된다.
  • IDSEL
    • Initialization Device Select
    • 타겟 디바이스가 이 시그널을 읽어 configuration 레지스터 중 하나를 읽을 때 사용된다.

 

버스 마스터가 발생시키는 시그널 라인들에 대한 설명이다.

  • FRAME#
    • 현재 initiator(버스 마스터)로부터 버스 엑세스가 시작되고 계속됨을 알리기 위해 low active 시그널을 발생한다.
  • ADx
    • Address / Data
    • initiator가 시 분할 방법을 사용하여 32비트 주소 또는 데이터 비트 둘 중 하나를 출력한다.
  • C/BEx#
    • 명령 또는 바이트 enable
      • Command
        • initiator가 트랜잭션 타입을 정의하며 4 비트 조합을 사용한다. (C/BE0 ~ C/BE3)
      • BE0# ~ BE3#
        • 한 번에 보낼 수 있는 바이트를 지정한다. (low active)
          • 예) BE3#=1, BE2#=1, BE1#=0, BE0#=0
            • 하위 2 바이트만 전송한다.
  • IRDY#
    • Initiator Ready for data
    • 현재 initiator(버스 마스터)가 AD# 및 C/BEx를 준비한 후 Ready되었음을 알리기 위해 low active 시그널을 발생한다.
    • high일 때에는 idle 상태이다.
    • 전송은 반드시 8 사이클 이내에 이루어져야 한다.
  • LOCK#
    •  Initiator가 atomic 트랜잭션을 처리하기 위해 현재 타겟 디바이스를 고정(lock)하도록 low active 시그널을 발생한다.

 

다음 파란색 시그널은 타겟 디바이스가 발생시키는 시그널 라인들에 대한 설명이다. (# 기호는 low active로 동작함을 의미한다.)

  • STOP#
    • 데이터 전송 중 타겟 디바이스가 전송을 중단해달라고 요청할 때 low active 시그널을 발생한다.
  • TRDY#
    • Target Ready
    • 타겟 디바이스가 응답하며, 준비되어 전송 가능한 상태에서 low active 신호를 시그널을 발생한다.
  • DEVSEL#
    • Device Select
    • 타겟 디바이스가 자신이 선택되었음을 initiator에게 알리기 위해 low active 시그널을 발생한다.
    • initiator가 6 사이클 이내에 이 시그널을 받지 못하면 취소된다.
  • INTA# ~ INTD#
    • 4개의 인터럽트 라인을 가지며 PCI 디바이스는 이 라인을 share하여 사용하며 인터럽트를 발생시킬 때 low active 시그널을 발생한다.

 

 

PCI Bus Arbiter

PCI Bus Arbiter의 구성은 다음과 같이 Centralized 또는 Daisy Chain 방식을 사용한다.

  • Centralized Arbiter
    • 별도의 회로이지만 보통 PCI host 또는 PCI 브리지에 통합되어있다.
    • 여러가지 선택 알고리즘
      • Fairness (default)
        • 가장 중요한 디바이스를 우선 선택한다.
        • 디바이스별 우선 순위에 의해 최대 Latency 레지스터를 설정한다. 그런 후 이 latency를 만족하도록 디바이스를 선택한다.
      •  FIFO
        • 가장 먼저 요청한 디바이스 우선 선택
      • RR
        • 라운드 로빈 방식에 의한 선택

  • Daisy Chain Arbiter
    • Arbiter에 가까운 슬롯에 위치한 디바이스가 먼저 선택된다.

 

다음 시그널은 PCI 버스 arbiter 회로가 수신또는 발생시키는 시그널 라인들에 대한 설명이다.

  • REQ#
    • 버스 사용 요청을 의미한다.
    • 버스를 사용하고자 하는 버스 마스터 디바이스가 PCI bus Arbiter에 요청하며 여러 버스 마스터 디바이스가 동시 요청한 경우 GNT#를 통해 하나의 디바이스를 선택한다.
    • 참고로 대부분의 PCI 디바이스들은 bus-master 기능을 갖는다. 이 들은 peer-to-peer 전송 및 DMA 전송을 위해 버스 요청을 할 때 다른 PCI 디바이스들과 버스를 차지하기 위해 경합(arbitration) 한다.
  • GNT#
    • PCI 버스 arbiter가 발생시키는 신호로 arbitration 상태에서 승리한 경우를 의미한다. 그런 경우 다음 시작 시 처리하게됨을 의미한다. (first request ownership)

 

PCI 전송 에러 체크

PCI 전송 에러를 감지하는 회로이다. 오래된 pc에서는 단순하게 처리하기 위해 전송 에러를 감지하면 NMI를 발생시킨 후 시스템을 정지시키기도 했다.

  • PAR
    • Initiator가 Parity 부호로 마스터 버스가 짝수 사이클의 주소 및 데이터에 대해 parity 부호를 전송한다.
  • PERR#
    • Parity Error
    • 타겟 디바이스가 짝수 사이클에 수신한 데이터와 그 다음 사이클에 받은 Parity 부호를 비교하여 문제가 발생하였을 때 PERR#에 low active 시그널을 발생시킨다.
  • SERR#
    • System Error
    • 위와 동일하지만 타겟 디바이스가 데이터가 아닌 타겟 주소에 대한 전송에 문제가 발생했을 때 SERR#에 low active 시그널을 발생시킨다.

 

 버스 접근 Latency

Bus Access Latency는 다음 3가지를 포함한다. 참고로 MLT(Master Latency Timer)는 configuration 레지스터 중 offset=14 바이트 위치에 있다.

  • Arbitration Latency
  • Bus Acquisition Latency
    • 최대 16 클럭 이내
  • Initiator and Target Latency
    • 마스터의 경우 최대 8클럭 이내
    • 타겟의 경우 최대 16클럭 이내

 

PCI Command – C/BE[3:0]#

4비트를 사용하여 명령 타입을 구분한다.

  • Special Cycle
    • AD[15:0]를 사용하여 메시지 전달
      • 0000h
        • Shutdown
      • 0001h
        • Halt
  • Memory 영역에 대해서만 버스트 전송을 지원한다. (1100)
  • Memory 영역에 대해서만 캐시 명령을 지원한다. (1110, 1111)

 

 

PCI History

PCI H/W는 다음과 같이 3가지 종류의 버스를 사용한다.

  • PCI
    • 병렬 32bit 버스
  •  PCI/X
    • 병렬 64bit 버스
  • PCIe
    • 직렬 버스

 

PCI

  • PCI 2.2 규격부터슬롯 크기를 줄인 miniPCI 규격이 추가로 발표되었다.
  • PCI 버스 당 공유하여 사용할 수 있는 최대 슬롯 수
    • 33Mhz: 4~5 슬롯
    • 66Mhz : 1~2 슬롯

 

PCI/X

  • PCIe로 대체되어 오늘날 거의 사용되지 않는다.
  • PCI 버스 당 공유하여 사용할 수 있는 최대 슬롯 수
    • 66Mhz: 4 슬롯
    • 133Mhz: 1~2 슬롯

 

 

PCI Address Space Map

PCI는 다음과 같이 3가지 주소 영역을 지원한다. (주의: PCI 고유의 주소 공간이며 CPU가 access할 수 있는 물리 주소 영역이 아니다)

  • PCI Configuration Address Space
    • 16MB 영역의 PCI configuration 영역을 지원한다.
    • 하나의 펑션 당 256 바이트를 지원한다.
      • 버스(256) * 디바이스(32) * 펑션(8)
    • 이 영역은 다음과 같은 API를 사용하여 접근할 수 있다.
      • pci_read_config_byte() & pci_write_config_byte()
      • pci_read_config_word() & pci_write_config_word()
      • pci_read_config_dword() & pci_write_config_dword()
  • PCI I/O Address Space
    • 64K 영역의 I/O 메모리 주소 영역을 지원한다.
    • x86등 특정 아키텍처에서 사용하는 IO 포트 주소에 매핑하여 접근할 수 있다.
    • arm, arm64등 아키텍처가 IO 포트를 지원하지 않는 경우 PCI Memory와 동일하게 CPU의 물리 주소 공간에 노출시켜 사용할 수 있다.
      • 리눅스 커널에서 물리 주소 공간에 노출된 이 영역을 사용할 때에는 가상 주소 공간에 매핑시켜 접근한다.
  • PCI Memory Address Space
    • 4G 영역의 메모리 주소 영역을 지원한다.
    • CPU의 비어 있는 물리 주소 공간에 노출시켜 사용할 수 있다.
      • 리눅스 커널에서 물리 주소 공간에 노출된 이 영역을 사용할 때에는 가상 주소 공간에 매핑시켜 접근한다.

 

Single Function & Multi Function Device

Single Function Device
  • 하나의 물리 디바이스내에 하나의 기능을 가진 pci 디바이스
Multi Function Device
  • 하나의 물리 디바이스내에 여러개의 function을 가진 pci 디바이스이다.
  • 물리적으로 하나의 패키지로 묶여 있지만 각각의 function들은 별도의 pci 디바이스이다.

 

Legacy Interrupts

INTA ~ INTD 인터럽트 라인

  • 싱글 펑션 디바이스의 경우 레거시 인터럽트를 사용하려면 INTA 라인만을 지정하여 사용할 수 있다.
  • 멀티 펑션 디바이스의 경우 각각의 펑션은 레거시 인터럽트로 INTA ~ INTD 까지 4개의 인터럽트 라인 중 하나를 지정할 수 있다.
    • 여러 개의 펑션이 하나의 인터럽트 라인을 공유하여 지정할 수도 있다.
    • 예) 최대 8개의 펑션을 가진 pci 디바이스가 7개의 펑션이 INTA를 지정하고, 마지막 1개의 펑션만 INTB를 지정한다.

 

Legacy Interrupt 라우팅

시스템에 여러 개의 PCI 슬롯을 지원하는 경우 각 슬롯의 INTA ~ INTD 라인들의 라우팅은 시스템 설계에 따라 다르다.

  • 독립된 인터럽트 라인
    • 각각의 PCI 슬롯에 사용된 INTA ~ INTD 까지의 인터럽트를 인터럽트 컨트롤러의 인터럽트 라인에 중복되지 않도록 독립적으로 연결한다.
      • 슬롯 1번 INTA, INTB, INTC, INTD -> 56, 57, 58, 59
      • 슬롯 2번 INTA, INTB, INTC, INTD -> 60, 61, 62, 63
  • 공유 인터럽트 라인
    • 모든 슬롯의 INTA를 하나로 묶어 인터럽트 컨트롤러의 인터럽트 라인에 연결한다. 나머지 INTB ~ INTD도 같은 방식으로 연결하는 방법으로 4개의 인터럽트 라인만을 공유하여 사용한다.
      • 슬롯 1번 INTA, INTB, INTC, INTD -> 56, 57, 58, 59
      • 슬롯 2번 INTA, INTB, INTC, INTD -> 56, 57, 58, 59
  • Mixed 인터럽트 라인
    • 위의 2 가지 방법을 섞어 구성한다.

 

MSI(Message Signaled Interrupt)

PCI 디바이스가 레거시 인터럽트 대신 MSI를 사용하면 다음과 같은 장점들이 있다.

  • 인터럽트 라인의 트레이스(라우팅)가 필요 없다.
  • 멀티 펑션 디바이스들이 같은 번호의 인터럽트 라인을 공유하여 사용할 필요없다.
  • 디바이스 드라이버들 간의 인터럽트 chain이 필요없어진다.

 

PCI Express

PCI Express 버스는 여러 개의 슬롯을 공유하지 않고, 1 vs 1로 연결하여 사용한다.  이러한 특성으로 인해 여러 개의 슬롯이 필요한 경우 PCI express 스위치를 사용하여 확장한다.

 

PCI Express 슬롯 (PCIe)

각각의 lane 수를 가지는 PCIe 슬롯 및 PCIe 카드를 알아본다.

  • PCI Express 버스는 x1, x2, x4, x8, x12, x16, x32 레인을 지원한다.
  • 표준 PCI Express 슬롯 형태로는 아래 그림과 같이 x1, x4, x8, x16 레인을 지원한다.

 

 

miniPCI 및 miniPCIe 슬롯

PCI 및 PCIe의 미니 사이즈로도 사용되고 있다. 이 폼팩터들은 점점 사용되지 않고 있으며 이들은 m.2 폼팩터로 사용 추세가 이동하고 있다.

miniPCIe 핀

 

M.2

  • m.2는 NGFF(Next Gen Form Factor)로 알려져 있으며 여러가지 버스들을 조합하여 제공한다.
    • PCIe 3.0
      • 기존 mPCIe 슬롯이 1개의 레인만을 지원한 것에 비해 최대 x4 레인을 지원한다.
        • x2 레인을 지원하는 슬롯은 m.2 B key (양방향 4GB)
        • x4 레인을 지원하는 슬롯은 m.2 M key (양방향 8GB)
    • SATA 3.0
      • 최대 6Gbps 속도
    • USB 3.0
      • 최대 5Gbps 속도
    • 그 외
      • USB 2.0, I2C, SMBus, DP x4, audio, UIM, SSIC, SDIO, UART, PCM, FMI
  • keying
      • 75개의 핀 포지션 중 key A ~ M 까지의 자리를 막아 구분한다.
  • 표준 사이즈
    •  폭(mm)
      • 12, 16, 22, 30
    • 길이(mm)
      • 16, 26, 30, 38, 42, 60, 80, 110
    • 두께(mm)
      • 상면을 사용한 싱글 사이드 또는 상/하면을 사용한 더블 사이드
        • 상면
          • 1.20, 1.35, 1.50
        • 하면
          • 0.7, 1.35, 1.50
    • 예) WWLL-HH-K-[K]
      • 2280 -> 폭은 22mm, 길이는 80mm
      • D1 -> Double (상면은 1.20mm, 하면은 1.35mm)
      • BM -> b key, m key

 

M.2 Keying

M.2 소켓(슬롯)을 통해 여러 가지 버스를 제공하는데 이의 제공 여부 구분을 정의한다.

  • M.2 소켓에서 Key ID에 맞는 핀들을 막았으므로 이 M.2 소켓에 연결할 수 있는 M.2 카드도 동일한 key ID를 보유해야 한다.
    • M.2 슬롯은 1개의 key ID를 가진다.
    • M.2 카드는 1~2 개의 key ID를 가지고 있다.
  • 가장 많이 사용하는 M.2 소켓의 key ID는 다음과 같다.
    • Key B
      • M.2 소켓에서 B 포지션에 해당하는 12~19번 핀 자리에 핀들이 연결되지 못하게 연결 구멍을 막는다.
        • B 포지션만 막는 제품은 많지 않고, 대부분 B+M을 사용한다. 이 경우 key M을 사용하는 카드도 허용되지만 x4 레인을 사용하지 못한다.
    • Key M
      • M.2 소켓에서 B 포지션에 해당하는 59~66번 핀 자리에 핀들이 연결되지 못하게 연결 구멍을 막는다.

M.2 카드에서 가장 많이 사용하는 key ID는 다음과 같다.

  • Key B+M
    • M.2 카드에서 B 포지션 및 M 포지션에 해당하는 핀 자리를 hole 상태로 비워둔다.
      • Key B 및 Key M을 사용하는 M.2 소켓에 연결할 수 있다.
  • Key M
    • M.2 카드에서 M 포지션에 해당하는 핀 자리를 hole 상태로 비워둔다.

 

아래 그림을 보고 key B+M과 key M 타입을 구분하는 방법을 알아두자.

  • 아래 카드 그림은 top면에서 볼때의 기준이다.

 

다음은 여러 가지 SSD 타입에 대해 설명하였다.

  • SATA 커넥터
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 SATA 커넥터에 연결된 SSD를 사용한다.
    • 예) 삼성 2.5인치 850 SATA SSD
  • mSATA 커넥터
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 mSATAQ 커넥터에 연결된 SSD를 사용한다.
    • 예) 삼성 850 EVO mSATA SSD
  • M.2 커넥터 – M.2 SATA
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 M.2 커넥터의 SATA3 버스에 연결된 SSD를 사용한다.
    • 예) 삼성 850 EVO M.2 2282 SSD
    • 참고로 USB 3.0/3.1 등의 외장형 SSD 케이스에 포함되는 경우 대역폭이 모자라므로 M.2 SATA 타입의 SSD를 사용한다.
  • M.2 커넥터 – M.2 PCIe AHCI
    • AHCI 프로토콜을 채용한 드라이버를 사용하여 M.2 커넥터의 PCIe 버스에 연결된 SSD를 사용한다.
    • 현재는 이 방식을 거의 사용하지 않는다.
  • M.2 커넥터 – M.2 PCIe NVMe – 가장 빠름
    • NVME 프로토콜을 채용한 드라이버를 사용하여 m.2 커넥터의 PCIe 버스에 연결된 SSD를 사용한다.
    • 예) 삼성 970 EVO M.2 2282 SSD (x4 레인용)
    • 예) 킹스톤 A1000 M.2 2280 (x2 레인용)

 

위에서 구분한 내용을 아래 그림을 보고 실제 제품 형태와 비교해보자.

  • 주의할 것은M.2 key B+M 타입의 카드들은 외형만을 보고 SATA 버스를 사용하는 SSD인지 고속 PCIe 버스를 사용하는 SSD인지 구분할 수 없으므로 주의해야 한다.

 

PCIe 버전

  • 오늘날 가장 많이 사용되는 규격이다.
  • 최근 임베디드 시스템에서 사용하는 PCI 규격이다.

 

PCIe 속도 비교

 

PCI Express 특징

  • 저비용
    • 저 비용으로 대량 양산 가능한 솔루션이다.
  • 인터커넥트
    • 커넥터나 케이블링을 통해 칩-to-칩 또는 보드-to-보드 연결 구성이 가능하다.
  • 새로운 폼 팩터
    • 모바일 및 모듈라 폼팩터 및 카트리지 폼팩터를 지원하는 새로운 폼 팩터이다.
  • PCI 호환
    • 기존 PCI 드라이버를 수정 없이 동작시킬 수 있는 호환성을 제공한다.
  • 성능
    • low-overhead, low-latency 통신을 통해 밴드폭과 링크 효과를 최대화한다.
    • 최소한의 핀 수로 높은 밴드폭을 지원한다.
    • lane들을 통합한 scalable 성능을 낼 수 있다.
  • 향상된 기능
    • 서로 다른 데이터 타입과 오더링 룰을 제공한다.
    • 절전 관리
    • 미니 PCI Express
    • QoS
    • 기존 핫플러그 및 native 핫플러그 지원 및 비동기 removal 지원한다.
    • 모든 타입의 데이터에 대해 데이터 정합성(Data Integrity)을 제공한다.
    • 진보된 에러 리포팅 및 에러 핸들링을 통해 향상된 폴트 isolation 및 리커버리 솔루션을 제공한다.
    • 간편한 테스트 지원

 

PCIe 전송 H/W

PCI Express 링크

패킷을 주고(Transmit) 받을(Receive) 수 있는 하나의 lane으로 PCI Express 링크를 구성할 수 있다. 또한 2 개 이상의 lane을 묶어(aggregation) 하나의 링크로 만들면 통신 속도를 통합한 lane 만큼 높일 수 있다.

  • 1 개의 lane 부터 최대 32개의 lane을 하나 링크로 묶어 고속 통신이 가능하다.
    • x1, x2, x4, x8, x12, x16, x32 lane을 지원한다.
  • 1개의 lane에는 TX 페어와 RX 페어가 사용된다.
  • 1개의 링크는 두 개의 디바이스끼리만 통신할 수 있다.

 

PCI Express Fabric Topology

다음 그림에서 SoC가 Root Complex 가상 PCI Bridge를 통해 PCIe 버스를 3개 제공하는 것을 보여준다. 각각의 버스를 다음과 같이 사용하고 있다.

  • BUS 1
    • PCIe 버스를 브릿지를 통해 PCI 또는 PCIX 버스로 변환한다.
  • BUS 3
    • PCIe 버스에 PCI Express Switch를 연결하여 더 많은 PCIe 버스를 제공하기 위해 확장한다.
  • BUS 9
    • PCIe 버스에 하나의 디바이스만 곧장 연결하여 사용한다.

PCI 버스가 CPU와 연결되는 가장 상위는 Root Complex Device이고 버스를 확장하기 위해 PCI Switch를 사용할 수 있다.

 

1) Root Complex 디바이스

루트 컴플렉스(RC)는 CPU/메모리 서브시스템을 I/O 연결할 수 있는 I/O 계층 구조의 루트이다.

  • 루트 컴플렉스는 1개 이상의 PCI Express 포트를 가질 수 있다.
  • PCI Express 포트는 하나의 End Point 디바이스와 연결되거나, 브리지나 스위치를 통해 확장할 수 있다.
  • 구현 옵션에 따라 peer-to-peer 트랜잭션 전송이 가능하다.
  • 드물지만 디바이스별 페이로드 제한에 따라 하나의 패킷을 분할하여 전송하는 경우도 있다.

PCI Express 루트 컴플렉스 디바이스를 더 자세히 살펴보면 다수의 내부 PCI-PCI 브리지 구조로 구성된다.

  • 호스트 브릿지 디바이스
    • 호스트에 연결된다.
  • 루트 PCI Express 포트
    • 하나의 PCI Expresss이다.
    • 브리지의 세컨더리 버스에 해당하는 Configuration Space에 매핑된다.

 

 

2) Endpoint 디바이스

  • Legacy PCI Endpoint
    • Type 0 Configuration 헤더를 제공하는 Function이어야 한다.
    • 반드시 Configuration을 제공해야 한다.
    • I/O 요청을 제공할 수도 있다.
    • I/O 요청을 발생시킬 수 있다.
    • Locked 요청을 할 수 없다.
    • MSI/MSI-X를 지원한다.
    • 4G 주소를 초과하는 메모리 트랜잭션을 할 수 없다.
      • 32비트 어드레싱 레지스터
  • PCI Express Endpoint
    • Type 0 Configuration Space를 제공하는 Function이어야한다.
    • 반드시 Configuration을 제공해야 한다.
    • I/O 요청을 발생시키면 안된다.
    • Locked 요청을 지원하지 않는다.
    • MSI/MSI-X를 지원한다.
    • 4G 주소를 초과하는 메모리 트랜잭션이 가능하다.
      • 64비트 어드레싱 레지스터
      • 32비트 어드레싱도 가능하다.
  • Root Complex Integrated Endpoint
    • 루트 컴플렉스의 내부 로직에 구현된 루트 포트이다.
    • Type 0 Configuration Space를 제공하는 Function이어야한다.
    • 반드시 Configuration을 제공해야 한다.
    • I/O 요청을 발생시키면 안된다.
    • Locked 요청을 지원하지 않는다.
    • MSI/MSI-X를 지원한다.
    • 32비트 BAR를 통한 메모리 리소스를 지원한다.
    • PCI Express Extended Capability를 구현하면 안된다.
    • 전원 관리를 구현하지 않는다.
    • 핫플러그를 지원하지 않는다. (당연하다.)
    • 루트 컴플렉스가 노출하는 계층에 나타나면 안된다.
    • 스위치에서도 나타나면 안된다.

3) Switch 디바이스

스위치는 다 수의 가상 PCI-to-PCI 브리지 디바이스들로 내부적으로 구성되어 있다.

  • 멀티캐스트를 제외한 트랜잭션을 라우팅 주소 기반으로 PCI 브리지 메커니즘을 사용하여 전달(forward)한다.
  • 다운스트림 포트 방향으로 Locked 요청을 지원한다.
  • 모든 활성화된 스위치 포트는 플로우 컨트롤을 지원할 수 있어야 한다.
  • 패킷을 작은 사이즈로 분리하는 것을 지원하지 않는다.
  • 인바운드된 포트 간에 중재는 라운드 로빈 또는 가중치 라운드 로빈을 구현할 수 있다.
  • Endpoint는 스위치 내부 버스의 가상 PCI-to-PCI 브릿지의 피어로 노출되면 안된다.
  • 하나의 업스트림 PCI Express 포트와 다수의 다운스트림 PCI Express 포트로 구성된다.

 

4) Root Complex Event Collector

  • 메모리 및 IO 리소스를 디코딩할 필요가 없다.
  • 다비이스/포트 타입 값으로 식별할 수 있다.
  • 베이스 클래스(0x08), 서브 클래스(0x07)과 프로그래밍 인터페이스(0x00) 값을 사용한다.
  • 싱글 로지컬 버스에 상주한다.
  • 옵션으로 제공된다.

 

5) PCI Express to PCI/PCI-X Bridge

  • PCI Express fabric과 PCI/PCI-X 계층간의 연결을 지원한다.

 

전송 계층

PCI 전송에 다음 3가지의 전송 계층을 사용한다.

  • Transaction Layer
  • Data Link Layer
  • Physical Layer

 

3 계층의 TLP(Transaction Layer Packet) 또는 2 계층의 DLLP(Data Link Layer Packet)을 전송하며 각 계층에서 취급하는 데이터는 다음과 같다.

 

TLP(Transaction Layer Packet)

  • Physical Layer
    • Framing
      • Start (1B)
      • End (1B)
  • Data Link Layer
    • Sequence Number (2B)
    • LCRC (1DW)
  •  Transaction Layer
    •  Header (3~4DW)
    • Data (0~1024DW)
    • ECRC (1DW)

 

DLLP(Data Link Layer Packet)

  • Physical Layer
    • Framing
      • Start (1B)
      • End (1B)
  • Data Link Layer
    • DLLP Type + Misc (1DW)
    • CRC (2B)

 

Transaction Layer

  • TLPs(Transaction Layer Packets)의 생성과 처리
    • 메모리 트랜잭션
      • Memory 주소 공간에 매핑
    • I/O 트랜잭션
      • I/O 주소 공간에 매핑
    • Configuration 트랜잭션
      • 디바이스 펑션 설정 및 셋업
    • 메시지 트랜잭션
      • 이벤트 시그널링에서 일반적인 메시징까지
  • 플로우 컨트롤
  • 가상 채널 관리
  • PCI/PCI-X 호환 오더링
  • 트래픽 클래스 differentiation 포함

 

TLPs(Transaction Layer Packets) Header 포맷

트랜잭션 레이어 패킷은 다음과 같은 구성을 사용한다. 전송 시 Byte 0 위치부터 시작한다.

  • TLP Prefixes (optional)
  • TLP Header
  • Data Payload
  • TLP Digest (optional)

 

 

TLP 헤더

다음 그림은 TLP 헤더와 패킷 포맷을 보여준다. 패킷 포맷에 따라 1~4바이트의 TLP 헤더를 갖는다.

  • 아래 그림에 이어 32비트 주소 또는 64비트 주소가 추가된 주소 라우팅을 표시한다.
    • 32비트 주소 라우팅의 경우 3 DW 헤더 길이를 갖는다.
    • 64비트 주소 라우팅의 경우 4 DW 헤더 길이를 갖는다.

  • Fmt
    • 패킷 포맷
  • Type
    • 패킷 타입
  • R
    • Reserved로 반드시 0 이어야 한다.
  • TC
    • 트래픽 클래스로 QoS 목적으로 사용한다.
      • 000: TC0로 가장 우선 순위가 느린 베스트 Effort 처리 방식이다.
      • 001 ~ 111: TC1 ~ TC7으로 스케줄러를 통해 처리 순서를 제어할 수 있다.
  • Attr
    • 속성
  • LN
    • 메모리 요청이 LN Read, LN Write 이거나,
    • 컴플리션이 LN Completion인 경우이다.
  • TH
    • TLP 처리 힌트를 의미한다.
  • TD
    • TLP 다이제스트를 의미한다.
  • EP
    • poisned TLP를 의미한다.
  • Attr
    • 오더링 타입을 표시하는 속성이다.
    • Attr[2:1]
      • 00: 디폴트 오더링
        • PCI Strongly 오더드 모델이다.
      • 01: 릴렉스드 오더링
        • PCI-X 릴렉스드 오더링 모델이다.
      • 10: ID 베이스 오더링
        • 리퀘스터와 컴플리터 ID 기반의 독립적인 오더링이다.
      • 11: 릴렉스드 오더링 + ID 베이스 오더링
    • Attr[0]
      • 0: 디폴트
        • 하드웨어가 수행하는 캐시 코히런시
      • 1: No Snoop
        • 하드웨어가 캐시 코히런시를 수행하지 않는다.
  • AT
    • 주소 타입으로 주소  변환이 필요한지 여부를 의미한다.
    • 00: 디폴트로 변환하지 않는다.
    • 01: 변환 요청
    • 10: 변환됨
    • 11: Reserved
  • Length
    • DW(4바이트, 더블 워드) 단위의 데이터 페이로드 길이
    • 0~1023까지 지정이 가능하다. 단 0의 경우 1024 DW를 의미한다.
    • 트랜스미터(Transmiter) 또는 리시버(Receiver) 디바이스 컨트롤 레지스터의 Max_Payload_Size 필드(DW 단위)를 초과하면 안된다.

 

패킷 포맷

 

TLP 타입

다음 표에서 트랜잭션 타입을 보여준다.

 

 

트랜잭션 처리 방향

 Non-Posted (응답 확인 필요)

  • Memory Reads.
    • MRd 요청: 디바이스가 Root Complex에 메모리 읽기 요청
    • CplD 응답: Root Complex에 연결된 시스템 메모리를 읽어 디바이스에 Completion 데이터 응답
  • Memory Locked Reads
    • MRdLk 요청: Root Complex가 락을 유지한채 디바이스에 메모리 읽기 요청
    • CplDLk 응답: 디바이스가 Completion 데이터 응답
  • IO 및 Configuration Writes
    • IOWr 요청: Root Complex가 디바이스에 IO 쓰기 요청
    • Cpl 응답: 디바이스가 데이터를 기록한 후 Completion 응답

Posted (응답 확인 없음)

  • Memory Writes
    • MWr 요청: Root Complex가 디바이스에 메모리 기록 요청한다. 그 후 응답은 받지 않는다.
  • Message Writes
    • Msg 요청: 트랜잭션 메시지 라우팅

 

트랜잭션 라우팅

다음과 같이 두 가지 타입의 라우팅이 지원된다.

  • 주소 라우팅
    • 64비트 주소 라우팅
    • 32비트 주소 라우팅
  • ID 라우팅
    • non-ARI ID 라우팅
    • ARI ID 라우팅

 

64/32 비트 주소 라우팅

 

  • PH
    • 00: 양방향 데이터
    • 01: 요청자
    • 10: 타겟
    • 11: 우선순위 포함한 타겟

 

ID 라우팅
  • non ARI ID 라우팅
    • Bus 번호, 디바이스 번호 및 펑션 번호 세 가지를 사용한다.
  • ARI ID 라우팅
    • Bus 번호 및 펑션 번호 두 가지를 사용한다.

 

아래 그림은 4 DW 길이의 TLP 헤더이다. 포맷이 0 x 0 인 경우 3 DW 길이의 TLP 헤더를 사용한다.

 

First/Last DW Byte Enables

TH 비트가 설정된 메모리 읽기 요청은 다음표와 같이 첫번째와 마지막 DW에 대한 바이트 enable을 표시한다.

 

Request (메모리, I/O, Configuration)

Request Memory

다음 그림은 메모리, I/O 및 Configuration 트랜잭션 데이터를 요청할 때 사용하는 TLP 헤더이다. (Memory 32비트는 생략)

 

Data Link Layer

  • 링크 트레이닝
    • 링크 폭
    • 링크 데이터 rate
    • Lane reversal (Lane 역 방향 순서 지원)
    • Polarity inversion (극성 반대 지원)
    • 멀티 레인에서 레인 to 레인 de‐skew
  • 전원 관리
    • 트랜잭션 게층에서 요청한 전원 상태를 수락하고 물리 계층으로 전달한다.
    • 활성/재설정/연결 해제/전원 관리 상태를 트랜잭션 계층으로 전달한다.
  • 데이터 프로텍션, 에러 체킹
    • CRC 생성
    • 에러 체킹
    • 재전송 메시리지대한 TLP 응답
    • 에러 리포팅 및 로깅을 위한 에러 인디케이션

 

Physical Layer

  • 인터페이스 초기화, 메인트넌스 제어 및 스테이터스 트래킹
    • 리셋/핫플러그 컨트롤
    • 전원 관리 인터커넥트
    • width & lane 매핑 협상
    • lane 극성 반전
  • 심볼 및 오더
    • 8b/10b 엔코딩 및 디코딩
    • 임베디드 클럭 튜닝 및 정렬
  • 심볼 전송 및 정렬
    • 전송 회로
    • 리셉션 회로
    • 수신 측 Elastic 버퍼
    • 수신 측 멀티 lane de-skew

 

MAC & PHY 인터페이스

데이터 전송 계층의 MAC과 물리 계층의 PHY간의 인터커넥트

  • MAC
    • 링크 관리
  • PHY
    • PCS(Physical Coding Sublayer)
      • 코드 변환을 수행한다.
    • PMA(Physical Medium Attachment)
      • 물리 매체 접속부로 Serialization/Deserialization 기능을 수행한다.
      • 수신부(Receiver)에는 클럭 및 데이터의 리커버리 기능도 포함된다.
    • PMD(Physical Medium Dependent Sublayer)
      • 물리 매체 전송부로 전송 매체에 최적화된 트랜시버 모듈(광 트랜시버 등)

 

PCS & PMA & PMD

물리 전송 계층의 3가지 서브 레이어를 각 기능별로 송/수신 흐름을 알아본다.

 

PCIe Gen1 & Gen2

 

PCIe Gen3 & Gen4 & Gen5

 

Encoding

다음과 같은 엔코딩을 사용하여 5개 이상의 비트가 연속되지 않게 제한시킨다. 이렇게 하므로써 출력이 커지는 것을 방지하여 EMI 방사(radiation)를 줄여준다.

  •  8b/10b
    • PCIe Gen 1과 Gen 2에서 사용한다.
    • 8비트 입력에 대해 2비트가 추가되어 10비트가 출력된다.
      • 25%의 추가 대역폭이 필요하다.
        • 예) 1Gbps -> 1.25Gbps
  • 128b/130b
    • PCIe Gen3 이후부터 사용한다.
    • 128비트 입력에 대해 2비트가 추가되어 130비트가 출력된다.
      • 소모되는 추가 대역폭이 1% 미만으로 거의 없다.

 

Configuration Topology

PCI Express Configuration 모델은 두 개의 Configuration Space access 매커니즘을 지원한다.

  • PCI compatible CAM(Configuration Access Mechanism)
    • 호환을 위해 PCI express 디바이스는 이 항목을 항상 제공한다.
  • PCI Express ECAM(Enhanced Configuration Access Mechanism)

 

PCI Express Address Space

PCI Express는 PCI와 유사하지만 다음과 같은 변화가 있다.

  • Configuration Address space의 크기가 256 바이트에서 4K 바이트로 확장되었다.
  • Memory Address space는 32비트와 64비트 두 가지 방법으로 주소 지정할 수 있다.

PCI Express Configuration Space Layout

PCI Express 하나를 담당하는 Configuration Space를 알아본다.

  • PCI 호환 주소 영역
    • 0x00 ~ 0xff의 영역을 갖는다.
    • 그 중 헤더 영역은 0x00 ~ 0x3f이다.
  • PCI Express 확장 주소 영역
    • 0x100 ~ 0xfff의 영역을 갖는다.

 

ECAM(Enhanced Configuration Access Mechanism)

다수의 PCI Express 버스를 관리하기 위해 아래와 같은 주소 체제로 나누어 사용한다.

  • 버스 번호
    • 1부터 최대 256개의 버스 번호를 가질 수 있다.
  • 디바이스 번호
    • 최대 32개의 디바이스 번호를 가질 수 있다.
  • 펑션 번호
    • 최대 8개의 펑션 번호를 가질 수 있다.
  • PCI Express Configuration Space
    • 0x0 ~ 0xfff까지의 영역을 담당한다.

다음 그림은 Root Complex 가상 PCI Bridge가 3개의 PCIe 버스를 제공하며 이에 대한 pci 주소 공간을 보여준다.

  • Root Complex에 해당하는 configuration space가 주어진다.
  • 그 아래에 각 가상 PCI Bridge에 해당하는 configuration space가 있음을 알 수 있다.

 

Configuration Space Header

디바이스 타입에 따라 Configuration Space Header는 다음과 같이 2 가지 타입을 지원한다.

  • Type 0 header
    • PCI Express 디바이스
  • Type 1 header
    • Switch 및 Root Complex 가상 PCI Bridges

Type 0 헤더

 

Type 1 헤더

 

각 헤더에서 사용되는 공통 레지스터들

Vendor ID (0x00)
  • 2바이트 벤더 ID
  • 예) 인텔: 0x8086
Device ID (0x02)
  • 2바이트 디바이스 ID
  • 예) 고속 이더넷 장치: 0x0009
Command (0x04)
  • 2바이트 명령 레지스터

Status (0x06)
  • 2 바이트 상태 레지스터
  • RW1C (Read, Write 1 to Clear Status)
    • Read는 동일하되, 1 값을 기록 시에만 클리어한다. 0을 기록시에는 아무런 변화가 없다.

 

Revision ID (0x08)
  • 1 바이트 Revision ID
Class Code (0x09)
  • 3 바이트 클래스 코드로 장치 유형을 표시한다.
  • 예) SCSI: 0x0100
Cache Line Size (0x0c)
  • 1바이트 캐시 라인 사이즈
  • 호환 목적으로 사용되며 PCI express에서는 사용하지 않으므로 항상 0이다.
Primary Latency Timer (0x0d)
  • 1바이트 대표 Latency 타이머
  • PCI express에서는 사용하지 않으므로 항상 0이어야 한다.
Header Type
  • 1바이트 헤더 타입
BIST
  • 1바이트 BIST
Interrupt Line (0x3c)
  • 1 바이트 인터럽트 번호로 디바이스 드라이버에서 사용하는 정보이다.
Interrupt Pin (0x3d)
  • 1 바이트 실제 라우팅되는 legacy 인터럽트 라인 번호로 1~4까지 (INTA, INTB, INTC, INTD) 주어질 수 있다. 0인 경우 사용하지 않는다.

 

Type 0 레지스터들

BAR(Base Address Register) (0x10~0x24)
  • 6개의 BAR를 제공한다.
  • PCI I/O, PCI 메모리 공간의 유형과 크기, 위치를 지정하는데 사용된다.
Min_Gnt (0x3e)
  • 1 바이트로 PCI Express에서는 사용되지 않고, 0으로 고정되어 있다.
Max_Lat (0x3f)
  • 바이트로 PCI Express에서는 사용되지 않고, 0으로 고정되어 있다.

 

Type 1 레지스터들

BAR(Base Address Register) (0x10~0x24)
  • 2개의 BAR를 제공한다.
Primary Bus Number (0x18)
  • 1 바이트로 PCI Express에서는 사용되지 않는다.
Secondary Bus Number (0x19)
  • 1 바이트
Subordinate Bus Number (0x1a)
  • 1 바이트
Secondary Latency Timer (0x1b)
  • 1 바이트로 PCI Express에서는 사용되지 않고, 0으로 고정되어 있다.
I/O Base (0x1c)
I/O Limit (0x1d)
Secondary Status Register (0x1e)
  • 2 바이트 두 번째 상태 레지스터

Prefetchable Memory Base
  • 전체 64비트로 PCI Bridge에서 사용된다.
Prefetchable Memory Limit
  • 전체 64비트로 PCI Bridge에서 사용된다.
Capability Pointer (0x34)
  • 1 바이트로 Capability가 있는 경우 첫 Capability offset이 기록되어 있다.
Expansion ROM Base Address (0x38)
  • 4 바이트
Bridge Control (0x3e)
  • 2 바이트로 PCI Bridge에서 사용된다.

 

참고

 

I2C Subsystem -4- (I2C-Mux)

 

I2C-Mux Core

다음 그림은 i2c 버스에 연결된 i2c 멀티플레서를 블럭 다이아그램으로 표현하였다.

 

i2c_mux_core 구조체

include/linux/i2c-mux.h

struct i2c_mux_core {                                                           
        struct i2c_adapter *parent;                                             
        struct device *dev;                                                     
        unsigned int mux_locked:1;                                              
        unsigned int arbitrator:1;                                              
        unsigned int gate:1;                                                    
                                                                                
        void *priv;                                                             
                                                                                
        int (*select)(struct i2c_mux_core *, u32 chan_id);                      
        int (*deselect)(struct i2c_mux_core *, u32 chan_id);                    
                                                                                
        int num_adapters;                                                       
        int max_adapters;                                                       
        struct i2c_adapter *adapter[0];                                         
};
  • *parent
    • 부모 adapter를 가리킨다.
  • *dev
    • 현 멀티플레서 디바이스를 가리킨다.
  • mux_locked
    • mux-lock 오퍼레이션을 선택사용하게 한다.
      • 1인 경우 i2c_mux_lock_ops를 사용한다.
        • I2C_LOCK_ROOT_ADAPTER 플래그를 사용한 경우에만 adapter->lock_ops->lock_bus() 후크 함수를 호출하게 한다.
      • 0인 경우 &i2c_parent_lock_ops를 사용한다.
        • 항상 adapter->lock_ops->lock_bus() 후크 함수를 호출하게 한다.
    • 다음 드라이버들이 지원한다.
      • drivers/iio/gyro/mpu3050-i2c
      • drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
      • drivers/i2c/muxes/i2c-mux-ltc4306.c
      • drivers/media/dvb-frontends/lgdt3306a.c
      • drivers/media/dvb-frontends/si2168.c
      • drivers/media/dvb-frontends/rtl2832.c
  • arbitrator
    • mux-arbitrator 지원하는 경우 1
    • i2c 멀티플렉서가 1:N 형태로 버스를 확장시키는데 반해 N:1 형태로 버스를 합치는 성격의 멀티플렉서이다.
      • 예) pca9541 칩: 2개의 i2c 버스를 1개의 i2c 버스로 합치는 기능을 제공한다.
    • 디바이스 트리의 i2c-mux 노드의 서브 노드로 i2c-arb 노드가 있는 경우이다.
    • 다음 두 드라이버가 지원한다.
      • drivers/i2c/muxes/i2c-mux-pca9541.c
      • drivers/i2c/muxes/i2c-arb-gpio-challenge.c
  • gate
    • mux-gate 지원하는 경우 1
    • 디바이스 트리의 i2c-mux 노드의 서브 노드로 i2c-gate 노드가 있는 경우이다.
    • 다음 두 드라이버가 지원한다.
      • drivers/iio/gyro/mpu3050-i2c
      • drivers/iio/imu/inv_mpu6050/inv_mpu_i2c.c
  • *priv
    • 사용자 private 데이터가 있을 때 사용한다.
  • (*select)
    • i2c-mux에 있는 i2c 버스를 선택할 때 호출되는 후크이다.
  • (*deselect)
    • i2c-mux에 있는 i2c 버스의 사용이 완료한 후 호출되는 후크이다.
  • num_adapters
    • adpater 수
  • max_adapters
    • 최대 adapter 수
  • *adapter[0]
    • 최대 adapter 수 만큼의 adapter 포인터 배열이 할당될 위치이다.

 

i2c_mux_priv 구조체

drivers/i2c/muxes/i2c-mux.c

/* multiplexer per channel data */                                              
struct i2c_mux_priv {                                                           
        struct i2c_adapter adap;                                                
        struct i2c_algorithm algo;                                              
        struct i2c_mux_core *muxc;                                              
        u32 chan_id;                                                            
};
  • adap
    • i2c_adapter가 임베드되어 있다.
  • algo
    • i2c_algorithm이 임베드되어 있다.
  • *muxc
    • i2c mux를 가리킨다.
  • chan_id
    • 채널 번호

 

i2c-mux 할당

i2c_mux_alloc()

drivers/i2c/muxes/i2c-mux.c

struct i2c_mux_core *i2c_mux_alloc(struct i2c_adapter *parent,                  
                                   struct device *dev, int max_adapters,        
                                   int sizeof_priv, u32 flags,                  
                                   int (*select)(struct i2c_mux_core *, u32),   
                                   int (*deselect)(struct i2c_mux_core *, u32)) 
{                                                                               
        struct i2c_mux_core *muxc;                                              
                                                                                
        muxc = devm_kzalloc(dev, sizeof(*muxc)                                  
                            + max_adapters * sizeof(muxc->adapter[0])           
                            + sizeof_priv, GFP_KERNEL);                         
        if (!muxc)                                                              
                return NULL;                                                    
        if (sizeof_priv)                                                        
                muxc->priv = &muxc->adapter[max_adapters];                      
                                                                                
        muxc->parent = parent;                                                  
        muxc->dev = dev;                                                        
        if (flags & I2C_MUX_LOCKED)                                             
                muxc->mux_locked = true;                                        
        if (flags & I2C_MUX_ARBITRATOR)                                         
                muxc->arbitrator = true;                                        
        if (flags & I2C_MUX_GATE)                                               
                muxc->gate = true;                                              
        muxc->select = select;                                                  
        muxc->deselect = deselect;                                              
        muxc->max_adapters = max_adapters;                                      
                                                                                
        return muxc;                                                            
}                                                                               
EXPORT_SYMBOL_GPL(i2c_mux_alloc);

i2c_mux_core를 할당한다.

  • 코드 라인 9~13에서 i2c_mux_core 구조체 + adapter 포인터 수 + private 데이터 크기 만큼의 공간을 할당한다.
  • 코드 라인 15~16에서 muxc의 priv 포인터가 할당한 private 데이터 공간을 가리키게한다.
  • 코드 라인 18~19에서 부모 i2c adapter와 디바이스를 지정한다.
  • 코드 라인 20~25에서 플래그에 해당하는 muxc 멤버(locked, arbtrator, gate) 변수를 설정한다.
  • 코드 라인 26~28에서 (*select) 및 (*deselect) 후크 함수를 지정하고, max_adapters 까지 설정한다.

 

다음 그림은 8개의 adapter를 위한 i2c_mux_core가 준비됨을 알 수 있다.

 

 

mux에 adapter 추가

i2c_mux_add_adapter()

drivers/i2c/muxes/i2c-mux.c -1/3-

int i2c_mux_add_adapter(struct i2c_mux_core *muxc,                              
                        u32 force_nr, u32 chan_id,                              
                        unsigned int class)                                     
{                                                                               
        struct i2c_adapter *parent = muxc->parent;                              
        struct i2c_mux_priv *priv;                                              
        char symlink_name[20];                                                  
        int ret;                                                                
                                                                                
        if (muxc->num_adapters >= muxc->max_adapters) {                         
                dev_err(muxc->dev, "No room for more i2c-mux adapters\n");      
                return -EINVAL;                                                 
        }                                                                       
                                                                                
        priv = kzalloc(sizeof(*priv), GFP_KERNEL);                              
        if (!priv)                                                              
                return -ENOMEM;                                                 
                                                                                
        /* Set up private adapter data */                                       
        priv->muxc = muxc;                                                      
        priv->chan_id = chan_id;                                                
                                                                                
        /* Need to do algo dynamically because we don't know ahead              
         * of time what sort of physical adapter we'll be dealing with.         
         */                                                                     
        if (parent->algo->master_xfer) {                                        
                if (muxc->mux_locked)                                           
                        priv->algo.master_xfer = i2c_mux_master_xfer;           
                else                                                            
                        priv->algo.master_xfer = __i2c_mux_master_xfer;         
        }                                                                       
        if (parent->algo->smbus_xfer) {                                         
                if (muxc->mux_locked)                                           
                        priv->algo.smbus_xfer = i2c_mux_smbus_xfer;             
                else                                                            
                        priv->algo.smbus_xfer = __i2c_mux_smbus_xfer;           
        }                                                                       
        priv->algo.functionality = i2c_mux_functionality;                       
                                                                                
        /* Now fill out new adapter structure */                                
        snprintf(priv->adap.name, sizeof(priv->adap.name),                      
                 "i2c-%d-mux (chan_id %d)", i2c_adapter_id(parent), chan_id);   
        priv->adap.owner = THIS_MODULE;                                         
        priv->adap.algo = &priv->algo;                                          
        priv->adap.algo_data = priv;                                            
        priv->adap.dev.parent = &parent->dev;                                   
        priv->adap.retries = parent->retries;                                   
        priv->adap.timeout = parent->timeout;                                   
        priv->adap.quirks = parent->quirks;                                     
        if (muxc->mux_locked)                                                   
                priv->adap.lock_ops = &i2c_mux_lock_ops;                        
        else                                                                    
                priv->adap.lock_ops = &i2c_parent_lock_ops;                     
                                                                                
        /* Sanity check on class */                                             
        if (i2c_mux_parent_classes(parent) & class)                             
                dev_err(&parent->dev,                                           
                        "Segment %d behind mux can't share classes with ancestors\n",
                        chan_id);                                               
        else                                                                    
                priv->adap.class = class;

adapter를 추가한 후 i2c_mux_core에 연결한다.

  • 코드 라인 10~13에서 이미 최대 adapter 수 만큼 등록한 경우 추가할 수 없다.
  • 코드 라인 15~21에서 i2c_mux_priv를 할당하고 muxc 및 chan_id를 지정한다.
  • 코드 라인 26~31에서 부모 i2c adapter의 i2c 전송 후크가 있는 경우 현재 생성할 adapter의 i2c 전송 후크에 select가 포함된 부모 adapter로의 i2c 전송 함수를 지정한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 32~38에서부모 i2c adapter의 smbus 전송 후크가 있는 경우 현재 생성할 adapter의 smbus 전송 후크에 select가 포함된 부모 adapter로의 smbus 전송 함수를 지정한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 39에서 부모 i2c adapter가 사용하는 functionality를 지정한다.
  • 코드 라인44~50에서 할당한 adapter의 멤버를 설정한다. retries, timeout 및 quirks는 부모 adapter가 사용하는 값을 그대로 적용한다.
  • 코드 라인 51~54에서 할당한 adapter에서 사용할 lock operation은 부모 adapter가 사용하는 lock 버스를 사용한다. mux_locked 여부에 따라 2개가 준비되었다.
  • 코드 라인 57~62에서 adapter의 클래스를 지정한다. 만일 클래스와 부모 adapter의 클래스가 동일한 경우 에러 메시지를 출력한다.

 

drivers/i2c/muxes/i2c-mux.c -2/3-

.       /*                                                                      
         * Try to populate the mux adapter's of_node, expands to                
         * nothing if !CONFIG_OF.                                               
         */                                                                     
        if (muxc->dev->of_node) {                                               
                struct device_node *dev_node = muxc->dev->of_node;              
                struct device_node *mux_node, *child = NULL;                    
                u32 reg;                                                        
                                                                                
                if (muxc->arbitrator)                                           
                        mux_node = of_get_child_by_name(dev_node, "i2c-arb");   
                else if (muxc->gate)                                            
                        mux_node = of_get_child_by_name(dev_node, "i2c-gate");  
                else                                                            
                        mux_node = of_get_child_by_name(dev_node, "i2c-mux");   
                                                                                
                if (mux_node) {                                                 
                        /* A "reg" property indicates an old-style DT entry */  
                        if (!of_property_read_u32(mux_node, "reg", &reg)) {     
                                of_node_put(mux_node);                          
                                mux_node = NULL;                                
                        }                                                       
                }                                                               
                                                                                
                if (!mux_node)                                                  
                        mux_node = of_node_get(dev_node);                       
                else if (muxc->arbitrator || muxc->gate)                        
                        child = of_node_get(mux_node);                          
                                                                                
                if (!child) {                                                   
                        for_each_child_of_node(mux_node, child) {               
                                ret = of_property_read_u32(child, "reg", &reg); 
                                if (ret)                                        
                                        continue;                               
                                if (chan_id == reg)                             
                                        break;                                  
                        }                                                       
                }                                                               
                                                                                
                priv->adap.dev.of_node = child;                                 
                of_node_put(mux_node);                                          
        }
  • 코드 라인 5~15에서 디바이스 트리에 mux 디바이스 노드가 있는 경우 mux의 arbitratory, gate 유무에 따라 mux 디바이스의 서브 노드에서 mux 노드를 알아온다. 각각 mux 디바이스 노드의 서브 노드에 “i2c-arb”, “i2c-gate”, “i2c-mux” 이름으로 노드가 있으면 mux_node로 알아온다.
  • 코드 라인 17~23에서 mux_node에 “reg” 속성이 존재하면 old-style DT 이다. 이러한 경우 mux_node를 null로 설정한다.
    • arbitratory 및 gate 타입의 경우 i2c 버스가 1개이다. 따라서 adapter 번호가 지정될 필요 없다.
  • 코드 라인 25~26에서 mux_node가 null인 경우 디바이스 노드를 mux_node로 사용한다.
  • 코드 라인 27~28에서 arbitrator 또는 gate 타입의 경우 mux_node를 child에 대입한다.
  • 코드 라인 30~38에서 arbitrator, gate에 대한 노드가 없는 경우 mux 노드의 child 노드의 “reg” 속성 값을 읽어서 chan_id로 사용한다.
  • 코드 라인 40~41에서 adapter의 노드를 지정한다.

 

drivers/i2c/muxes/i2c-mux.c -3/3-

        /*                                                                      
         * Associate the mux channel with an ACPI node.                         
         */                                                                     
        if (has_acpi_companion(muxc->dev))                                      
                acpi_preset_companion(&priv->adap.dev,                          
                                      ACPI_COMPANION(muxc->dev),                
                                      chan_id);                                 
                                                                                
        if (force_nr) {                                                         
                priv->adap.nr = force_nr;                                       
                ret = i2c_add_numbered_adapter(&priv->adap);                    
                if (ret < 0) {                                                  
                        dev_err(&parent->dev,                                   
                                "failed to add mux-adapter %u as bus %u (error=%d)\n",
                                chan_id, force_nr, ret);                        
                        goto err_free_priv;                                     
                }                                                               
        } else {                                                                
                ret = i2c_add_adapter(&priv->adap);                             
                if (ret < 0) {                                                  
                        dev_err(&parent->dev,                                   
                                "failed to add mux-adapter %u (error=%d)\n",    
                                chan_id, ret);                                  
                        goto err_free_priv;                                     
                }                                                               
        }                                                                       
                                                                                
        WARN(sysfs_create_link(&priv->adap.dev.kobj, &muxc->dev->kobj,          
                               "mux_device"),                                   
             "can't create symlink to mux device\n");                           
                                                                                
        snprintf(symlink_name, sizeof(symlink_name), "channel-%u", chan_id);    
        WARN(sysfs_create_link(&muxc->dev->kobj, &priv->adap.dev.kobj,          
                               symlink_name),                                   
             "can't create symlink for channel %u\n", chan_id);                 
        dev_info(&parent->dev, "Added multiplexed i2c bus %d\n",                
                 i2c_adapter_id(&priv->adap));                                  
                                                                                
        muxc->adapter[muxc->num_adapters++] = &priv->adap;                      
        return 0;                                                               
                                                                                
err_free_priv:                                                                  
        kfree(priv);                                                            
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL_GPL(i2c_mux_add_adapter);
  • 코드 라인 4~7에서 acpi 노드와 chan_id를 연결한다.
  • 코드 라인 9~17에서 adapter 번호를 지정하여 adapter를 추가한다.
  • 코드 라인 18~26에서 adapter 번호를 자동 할당하여 adapter를 추가한다.
  • 코드 라인 28~31에서 adapter 디렉토리에 mux_device 심볼링크를 생성하여 mux 디렉토리를 가리킨다.
    • /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/i2c-1/mux_device ->  /sys/devices/platform/660a0000.i2c/i2c-0/0-0070
  • 코드 라인 33~36에서 mux 디렉토리에 “channel-N” 라는 심볼링크를 생성하여 adapter 디렉토리를 가리킨다.
    • /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/channle-0 ->  /sys/devices/platform/660a0000.i2c/i2c-0/0-0070/i2c-1
  • 코드 라인 i2c_mux_core의 adapter 포인터가 생성된 i2c_mux_priv의 adapter를 가리키도록 한다.

 

다음 그림은 i2c_mux_add_adapter() 함수가 4번 호출되어 4개의 i2c 채널(i2c_mux_priv)이 생성되고 그 내부에 adapter와 algorithm이 있는 것을 확인할 수 있다.

 

다음 그림과 같이 i2c-mux의 경우 child 노드의 파란색 reg 속성 값이 adapter 번호가 된다. 즉 i2c 버스 번호가 된다. 그러나 적색으로된 i2c-arb 또는 i2c-gate 노드의 경우 i2c 버스가 하나이므로 reg 속성이 주어지지 않는 것을 알 수 있다.

 

채널 셀렉트 및 전송

전송 Core

다음 그림은 i2c_transfer() 함수를 사용 시 버스 락 처리 및 i2c-mux의 채널 선택과 전송을 수행하는 과정을 보여준다.

 

i2c-mux core 전송

__i2c_mux_master_xfer()

drivers/i2c/i2c-mux.c

static int __i2c_mux_master_xfer(struct i2c_adapter *adap,                      
                                 struct i2c_msg msgs[], int num)                
{                                                                               
        struct i2c_mux_priv *priv = adap->algo_data;                            
        struct i2c_mux_core *muxc = priv->muxc;                                 
        struct i2c_adapter *parent = muxc->parent;                              
        int ret;                                                                
                                                                                
        /* Switch to the right mux port and perform the transfer. */            
                                                                                
        ret = muxc->select(muxc, priv->chan_id);                                
        if (ret >= 0)                                                           
                ret = __i2c_transfer(parent, msgs, num);                        
        if (muxc->deselect)                                                     
                muxc->deselect(muxc, priv->chan_id);                            
                                                                                
        return ret;                                                             
}

adapter에 해당하는 채널을 선택한 채로 부모 adapter를 통해 메시지를 전송한다. 사용이 완료되면 deselect 하여야 한다.

 

i2c-mux 버스 락 core

i2c_parent_lock_bus()

drivers/i2c/i2c-mux.c

static void i2c_parent_lock_bus(struct i2c_adapter *adapter,                    
                                unsigned int flags)                             
{                                                                               
        struct i2c_mux_priv *priv = adapter->algo_data;                         
        struct i2c_adapter *parent = priv->muxc->parent;                        
                                                                                
        rt_mutex_lock(&parent->mux_lock);                                       
        i2c_lock_bus(parent, flags);                                            
}

mux 뮤텍스 락을 건채로 부모 adapter 버스 락을 건다.

 

i2c_lock_bus()

include/linux/i2c.h

/**                                                                             
 * i2c_lock_bus - Get exclusive access to an I2C bus segment                    
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT   
 *      locks only this branch in the adapter tree                              
 */                                                                             
static inline void                                                              
i2c_lock_bus(struct i2c_adapter *adapter, unsigned int flags)                   
{                                                                               
        adapter->lock_ops->lock_bus(adapter, flags);                            
}

adapter에 해당하는 버스 락 오퍼레이션을 수행한다.

 

채널 선택 (for 954x chip)

pca954x_select_chan()

drivers/i2c/muxes/i2c-mux-pca954x.c

static int pca954x_select_chan(struct i2c_mux_core *muxc, u32 chan)             
{                                                                               
        struct pca954x *data = i2c_mux_priv(muxc);                              
        struct i2c_client *client = data->client;                               
        const struct chip_desc *chip = data->chip;                              
        u8 regval;                                                              
        int ret = 0;                                                            
                                                                                
        /* we make switches look like muxes, not sure how to be smarter */      
        if (chip->muxtype == pca954x_ismux)                                     
                regval = chan | chip->enable;                                   
        else                                                                    
                regval = 1 << chan;                                             
                                                                                
        /* Only select the channel if its different from the last channel */    
        if (data->last_chan != regval) {                                        
                ret = pca954x_reg_write(muxc->parent, client, regval);          
                data->last_chan = ret < 0 ? 0 : regval;                         
        }                                                                       
                                                                                
        return ret;                                                             
}

i2c-mux의 요청받은 채널 번호에 해당하는 i2c 버스를 HW 선택한다.

  • 채널을 선택하기 위해서는 부모 i2c adapter를 통해 i2c-mux 장치에게 1바이트의 채널 선택 value를 전송해야 한다. 즉 일반적인 1 바이트의 i2c 메시지를 전송한다.
  • pca954x 시리즈는 i2c 멀티플렉서와 i2c 스위치 두 타입이 있다. 타입에 따라 전송하는 값은 다음과 같다.
    • i2c 멀티 플렉서
      • 채널 번호 + enable 비트
      • enable 비트는 다음과 같이 결정된다.
        • 2채널 멀티플렉서 -> bit1
        • 4채널 멀티플렉서 -> bit2
        • 8채널 멀티플렉서 -> bit3
    • i2c 스위치
      • 각 비트가 하나의 채널을 담당한다.
      • 하나의 채널만 선택하려면
        • 1 << 채널 번호

 

pca954x_reg_write()

drivers/i2c/muxes/i2c-mux-pca954x.c

/* Write to mux register. Don't use i2c_transfer()/i2c_smbus_xfer()             
   for this as they will try to lock adapter a second time */                   
static int pca954x_reg_write(struct i2c_adapter *adap,                          
                             struct i2c_client *client, u8 val)                 
{                                                                               
        int ret = -ENODEV;                                                      
                                                                                
        if (adap->algo->master_xfer) {                                          
                struct i2c_msg msg;                                             
                char buf[1];                                                    
                                                                                
                msg.addr = client->addr;                                        
                msg.flags = 0;                                                  
                msg.len = 1;                                                    
                buf[0] = val;                                                   
                msg.buf = buf;                                                  
                ret = __i2c_transfer(adap, &msg, 1);                            
                                                                                
                if (ret >= 0 && ret != 1)                                       
                        ret = -EREMOTEIO;                                       
        } else {                                                                
                union i2c_smbus_data data;                                      
                ret = adap->algo->smbus_xfer(adap, client->addr,                
                                             client->flags,                     
                                             I2C_SMBUS_WRITE,                   
                                             val, I2C_SMBUS_BYTE, &data);       
        }                                                                       
                                                                                
        return ret;                                                             
}

i2c 전송 후크가 있는 경우 i2c 버스에 인자로 요청한 1 바이트 값을 전송한다. i2c 전송 후크가 없는 경우 smbus에 인자로 요청한 1 바이트 값을 전송한다.

 

필립스 i2c 멀티플렉서/스위치 pca954x 시리즈

pca9541x 제품 종류

가장 많이 사용되는 필립스 반도체의 i2c-mux 제품들 중 954X 시리즈를 기능별로 구분할 수 있는 테이블이다.

 

i2c-스위치 pca9549 블록 다이어그램

  • 8개 채널을 각각 선택할 수 있다.
  • hw reset 핀이 지원된다.
  • a0~a2 핀을 사용하여 주소를 설정할 수 있다.

 

주소 설정

각 제품마다 하위 2~4개의 비트를 설정하여 주소를 설정할 수 있다.

  • 예) pca9545a의 경우 0x70 ~ 0x73까지 4개의 주소를 설정하여 사용할 수 있다.

 

채널 선택(select)

다음과 같이 채널을 선택한다.

  • 4채널 i2c 멀티 플렉서 pca9544a의 경우
    • 채널 선택: bit0 ~ bit1
    • enable bit: bit2
    • 예) 0x6 -> 채널 2 선택
  • 2채널 i2c 스위치 pca9543a의 경우
    • 채널 선택: bit0 ~ bit3
    • 예) 0x3 -> 채널 0과 채널 1이 동시 선택

 

다음 그림은 some device와 통신하기 위해 i2c-mux 칩인 pca9545a 디바이스에 해당하는 주소 0x72로 0x8 값을 전송하여 #3번 채널을 선택하는 모습을 보여준다.

 

참고

 

I2C Subsystem -3- (Transfer)

<kernel v4.14>

전송에 관여하는 다음 항목들에 대해 알아본다.

  • I2C Quirks
  • I2C Bus Recovery
  • I2C Bus Lock
  • I2C Transfer

 

I2C Quirks

Kernel v4.1-rc1에서 추가된 기능으로 특정 i2c 호스트 컨트롤러들에서 HW 전송 제한이 걸려 있는 경우 이에 대한 전송 제한 체크를 수행할 수 있도록 하였다.

 

I2C Quirks 체크

i2c_check_for_quirks()

drivers/i2c/base/i2c-core-base.c

static int i2c_check_for_quirks(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{                                                                               
        const struct i2c_adapter_quirks *q = adap->quirks;                      
        int max_num = q->max_num_msgs, i;                                       
        bool do_len_check = true;                                               
                                                                                
        if (q->flags & I2C_AQ_COMB) {                                           
                max_num = 2;                                                    
                                                                                
                /* special checks for combined messages */                      
                if (num == 2) {                                                 
                        if (q->flags & I2C_AQ_COMB_WRITE_FIRST && msgs[0].flags & I2C_M_RD)
                                return i2c_quirk_error(adap, &msgs[0], "1st comb msg must be write");
                                                                                
                        if (q->flags & I2C_AQ_COMB_READ_SECOND && !(msgs[1].flags & I2C_M_RD))
                                return i2c_quirk_error(adap, &msgs[1], "2nd comb msg must be read");
                                                                                
                        if (q->flags & I2C_AQ_COMB_SAME_ADDR && msgs[0].addr != msgs[1].addr)
                                return i2c_quirk_error(adap, &msgs[0], "comb msg only to same addr");
                                                                                
                        if (i2c_quirk_exceeded(msgs[0].len, q->max_comb_1st_msg_len))
                                return i2c_quirk_error(adap, &msgs[0], "msg too long");
                                                                                
                        if (i2c_quirk_exceeded(msgs[1].len, q->max_comb_2nd_msg_len))
                                return i2c_quirk_error(adap, &msgs[1], "msg too long");
                                                                                
                        do_len_check = false;                                   
                }                                                               
        }                                                                       
                                                                                
        if (i2c_quirk_exceeded(num, max_num))                                   
                return i2c_quirk_error(adap, &msgs[0], "too many messages");    
                                                                                
        for (i = 0; i < num; i++) {                                             
                u16 len = msgs[i].len;                                          
                                                                                
                if (msgs[i].flags & I2C_M_RD) {                                 
                        if (do_len_check && i2c_quirk_exceeded(len, q->max_read_len))
                                return i2c_quirk_error(adap, &msgs[i], "msg too long");
                } else {                                                        
                        if (do_len_check && i2c_quirk_exceeded(len, q->max_write_len))
                                return i2c_quirk_error(adap, &msgs[i], "msg too long");
                }                                                               
        }                                                                       
                                                                                
        return 0;                                                               
}

송/수신할 메시지에 대해 메시지 수 및 길이 제한을 체크한다. 성공 시 0을 반환한다.

  • 코드 라인  7~29에서 I2C_AQ_COMB 플래그를 사용한 combined 메시지인 경우 2개의 메시지만을 송/수신할 수 있는데 특별히 다음과 같은 조건인 경우 에러 메시지를 출력하고 에러를 반환한다.
    • 첫 번째 메시지가 read인 경우
    • 두 번째 메시지가 read가 아닌 경우
    • 첫 번째와 두 번째 메시지의 타겟 주소가 동일하지 않는 경우
    • 첫 번째 메시지의 길이가 quirks에서 지정한 첫 번째 메시지보다 큰 경우
    • 두 번째 메시지의 길이가 quirks에서 지정한 두 번째 메시지보다 큰 경우
  • 코드 라인 31~32에서 인자로 요청한 일반 메시지 수가 quirks에서 지정한 메시지 수 제한을 초과하는 경우 에러 메시지를 출력하고 에러를 반환한다.
    • 단 quirks에 메시지 수가 지정되지 않은 경우 제한 초과 체크하지 않는다.
    • #define i2c_quirk_exceeded(val, quirk)    ((quirk) && ((val) > (quirk)))
  • 코드 라인 34~44에서 모든 메시지 수 만큼 순회하며 read 및 write 메시지의 경우 quirks에서 제한한 길이를 초과하는 경우 에러 메시지를 출력하고 에러를 반환한다.

 

다음 그림은 quirks 체크를 수행하는 모습을 보여준다.

 

i2c_adapter_quirks 구조체

/**                                                                             
 * struct i2c_adapter_quirks - describe flaws of an i2c adapter                 
 * @flags: see I2C_AQ_* for possible flags and read below                       
 * @max_num_msgs: maximum number of messages per transfer                       
 * @max_write_len: maximum length of a write message                            
 * @max_read_len: maximum length of a read message                              
 * @max_comb_1st_msg_len: maximum length of the first msg in a combined message 
 * @max_comb_2nd_msg_len: maximum length of the second msg in a combined message
 *                                                                              
 * Note about combined messages: Some I2C controllers can only send one message 
 * per transfer, plus something called combined message or write-then-read.     
 * This is (usually) a small write message followed by a read message and       
 * barely enough to access register based devices like EEPROMs. There is a flag 
 * to support this mode. It implies max_num_msg = 2 and does the length checks  
 * with max_comb_*_len because combined message mode usually has its own        
 * limitations. Because of HW implementations, some controllers can actually do 
 * write-then-anything or other variants. To support that, write-then-read has  
 * been broken out into smaller bits like write-first and read-second which can 
 * be combined as needed.                                                       
 */
struct i2c_adapter_quirks {                                                     
        u64 flags;                                                              
        int max_num_msgs;                                                       
        u16 max_write_len;                                                      
        u16 max_read_len;                                                       
        u16 max_comb_1st_msg_len;                                               
        u16 max_comb_2nd_msg_len;                                               
};
  • flags
    • I2C_AQ_COMB(1)
      • 메시지의 전송을 2개로 제한하고, max_comb_1st_msg_len 및 max_comb_2nd_msg_len 길이 체크를 사용한다.
    • I2C_AQ_COMB_WRITE_FIRST(2)
      • 첫 번째 combined 메시지를 먼저 write 해야한다.
    • I2C_AQ_COMB_READ_SECOND(4)
      • 두 번째 combined 메시지를 read 해야 한다.
    • I2C_AQ_COMB_SAME_ADDR(8)
      • 두 개의 메시지의 타겟 주소가 반드시 동일해야 한다.
    • I2C_AQ_COMB_WRITE_THEN_READ(15)
      • 같은 디바이스에 주소로 한 번 쓴 후, 다시 한 번 읽는다.
      • 위의 4개 플래그를 동시에 사용하여 편리하게 사용할 수 있도록 하였다.
    • I2C_AQ_NO_CLK_STRETCH(16)
      • clock stretching을 지원하지 않는다.
  • max_num_msgs
    • 전송할 메시지의 최대 수
  • max_write_len
    • 최대 전송 길이
  • max_read_len
    • 최대 수신 길이
  • max_comb_1st_msg_len
    • 첫 번째 메시지 최대 길이
  • max_comb_2nd_msg_len
    • 두 번째 메시지 최대 길이

 

I2C Bus Recovery

I2C 버스 리커버리는 kernel v3.10-rc1에서 소개되었다.

 

Bus Clear

i2c 프로토콜 규격의 섹션 3.1.16에서 “Bus clear”라는 항목이 구현되었다.

  • 드물게 클록(SCL) 또는 데이터 라인(SDA)가 고정되어 움직이지 않는 경우가 있다. 이러한 경우 다음과 같이 처리한다.
    • A) 클록 (SCL)이 LOW로 고정된 경우
      • I2C 디바이스에 HW 리셋 신호를 사용하여 버스를 리셋한다.
      • 만일 I2C 디바이스에 HW 리셋 입력이 없으면 디바이스 전원을 껏다 켠다.
    • B) 데이터 라인 (SDA)이 LOW로 고정된 경우
      • 버스 마스터는 9 개의 클럭 펄스를 전송해야 한다.
      • 버스를 LOW로 유지 한 장치는 9 클럭 내에서 이에 반응하여 해제해야 한다.
      • 여전히 동작하지 않는 경우 A)와 같이 처리한다.

 

i2c 프로토콜 규격 – 섹션 3.1.16에서 “Bus clear” 원문
3.1.16 Bus clear

In the unlikely event where the clock (SCL) is stuck LOW, the preferential procedure is to reset the bus using the HW reset signal if your I2C devices have HW reset inputs. If the I2C devices do not have HW reset inputs, cycle power to the devices to activate the mandatory internal Power-On Reset (POR) circuit.

If the data line (SDA) is stuck LOW, the master should send nine clock pulses. The device that held the bus LOW should release it sometime within those nine clocks. If not, then
use the HW reset or cycle power to clear the bus.

 

i2c Bus Recovery 준비

i2c 버스를 리커버리하는 방법은 다음과 같은 방법들이 있다.

  • 커널에 미리 준비된 i2c_generic_scl_recovery() 함수 사용
  • 커널에 미리 준비된 i2c_generic_gpio_recovery() 함수 사용
  • custom 리커버리 함수를 사용하여 직접 HW 설정

 

i2c_init_recovery()

drivers/i2c/base/i2c-core-base.c

static void i2c_init_recovery(struct i2c_adapter *adap)                         
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        char *err_str;                                                          
                                                                                
        if (!bri)                                                               
                return;                                                         
                                                                                
        if (!bri->recover_bus) {                                                
                err_str = "no recover_bus() found";                             
                goto err;                                                       
        }                                                                       
                                                                                
        /* Generic GPIO recovery */                                             
        if (bri->recover_bus == i2c_generic_gpio_recovery) {                    
                if (!gpio_is_valid(bri->scl_gpio)) {                            
                        err_str = "invalid SCL gpio";                           
                        goto err;                                               
                }                                                               
                                                                                
                if (gpio_is_valid(bri->sda_gpio))                               
                        bri->get_sda = get_sda_gpio_value;                      
                else                                                            
                        bri->get_sda = NULL;                                    
                                                                                
                bri->get_scl = get_scl_gpio_value;                              
                bri->set_scl = set_scl_gpio_value;                              
        } else if (bri->recover_bus == i2c_generic_scl_recovery) {              
                /* Generic SCL recovery */                                      
                if (!bri->set_scl || !bri->get_scl) {                           
                        err_str = "no {get|set}_scl() found";                   
                        goto err;                                               
                }                                                               
        }                                                                       
                                                                                
        return;                                                                 
 err:                                                                           
        dev_err(&adap->dev, "Not using recovery: %s\n", err_str);               
        adap->bus_recovery_info = NULL;                                         
}

i2c 버스 리커버리를 준비한다.

  • 코드 라인 9~12에서 리커버리용 후크가 설정되지 않은 경우 함수를 빠져나간다.
  • 코드 라인 15~27에서 gpio를 사용한 리커버리인 경우 scl_gpio 및 sda_gpio 핀 번호 값이 이상 없는지 확인한다. 그런 후 get_scl, set_scl 및 get_sda의 디폴트 함수를 설정 한다.
  • 코드 라인 28~34에서 scl을 사용한 리커버리인 경우 set_scl 및 get_scl 후크가 지정되지 않은 경우 에러를 출력하고 함수를 빠져나간다.

 

다음 그림은 i2c_init_recovery() 함수에서 3가지 타입의 (*recover_bus) 후크에 대해 처리하는 과정을 보여준다.

  • A) SCL 리커버리: (*get_scl) 및 (*set_scl) 후크 함수 체크
  • B) gpio 리커버리: (*get_scl), (*set_scl) 및 (*get_sda) 후크에 디폴트 함수 지정 및 scl_gpio, sda_gpio 핀 번호 체크

 

i2c_bus_recovery_info 구조체

include/linux/i2c.h

/**                                                                             
 * struct i2c_bus_recovery_info - I2C bus recovery information                  
 * @recover_bus: Recover routine. Either pass driver's recover_bus() routine, or
 *      i2c_generic_scl_recovery() or i2c_generic_gpio_recovery().              
 * @get_scl: This gets current value of SCL line. Mandatory for generic SCL     
 *      recovery. Used internally for generic GPIO recovery.                    
 * @set_scl: This sets/clears SCL line. Mandatory for generic SCL recovery. Used
 *      internally for generic GPIO recovery.                                   
 * @get_sda: This gets current value of SDA line. Optional for generic SCL      
 *      recovery. Used internally, if sda_gpio is a valid GPIO, for generic GPIO
 *      recovery.                                                               
 * @prepare_recovery: This will be called before starting recovery. Platform may
 *      configure padmux here for SDA/SCL line or something else they want.     
 * @unprepare_recovery: This will be called after completing recovery. Platform 
 *      may configure padmux here for SDA/SCL line or something else they want. 
 * @scl_gpio: gpio number of the SCL line. Only required for GPIO recovery.     
 * @sda_gpio: gpio number of the SDA line. Only required for GPIO recovery.     
 */
struct i2c_bus_recovery_info {                                                  
        int (*recover_bus)(struct i2c_adapter *);                               
                                                                                
        int (*get_scl)(struct i2c_adapter *);                                   
        void (*set_scl)(struct i2c_adapter *, int val);                         
        int (*get_sda)(struct i2c_adapter *);                                   
                                                                                
        void (*prepare_recovery)(struct i2c_adapter *);                         
        void (*unprepare_recovery)(struct i2c_adapter *);                       
                                                                                
        /* gpio recovery */                                                     
        int scl_gpio;                                                           
        int sda_gpio;                                                           
};
  • (*recover_bus)
    • 이 후크 함수를 호출하여 i2c 리커버리를 시도한다.
      • scl 또는 sda가 low 상태에서 벗어나지 못하면 이 함수를 호출하여 9번의 클럭을 만들면서 sda가 high 상태로 풀려나는지 체크한다.
  • (*get_scl)
    • 시리얼 클럭(scl) 상태를 알아온다. 1/0
  • (*set_scl)
    • 시리얼 클럭(scl) 상태를 설정한다. 1/0
  •  (*get_sda)
    • 시리얼 데이터(sda) 상태를 알아온다. 1/0
  • (*prepare_recovery)
    • 버스 리커버리를 수행하기 전에 먼저 처리할 일을 지정한다.
  • (*prepare_recovery)
    • 버스 리커버리를 완료한 후 처리할 일을 지정한다.
  • scl_gpio
    • gpio를 사용하여 시리얼 클럭(scl)을 만들 gpio  핀 번호
  • sda_gpio
    • gpio를 사용하여 시리얼 데이터(sda) 값을 읽을 핀 번호

 

i2c Bus Recovery 동작

다음 그림은 i2c 버스가 stuck 상태일 때 100Khz의 클럭 펄스를 9번 만들어 i2c 버스가 리커버리를 시도하는 모습을 보여준다.

  • 100Khz 클럭을 만들기 위해 매 사이클마다 low에서 5ms, high에서 5ms씩 delay해야 한다.

 

i2c_recover_bus()

drivers/i2c/base/i2c-core-base.c

int i2c_recover_bus(struct i2c_adapter *adap)                                   
{                                                                               
        if (!adap->bus_recovery_info)                                           
                return -EOPNOTSUPP;                                             
                                                                                
        dev_dbg(&adap->dev, "Trying i2c bus recovery\n");                       
        return adap->bus_recovery_info->recover_bus(adap);                      
}                                                                               
EXPORT_SYMBOL_GPL(i2c_recover_bus);

i2c 버스 리커버리를 시도한다.

 

i2c_generic_scl_recovery()

drivers/i2c/base/i2c-core-base.c

int i2c_generic_scl_recovery(struct i2c_adapter *adap)                          
{                                                                               
        return i2c_generic_recovery(adap);                                      
}                                                                               
EXPORT_SYMBOL_GPL(i2c_generic_scl_recovery);

scl 핀에 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다.

 

i2c_generic_recovery()

drivers/i2c/base/i2c-core-base.c

/*                                                                              
 * We are generating clock pulses. ndelay() determines durating of clk pulses.  
 * We will generate clock with rate 100 KHz and so duration of both clock levels
 * is: delay in ns = (10^6 / 100) / 2                                           
 */                                                                             
#define RECOVERY_NDELAY         5000                                            
#define RECOVERY_CLK_CNT        9                                               
                                                                                
static int i2c_generic_recovery(struct i2c_adapter *adap)                       
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        int i = 0, val = 1, ret = 0;                                            
                                                                                
        if (bri->prepare_recovery)                                              
                bri->prepare_recovery(adap);                                    
                                                                                
        bri->set_scl(adap, val);                                                
        ndelay(RECOVERY_NDELAY);                                                
                                                                                
        /*                                                                      
         * By this time SCL is high, as we need to give 9 falling-rising edges  
         */                                                                     
        while (i++ < RECOVERY_CLK_CNT * 2) {                                    
                if (val) {                                                      
                        /* Break if SDA is high */                              
                        if (bri->get_sda && bri->get_sda(adap))                 
                                        break;                                  
                        /* SCL shouldn't be low here */                         
                        if (!bri->get_scl(adap)) {                              
                                dev_err(&adap->dev,                             
                                        "SCL is stuck low, exit recovery\n");   
                                ret = -EBUSY;                                   
                                break;                                          
                        }                                                       
                }                                                               
                                                                                
                val = !val;                                                     
                bri->set_scl(adap, val);                                        
                ndelay(RECOVERY_NDELAY);                                        
        }                                                                       
                                                                                
        if (bri->unprepare_recovery)                                            
                bri->unprepare_recovery(adap);                                  
                                                                                
        return ret;                                                             
}

scl 핀에 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다.

  • 코드 라인 14~15에서 버스 리커버리를 시도하기 전에 먼저 수행해야 할 일을 처리한다.
  • 코드 라인 17~41에서 9번 루프를 돌며 scl을 high 상태로 설정하고, sda 상태를 읽어본다. sda 상태가 high가 되면 리커버리가 되었으므로 루프를 벗어나다. 그렇지 않은 경우 scl 상태가 변경이 잘 되었는지 확인해본다. high 상태가 아니면 scl이 고정되어 stuck이 걸렸으므로 -EBUSY 에러를 반환한다. SCL 상태가 잘 변경이 되는 경우에는 계속하여 scl을 low 및 high로 변경해가면서 루프를 돌며 리커버리를 시도한다.
  • 코드 라인 43~44에서 버스 리커버리를 완료한 후 수행해야 할 일을 처리한다.

 

i2c_generic_gpio_recovery()

drivers/i2c/base/i2c-core-base.c

int i2c_generic_gpio_recovery(struct i2c_adapter *adap)                         
{                                                                               
        int ret;                                                                
                                                                                
        ret = i2c_get_gpios_for_recovery(adap);                                 
        if (ret)                                                                
                return ret;                                                     
                                                                                
        ret = i2c_generic_recovery(adap);                                       
        i2c_put_gpios_for_recovery(adap);                                       
                                                                                
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL_GPL(i2c_generic_gpio_recovery);

gpio 모드로 변경하여 scl을 준비하고 9번의 클럭을 만들어 i2c 버스 리커버리를 시도한다. 처리한 후 다시 gpio 모드를 해제한다.

  • pinmux에 i2c 기능과 gpio 기능이 모두 있는 경우 i2c 기능으로 동작하다가 리커버리가 필요할 때 gpio 모드로 전환하여 리커버리를 수행할 수 있다.

 

다음 그림은 pinmux를 통해 i2c 펑션 모드로 사용하다가 i2c 버스 리커버리를 시도하는 모습을 보여준다.

  • gpio 펑션 모드로 전환 시켜 i2c 버스 리커버리를 하는 것을 알 수 있다.

 

i2c_get_gpios_for_recovery()

drivers/i2c/base/i2c-core-base.c

static int i2c_get_gpios_for_recovery(struct i2c_adapter *adap)                 
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
        struct device *dev = &adap->dev;                                        
        int ret = 0;                                                            
                                                                                
        ret = gpio_request_one(bri->scl_gpio, GPIOF_OPEN_DRAIN |                
                        GPIOF_OUT_INIT_HIGH, "i2c-scl");                        
        if (ret) {                                                              
                dev_warn(dev, "Can't get SCL gpio: %d\n", bri->scl_gpio);       
                return ret;                                                     
        }                                                                       
                                                                                
        if (bri->get_sda) {                                                     
                if (gpio_request_one(bri->sda_gpio, GPIOF_IN, "i2c-sda")) {     
                        /* work without SDA polling */                          
                        dev_warn(dev, "Can't get SDA gpio: %d. Not using SDA polling\n",
                                        bri->sda_gpio);                         
                        bri->get_sda = NULL;                                    
                }                                                               
        }                                                                       
                                                                                
        return ret;                                                             
}

scl_gpio 번 핀을 scl로 사용하기 위해 open-drain 설정으로 초기 high 출력 상태로 요청한다. (*get_sda) 후크가 사용되는 경우 sda_gpio 번 핀을 sda로 input mode로 사용 요청한다.

 

i2c_put_gpios_for_recovery()

drivers/i2c/base/i2c-core-base.c

static void i2c_put_gpios_for_recovery(struct i2c_adapter *adap)                
{                                                                               
        struct i2c_bus_recovery_info *bri = adap->bus_recovery_info;            
                                                                                
        if (bri->get_sda)                                                       
                gpio_free(bri->sda_gpio);                                       
                                                                                
        gpio_free(bri->scl_gpio);                                               
}

리커버리를 종료하였으므로 scl_gpio 및 sda_gpio 번 핀을 gpio 모드에서 해제한다.

 

gpio 리커버리에서 사용하는 세 함수들

get_scl_gpio_value()

drivers/i2c/base/i2c-core-base.c

static int get_scl_gpio_value(struct i2c_adapter *adap)                         
{                                                                               
        return gpio_get_value(adap->bus_recovery_info->scl_gpio);               
}

scl_gpio 번 핀의 값을 알아온다.

 

set_scl_gpio_value()

drivers/i2c/base/i2c-core-base.c

static void set_scl_gpio_value(struct i2c_adapter *adap, int val)               
{                                                                               
        gpio_set_value(adap->bus_recovery_info->scl_gpio, val);                 
}

scl_gpio 번 핀에 값을 출력한다.

 

get_sda_gpio_value()

drivers/i2c/base/i2c-core-base.c

static int get_sda_gpio_value(struct i2c_adapter *adap)                         
{                                                                               
        return gpio_get_value(adap->bus_recovery_info->sda_gpio);               
}

sda_gpio 번 핀의 값을 알아온다.

 

 

I2C 전송 및 버스 락

I2C 버스 락

i2c 호스트 컨트롤러를 통해 i2c 버스에 데이터를 송/수신할 때 먼저 버스를 점유하기 위해 i2c 버스 락을 걸고 사용한다. 이 때 대부분의 호스트 컨트롤러의 adapter 인터페이스는 lock_ops가 주어지지 않았을 때 디폴트로 adapter의 bus_lock 뮤텍스를 사용한다.

 

다음 그림은 i2c 전송 전에 디폴트 i2c 버스 락 오퍼레이션인 adapter의 bus_lock 뮤텍스를 사용하는 것을 보여준다.

  • i2c-mux 호스트 컨트롤러를 사용할 때 채널별로 있는 adapter 들의 lock 오퍼레이션은 별도로 지정하여 사용한다.

 

i2c_adapter_lock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_lock_bus - Get exclusive access to an I2C bus segment            
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER locks the root i2c adapter, I2C_LOCK_SEGMENT   
 *      locks only this branch in the adapter tree                              
 */                                                                             
static void i2c_adapter_lock_bus(struct i2c_adapter *adapter,                   
                                 unsigned int flags)                            
{                                                                               
        rt_mutex_lock(&adapter->bus_lock);                                      
}

adapter에 있는 버스 뮤텍스 락을 건다.

 

i2c_adapter_trylock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_trylock_bus - Try to get exclusive access to an I2C bus segment  
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER trylocks the root i2c adapter, I2C_LOCK_SEGMENT
 *      trylocks only this branch in the adapter tree                           
 */                                                                             
static int i2c_adapter_trylock_bus(struct i2c_adapter *adapter,                 
                                   unsigned int flags)                          
{                                                                               
        return rt_mutex_trylock(&adapter->bus_lock);                            
}

adapter에 있는 버스 뮤텍스 락을 시도한다.

 

i2c_adapter_unlock_bus()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_adapter_unlock_bus - Release exclusive access to an I2C bus segment      
 * @adapter: Target I2C bus segment                                             
 * @flags: I2C_LOCK_ROOT_ADAPTER unlocks the root i2c adapter, I2C_LOCK_SEGMENT 
 *      unlocks only this branch in the adapter tree                            
 */                                                                             
static void i2c_adapter_unlock_bus(struct i2c_adapter *adapter,                 
                                   unsigned int flags)                          
{                                                                               
        rt_mutex_unlock(&adapter->bus_lock);                                    
}

adapter에 있는 버스 뮤텍스 락을 해제한다.

 

I2C 전송 Core

i2c 전송은 i2c_algorithm에 구현되어 있는 i2c용 전송 후크를 이용한다. 만일 smbus 전송이 필요한 경우 smbus용 후크도 지정하여 사용할 수 있다.

 

다음 그림은 i2c 전송에 관여하는 lock operation과 알고리즘 및 전송 제한 값 및 i2c 전송 함수의 호출 단계를 보여준다.

 

i2c_transfer()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * i2c_transfer - execute a single or combined I2C message                      
 * @adap: Handle to I2C bus                                                     
 * @msgs: One or more messages to execute before STOP is issued to              
 *      terminate the operation; each message begins with a START.              
 * @num: Number of messages to be executed.                                     
 *                                                                              
 * Returns negative errno, else the number of messages executed.                
 *                                                                              
 * Note that there is no requirement that each message be sent to               
 * the same slave address, although that is the most common model.              
 */
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)       
{                                                                               
        int ret;                                                                
                                                                                
        /* REVISIT the fault reporting model here is weak:                      
         *                                                                      
         *  - When we get an error after receiving N bytes from a slave,        
         *    there is no way to report "N".                                    
         *                                                                      
         *  - When we get a NAK after transmitting N bytes to a slave,          
         *    there is no way to report "N" ... or to let the master            
         *    continue executing the rest of this combined message, if          
         *    that's the appropriate response.                                  
         *                                                                      
         *  - When for example "num" is two and we successfully complete        
         *    the first message but get an error part way through the           
         *    second, it's unclear whether that should be reported as           
         *    one (discarding status on the second message) or errno            
         *    (discarding status on the first one).                             
         */                                                                     
                                                                                
        if (adap->algo->master_xfer) {                                          
#ifdef DEBUG                                                                    
                for (ret = 0; ret < num; ret++) {                               
                        dev_dbg(&adap->dev,                                     
                                "master_xfer[%d] %c, addr=0x%02x, len=%d%s\n",  
                                ret, (msgs[ret].flags & I2C_M_RD) ? 'R' : 'W',  
                                msgs[ret].addr, msgs[ret].len,                  
                                (msgs[ret].flags & I2C_M_RECV_LEN) ? "+" : ""); 
                }                                                               
#endif                                                                          
                                                                                
                if (in_atomic() || irqs_disabled()) {                           
                        ret = i2c_trylock_bus(adap, I2C_LOCK_SEGMENT);          
                        if (!ret)                                               
                                /* I2C activity is ongoing. */                  
                                return -EAGAIN;                                 
                } else {                                                        
                        i2c_lock_bus(adap, I2C_LOCK_SEGMENT);                   
                }                                                               
                                                                                
                ret = __i2c_transfer(adap, msgs, num);                          
                i2c_unlock_bus(adap, I2C_LOCK_SEGMENT);                         
                                                                                
                return ret;                                                     
        } else {                                                                
                dev_dbg(&adap->dev, "I2C level transfers not supported\n");     
                return -EOPNOTSUPP;                                             
        }                                                                       
}                                                                               
EXPORT_SYMBOL(i2c_transfer);

i2c 버스에 락을 건채 요청한 메시지 수 만큼 전송을 수행한다. 성공 시 0을 반환한다.

  • 코드 라인 22에서 i2c 전송을 지원하는 후크 함수가 있는 경우이다.
    • pc의 smbus는 i2c 전송은 지원하지 않고, smbus 전송만 지원하는 경우가 있다.
  • 코드 라인 34~38에서 전송을 하기 전에 먼저 버스를 점유해야 한다. irq context이거나 preempt disable 상태인 경우 i2c bus 락을 시도한다. 이 때 실패하는 경우 -EAGAIN 에러를 반환한다.
  • 코드 라인 39~41에서 i2c 버스 락을 건다.
  • 코드 라인 43~44에서 요청한 메시지 수 만큼 전송을 한 후 i2c 버스 언락을 한다.

 

__i2c_transfer()

drivers/i2c/i2c-core-base.c

/**                                                                             
 * __i2c_transfer - unlocked flavor of i2c_transfer                             
 * @adap: Handle to I2C bus                                                     
 * @msgs: One or more messages to execute before STOP is issued to              
 *      terminate the operation; each message begins with a START.              
 * @num: Number of messages to be executed.                                     
 *                                                                              
 * Returns negative errno, else the number of messages executed.                
 *                                                                              
 * Adapter lock must be held when calling this function. No debug logging       
 * takes place. adap->algo->master_xfer existence isn't checked.                
 */
int __i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)     
{                                                                               
        unsigned long orig_jiffies;                                             
        int ret, try;                                                           
                                                                                
        if (adap->quirks && i2c_check_for_quirks(adap, msgs, num))              
                return -EOPNOTSUPP;                                             
                                                                                
        /* i2c_trace_msg gets enabled when tracepoint i2c_transfer gets         
         * enabled.  This is an efficient way of keeping the for-loop from      
         * being executed when not needed.                                      
         */                                                                     
        if (static_key_false(&i2c_trace_msg)) {                                 
                int i;                                                          
                for (i = 0; i < num; i++)                                       
                        if (msgs[i].flags & I2C_M_RD)                           
                                trace_i2c_read(adap, &msgs[i], i);              
                        else                                                    
                                trace_i2c_write(adap, &msgs[i], i);             
        }                                                                       
                                                                                
        /* Retry automatically on arbitration loss */                           
        orig_jiffies = jiffies;                                                 
        for (ret = 0, try = 0; try <= adap->retries; try++) {                   
                ret = adap->algo->master_xfer(adap, msgs, num);                 
                if (ret != -EAGAIN)                                             
                        break;                                                  
                if (time_after(jiffies, orig_jiffies + adap->timeout))          
                        break;                                                  
        }                                                                       
                                                                                
        if (static_key_false(&i2c_trace_msg)) {                                 
                int i;                                                          
                for (i = 0; i < ret; i++)                                       
                        if (msgs[i].flags & I2C_M_RD)                           
                                trace_i2c_reply(adap, &msgs[i], i);             
                trace_i2c_result(adap, i, ret);                                 
        }                                                                       
                                                                                
        return ret;                                                             
}                                                                               
EXPORT_SYMBOL(__i2c_transfer);

i2c 버스에 요청한 메시지 수 만큼 전송을 수행한다. 성공 시 0을 반환한다.

  • 코드 라인 6~7에서 quirks 전송 제한에 걸린 경우 -EOPNOTSUPP 에러를 반환한다.
  • 코드 라인 13~20에서 읽고 쓸 메시지에 대해 trace 출력을 한다.
  • 코드 라인 23~30에서 i2c 전송을 한다. 전송 후 -EAGAIN 에러인 경우 retries 수 이내에서 루프를 돌며 시도한다. 만일 제한 시간을 초과하는 경우 재시도를 하지 않는다.
  • 코드 라인 32~38에서 읽은 메시지가 있는 경우 이에 대한 trace 출력을 한다.

 

i2c 전송 (for bcm-i2c-iproc)

broadcom ns2에 내장된 i2c에 사용하는 bcm-i2c-iproc 드라이버에서 사용하는 전송함수을 예로 알아본다.

 

bcm_iproc_i2c_xfer()

drivers/i2c/busses/i2c-bcm-iproc.c

static int bcm_iproc_i2c_xfer(struct i2c_adapter *adapter,                      
                              struct i2c_msg msgs[], int num)                   
{                                                                               
        struct bcm_iproc_i2c_dev *iproc_i2c = i2c_get_adapdata(adapter);        
        int ret, i;                                                             
                                                                                
        /* go through all messages */                                           
        for (i = 0; i < num; i++) {                                             
                ret = bcm_iproc_i2c_xfer_single_msg(iproc_i2c, &msgs[i]);       
                if (ret) {                                                      
                        dev_dbg(iproc_i2c->device, "xfer failed\n");            
                        return ret;                                             
                }                                                               
        }                                                                       
                                                                                
        return num;                                                             
}

요청한 메시지 수 만큼 메시지들을 전송한다. 성공 시 메시지 수가 반환된다. 실패의 경우 음수를 반환한다.

 

bcm_iproc_i2c_xfer_single_msg()

drivers/i2c/busses/i2c-bcm-iproc.c -1/2-

static int bcm_iproc_i2c_xfer_single_msg(struct bcm_iproc_i2c_dev *iproc_i2c,   
                                         struct i2c_msg *msg)                   
{                                                                               
        int ret, i;                                                             
        u8 addr;                                                                
        u32 val;                                                                
        unsigned int tx_bytes;                                                  
        unsigned long time_left = msecs_to_jiffies(I2C_TIMEOUT_MSEC);           
                                                                                
        /* check if bus is busy */                                              
        if (!!(readl(iproc_i2c->base + M_CMD_OFFSET) &                          
               BIT(M_CMD_START_BUSY_SHIFT))) {                                  
                dev_warn(iproc_i2c->device, "bus is busy\n");                   
                return -EBUSY;                                                  
        }                                                                       
                                                                                
        iproc_i2c->msg = msg;                                                   
                                                                                
        /* format and load slave address into the TX FIFO */                    
        addr = i2c_8bit_addr_from_msg(msg);                                     
        writel(addr, iproc_i2c->base + M_TX_OFFSET);                            
                                                                                
        /*                                                                      
         * For a write transaction, load data into the TX FIFO. Only allow      
         * loading up to TX FIFO size - 1 bytes of data since the first byte    
         * has been used up by the slave address                                
         */                                                                     
        tx_bytes = min_t(unsigned int, msg->len, M_TX_RX_FIFO_SIZE - 1);        
        if (!(msg->flags & I2C_M_RD)) {                                         
                for (i = 0; i < tx_bytes; i++) {                                
                        val = msg->buf[i];                                      
                                                                                
                        /* mark the last byte */                                
                        if (i == msg->len - 1)                                  
                                val |= 1 << M_TX_WR_STATUS_SHIFT;               
                                                                                
                        writel(val, iproc_i2c->base + M_TX_OFFSET);             
                }                                                               
                iproc_i2c->tx_bytes = tx_bytes;                                 
        }                                                                       
                                                                                
        /* mark as incomplete before starting the transaction */                
        reinit_completion(&iproc_i2c->done);                                    
        iproc_i2c->xfer_is_done = 0;                                            
                                                                                
        /*                                                                      
         * Enable the "start busy" interrupt, which will be triggered after the 
         * transaction is done, i.e., the internal start_busy bit, transitions  
         * from 1 to 0.                                                         
         */                                                                     
        val = BIT(IE_M_START_BUSY_SHIFT);                                       
                                                                                
        /*                                                                      
         * If TX data size is larger than the TX FIFO, need to enable TX        
         * underrun interrupt, which will be triggerred when the TX FIFO is     
         * empty. When that happens we can then pump more data into the FIFO    
         */                                                                     
        if (!(msg->flags & I2C_M_RD) &&                                         
            msg->len > iproc_i2c->tx_bytes)                                     
                val |= BIT(IE_M_TX_UNDERRUN_SHIFT);                             
                                                                                
        writel(val, iproc_i2c->base + IE_OFFSET);

1개의 메시지를 전송한다. 실패 시 음수를 반환한다.

  • 코드 라인 11~15에서 이미 명령이 시작되어 처리되고 있는 중이라 i2c 버스 Busy 상태이다. 이러한 경우 -EBUSY 에러를 반환한다.
  • 코드 라인 17~21에서 메시지에서 7비트 주소와 R/W 비트를 추가한 8비트 값을 tx 레지스터에 기록한다.
  • 코드 라인 28에서 전송할 메시지 길이를 최대 63바이트 범위이내로 제한한다.
  • 코드 라인 28~40에서 Read 메시지가 아니면 송신할 바이트 수 만큼 순회하며 tx 레지스터에 기록한다. 32비트 tx 레지스터에 1 바이트를 기록할 때 tx 레지스터의 msb를 항상 1로 설정한다.
    • 예) { 0x30, 0x40, 0x50, 0x60 } 을 tx 레지스터에 기록 시
      • -> { 0x8000_0030, 0x8000_0040, 0x8000_0050, 0x8000_0060 }
  • 코드 라인 43~44에서 전송이 완료되었음을 알린다.
  • 코드 라인 50~61에서 IE 레지스터에 값을 기록한다.
    • start_busy 비트와 송신 메시지인 경우 adapter의 tx FIFO 버퍼 크기보다 큰 경우 tx underrun 비트를 합친 값이다.

 

drivers/i2c/busses/i2c-bcm-iproc.c -2/2-

        /*                                                                      
         * Now we can activate the transfer. For a read operation, specify the  
         * number of bytes to read                                              
         */                                                                     
        val = BIT(M_CMD_START_BUSY_SHIFT);                                      
        if (msg->flags & I2C_M_RD) {                                            
                val |= (M_CMD_PROTOCOL_BLK_RD << M_CMD_PROTOCOL_SHIFT) |        
                       (msg->len << M_CMD_RD_CNT_SHIFT);                        
        } else {                                                                
                val |= (M_CMD_PROTOCOL_BLK_WR << M_CMD_PROTOCOL_SHIFT);         
        }                                                                       
        writel(val, iproc_i2c->base + M_CMD_OFFSET);                            
                                                                                
        time_left = wait_for_completion_timeout(&iproc_i2c->done, time_left);   
                                                                                
        /* disable all interrupts */                                            
        writel(0, iproc_i2c->base + IE_OFFSET);                                 
        /* read it back to flush the write */                                   
        readl(iproc_i2c->base + IE_OFFSET);                                     
                                                                                
        /* make sure the interrupt handler isn't running */                     
        synchronize_irq(iproc_i2c->irq);                                        
                                                                                
        if (!time_left && !iproc_i2c->xfer_is_done) {                           
                dev_err(iproc_i2c->device, "transaction timed out\n");          
                                                                                
                /* flush FIFOs */                                               
                val = (1 << M_FIFO_RX_FLUSH_SHIFT) |                            
                      (1 << M_FIFO_TX_FLUSH_SHIFT);                             
                writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET);              
                return -ETIMEDOUT;                                              
        }                                                                       
                                                                                
        ret = bcm_iproc_i2c_check_status(iproc_i2c, msg);                       
        if (ret) {                                                              
                /* flush both TX/RX FIFOs */                                    
                val = (1 << M_FIFO_RX_FLUSH_SHIFT) |                            
                      (1 << M_FIFO_TX_FLUSH_SHIFT);                             
                writel(val, iproc_i2c->base + M_FIFO_CTRL_OFFSET);              
                return ret;                                                     
        }                                                                       
                                                                                
        /*                                                                      
         * For a read operation, we now need to load the data from FIFO         
         * into the memory buffer                                               
         */                                                                     
        if (msg->flags & I2C_M_RD) {                                            
                for (i = 0; i < msg->len; i++) {                                
                        msg->buf[i] = (readl(iproc_i2c->base + M_RX_OFFSET) >>  
                                      M_RX_DATA_SHIFT) & M_RX_DATA_MASK;        
                }                                                               
        }                                                                       
                                                                                
        return 0;                                                               
}
  • 코드 라인 5~12에서 M_CMD 레지스터에 적절한 값을 기록한다.
  • 코드 라인 14에서 남은 타임아웃 시간만큼 전송이 완료될 때 까지 기다린다.
  • 코드 라인 17~19에서 모든 인터럽트를 disable하고 write 플러쉬를 수행한다.
  • 코드 라인 22에서 인터럽트 핸들러가 처리 중인 경우 완료될 때 까지 대기한다.
  • 코드 라인 24~32에서 시간이 만료되었고 여전히 전송이 완료되지 않은 경우 RX, TX FIFO 버퍼를 flush한 후 -ETIMEOUT 에러를 반환한다.
  • 코드 라인 34~41에서 다시 i2c 버스 상태가 성공(0) 상태가 아닌 경우에도 RX, TX FIFO 버퍼를 flush한 후 에러를 반환한다.
  • 코드 라인 47~52에서 read 메시지 요청인 경우 M_RX 레지스터에서 요청한 메시지 길이 만큼 반복하여 읽어온다.
    • 수신한 데이터의 하위 8비트만 mask하여 바이트로 저장한다.

 

Broadcom ns2 I2C Register

다음 그림은 broadcom ns2 SoC 내부에 임베드된 i2c 호스 컨트롤러에서 사용되는 주요 레지스터들이다.

 

참고