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

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다