Scheduler -3a- (PELT-2)

<kernel v5.4>

Scheduler -3- (PELT-2)

 

PELT Migration Propagation

kernel/sched/fair.c

/*
 * When on migration a sched_entity joins/leaves the PELT hierarchy, we need to
 * propagate its contribution. The key to this propagation is the invariant
 * that for each group:
 *
 *   ge->avg == grq->avg                                                (1)
 *
 * _IFF_ we look at the pure running and runnable sums. Because they
 * represent the very same entity, just at different points in the hierarchy.
 *
 * Per the above update_tg_cfs_util() is trivial and simply copies the running
 * sum over (but still wrong, because the group entity and group rq do not have
 * their PELT windows aligned).
 *
 * However, update_tg_cfs_runnable() is more complex. So we have:
 *
 *   ge->avg.load_avg = ge->load.weight * ge->avg.runnable_avg          (2)
 *
 * And since, like util, the runnable part should be directly transferable,
 * the following would _appear_ to be the straight forward approach:
 *
 *   grq->avg.load_avg = grq->load.weight * grq->avg.runnable_avg       (3)
 *
 * And per (1) we have:
 *
 *   ge->avg.runnable_avg == grq->avg.runnable_avg
 *
 * Which gives:
 *
 *                      ge->load.weight * grq->avg.load_avg
 *   ge->avg.load_avg = -----------------------------------             (4)
 *                               grq->load.weight
 *
 * Except that is wrong!
 *
 * Because while for entities historical weight is not important and we
 * really only care about our future and therefore can consider a pure
 * runnable sum, runqueues can NOT do this.
 *
 * We specifically want runqueues to have a load_avg that includes
 * historical weights. Those represent the blocked load, the load we expect
 * to (shortly) return to us. This only works by keeping the weights as
 * integral part of the sum. We therefore cannot decompose as per (3).
 *
 * Another reason this doesn't work is that runnable isn't a 0-sum entity.
 * Imagine a rq with 2 tasks that each are runnable 2/3 of the time. Then the
 * rq itself is runnable anywhere between 2/3 and 1 depending on how the
 * runnable section of these tasks overlap (or not). If they were to perfectly
 * align the rq as a whole would be runnable 2/3 of the time. If however we
 * always have at least 1 runnable task, the rq as a whole is always runnable.
 *
 * So we'll have to approximate.. :/
 *
 * Given the constraint:
 *
 *   ge->avg.running_sum <= ge->avg.runnable_sum <= LOAD_AVG_MAX
 *
 * We can construct a rule that adds runnable to a rq by assuming minimal
 * overlap.
 *
 * On removal, we'll assume each task is equally runnable; which yields:
 *
 *   grq->avg.runnable_sum = grq->avg.load_sum / grq->load.weight
 *
 * XXX: only do this for the part of runnable > running ?
 *
 */

엔티티의 마이그레이션 시 엔티티가 PELT 계층 구조에 가입/탈퇴할 때 그 기여도를 전파(propagate)해야 한다. 이 전파의 핵심은 불변성이다.

  • 다음 약자를 혼동하지 않도록 한다.
    • tg: 태스크 그룹 (struct task_group *)
    • ge: 그룹 엔티티 (struct sched_entity *)
    • grq: 그룹 cfs 런큐 (struct cfs_rq *)

 

  • (1) ge->avg == grq->avg
    • 그룹을 대표하는 그룹 엔티티의 로드 통계와 그룹 cfs 런큐의 통계는 같다.
    • 순수(pure) 러닝 합계와 순수(pure) 러너블 합계를 검토한다. 왜냐하면 그들은 계층의 서로 다른 지점에서 매우 동일한 엔티티를 표현하고 있기 때문이다.
    • update_tg_cfs_util()은 간단하게 러닝 합계를 상위로 복사한다.
      • 복사한 값은 서로 다른 PELT 계층 구조에서 윈도우가 정렬되어 있지 않기 때문에 약간 다르므로 잘못되었다.
      • 추후 커널 v5.8-rc1에서 그룹 엔티티와 그룹 cfs 런큐 사이의 PELT window를 정렬하여 약간의 잘못된 점을 바로 잡았다.
  • (2) ge->avg.load_avg = ge->load.weight * ge->avg.runnable_avg
    • 그룹 엔티티의 로드 평균은 그룹 엔티티의 러너블 평균에 가중치(weight)를 곱한 값이다.
  • (3) grq->avg.load_avg = grq->load.weight * grq->avg.runnable_avg
    • util과 마찬가지로, 러너블 파트는 직접 이전할 수 있어야 하므로 직선적 전진 접근방식이 될 것이다
  • (4) ge->avg.load_avg = ge->load.weight * grq->avg.load_avg / grq->load.weight
    • (2)번 수식을 사용하고 싶지만, 엔티티의 러너블 평균 대신 (3)번과 같이 cfs 런큐의 러너블 평균을 사용한다.
    • (1) 번에서 말한 것처럼 ge->avg.runnable_avg == grq->avg.runnable_avg 이다. 이를 이용하여 (4)번 수식이 성립한다.

 

다만 위의 접근식에는 다음과 같은 잘못된 점이 있다.

  • historical weights는 중요하지 않지만 cfs 런큐는 순수한 러너블 로드를 산출할 수 없어 historical weights를 고려한다는 점
  • cfs 런큐의 historical weights를 포함하는 로드 평균은 블럭드 로드(uninterruptible 상태의 슬립 태스크지만 금방 깨어날거라 예상)를 포함한다는 점 때문에 (3)번으로 해결할 수 없다. 산술적으로 슬립상태의 태스크를 로드로 계산해야 하므로 정확히 산출할 수 없다.
  • 또 다른 이유로 러너블 기간이 얼마나 중복되는지를 정확히 알 수 없다.
    • 러너블이 0-sum 엔티티가 아니다.
    • 각각 2/3가 러너블한 2개의 작업이 포함된 rq를 상상해 보자. 그런 다음, rq 자체는 이러한 작업의 러너블 섹션이 어떻게 겹치는지에 따라 2/3와 1 사이의 어느 곳에서나 실행될 수 있다(또는 그렇지 않다). 만약 전체적으로 rq를 완벽하게 정렬한다면, 그 시간의 2/3가 실행될 수 있을 것이다.
    • 최소 하나의 실행 가능한 작업을 가지고 있다면, rq 전체는 항상 러너블 상태이다.

 

그래서 제약 조건을 주면 :

  • ge->avg.running_sum <= ge->avg.runnable_sum <= LOAD_AVG_MAX
  • 러너블을 추가 시 최소한의 중복(overlap)을 가정한다.
    • 얼마 기간이 겹쳤는지 모른다. 이러한 경우에는 최소한의 중복(overlap) 기간만을 사용한다.
  • 제거시 각 작업을 동일하게 실행할 수 있다고 가정한다.
    • grq-> avg.runnable_sum = grq-> avg.load_sum / grq-> load.weight

 

다음 그림은 그룹 엔티티와 cfs 런큐의 로드 평균에 대해 이론적으로 동일하다는 것을 보여준다.

 

다음은 그룹 엔티티와 cfs 런큐의 로드 평균이 실제로는 약간 다른 것을 보여준다.

cfs_rq[5]:/B
  .exec_clock                    : 143287629.494567
  .MIN_vruntime                  : 0.000001
  .min_vruntime                  : 143287628.829813
  .max_vruntime                  : 0.000001
  .spread                        : 0.000000
  .spread0                       : -625159177.736145
  .nr_spread_over                : 0
  .nr_running                    : 1
  .load                          : 1024
  .load_avg                      : 1004
  .runnable_load_avg             : 1004
  .util_avg                      : 459
  .removed_load_avg              : 0
  .removed_util_avg              : 0
  .tg_load_avg_contrib           : 1007
  .tg_load_avg                   : 1007
  .throttled                     : 0
  .throttle_count                : 0
  .se->exec_start                : 1193546377.026124
  .se->vruntime                  : 179287288.570602
  .se->sum_exec_runtime          : 143287629.494567
  .se->statistics.wait_start     : 0.000000
  .se->statistics.sleep_start    : 0.000000
  .se->statistics.block_start    : 0.000000
  .se->statistics.sleep_max      : 0.000000
  .se->statistics.block_max      : 0.000000
  .se->statistics.exec_max       : 1.007417
  .se->statistics.slice_max      : 10.040917
  .se->statistics.wait_max       : 22.002458
  .se->statistics.wait_sum       : 5636994.599562
  .se->statistics.wait_count     : 19461708
  .se->load.weight               : 1024
  .se->avg.load_avg              : 1007
  .se->avg.util_avg              : 460

 

다음 그림은 태스크가 있는 태스크 그룹내에서 로드 평균을 관리하는 모습을 보여준다.

  • 엔티티  —(add)—> cfs 런큐  —(sum)—> 태스크 그룹

 

다음 그림은 attach 또는 detach 태스크 시 상위로 러너블 합계를 전파하기 위해 설정하는 모습을 보여준다.

 

다음 그림은 attach 또는 detach 태스크되는 그룹의 상위 그룹에서 그룹 엔티티와 cfs 런큐의 로드 평균이 갱신되는 과정을 보여준다.

 

 

다음 그림은 attach/detach된 엔티티의 +-load_sum을 상위 cfs 런큐로 전파(propagate)한 후 변화된 로드에 대해 그룹 엔티티 및 cfs 런큐의 로드 평균들을 재산출하는 경로를 보여준다.

  • attach_entity_load_avg() 또는 detach_entity_load_avg()
    • attach/detach된 엔티티의 load_sum을 —–> 소속 cfs 런큐의 prop_runnable_sum에 기록해둔다.
  • update_tg_cfs_runnable()
    • 정규 그룹 엔티티 갱신시 하위의 그룹 cfs 런큐에서 전파 값을 읽어온 후 그룹 엔티티 및 cfs 런큐의 로드 평균들을 재산출한다.

 

태스크 그룹 유틸 갱신

update_tg_cfs_util()

kernel/sched/fair.c

static inline void
update_tg_cfs_util(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
        long delta = gcfs_rq->avg.util_avg - se->avg.util_avg;

        /* Nothing to update */
        if (!delta)
                return;

        /*
         * The relation between sum and avg is:
         *
         *   LOAD_AVG_MAX - 1024 + sa->period_contrib
         *
         * however, the PELT windows are not aligned between grq and gse.
         */

        /* Set new sched_entity's utilization */
        se->avg.util_avg = gcfs_rq->avg.util_avg;
        se->avg.util_sum = se->avg.util_avg * LOAD_AVG_MAX;

        /* Update parent cfs_rq utilization */
        add_positive(&cfs_rq->avg.util_avg, delta);
        cfs_rq->avg.util_sum = cfs_rq->avg.util_avg * LOAD_AVG_MAX;
}

태스크 그룹 유틸을 갱신한다. (새 엔티티 attach 후 상위 태스크 그룹(그룹 엔티티 및 cfs 런큐) 갱신을 위해 진입하였다.)

  • 코드 라인 4에서 그룹 cfs 런큐 @gcfs_rq의 유틸 평균에서 그룹 엔티티 @se의 유틸 평균을 뺀 delta를 구한다.
  • 코드 라인 7~8에서 변화가 없으면 그냥 함수를 빠져나간다.
  • 코드 라인 19에서 새 그룹 엔티티의 유틸 평균은 그룹 cfs 런큐의 유틸 평균을 그대로 사용한다.
  • 코드 라인 20에서 새 그룹 엔티티의 유틸 합계를 갱신한다.
    • 유틸 합계 = 유틸 평균 * 최대 로드 평균 기간
  • 코드 라인 23에서 그룹 엔티티의 부모 cfs 런큐의 유틸 평균에 delta를 추가한다.
  • 코드 라인 24에서 그룹 엔티티의 부모 cfs 런큐의 유틸 합계를 갱신한다.
    • 유틸 합계 = 유틸 평균 * 최대 로드 평균 기간

 

다음 그림은 태스크 그룹에 대한 유틸을 산출하는 과정을 보여준다.

 

러너블 합계 상위 전파(propagate)

propagate_entity_load_avg()

kernel/sched/fair.c

/* Update task and its cfs_rq load average */
static inline int propagate_entity_load_avg(struct sched_entity *se)
{
        struct cfs_rq *cfs_rq, *gcfs_rq;

        if (entity_is_task(se))
                return 0;

        gcfs_rq = group_cfs_rq(se);
        if (!gcfs_rq->propagate)
                return 0;

        gcfs_rq->propagate = 0;

        cfs_rq = cfs_rq_of(se);

        add_tg_cfs_propagate(cfs_rq, gcfs_rq->prop_runnable_sum);

        update_tg_cfs_util(cfs_rq, se, gcfs_rq);
        update_tg_cfs_runnable(cfs_rq, se, gcfs_rq);

        trace_pelt_cfs_tp(cfs_rq);
        trace_pelt_se_tp(se);

        return 1;
}

attach/detach에 따라 러너블 로드의 상위 전파를 위한 값이 있으면 이를 현재 그룹 엔티티와 cfs 런큐에 반영하고, 상위에도 전파한다. 전파할 값이 있어 갱신된 경우 1을 반환하고, 전파할 값이 없어 갱신하지 않은 경우 0을 반환한다.

  • 코드 라인 6~7에서 태스크용 엔티티인 경우 이 함수를 수행할 필요가 없으므로 0을 반환한다.
  • 코드 라인 9~11에서 그룹 cfs 런큐에 propagate가 설정되지 않은 경우 0을 반환한다.
  • 코드 라인 13에서 먼저 그룹 cfs 런큐에서 propagate를 클리어하고, 상위 cfs 런큐에 prop_runnable_sum을 전파한다.
  • 코드 라인 15에서 그룹 엔티티와 cfs 런큐에서 util 로드를 갱신한다.
  • 코드 라인 16에서 그룹 엔티티와 cfs 런큐에서 로드 및 러너블 로드를 갱신한다.
  • 코드 라인 21에서 갱신을 하였으므로 1을 반환한다.

 

add_tg_cfs_propagate()

kernel/sched/fair.c

static inline void add_tg_cfs_propagate(struct cfs_rq *cfs_rq, long runnable_sum)
{
        cfs_rq->propagate = 1;
        cfs_rq->prop_runnable_sum += runnable_sum;
}

엔티티가 attach/detach 되거나 최상위 태스크 그룹까지 러너블 합계를 전파하도록 요청한다.

  • 추후 update_tg_cfs_runnable() 함수에서 이 값을 읽어들여 사용한다. 읽어들인 후에는 이 값은 0으로 변경된다.

 

태스크 그룹 러너블 갱신

update_tg_cfs_runnable()

kernel/sched/fair.c

static inline void
update_tg_cfs_runnable(struct cfs_rq *cfs_rq, struct sched_entity *se, struct cfs_rq *gcfs_rq)
{
        long delta_avg, running_sum, runnable_sum = gcfs_rq->prop_runnable_sum;
        unsigned long runnable_load_avg, load_avg;
        u64 runnable_load_sum, load_sum = 0;
        s64 delta_sum;

        if (!runnable_sum)
                return;

        gcfs_rq->prop_runnable_sum = 0;

        if (runnable_sum >= 0) {
                /*
                 * Add runnable; clip at LOAD_AVG_MAX. Reflects that until
                 * the CPU is saturated running == runnable.
                 */
                runnable_sum += se->avg.load_sum;
                runnable_sum = min(runnable_sum, (long)LOAD_AVG_MAX);
        } else {
                /*
                 * Estimate the new unweighted runnable_sum of the gcfs_rq by
                 * assuming all tasks are equally runnable.
                 */
                if (scale_load_down(gcfs_rq->load.weight)) {
                        load_sum = div_s64(gcfs_rq->avg.load_sum,
                                scale_load_down(gcfs_rq->load.weight));
                }

                /* But make sure to not inflate se's runnable */
                runnable_sum = min(se->avg.load_sum, load_sum);
        }

        /*
         * runnable_sum can't be lower than running_sum
         * Rescale running sum to be in the same range as runnable sum
         * running_sum is in [0 : LOAD_AVG_MAX <<  SCHED_CAPACITY_SHIFT]
         * runnable_sum is in [0 : LOAD_AVG_MAX]
         */
        running_sum = se->avg.util_sum >> SCHED_CAPACITY_SHIFT;
        runnable_sum = max(runnable_sum, running_sum);

        load_sum = (s64)se_weight(se) * runnable_sum;
        load_avg = div_s64(load_sum, LOAD_AVG_MAX);

        delta_sum = load_sum - (s64)se_weight(se) * se->avg.load_sum;
        delta_avg = load_avg - se->avg.load_avg;

        se->avg.load_sum = runnable_sum;
        se->avg.load_avg = load_avg;
        add_positive(&cfs_rq->avg.load_avg, delta_avg);
        add_positive(&cfs_rq->avg.load_sum, delta_sum);

        runnable_load_sum = (s64)se_runnable(se) * runnable_sum;
        runnable_load_avg = div_s64(runnable_load_sum, LOAD_AVG_MAX);
        delta_sum = runnable_load_sum - se_weight(se) * se->avg.runnable_load_sum;
        delta_avg = runnable_load_avg - se->avg.runnable_load_avg;

        se->avg.runnable_load_sum = runnable_sum;
        se->avg.runnable_load_avg = runnable_load_avg;

        if (se->on_rq) {
                add_positive(&cfs_rq->avg.runnable_load_avg, delta_avg);
                add_positive(&cfs_rq->avg.runnable_load_sum, delta_sum);
        }
}

하위 그룹 cfs 런큐에서 전파 받은 러너블 합계가 있는 경우 그룹 엔티티 및 cfs 런큐의 로드 평균을 갱신한다.

  • 코드 라인 4~12에서 그룹 cfs 런큐에서 전파 받은 러너블 섬을 알아온 후 클리어해둔다. 이 값이 0이면 갱신이 필요 없으므로 그냥 함수를 빠져나간다.
    • 전파 받은 gcfs_rq->prop_runnable_sum이 존재하는 경우 하위 그룹에서 엔티티가 attach(양수) 또는 detach(음수)된 상황이다.
attach/detach 엔티티로부터 propagate된 러너블 합계를 사용한 로드들 산출
  • 코드 라인 14~20에서 하위 그룹에서 엔티티가 attach되어 전파 받은 러너블 합계에 현재 그룹 엔티티의 로드 합계를 추가한다. 단 이 값이 최대치 LOAD_AVG_MAX를 넘을 수는 없다.
    • attach: runnable_sum = gcfs_rq->prop_runnable_sum + se->avg.load_sum
      • 엔티티의 러너블 합계 증가 시 최고 LOAD_AVG_MAX를 초과할 수 없다.
      • runnable_sum <= LOAD_AVG_MAX(47742)
  • 코드 라인 21~33에서 하위 그룹에서 엔티티가 detach되어 전파 받은 러너블 섬이 음수이면 이 값을 사용하지 않는다. 대신 하위 그룹 런큐에서 엔티티 평균 러너블 합계를 사용한다.
    • 그룹 cfs 런큐의 로드 합계를 그룹 cfs 런큐의 로드 weight 값으로 나눈 값으로 대략적인 러너블 합계를 구한다. 단 이 값은 엔티티의 로드 합보다 더 커질 수는 없다.(detach되었는데 더 커지는 상황은 어울리지 않는다.)
    • detach: runnable_sum = gcfs_rq->avg.load_sum / scale_load_down(gcfs_rq->load.weight)
      • 엔티티의 러너블 합계 감소 시 엔티티의 로드 합계보다 작아질 수 없다.
      • se->avg.load_sum <= runnable_sum
  • 코드 라인 41~42에서 그룹에서 엔티티의 유틸 합계를 SCHED_CAPACITY_SHIFT(10) 비트만큼 스케일을 낮춰 러닝 합계를 구한다. 단 이 값은 러너블 합계보다 작지 않아야 한다.
    • running_sum = se->avg.util_sum >> 10
      • runnable_sum이 running_sum보다 작을 수는 없으므로 클립한다.
      • running_sum <= runnable_sum
  • 코드 라인 44~45에서 attach/detach 상황을 포함한 runnable_sum이 구해졌으므로 먼저 다음과 같이 runnable_sum에 엔티티의 로드 weight을 곱하여 반영된 로드 합계를 산출하고, 로드 평균은 최대 로드 평균 기간으로 나누어 산출한다.
    • load_sum(weight 반영 상태) = se_weight(se) * runnable_sum
    • load_avg = load_sum / LOAD_AVG_MAX(최대 로드 평균 기간)
그룹 엔티티에 새 로드 합계와 평균 반영
  • 코드 라인 47~48에서 attach/detach 상황에서 산출한 로드 합계와 평균으로 기존 엔티티의 로드합계와 평균 사이의 차이(delta) 값을 구한다.
    • delta_sum(weight 반영 상태) = load_sum – se_weight(se) * se->avg.load_sum
    • delta_avg = load_avg – se->avg.load_avg
  • 코드 라인 50~51에서 엔티티에 새롭게 산출된 로드 합계(weight 제거된)와 평균을 대입한다.
    • se->avg.load_sum = runnable_sum
    • se->avg.load_avg = load_avg
cfs 런큐에 엔티티 로드 변화분(delta)을 반영
  • 코드 라인 52~53에서 cfs 런큐의 로드 평균과 로드 합계에 위에서 산출한 델타 평균과 합계를 추가한다.
    • cfs_rq->avg.load_sum(weight 반영 상태) += delta_sum
    • cfs_rq->avg.load_avg += delta_avg
그룹 엔티티에 러너블 로드 합계와 평균 반영
  • 코드 라인 55~56에서 러너블 로드 합계와 평균을 산출한다.
    • runnable_load_sum(weight 반영 상태) = se->runnable(se) * runnable_sum
    • runnable_load_avg = runnable_load_sum / LOAD_AVG_MAX(최대 로드 평균 기간)
  • 코드 라인 57~58에서 재산출된 러너블 로드 합계와 평균에서 기존 엔티티의 러너블 로드 합계와 평균 사이의 차이(delta) 값을 구한다.
    • delta_sum = runnable_load_sum – se_weight(se) * se->avg.runnable_load_sum
    • delta_avg = runnable_load_avg – se->avg.runnable_load_avg
  • 코드 라인 60~61에서 엔티티의 러너블 로드 합계 및 평균에 산출해둔 러너블 로드 합계와 러너블 로드 평균을 대입한다.
    • se->avg.runnable_load_sum(weight 미반영 상태) = runnable_sum
    • se->avg.runnable_load_avg = runnable_load_avg
cfs 런큐에 러너블 로드 합계와 평균 변화분 추가
  • 코드 라인 63~66에서 엔티티가 런큐에 있어 러너블 상태인 경우 cfs 런큐의 러너블 로드 합계와 평균에 위에서 산출해둔 델타 합계와 평균을 추가한다.
    • cfs_rq->avg.runnable_load_sum += delta_sum
    • cfs_rq->avg.runnable_load_avg += delta_avg

 

다음 그림은 엔티티 attach 상황에서 상위 그룹의 로드 평균을 갱신하는 과정을 보여준다.

 

다음 그림은 엔티티 attach 상황에서 상위 그룹의 로드 평균을 갱신하는 과정을 보여준다.

 

 


태스크용 엔티티 -> CFS 런큐 -> 그룹 엔티티의 로드 관계

 

/*
 * sched_entity:
 *
 *   task:
 *     se_runnable() == se_weight()
 *
 *   group: [ see update_cfs_group() ]
 *     se_weight()   = tg->weight * grq->load_avg / tg->load_avg
 *     se_runnable() = se_weight(se) * grq->runnable_load_avg / grq->load_avg
 *
 *   load_sum := runnable_sum
 *   load_avg = se_weight(se) * runnable_avg
 *
 *   runnable_load_sum := runnable_sum
 *   runnable_load_avg = se_runnable(se) * runnable_avg
 *
 * XXX collapse load_sum and runnable_load_sum
 *
 * cfq_rq:
 *
 *   load_sum = \Sum se_weight(se) * se->avg.load_sum
 *   load_avg = \Sum se->avg.load_avg
 *
 *   runnable_load_sum = \Sum se_runnable(se) * se->avg.runnable_load_sum
 *   runnable_load_avg = \Sum se->avg.runable_load_avg
 */

엔티티:

태스크용 엔티티:
  • se_runnable() == se_weight()
    • 태스크용 엔티티는 러너블과 러닝 비율이 항상 같으므로 러너블 가중치와 로드 가중치가 항상 같다.

 

그룹 엔티티:
  • update_cfs_group() -> calc_group_shares() – (3)번 참고:
    •                                                         grq->load_avg
    • se_weight() = tg->weight * ————————–
    •                                                           tg->load_avg
    •                                                                 grq->runnable_load_avg
    • se_runnable() = se_weight(se) * ————————————
    •                                                                          grq->load_avg

 

  • load_sum := runnable_sum
    • 태스크 엔티티등의 러너블 로드에 변화가 생길 때 상위 그룹에 러너블 합계를 전파한다. 상위 그룹에서는 전파한 러너블 합계 값을 더해 이 값을 적용한다. 단 이 값은 LOAD_AVG_MAX ~ running_sum 범위에서 clamp 한다.
    • 참고: update_tg_cfs_runnable()
  • load_avg = se_weight(se) * runnable_avg
    •                                      runnable_sum
    • runnable_avg = —————————–
    •                                   LOAD_AVG_MAX
  • runnable_load_sum := runnable_sum
    • 로드 합계와 동일하게 러너블 로드 합계에도 적용한다.
  • runnable_load_avg = se_runnable(se) * runnable_avg
    • 러너블 로드 평균은 그룹 엔티티의 러너블 가중치와 runnable_avg를 곱하여 산출한다.

 

 CFS 런큐:

  • load_sum = \Sum se_weight(se) * se->avg.load_sum
    • cfs 런큐에 엔큐된 엔티티들의 가중치와 로드 합계를 곱한 값들을 모두 더한 값이다.
  • load_avg = \Sum se->avg.load_avg
    • cfs 런큐에 엔큐된 엔티티들의 로드 평균을 모두 더한 값이다.
  • runnable_load_sum = \Sum se_runnable(se) * se->avg.runnable_load_sum
    • cfs 런큐에 엔큐된 엔티티들의 러너블 가중치와 러너블 로드 합계를 곱한 값들을 모두 더한 값이다.
  • runnable_load_avg = \Sum se->avg.runable_load_avg
    • cfs 런큐에 엔큐된 엔티티들의 러너블 로드 평균을 모두 더한 값이다.

 

다음 그림은 루트 그룹에 두 개의 하위 그룹 A, B를 만들고 다음과 같은 설정을 하였을 때 예상되는 cpu utilization을 보고자 한다.

  • A 그룹
    • cpu.shares=1024 (default)
    • nice 0 태스크 (no sleep)
  • B 그룹
    • cpu.shares=2048
    • nice -10 태스크 (no sleep)
    • nice -13 태스크 (no sleep)

다음 그림은 A 그룹에서 1개의 태스크만을 가동하였을 때 load 평균등을 알아본다.

 

다음 그림은 A, B 그룹에서 각각 1개의 태스크를 가동하였을 때 load 평균등을 알아본다.

 

다음 그림은 A 그룹에 1개의 태스크, B 그룹에 2개의 태스크를 가동하였을 때 load 평균등을 알아본다.

실제 평균은 다음과 같이 확인할 수 있다.

  • 실제 다른 프로세스들이 약간 동작하고 있음을 감안하여야 한다.
  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
----------------------------------------------------------------------------
 1032 root       7 -13    1608    324    256 R 41.3  0.0   0:21.67 full_load
  969 root      20   0    1608    356    288 R 31.3  0.0  29:24.63 full_load
 1011 root      10 -10    1608    368    304 R 21.0  0.0   6:52.56 full_load

 


태스크 그룹 로드 평균 갱신

update_tg_load_avg()

kernel/sched/fair.c

/**
 * update_tg_load_avg - update the tg's load avg
 * @cfs_rq: the cfs_rq whose avg changed
 * @force: update regardless of how small the difference
 *
 * This function 'ensures': tg->load_avg := \Sum tg->cfs_rq[]->avg.load.
 * However, because tg->load_avg is a global value there are performance
 * considerations.
 *
 * In order to avoid having to look at the other cfs_rq's, we use a
 * differential update where we store the last value we propagated. This in
 * turn allows skipping updates if the differential is 'small'.
 *
 * Updating tg's load_avg is necessary before update_cfs_share().
 */
static inline void update_tg_load_avg(struct cfs_rq *cfs_rq, int force)
{
        long delta = cfs_rq->avg.load_avg - cfs_rq->tg_load_avg_contrib;

        /*
         * No need to update load_avg for root_task_group as it is not used.
         */
        if (cfs_rq->tg == &root_task_group)
                return;

        if (force || abs(delta) > cfs_rq->tg_load_avg_contrib / 64) {
                atomic_long_add(delta, &cfs_rq->tg->load_avg);
                cfs_rq->tg_load_avg_contrib = cfs_rq->avg.load_avg;
        }
}

태스크 그룹 로드 평균을 갱신한다.

  • 코드 라인 3에서 cfs 런큐의 로드 평균과 지난번 마지막에 태스크 그룹에 기록한 로드 평균의 차이를 delta로 알아온다.
  • 코드 라인 8~9에서 루트 태스크 그룹의 로드 평균은 사용하지 않으므로 산출하지 않고 함수를 빠져나간다.
  • 코드 라인 11~14에서 cfs 로드 평균이 일정량 이상 변화가 있는 경우에만 태스크 그룹의 로드 평균에 추가한다. 태스크 그룹에 기록해둔 값은 다음 산출을 위해 cfs_rq->tg_load_avg_contrib에 기록해둔다.
    • cfs 런큐의 tg_load_avg_contrib 값의 1/64보다 델타 값이 큰 경우 또는 @force 요청이 있는 경우 다음과 같이 갱신한다.
    • 델타 값을 cfs 런큐가 가리키는 태스크 그룹의 로드 평균에 추가한다.
    • cfs 런큐의 tg_load_avg_contrib 는 cfs 런큐의 로드 평균으로 싱크한다.

 

tg->load_avg

태스크 그룹의 로드 평균은 cpu별로 동작하는 cfs 런큐들의 로드 평균을 모두 모아 반영한 글로벌 값이다.

 

cfs_rq->tg_load_avg_contrib

태스크 그룹에 대한 이 cfs 런큐 로드 평균의 기여분이다.

  • cfs 런큐의 로드 평균을 태스크 그룹에 추가한 후에, 이 멤버 변수에도 저장해둔다.
  • 이 값은 다음 갱신 시의 cfs 런큐의 로드 평균과 비교하기 위해 사용된다.

 

tg->load_avg 갱신 조건

글로벌인 태스크 그룹의 로드 평균 값을 변경하려면 atomic 연산을 사용해야 하는데 빈번한 갱신은 성능을 떨어뜨린다. 따라서 다음 조건에서만 갱신을 허용한다.

  • delta = 이번 cfs 런큐의 로드 평균과 지난번 태스크 그룹에 기여한 cfs 런큐의 로드 평균의 절대 차이 값
  • 지난번 cfs 런큐의 로드 평균을 태스크 그룹에 기여한 값의 1/64 보다 큰 변화(delta)가 있을 경우에만 갱신한다.

 

다음 그림은 각 cpu의 cfs 런큐가 태스크 그룹에 기여하는 로드 평균을 보여준다.

 

다음 그림은 update_tg_load_avg() 함수가 요청한 cfs 런큐의 로드 평균을 태스크 그룹의 로드 평균에 추가하는 과정을 보여준다.

 


그룹 엔티티 갱신

update_cfs_group()

kernel/sched/fair.c

/*
 * Recomputes the group entity based on the current state of its group
 * runqueue.
 */
static void update_cfs_group(struct sched_entity *se)
{
        struct cfs_rq *gcfs_rq = group_cfs_rq(se);
        long shares, runnable;

        if (!gcfs_rq)
                return;

        if (throttled_hierarchy(gcfs_rq))
                return;

#ifndef CONFIG_SMP
        runnable = shares = READ_ONCE(gcfs_rq->tg->shares);

        if (likely(se->load.weight == shares))
                return;
#else
        shares   = calc_group_shares(gcfs_rq);
        runnable = calc_group_runnable(gcfs_rq, shares);
#endif

        reweight_entity(cfs_rq_of(se), se, shares, runnable);
}

태스크 그룹에 설정된 shares(디폴트: 1024) 값에 cfs 런큐 로드 비율을 적용하여 그룹 엔티티의로드 weight을 재산출하여 갱신한다.

  • 코드 라인 3~7에서 그룹 엔티티의 그룹 cfs 런큐를 알아온다.
  • 코드 라인 9~10에서 그룹 cfs 런큐가 스로틀 중인 경우 함수를 빠져나간다.
그룹 엔티티의 로드 weight 재산출
  • 코드 라인 13~16에서 UP 시스템에서 태스크 그룹의 shares 값과 스케줄 엔티티의 로드 값이 동일하면 최대치를 사용하는 중이므로 함수를 빠져나간다.
  • 코드 라인 18에서 SMP 시스템에서 설정된 태스크 그룹의 shares 값을 전체 cpu 대비 해당 cpu의 cfs 런큐가 가진 가중치(weight) 비율만큼 곱하여 반환한다.
    •                                              해당 cpu의 cfs 런큐에 load.weight
    • shares = tg->shares =  ———————————————
    •                                                   태스크 그룹의 weight
  • 코드 라인 19에서 위에서 산출된 shares를 해당 cfs 런큐의 로드 평균 대비 러너블 로드 평균 비율만큼 곱하여 runnable 값으로 알아온다.
    •                                                  그룹 cfs 런큐의 러너블 로드 평균
    • runnable = shares * 비율 = ——————————————
    •                                                      그룹 cfs 런큐의 로드 평균
  • 코드 라인 22에서 산출된 shares 및 runnable 값으로 그룹 엔티티의 로드 weight과 러너블 로드 weight 값을 재계산한다

 

Shares에 따른 그룹 엔티티 로드 weight 산출

calc_group_shares()

kernel/sched/fair.c

/*
 * All this does is approximate the hierarchical proportion which includes that
 * global sum we all love to hate.
 *
 * That is, the weight of a group entity, is the proportional share of the
 * group weight based on the group runqueue weights. That is:
 *
 *                     tg->weight * grq->load.weight
 *   ge->load.weight = -----------------------------               (1)
 *                        \Sum grq->load.weight
 *
 * Now, because computing that sum is prohibitively expensive to compute (been
 * there, done that) we approximate it with this average stuff. The average
 * moves slower and therefore the approximation is cheaper and more stable.
 *
 * So instead of the above, we substitute:
 *
 *   grq->load.weight -> grq->avg.load_avg                         (2)
 *
 * which yields the following:
 *
 *                     tg->weight * grq->avg.load_avg
 *   ge->load.weight = ------------------------------              (3)
 *                              tg->load_avg
 *
 * Where: tg->load_avg ~= \Sum grq->avg.load_avg
 *
 * That is shares_avg, and it is right (given the approximation (2)).
 *
 * The problem with it is that because the average is slow -- it was designed
 * to be exactly that of course -- this leads to transients in boundary
 * conditions. In specific, the case where the group was idle and we start the
 * one task. It takes time for our CPU's grq->avg.load_avg to build up,
 * yielding bad latency etc..
 *
 * Now, in that special case (1) reduces to:
 *
 *                     tg->weight * grq->load.weight
 *   ge->load.weight = ----------------------------- = tg->weight   (4)
 *                          grp->load.weight
 *
 * That is, the sum collapses because all other CPUs are idle; the UP scenario.
 *
 * So what we do is modify our approximation (3) to approach (4) in the (near)
 * UP case, like:
 *
 *   ge->load.weight =
 *
 *              tg->weight * grq->load.weight
 *     ---------------------------------------------------         (5)
 *     tg->load_avg - grq->avg.load_avg + grq->load.weight
 *
 * But because grq->load.weight can drop to 0, resulting in a divide by zero,
 * we need to use grq->avg.load_avg as its lower bound, which then gives:
 *
 *
 *                     tg->weight * grq->load.weight
 *   ge->load.weight = -----------------------------               (6)
 *                              tg_load_avg'
 *
 * Where:
 *
 *   tg_load_avg' = tg->load_avg - grq->avg.load_avg +
 *                  max(grq->load.weight, grq->avg.load_avg)
 *
 * And that is shares_weight and is icky. In the (near) UP case it approaches
 * (4) while in the normal case it approaches (3). It consistently
 * overestimates the ge->load.weight and therefore:
 *
 *   \Sum ge->load.weight >= tg->weight
 *
 * hence icky!
 */

이 모든 작업은 우리 모두가 싫어하는 global 합계를 포함하는 계층적 비율에 가깝다.

즉, 그룹 엔티티의 가중치(weight)는 태스크 그룹에 설정된 weight 곱하기 전체 cpu 중 해당 cpu의 그룹 런큐 가중치 비율만큼으로 다음 수식과 같다.

  •                                                                         grq->load.weight
  • (1) ge->load.weight = tg->weight * ——————————–
  •                                                                    \Sum grq->load.weight
  •                                                                     해당 cpu의 그룹 런큐->load.weight
  •                                     = tg->weight * ——————————————————-
  •                                                                     전체 cpu들의 그룹 런큐->load.weight 총합

위와 같이 전체 cpu의 그룹 런큐 가중치(weight)를 모두 합하는 것은 매우 비싼 코스트이다. 어짜피 평균은 천천히 움직이므로 대략 산출하는 것이 값싸고, 더 안정적이다. 그래서 위의 그룹 런큐의 load.weight 대신 그룹 런큐의 로드 평균으로 대체한다.

  • (2) grq->load.weight -> grq->avg.load_avg

결과는 다음과 같다.

  •                                                                     grq->avg.load_avg
  • (3) ge->load.weight = tg->weight * —————————
  •                                                                          tg->load_avg
  •                                                                     해당 cpu의 그룹 런큐 로드 평균
  •                                      = tg->weight * ——————————————-
  •                                                                           태스크 그룹의 로드 평균

여기: tg->load_avg ~= \Sum grq->avg.load_avg

태스크 그룹의 로드 평균에는 이미 모든 cpu의 그룹 런큐에서 일정량 이상의 큰 변화들은 집계된다.(적은 변화는 성능을 위해 추후 모아 변화량이 커질때에만 처리한다.)

그런데 평균은 느린 문제가 있다. 이로 인해 경계 조건에서 과도 현상이 발생한다. 구체적으로, 그룹이 idle 상태이고 하나의 작업을 시작하는 경우이다. cpu의 grq-> avg.load_avg가 쌓이는 데 시간이 걸리고 지연 시간이 나빠진다.

이제 특별한 경우 (1)번 케이스는 다음과 같이 축소한다.

  •                                                                      grq->load.weight
  • (4)                               = tg->weight * ————————– = tg->weight
  •                                                                      grq->load.weight

위의 시나리오에선 다른 모든 cpu가 idle 상태일 때 합계가 축소된다. 그래서 다음과 같이 UP(Uni Processor) 케이스에서 (4)에 접근하도록 다음과 같이 근사값 (3)을 수정하였다.

  •                                                                                                    grq->load.weight
  • (5)                               = tg->weight * ——————————————————————–
  •                                                                    tg->load_avg – grq->avg.load_avg + grq->load.weight

그러나 grq-> load.weight이 0으로 떨어질 수 있고 결과적으로 0으로 나눌 수 있으므로 grq-> avg.load_avg를 하한값으로 사용해야 한다.

  •                                                                      grq->load.weight
  • (6)                               = tg->weight * —————————–
  •                                                                         tg_load_avg’
  • tg_load_avg’ = tg->load_avg – grq->avg.load_avg + max(grq->load.weight, grq->avg.load_avg)

UP(Uni Processor)의 경우 (4)번 방식을 사용하고, 정상적인 경우 (3)번 방식을 사용한다. ge-> load.weight를 지속적으로 과대 평가하므로 다음과 같다.

  • \Sum ge->load.weight >= tg->weight

 

static long calc_group_shares(struct cfs_rq *cfs_rq)
{
        long tg_weight, tg_shares, load, shares;
        struct task_group *tg = cfs_rq->tg;

        tg_shares = READ_ONCE(tg->shares);

        load = max(scale_load_down(cfs_rq->load.weight), cfs_rq->avg.load_avg);

        tg_weight = atomic_long_read(&tg->load_avg);

        /* Ensure tg_weight >= load */
        tg_weight -= cfs_rq->tg_load_avg_contrib;
        tg_weight += load;

        shares = (tg_shares * load);
        if (tg_weight)
                shares /= tg_weight;

        /*
         * MIN_SHARES has to be unscaled here to support per-CPU partitioning
         * of a group with small tg->shares value. It is a floor value which is
         * assigned as a minimum load.weight to the sched_entity representing
         * the group on a CPU.
         *
         * E.g. on 64-bit for a group with tg->shares of scale_load(15)=15*1024
         * on an 8-core system with 8 tasks each runnable on one CPU shares has
         * to be 15*1024*1/8=1920 instead of scale_load(MIN_SHARES)=2*1024. In
         * case no task is runnable on a CPU MIN_SHARES=2 should be returned
         * instead of 0.
         */
        return clamp_t(long, shares, MIN_SHARES, tg_shares);
}

태스크 그룹의 shares 값을 전체 cpu 대비 해당 cpu의 cfs 런큐가 가진 가중치(weight) 비율만큼 곱하여 반환한다.

  • 코드 라인 4~6에서 그룹 런큐가 소속된 태스크 그룹에 설정된 shares 값을 알아온다.
    • 예) 루트 태스크 그룹의 디폴트 share 값(/sys/fs/cgroup/cpu/cpu.shares) = 1024
  • 코드 라인 8~18에서 다음 수식과 같이 shares 값을 산출한다.
    •                                                                  max(grq->load.weight, grq->avg.load_avg)
    • = tg->shares * ———————————————————————————————————–
    •                              tg->load_avg – grq->tg_load_avg_contrib + max(grq->load.weight, grq->avg.load_avg)
  • 코드 라인 32에서 산출한 shares 값은 MIN_SHARES(2) ~ tg->shares 범위를 벗어나는 경우 clamp하여 반환한다.

 

예) tg->shares=1024, cpu#0의 qrq->load.weight=1024, cpu#1의 qrq->load.weight=2048

  • cpu#0: 1024 * 1024 / 3072 = 341
  • cpu#1: 1024 * 2048 / 3072 = 682

 

Shares에 따른 그룹 엔티티 러너블 weight 재산출

 

/*
 * This calculates the effective runnable weight for a group entity based on
 * the group entity weight calculated above.
 *
 * Because of the above approximation (2), our group entity weight is
 * an load_avg based ratio (3). This means that it includes blocked load and
 * does not represent the runnable weight.
 *
 * Approximate the group entity's runnable weight per ratio from the group
 * runqueue:
 *
 *                                           grq->avg.runnable_load_avg
 *   ge->runnable_weight = ge->load.weight * -------------------------- (7)
 *                                               grq->avg.load_avg
 *
 * However, analogous to above, since the avg numbers are slow, this leads to
 * transients in the from-idle case. Instead we use:
 *
 *   ge->runnable_weight = ge->load.weight *
 *
 *              max(grq->avg.runnable_load_avg, grq->runnable_weight)
 *              -----------------------------------------------------   (8)
 *                    max(grq->avg.load_avg, grq->load.weight)
 *
 * Where these max() serve both to use the 'instant' values to fix the slow
 * from-idle and avoid the /0 on to-idle, similar to (6).
 */

위 함수에서 산출한 그룹 엔티티 가중치(weight)를 기반으로 그룹 엔티티의 유효 러너블 가중치(weight)를 산출한다.

위의 근사치 (2)번을 이유로, 그룹 엔티티 가중치(weight)는 (3)의 로드 평균 기반 비율이다. 이는 러너블 가중치(weight)를 나타내게 한 게 아니라, blocked 로드를 포함함을 의미한다. (가중치 비율이 아닌 로드 평균 비율을 사용하면 러너블과 blocked 로드 둘다 포함한다.)

그룹 런큐별 그룹 엔티티의 러너블 가중치(weight)를 대략적으로 산출한다.

  •                                                                                        grq->avg.runnable_load_avg
  • (7) ge->runnable_weight = ge->load.weight * —————————————–
  •                                                                                                 grq->avg.load_avg
그러나 위와 유사하게 평균 숫자가 느리기 때문에 idle 상태에서 과도 현상이 발생한다. 대신 다음을 사용한다.
  •                                                                                         max(grq->avg.runnable_load_avg, grq->runnable_weight)
  • (8)                                          = ge->load.weight * —————————————————————————
  •                                                                                                    max(grq->avg.load_avg, grq->load.weight)
max()는 ‘인스턴트’값을 사용하여 다음 두 문제를 피하는데 모두 사용한다. (6)
  • idle로 시작하는 느림을 교정
  • idle 상태에서 0으로 나누는 문제

calc_group_runnable()

kernel/sched/fair.c

static long calc_group_runnable(struct cfs_rq *cfs_rq, long shares)
{
        long runnable, load_avg;

        load_avg = max(cfs_rq->avg.load_avg,
                       scale_load_down(cfs_rq->load.weight));

        runnable = max(cfs_rq->avg.runnable_load_avg,
                       scale_load_down(cfs_rq->runnable_weight));

        runnable *= shares;
        if (load_avg)
                runnable /= load_avg;

        return clamp_t(long, runnable, MIN_SHARES, shares);
}

@shares를 전체 cpu 대비 해당 cpu의 러너블 비율만큼 곱한 runnable 값을 반환한다.

  • 코드 라인 5~13에서 다음과 같이 runnable 값을 산출한다.
    •                         max(grq->avg.runnable_avg, grq->runnable.weight)
    • = shares * ———————————————————————
    •                                 max(grq->avg.load_avg, grq->load.weight)
  • 코드 라인 15에서 산출한 runnable 값은 MIN_SHARES(2) ~ @shares 범위를 벗어나는 경우 clamp하여 반환한다.

 

그룹 엔티티의 weight 재설정

reweight_entity()

kernel/sched/fair.c

static void reweight_entity(struct cfs_rq *cfs_rq, struct sched_entity *se,
                            unsigned long weight, unsigned long runnable)
{
        if (se->on_rq) {
                /* commit outstanding execution time */
                if (cfs_rq->curr == se)
                        update_curr(cfs_rq);
                account_entity_dequeue(cfs_rq, se);
                dequeue_runnable_load_avg(cfs_rq, se);
        }
        dequeue_load_avg(cfs_rq, se);

        se->runnable_weight = runnable;
        update_load_set(&se->load, weight);

#ifdef CONFIG_SMP
        do {
                u32 divider = LOAD_AVG_MAX - 1024 + se->avg.period_contrib;

                se->avg.load_avg = div_u64(se_weight(se) * se->avg.load_sum, divider);
                se->avg.runnable_load_avg =
                        div_u64(se_runnable(se) * se->avg.runnable_load_sum, divider);
        } while (0);
#endif

        enqueue_load_avg(cfs_rq, se);
        if (se->on_rq) {
                account_entity_enqueue(cfs_rq, se);
                enqueue_runnable_load_avg(cfs_rq, se);
        }
}

엔티티의 로드 및 러너블 weight 값을 재설정한다.

엔티티의 기존 가중치 관련 빼기
  • 코드 라인 4~10에서 엔티티가 cfs 런큐에 존재하는 경우 cfs 런큐에서 기존 가중치 및 로드등을 빼기 위해 다음을 수행한다.
    • 엔티티가 현재 러닝 중인 경우 현재 엔티티에 대해 로드 등을 갱신한다.
    • cfs 런큐에서 엔티티의 기존 가중치를 뺀다.
    • cfs 런큐에서 엔티티의 기존 러너블 가중치를 뺀다.
  • 코드 라인 11에서 cfs 런큐에서 엔티티의 기존 로드 합계 및 평균을 뺀다.
엔티티의 새 가중치 재설정
  • 코드 라인 13~14에서 엔티티에 인자로 전달 받은 새 @runnable과 @weight를 적용한다.
엔티티의 새 가중치 더하기
  • 코드 라인 18~22에서 엔티티의 새 가중치로 로드 평균과 러너블 평균을 재산출한다.
  • 코드 라인 26에서 cfs 런큐에 새 가중치 값을 가진 엔티티의 로드 합계 및 평균을 더한다.
  • 코드 라인 27~30에서 엔티티가 cfs 런큐에 존재하는 경우 cfs 런큐에 새 가중치 및 로드등을 더하기 위해 다음을 수행한다.
    • cfs 런큐에 엔티티의 새 가중치를 더한다.
    • cfs 런큐에 엔티티의 새 러너블 가중치를 더한다.

 

update_load_set()

kernel/sched/fair.c

static inline void update_load_set(struct load_weight *lw, unsigned long w)
{
        lw->weight = w;
        lw->inv_weight = 0;
}

로드 weight 걊을 설정하고 inv_weight 값은 0으로 리셋한다.

 


엔큐 & 디큐 엔티티 시 PELT

엔큐 엔티티

enqueue_entity()

kernel/sched/fair.c

static void
enqueue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
(...생략...)
        /*
         * When enqueuing a sched_entity, we must:
         *   - Update loads to have both entity and cfs_rq synced with now.
         *   - Add its load to cfs_rq->runnable_avg
         *   - For group_entity, update its weight to reflect the new share of
         *     its group cfs_rq
         *   - Add its new weight to cfs_rq->load.weight
         */
        update_load_avg(cfs_rq, se, UPDATE_TG | DO_ATTACH);
        update_cfs_group(se);
        enqueue_runnable_load_avg(cfs_rq, se);
        account_entity_enqueue(cfs_rq, se);
(...생략...)
}

엔티티를 cfs 런큐에 엔큐한다.

  • 코드 라인 13에서 요청 엔티티를 갱신한다.
    • UPDATE_TG 플래그는 태스크 그룹까지 갱신을 요청한다.
    • DO_ATTACH 플래그는 migration에 의해 attach된 태스크가 있는 경우 이의 처리를 요청한다.
  • 코드 라인 14에서 그룹 엔티티 및 cfs 런큐를 갱신한다.
  • 코드 라인 15에서 엔큐할 엔티티의 로드 평균 등을 cfs 런큐에 추가한다.
  • 코드 라인 16에서 cfs 런큐에 엔티티가 엔큐될 때의 account를 처리한다.

 

엔큐 엔티티 시 로드 평균

enqueue_load_avg()

kernel/sched/fair.c

static inline void
enqueue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        cfs_rq->avg.load_avg += se->avg.load_avg;
        cfs_rq->avg.load_sum += se_weight(se) * se->avg.load_sum;
}

엔티티 로드 평균을 cfs 런큐에 추가한다.

  • 코드 라인 4에서 엔티티 로드 평균을 cfs 런큐에 추가한다.
  • 코드 라인 5에서 엔티티의 weight에 로드 합계를 곱해 cfs 런큐에 추가한다.

 

엔큐 엔티티 시 러너블 로드 평균

enqueue_runnable_load_avg()

kernel/sched/fair.c

static inline void
enqueue_runnable_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        cfs_rq->runnable_weight += se->runnable_weight;

        cfs_rq->avg.runnable_load_avg += se->avg.runnable_load_avg;
        cfs_rq->avg.runnable_load_sum += se_runnable(se) * se->avg.runnable_load_sum;
}

엔티티 러너블 로드 평균을 cfs 런큐에 추가한다.

  • 코드 라인 4에서 엔티티 러너블 weight를 cfs 런큐에 추가한다.
  • 코드 라인 6에서 엔티티 러너블 로드 평균을 cfs 런큐에 추가한다.
  • 코드 라인 7에서 엔티티 러너블 수와 러너블 로드 합계를 곱해 cfs 런큐에 추가한다.

 

엔큐 엔티티 시 account

account_entity_enqueue()

kernel/sched/fair.c

static void
account_entity_enqueue(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        update_load_add(&cfs_rq->load, se->load.weight);
#ifdef CONFIG_SMP
        if (entity_is_task(se)) {
                struct rq *rq = rq_of(cfs_rq);

                account_numa_enqueue(rq, task_of(se));
                list_add(&se->group_node, &rq->cfs_tasks);
        }
#endif
        cfs_rq->nr_running++;
}

cfs 런큐에 엔티티가 엔큐될 때의 account를 처리한다.

  • 코드 라인 4에서 cfs 런큐에 엔티티의 로드를 추가한다.
  • 코드 라인 6~11에서 태스크용 엔티티가 enqueue된 경우 런큐의 NUMA 카운터를 갱신하고, 태스크를 런큐의 cfs_tasks 리스트에 추가한다.
  • 코드 라인 13에서 cfs 런큐에서 엔티티 수를 담당하는 nr_running 카운터를 1 증가시킨다.

 

account_numa_enqueue()

kernel/sched/fair.c

static void account_numa_enqueue(struct rq *rq, struct task_struct *p)
{
        rq->nr_numa_running += (p->numa_preferred_nid != NUMA_NO_NODE);
        rq->nr_preferred_running += (p->numa_preferred_nid == task_node(p));
}

태스크가 enqueue된 경우에 호출되며, 런큐의 NUMA 카운터를 갱신한다.

  • rq->nr_numa_running++ (태스크의 권장 누마 노드가 지정된 경우에만)
  • rq->nr_preferred_running++ (태스크의 노드가 권장 누마 노드와 동일한 경우에만)

 

디큐 엔티티

dequeue_entity()

kernel/sched/fair.c

static void
dequeue_entity(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
(...생략...) 
        /*
         * When dequeuing a sched_entity, we must:
         *   - Update loads to have both entity and cfs_rq synced with now.
         *   - Subtract its load from the cfs_rq->runnable_avg.
         *   - Subtract its previous weight from cfs_rq->load.weight.
         *   - For group entity, update its weight to reflect the new share
         *     of its group cfs_rq.
         */
        update_load_avg(cfs_rq, se, UPDATE_TG);
        dequeue_runnable_load_avg(cfs_rq, se);
(...생략...)
        account_entity_dequeue(cfs_rq, se);
(...생략...)
        update_cfs_group(se);
(...생략...) 
}

엔티티를 cfs 런큐로부터 디큐한다.

  • 코드 라인 13에서 요청 엔티티를 갱신한다.
    • UPDATE_TG 플래그는 태스크 그룹까지 갱신을 요청한다.
  • 코드 라인 14에서 디큐할 엔티티의 로드 평균 등을 cfs 런큐로부터 감산한다.
  • 코드 라인 16에서 cfs 런큐로부터 엔티티가 디큐될 때의 account를 처리한다.
  • 코드 라인 18에서 그룹 엔티티 및 cfs 런큐를 갱신한다.

 

디큐 엔티티 시 로드 평균

dequeue_load_avg()

kernel/sched/fair.c

static inline void
dequeue_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        sub_positive(&cfs_rq->avg.load_avg, se->avg.load_avg);
        sub_positive(&cfs_rq->avg.load_sum, se_weight(se) * se->avg.load_sum);
}

엔티티 로드 평균만큼을 cfs 런큐에서 감소시킨다.

  • 코드 라인 4에서 엔티티 로드 평균만큼을 cfs 런큐에서 감소시킨다.
  • 코드 라인 5에서 엔티티의 weight에 로드 합계를 곱한 만큼을 cfs 런큐에서 감소시킨다.

 

디큐 엔티티 시 러너블 로드 평균

dequeue_runnable_load_avg()

kernel/sched/fair.c

static inline void
dequeue_runnable_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        cfs_rq->runnable_weight -= se->runnable_weight;

        sub_positive(&cfs_rq->avg.runnable_load_avg, se->avg.runnable_load_avg);
        sub_positive(&cfs_rq->avg.runnable_load_sum,
                     se_runnable(se) * se->avg.runnable_load_sum);
}

엔티티 러너블 로드 평균만큼을 cfs 런큐에서 감소시킨다.

  • 코드 라인 4에서 엔티티 러너블 weight 만큼을 cfs 런큐에서 감소시킨다.
  • 코드 라인 6에서 엔티티 러너블 로드 평균 만큼을 cfs 런큐에서 감소시킨다.
  • 코드 라인 7~8에서 엔티티 러너블 수와 러너블 로드 합계를 곱한 만큼을 cfs 런큐에서 감소시킨다.

 

디큐 엔티티 시 account

account_entity_dequeue()

kernel/sched/fair.c

static void
account_entity_dequeue(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        update_load_sub(&cfs_rq->load, se->load.weight);
#ifdef CONFIG_SMP
        if (entity_is_task(se)) {
                account_numa_dequeue(rq_of(cfs_rq), task_of(se));
                list_del_init(&se->group_node);
        }
#endif
        cfs_rq->nr_running--;
}

cfs 런큐에 엔티티가 디큐될 때의 account를 처리한다.

  • 코드 라인 4에서 엔티티의 로드 weight 값을 cfs 런큐에서 차감한다.
  • 코드 라인 6~9에서 태스크용 엔티티의 경우 런큐의 누마 관련 카운터 값을 감소시키고, 런큐의 cfs_tasks 리스트에서 태스크를 제거한다.
  • 코드 라인 11에서 cfs 런큐에서 엔티티 수를 담당하는 nr_running을 1 감소시킨다.

 

account_numa_dequeue()

kernel/sched/fair.c

static void account_numa_dequeue(struct rq *rq, struct task_struct *p)
{
        rq->nr_numa_running -= (p->numa_preferred_nid != NUMA_NO_NODE);
        rq->nr_preferred_running -= (p->numa_preferred_nid == task_node(p));
}

태스크가 디큐된 경우에 호출되며, 런큐의 NUMA 카운터를 갱신한다.

  • rq->nr_numa_running– (태스크의 권장 누마 노드가 지정된 경우에만)
  • rq->nr_preferred_running– (태스크의 노드가 권장 누마 노드와 동일한 경우에만)

 


Attach & Detach 엔티티 시 PELT

Attach 엔티티

attach_entity_cfs_rq()

kernel/sched/fair.c

static void attach_entity_cfs_rq(struct sched_entity *se)
{
(...생략...)
        /* Synchronize entity with its cfs_rq */
        update_load_avg(cfs_rq, se, sched_feat(ATTACH_AGE_LOAD) ? 0 : SKIP_AGE_LOAD);
        attach_entity_load_avg(cfs_rq, se, 0);
        update_tg_load_avg(cfs_rq, false);
        propagate_entity_cfs_rq(se);
}

마이그레이션 또는 새 태스크의 로드를 cfs 런큐에 attach 하여 cfs 런큐 및 태스크 그룹의 로드들을 갱신한다.

  • 코드 라인 5에서 엔티티의 로드 평균 등을 갱신한다.
    • 단 ATTACH_AGE_LOAD feature가 없는 경우 엔티티의 로드 평균 갱신만 skip 하고 cfs 런큐의 로드 평균 및 propagation 등은 수행한다.
  • 코드 라인 6에서 엔티티 attach 시 CFS 런큐에  로드 평균을 추가한다.
  • 코드 라인 7에서 cfs 런큐의 로드가 추가되었으므로 태스크 그룹에 대한 로드 평균도 갱신한다.
  • 코드 라인 8에서 최상위 그룹 엔티티까지 전파된 러너블 로드를 추가하여 각 그룹을 재산출한다.

 

attach 엔티티 시 로드 평균

attach_entity_load_avg()

kernel/sched/fair.c

/**
 * attach_entity_load_avg - attach this entity to its cfs_rq load avg
 * @cfs_rq: cfs_rq to attach to
 * @se: sched_entity to attach
 * @flags: migration hints
 *
 * Must call update_cfs_rq_load_avg() before this, since we rely on
 * cfs_rq->avg.last_update_time being current.
 */
static void attach_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se, int flags)
{
        u32 divider = LOAD_AVG_MAX - 1024 + cfs_rq->avg.period_contrib;

        /*
         * When we attach the @se to the @cfs_rq, we must align the decay
         * window because without that, really weird and wonderful things can
         * happen.
         *
         * XXX illustrate
         */
        se->avg.last_update_time = cfs_rq->avg.last_update_time;
        se->avg.period_contrib = cfs_rq->avg.period_contrib;

        /*
         * Hell(o) Nasty stuff.. we need to recompute _sum based on the new
         * period_contrib. This isn't strictly correct, but since we're
         * entirely outside of the PELT hierarchy, nobody cares if we truncate
         * _sum a little.
         */
        se->avg.util_sum = se->avg.util_avg * divider;

        se->avg.load_sum = divider;
        if (se_weight(se)) {
                se->avg.load_sum =
                        div_u64(se->avg.load_avg * se->avg.load_sum, se_weight(se));
        }

        se->avg.runnable_load_sum = se->avg.load_sum;

        enqueue_load_avg(cfs_rq, se);
        cfs_rq->avg.util_avg += se->avg.util_avg;
        cfs_rq->avg.util_sum += se->avg.util_sum;

        add_tg_cfs_propagate(cfs_rq, se->avg.load_sum);

        cfs_rq_util_change(cfs_rq, flags);

        trace_pelt_cfs_tp(cfs_rq);
}

엔티티 attach 시 CFS 런큐에  로드 평균을 추가한다.

  • 코드 라인 3에서 divider는 로드 평균 산출에 사용할 기간을 구한다.
  • 코드 라인 12에서 엔티티의 cfs 런큐의 last_update_time을 엔티티에 대입한다.
  • 코드 라인 13에서 cfs 런큐의 decay하지 않은 period_contrib를 엔티티에 대입한다.
  • 코드 라인 21에서 엔티티의 유틸 평균에 기간 divider를 곱하여 유틸 합계를 구한다.
  • 코드 라인 23~27에서 엔티티의 로드 합계에 로드 평균 * 기간 divider / weight 결과를 대입한다.
  • 코드 라인 29에서 러너블 로드 합계에도 로드 합계를 대입한다.
  • 코드 라인 31에서 cfs 런큐에 로드 평균을 추가한다.
  • 코드 라인 32~33에서 cfs 런큐의 유틸 합계 및 평균에 엔티티의 유틸 합계 및 평균을 추가한다.
  • 코드 라인 35에서 태스크 그룹에 대한 전파를 한다.
  • 코드 라인 37에서 유틸 값이 바뀌면 cpu frequency 변경 기능이 있는 시스템의 경우 주기적으로 변경한다.

Detach 엔티티

detach_entity_cfs_rq()

kernel/sched/fair.c

static void detach_entity_cfs_rq(struct sched_entity *se)
{
        struct cfs_rq *cfs_rq = cfs_rq_of(se);

        /* Catch up with the cfs_rq and remove our load when we leave */
        update_load_avg(cfs_rq, se, 0);
        detach_entity_load_avg(cfs_rq, se);
        update_tg_load_avg(cfs_rq, false);
        propagate_entity_cfs_rq(se);
}

마이그레이션에 의해 cfs 런큐로부터 엔티티를 detach 한다.

  • 코드 라인 6에서 엔티티의 로드 평균 등을 갱신한다.
  • 코드 라인 7에서 엔티티 detach 시 CFS 런큐로부터 로드 평균을 감산한다.
  • 코드 라인 8에서 cfs 런큐에 로드가 감소하였으므로 태스크 그룹에 대한 로드 평균도 갱신한다.
  • 코드 라인 9에서 최상위 그룹 엔티티까지 전파하여 각 그룹의 로드 평균을 재산출한다.

 

Detach 엔티티 시 로드 평균

detach_entity_load_avg()

kernel/sched/fair.c

/**
 * detach_entity_load_avg - detach this entity from its cfs_rq load avg
 * @cfs_rq: cfs_rq to detach from
 * @se: sched_entity to detach
 *
 * Must call update_cfs_rq_load_avg() before this, since we rely on
 * cfs_rq->avg.last_update_time being current.
 */
static void detach_entity_load_avg(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
        dequeue_load_avg(cfs_rq, se);
        sub_positive(&cfs_rq->avg.util_avg, se->avg.util_avg);
        sub_positive(&cfs_rq->avg.util_sum, se->avg.util_sum);

        add_tg_cfs_propagate(cfs_rq, -se->avg.load_sum);

        cfs_rq_util_change(cfs_rq, 0);

        trace_pelt_cfs_tp(cfs_rq);
}

엔티티 detach 시 CFS 런큐에  로드 평균을 감소시킨다.

  • 코드 라인 3에서 엔티티 로드 평균만큼을 cfs 런큐에서 감소시킨다.
  • 코드 라인 4에서 엔티티 유틸 평균만큼을 cfs 런큐에서 감소시킨다.
  • 코드 라인 5에서 엔티티 유틸 합계 만큼을 cfs 런큐에서 감소시킨다.
  • 코드 라인 7에서 태스크 그룹에 대한 전파를 한다.
  • 코드 라인 9에서 유틸 값이 바뀌면 cpu frequency 변경 기능이 있는 시스템의 경우 주기적으로 변경한다.

 

엔티티 로드 평균 제거

remove_entity_load_avg()

kernel/sched/fair.c

/*
 * Task first catches up with cfs_rq, and then subtract
 * itself from the cfs_rq (task must be off the queue now).
 */
static void remove_entity_load_avg(struct sched_entity *se)
{
        struct cfs_rq *cfs_rq = cfs_rq_of(se);
        unsigned long flags;

        /*
         * tasks cannot exit without having gone through wake_up_new_task() ->
         * post_init_entity_util_avg() which will have added things to the
         * cfs_rq, so we can remove unconditionally.
         */

        sync_entity_load_avg(se);

        raw_spin_lock_irqsave(&cfs_rq->removed.lock, flags);
        ++cfs_rq->removed.nr;
        cfs_rq->removed.util_avg        += se->avg.util_avg;
        cfs_rq->removed.load_avg        += se->avg.load_avg;
        cfs_rq->removed.runnable_sum    += se->avg.load_sum; /* == runnable_sum */
        raw_spin_unlock_irqrestore(&cfs_rq->removed.lock, flags);
}

cfs 런큐에서 엔티티의 로드를 감소시키도록 요청하고, 추후 cfs 로드 평균을 갱신할 때 효과를 본다.

  • 코드 라인 12에서 엔티티 로드 평균을 동기화한다.
  • 코드 라인 14~19에서 cfs 런큐의 removed에 엔티티의 유틸/로드 평균 및 로드 합계를 추가한다.

 

sync_entity_load_avg()

kernel/sched/fair.c

/*
 * Synchronize entity load avg of dequeued entity without locking
 * the previous rq.
 */
static void sync_entity_load_avg(struct sched_entity *se)
{
        struct cfs_rq *cfs_rq = cfs_rq_of(se);
        u64 last_update_time;

        last_update_time = cfs_rq_last_update_time(cfs_rq);
        __update_load_avg_blocked_se(last_update_time, se);
}

엔티티 로드 평균을 동기화한다.

 


유틸 변경에 따른 cpu frequency 변경

cfs_rq_util_change()

kernel/sched/fair.c

static inline void cfs_rq_util_change(struct cfs_rq *cfs_rq, int flags)
{
        struct rq *rq = rq_of(cfs_rq);

        if (&rq->cfs == cfs_rq || (flags & SCHED_CPUFREQ_MIGRATION)) {
                /*
                 * There are a few boundary cases this might miss but it should
                 * get called often enough that that should (hopefully) not be
                 * a real problem.
                 *
                 * It will not get called when we go idle, because the idle
                 * thread is a different class (!fair), nor will the utilization
                 * number include things like RT tasks.
                 *
                 * As is, the util number is not freq-invariant (we'd have to
                 * implement arch_scale_freq_capacity() for that).
                 *
                 * See cpu_util().
                 */
                cpufreq_update_util(rq, flags);
        }
}

유틸 값이 바뀌면 cpu frequency를 바꿀 수 있는 기능을 가진 런큐인 경우 cpu frequency를 변경 시도를 한다.

 

cpufreq_update_util()

kernel/sched/sched.h

/**
 * cpufreq_update_util - Take a note about CPU utilization changes.
 * @rq: Runqueue to carry out the update for.
 * @flags: Update reason flags.
 *
 * This function is called by the scheduler on the CPU whose utilization is
 * being updated.
 *
 * It can only be called from RCU-sched read-side critical sections.
 *
 * The way cpufreq is currently arranged requires it to evaluate the CPU
 * performance state (frequency/voltage) on a regular basis to prevent it from
 * being stuck in a completely inadequate performance level for too long.
 * That is not guaranteed to happen if the updates are only triggered from CFS
 * and DL, though, because they may not be coming in if only RT tasks are
 * active all the time (or there are RT tasks only).
 *
 * As a workaround for that issue, this function is called periodically by the
 * RT sched class to trigger extra cpufreq updates to prevent it from stalling,
 * but that really is a band-aid.  Going forward it should be replaced with
 * solutions targeted more specifically at RT tasks.
 */
static inline void cpufreq_update_util(struct rq *rq, unsigned int flags)
{
        struct update_util_data *data;

        data = rcu_dereference_sched(*per_cpu_ptr(&cpufreq_update_util_data,
                                                  cpu_of(rq)));
        if (data)
                data->func(data, rq_clock(rq), flags);
}

유틸 평균이 바뀌면 cpu frequency를 바꿀 수 있도록 해당 후크 함수를 호출한다.

  • 각 드라이버에서 hw 특성으로 인해 cpu frequency를 변경하는 cost가 있어 각각의 정책에 따라 최소 수ms 또는 수십~수백 ms 주기마다 frequency를 설정한다.
  • cpufreq_add_update_util_hook() API 함수를 사용하여 후크 함수를 등록하고, 현재 커널에 등록된 후크 함수들은 다음과같다.
    • 기본 cpu frequency governors util
      • kernel/sched/cpufreq_schedutil.c – sugov_start()
    • CPUFREQ governors 서브시스템
      • drivers/cpufreq/cpufreq_governor.c – dbs_update_util_handler()
    • 인텔 x86 드라이버
      • drivers/cpufreq/intel_pstate.c – intel_pstate_update_util_hwp() 또는 intel_pstate_update_util()

 


기타

entity_is_task()

kernel/sched/sched.h

/* An entity is a task if it doesn't "own" a runqueue */
#define entity_is_task(se)      (!se->my_q)

스케줄 엔티티가 태스크인지 여부를 반환한다.

  • 태스크인 경우 se->cfs_rq를 사용하고 태스크 그룹을 사용하는 경우 se->my_q를 사용한다.

 

group_cfs_rq()

kernel/sched/fair.c

/* runqueue "owned" by this group */
static inline struct cfs_rq *group_cfs_rq(struct sched_entity *grp)
{
        return grp->my_q;
}

스케줄 엔티티의 런큐를 반환한다.

 


새 태스크의 유틸 평균 초기화

post_init_entity_util_avg()

kernel/sched/fair.c

/*
 * With new tasks being created, their initial util_avgs are extrapolated
 * based on the cfs_rq's current util_avg:
 *
 *   util_avg = cfs_rq->util_avg / (cfs_rq->load_avg + 1) * se.load.weight
 *
 * However, in many cases, the above util_avg does not give a desired
 * value. Moreover, the sum of the util_avgs may be divergent, such
 * as when the series is a harmonic series.
 *
 * To solve this problem, we also cap the util_avg of successive tasks to
 * only 1/2 of the left utilization budget:
 *
 *   util_avg_cap = (cpu_scale - cfs_rq->avg.util_avg) / 2^n
 *
 * where n denotes the nth task and cpu_scale the CPU capacity.
 *
 * For example, for a CPU with 1024 of capacity, a simplest series from
 * the beginning would be like:
 *
 *  task  util_avg: 512, 256, 128,  64,  32,   16,    8, ...
 * cfs_rq util_avg: 512, 768, 896, 960, 992, 1008, 1016, ...
 *
 * Finally, that extrapolated util_avg is clamped to the cap (util_avg_cap)
 * if util_avg > util_avg_cap.
 */
void post_init_entity_util_avg(struct task_struct *p)
{
        struct sched_entity *se = &p->se;
        struct cfs_rq *cfs_rq = cfs_rq_of(se);
        struct sched_avg *sa = &se->avg;
        long cpu_scale = arch_scale_cpu_capacity(cpu_of(rq_of(cfs_rq)));
        long cap = (long)(cpu_scale - cfs_rq->avg.util_avg) / 2;

        if (cap > 0) {
                if (cfs_rq->avg.util_avg != 0) {
                        sa->util_avg  = cfs_rq->avg.util_avg * se->load.weight;
                        sa->util_avg /= (cfs_rq->avg.load_avg + 1);

                        if (sa->util_avg > cap)
                                sa->util_avg = cap;
                } else {
                        sa->util_avg = cap;
                }
        }

        if (p->sched_class != &fair_sched_class) {
                /*
                 * For !fair tasks do:
                 *
                update_cfs_rq_load_avg(now, cfs_rq);
                attach_entity_load_avg(cfs_rq, se, 0);
                switched_from_fair(rq, p);
                 *
                 * such that the next switched_to_fair() has the
                 * expected state.
                 */
                se->avg.last_update_time = cfs_rq_clock_pelt(cfs_rq);
                return;
        }

        attach_entity_cfs_rq(se);
}

fork된 새 태스크가 동작할 cfs 런큐의 로드 평균을 사용하여 태스크의 유틸 평균에 대한 초기값을 결정한다.

  • 코드 라인 6에서 요청한 태스크가 동작 중인 cpu의 scale 적용된 capacity를 알아온다.
    • big/little 클러스터에서 big cpu는 최대 값인 1024
  • 코드 라인 7~17에서 알아온 capacity와 cfs 런큐의 유틸 평균과의 차이 절반을 cap에 대입하고, 이 cap이 0보다 큰 경우 다음과 같이 처리한다.
    • cfs 런큐의 유틸 평균이 0인 경우
      • 그룹 엔티티의 유틸 평균 = cap
    • cfs 런큐의 유틸 평균이 0보다 큰 경우 다음과 같이계산하고 최대 값은 cap으로 제한한다.
      • 엔티티의 유틸 평균 = cfs 런큐 유틸 평균 * 그룹 엔티티의 weight / cfs 런큐 로드 평균
  • 코드 라인 19~32에서 cfs 태스크가 아닌 경우엔 엔티티의 통계 갱신 시각을 현재 pelt 클럭으로 갱신하고 함수를 빠져나간다.
  • 코드 라인 34에서 cfs 태스크인 경우 엔티티의 로드를 cfs 런큐에 attach 하여 cfs 런큐 및 태스크 그룹의 로드들을 갱신한다.

 


구조체

sched_avg 구조체

include/linux/sched.h

/*
 * The load_avg/util_avg accumulates an infinite geometric series
 * (see __update_load_avg() in kernel/sched/fair.c).
 *
 * [load_avg definition]
 *
 *   load_avg = runnable% * scale_load_down(load)
 *
 * where runnable% is the time ratio that a sched_entity is runnable.
 * For cfs_rq, it is the aggregated load_avg of all runnable and
 * blocked sched_entities.
 *
 * [util_avg definition]
 *
 *   util_avg = running% * SCHED_CAPACITY_SCALE
 *
 * where running% is the time ratio that a sched_entity is running on
 * a CPU. For cfs_rq, it is the aggregated util_avg of all runnable
 * and blocked sched_entities.
 *
 * load_avg and util_avg don't direcly factor frequency scaling and CPU
 * capacity scaling. The scaling is done through the rq_clock_pelt that
 * is used for computing those signals (see update_rq_clock_pelt())
 *
 * N.B., the above ratios (runnable% and running%) themselves are in the
 * range of [0, 1]. To do fixed point arithmetics, we therefore scale them
 * to as large a range as necessary. This is for example reflected by
 * util_avg's SCHED_CAPACITY_SCALE.
 *
 * [Overflow issue]
 *
 * The 64-bit load_sum can have 4353082796 (=2^64/47742/88761) entities
 * with the highest load (=88761), always runnable on a single cfs_rq,
 * and should not overflow as the number already hits PID_MAX_LIMIT.
 *
 * For all other cases (including 32-bit kernels), struct load_weight's
 * weight will overflow first before we do, because:
 *
 *    Max(load_avg) <= Max(load.weight)
 *
 * Then it is the load_weight's responsibility to consider overflow
 * issues.
 */
struct sched_avg {
        u64                             last_update_time;
        u64                             load_sum;
        u64                             runnable_load_sum;
        u32                             util_sum;
        u32                             period_contrib;
        unsigned long                   load_avg;
        unsigned long                   runnable_load_avg;
        unsigned long                   util_avg;
        struct util_est                 util_est;
} ____cacheline_aligned;
  • last_update_time
    • 마지막 갱신된 시각(ns)이 저장되는데, us 단위로 내림 정렬된 값이 저장된다.
      • 예) 갱신 시각이 1025ns 라면 저장되는 값은 1024ns 이다.
    • 태스크 마이그레이션으로 인해 엔티티가 cfs 런큐에 attach/detach된 경우 이 값은 0으로 리셋된다.
  • load_sum
    • 로드 합계
    • 러너블 상태와 블럭드(uninterruptible sleep에 대해서도 로드가 추가) 상태의 기간 합계이다.
  • runnable_load_sum
    • 러너블 로드 합계
    • 러너블(curr + rb 트리에서 대기) 상태의 기간 합계이다.
  • util_sum
    • 엔티티 또는 cfs 런큐가 running(실제 cpu를 잡고 수행한) 상태였던 기간 합계
  • period_contrib
    • 로드는 산출시마다 1024us 기간(period)마다 decay하는 방식을 사용하는데, 현재 시각이 period-0 구간에서 로드에 기여한 값을 저장해둔다.
    • 즉 로드 산출시마다 0us ~ 1023 us 값이 저장된다.
  • load_avg
    • 로드 평균
    • = runnable% * scale_load_down(load)
      • runnable% = cfs 런큐에서 runnable한 시간 비율로 0~100%까지이다.
        • runnable과 blocked 엔티티들을 모두 합친 비율이다.
      • scale_load_down(load) = 10bit 정밀도의 이진화 정수 로드 값
    • 엔티티 가중치와 frequency 스케일링이 러너블 타임에 곱해져서 적용된다.
    • cfs 런큐에서 사용될 떄 이 값은 그룹 스케줄링에서 사용된다.
  • runnable_load_avg
    • 러너블 로드 평균
    • cfs 런큐에서 사용될 떄 이 값은 로드 밸런스에서 사용된다.
  • util_avg
    • 유틸 평균
    • = running% * SCHED_CAPACITY_SCALE(1024)
      • running% = 하나의 cpu에서 러닝중인 엔티티의 시간 비율로 0~100%까지이다.
    • cpu effiency와 frequency 스케일링이 러닝 타임에 곱해져서 적용된다.
    • 엔티티에서 사용될 때 이 값은 EAS(Energy Aware Scheduler)에서 에너지 효과적인 cpu를 선택하는 용도로 사용된다.
    • cfs 런큐에서 사용될 때 이 값은 schedutil governor에서 주파수 선택 시 사용된다.
  • util_est
    • max(util_avg, util_avg@dequeue)
    • 빅 태스크가 오랜 시간 슬립하였다가 깨어난 경우 슬립한 기간만큼 util 로드값이 decay되어 매우 낮아지면서 다시 런큐에 등록될 때 로드가 현저하게 적어 cpu freq가 낮게 배정되어 출발하는 이슈가 있다. 때문에 이러한 문제를 개선하기 위해 태스크가 슬립하여 deaueue되는 순간의 util 로드를 기억해두었다가, 다시 사용시 재계산된 util 로드와 비교하여 최대 값을 적용한 것이 util_est 로드이다.

 

태스크용 엔티티
  • load.weight
    • nice 우선 순위에 따른 로드 가중치(weight)
    • 예) nice 0 태스크
      • 1048576 (64비트 시스템의 경우 10bit scale-up)
    • 예) nice -10 태스크
      • 9777152 (64비트 시스템의 경우 10bit scale-up)
  • runnable_weight
    • load.weight와 동일
  • load_sum & load_avg
    • 예) nice 0 태스크
      • load_sum = 46,872
      • load_avg = load_sum * se_weight() / LOAD_AVG_MAX = 1,005
    • 예) nice -10 태스크
      • load_sum = 47,742
      • load_avg = load_sum / se_weight() / LOAD_AVG_MAX = 9,483
  • runnable_load_sum & runnable_load_avg
    • 태스크용 엔티티의 경우 load_sum & load_avg와 동일하다.
  • util_sum & util_avg
    • 예) 3개의 nice 0 태스크, 1 개의 nice-10 태스크
      • nice 0 태스크
        • util_sum = 6,033,955
        • util_avg = util_sum / 약 LOAD_AVG_MAX = 126
      • nice -10 태스크
        • util_sum = 36,261,788
        • util_avg = util_sum / 약 LOAD_AVG_MAX = 759

 

그룹 엔티티

예) A 그룹, cpu.shares=1024, 3개의 nice 0 태스크, 1 개의 nice-10 태스크, UP

  • load.weight
    • load.weight=12,922,880
  • runnable_weight
    • runnable_weight=12,922,880
  • load_sum & load_avg
    • load_sum = 598,090,350
    • load_avg = 12,620
  • runnable_sum & runnable_avg
    • load_sum & load_avg와 비슷하다.
  • util_sum & util_avg
    • util_sum = 48529673
    • util_avg = util_sum / 약 LOAD_AVG_MAX = 1024 (100%)

 

cfs 런큐

예) A 그룹, cpu.shares=1024, 3개의 nice 0 태스크, 1 개의 nice-10 태스크, UP

  • load.weight
    • 예) load.weight = 12,922,880
  • runnable_weight
    • 예) runnable_weight = 12,922,880
  • load_sum & load_avg
    • 예) load_sum = 12,903,424
    • 예) load_avg = load_sum / se_weight() = 12601
  • .runnable_load_sum & runnable_load_avg
    • load_sum & load_avg와 비슷하다.
  • util_sum & util_avg
    • 예) util_sum =48,529,673
    • 예) util_avg = util_sum / LOAD_AVG_MAX = 1024 <- A 그룹내에서 util 로드를 이 cpu가 모두 다 사용하는 상태이다.

 

task_group 구조체

kernel/sched/sched.h

/* Task group related information */
struct task_group {
        struct cgroup_subsys_state css;

#ifdef CONFIG_FAIR_GROUP_SCHED
        /* schedulable entities of this group on each CPU */
        struct sched_entity     **se;
        /* runqueue "owned" by this group on each CPU */
        struct cfs_rq           **cfs_rq;
        unsigned long           shares;

#ifdef  CONFIG_SMP
        /*
         * load_avg can be heavily contended at clock tick time, so put
         * it in its own cacheline separated from the fields above which
         * will also be accessed at each tick.
         */
        atomic_long_t           load_avg ____cacheline_aligned;
#endif
#endif

#ifdef CONFIG_RT_GROUP_SCHED
        struct sched_rt_entity  **rt_se;
        struct rt_rq            **rt_rq;

        struct rt_bandwidth     rt_bandwidth;
#endif

        struct rcu_head         rcu;
        struct list_head        list;

        struct task_group       *parent;
        struct list_head        siblings;
        struct list_head        children;

#ifdef CONFIG_SCHED_AUTOGROUP
        struct autogroup        *autogroup;
#endif

        struct cfs_bandwidth    cfs_bandwidth;

#ifdef CONFIG_UCLAMP_TASK_GROUP
        /* The two decimal precision [%] value requested from user-space */
        unsigned int            uclamp_pct[UCLAMP_CNT];
        /* Clamp values requested for a task group */
        struct uclamp_se        uclamp_req[UCLAMP_CNT];
        /* Effective clamp values used for a task group */
        struct uclamp_se        uclamp[UCLAMP_CNT];
#endif

};
  • css
    • cgroup 서브시스템 상태
  • rcu
    • 미사용?
  • list
    • 전역 task_groups 리스트에 연결될 때 사용하는 노드이다.
  • *parent
    • 부모 태스크 그룹
  • siblings
    • 부모 태스크 그룹의 children 리스트에 연결될 떄 사용된다.
  • children
    • 자식 태스크 그룹들이 연결될  리스트이다.
  • cfs_bandwidth
    • cfs 관련 밴드폭 정보
  • cfs 그룹 스케줄링 관련
    • **se
      • cpu 수 만큼 그룹용 cfs 스케줄 엔티티가 할당되어 연결된다.
    • **cfs_rq
      • cpu 수 만큼 cfs 런큐가 할당되어 연결된다.
    • shares
      • 그룹에 적용할 로드 weight 비율
    • load_avg
      • cfs 런큐의 로드 평균을 모두 합한 값으로 결국 그룹 cfs 런큐들의 로드 평균을 모두 합한 값이다.
  • rt 그룹 스케줄링 관련
    • **rt_se
      • cpu 수 만큼 그룹용 rt 스케줄 엔티티가 할당되어 연결된다.
    • **rt_rq
      • cpu 수 만큼 rt 런큐가 할당되어 연결된다.
    • rt_bandwidth
      • cfs 관련 밴드폭 정보
  • autogroup 관련
    • *autogroup

 

참고

 

어셈블리(head.S) – gdb 디버깅 방법

<kernel v6.0>

준비

먼저 gdb를 사용하여 커널 소스 중 start_kernel()로 시작하는 C 부분의 디버깅이 가능한 환경이 이미 구축되어 있고 경험이 있다는 전제로 시작한다.
커널이 매핑되는 주소를 랜덤 위치로 변경하지 않도록 커널 빌드 전에 커널 옵션 중 “Kernel Features -> Randomize the address of the kernel image” 항목이 꺼져있어야 한다.(CONFIG_RANDOMIZE_BASE)
아래 debug 환경은 각자 상황에 따라 스크립트는 고쳐사용하여야 한다.
  • 다음 두 개의 스크립트는 raspeberry-pi 4 보드에서 ~/workspace/linux/v6.0 디렉토리에서 빌드된 커널 이미지를 대상으로 디버깅해본다.
  • x86 호스트로 arm64를 emulation 하는 경우도 가능하며 -cpu host 대신 -cpu cortex-a57으로 변경하고, -enable-kvm을 제거해야 한다.

 

emu-kvm.sh

KER_VER=6.0
WORK_DIR=${HOME}"/workspace/linux"
KERNEL_DIR=${WORK_DIR}"/${KER_VER}"
KERNEL_IMG=${KERNEL_DIR}"/arch/arm64/boot/Image"
ROOTFS="rootfs.ext4"
DTB_FILE="virt.dtb"
DTB="-dtb ${DTB_FILE}"

echo "target remote localhost:1234"
qemu-system-aarch64 -s -S -smp 1 -m 1024 -cpu host -nographic -enable-kvm \
        -machine virt \
        -kernel ${KERNEL_IMG} ${DTB} \
        -nographic \
        -device virtio-net-device,netdev=mynet1 \
        -netdev tap,id=mynet1,ifname=tap0,script=no,downscript=no \
        -device virtio-blk-device,drive=disk \
        -drive if=sd,id=disk,format=raw,file=${ROOTFS} \
        -append 'root=/dev/vda rw rootwait'

 

debug.sh

KER_VER=6.0
VMLINUX=~/workspace/linux/${KER_VER}/vmlinux

echo "KER_VER=${KER_VER}"

gdb -ex "target remote localhost:1234" \
    -ex "set directories ${HOME}/workspace/linux/${KER_VER}" \
    ${VMLINUX}

 


gdb 시작

 

위의 두 커멘드를 각자의 창에서 순서대로 실행하여 vmlinux 파일을 로드하여 (gbd) 프롬프트가 출력되면,
다음과 같이 로드한 이미지내에 정의된 섹션들에 대한 offset 주소를 확인해본다.
(gdb) info target
Symbols from "~/workspace/linux/6.0/vmlinux".
Remote serial target in gdb-specific protocol:
Debugging a target over a serial line.
        While running this, GDB does not access memory from...
Local exec file:
        `/root/workspace/linux/6.0/vmlinux', file type elf64-littleaarch64.
        Entry point: 0xffff800008000000
        0xffff800008000000 - 0xffff800008010000 is .head.text
        0xffff800008010000 - 0xffff80000917c8d8 is .text
        0xffff80000917c8d8 - 0xffff80000917c8f0 is .got.plt
        0xffff800009180000 - 0xffff800009aeb5ae is .rodata
        0xffff800009aeb5b0 - 0xffff800009aedfe0 is .pci_fixup
        0xffff800009aedfe0 - 0xffff800009afde68 is __ksymtab
        0xffff800009afde68 - 0xffff800009b14e18 is __ksymtab_gpl
        0xffff800009b14e18 - 0xffff800009b55504 is __ksymtab_strings
        0xffff800009b55508 - 0xffff800009b59cc0 is __param
        0xffff800009b59cc0 - 0xffff800009b5a530 is __modver
        0xffff800009b5a530 - 0xffff800009b5d3e0 is __ex_table
        0xffff800009b5d3e0 - 0xffff800009b5d434 is .notes
        0xffff800009b5e000 - 0xffff800009b62000 is .hyp.rodata
        0xffff800009b62000 - 0xffff800009b66000 is .rodata.text
        0xffff800009b70000 - 0xffff800009bee668 is .init.text
        0xffff800009bee668 - 0xffff800009bf57ac is .exit.text
        0xffff800009bf57ac - 0xffff800009c31e84 is .altinstructions
        0xffff800009c43000 - 0xffff800009d6b6dd is .init.data
        0xffff800009d6c000 - 0xffff800009d7ffe8 is .data..percpu
        0xffff800009d80000 - 0xffff800009d81ee0 is .hyp.data..percpu
        0xffff800009d81ee0 - 0xffff800009d82138 is .hyp.reloc
        0xffff800009d82138 - 0xffff80000a5699b0 is .rela.dyn
        0xffff80000a570000 - 0xffff80000a9c2400 is .data
        0xffff80000a9c2400 - 0xffff80000a9dac4c is __bug_table
        0xffff80000a9db000 - 0xffff80000a9db008 is .mmuoff.data.write
        0xffff80000a9db800 - 0xffff80000a9db808 is .mmuoff.data.read
        0xffff80000a9db808 - 0xffff80000a9dba00 is .pecoff_edata_padding
        0xffff80000a9dc000 - 0xffff80000aa78fb8 is .bss

 

__HEAD(.head.text 섹션)  물리 주소

다음 시작 부분에서 “b primary_entry” 부분에 대해 gdb의 TUI를 켜 실제 아래 위치까지 이동할 계획이다.
__HEAD 위치는 컴파일 당시에 .head.text 섹션에 위치한 0xffff800008000000 주소이고, 실제 실행될 물리 주소는 다음 코드 진행을 통해서 0x40200000임을 알 수 있다.
#define __HEAD          .section        ".head.text","ax"

 

시작 부분의 소스

arch/arm64/kernel/head.S
/*
* Kernel startup entry point.
* ---------------------------
*
* The requirements are:
*   MMU = off, D-cache = off, I-cache = on or off,
*   x0 = physical address to the FDT blob.
*
* Note that the callee-saved registers are used for storing variables
* that are useful before the MMU is enabled. The allocations are described
* in the entry routines.
*/
        __HEAD
        /*
         * DO NOT MODIFY. Image header expected by Linux boot-loaders.
         */
        efi_signature_nop                       // special NOP to identity as PE/COFF executable
        b       primary_entry                   // branch to kernel start, magic
        .quad   0                               // Image load offset from start of RAM, little-endian
        le64sym _kernel_size_le                 // Effective size of kernel image, little-endian
        le64sym _kernel_flags_le                // Informative flags, little-endian
        .quad   0                               // reserved
        .quad   0                               // reserved
        .quad   0                               // reserved
        .ascii  ARM64_IMAGE_MAGIC               // Magic number
        .long   .Lpe_header_offset              // Offset to the PE header.

        __EFI_PE_HEADER

        __INIT

        /*
         * The following callee saved general purpose registers are used on the
         * primary lowlevel boot path:
         *
         *  Register   Scope                      Purpose
         *  x20        primary_entry() .. __primary_switch()    CPU boot mode
         *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0
         *  x22        create_idmap() .. start_kernel()         ID map VA of the DT blob
         *  x23        primary_entry() .. start_kernel()        physical misalignment/KASLR offset
         *  x24        __primary_switch()                       linear map KASLR seed
         *  x25        primary_entry() .. start_kernel()        supported VA size
         *  x28        create_idmap()                           callee preserved temp register
         */
SYM_CODE_START(primary_entry)
        bl      preserve_boot_args
        bl      init_kernel_el                  // w0=cpu_boot_mode
        mov     x20, x0
        bl      create_idmap

 

TUI 기동

먼저 layout prev 명령을 수행하면 TUI 화면이 나타나고(만일 TUI 기능이 포함된 gdb) 첫 시작 부분은 qemu 코드이므로 0x40200000까지 이동해야 하므로 ni 명령을 7번 수행한다.

(gdb) layout prev

┌──Register group: general──────────────────────────────────────────────────────────────────────────────────────┐
│x0             0x0      0                                        x1             0x0      0                     │
│x2             0x0      0                                        x3             0x0      0                     │
│x4             0x0      0                                        x5             0x0      0                     │
│x6             0x0      0                                        x7             0x0      0                     │
│x8             0x0      0                                        x9             0x0      0                     │
│x10            0x0      0                                        x11            0x0      0                     │
│x12            0x0      0                                        x13            0x0      0                     │
│x14            0x0      0                                        x15            0x0      0                     │
│x16            0x0      0                                        x17            0x0      0                     │
│x18            0x0      0                                        x19            0x0      0                     │
│x20            0x0      0                                        x21            0x0      0                     │
│x22            0x0      0                                        x23            0x0      0                     │
│x24            0x0      0                                        x25            0x0      0                     │
│x26            0x0      0                                        x27            0x0      0                     │
│x28            0x0      0                                        x29            0x0      0                     │
│x30            0x0      0                                        sp             0x0      0x0                   │
│pc             0x40000000       0x40000000                       cpsr           0x400003c5       1073742789    │
│fpsr           0x0      0                                        fpcr           0x0      0                     │
│MVFR6_EL1_RESERVED0x0   0                                        MVFR7_EL1_RESERVED0x0   0                     │
└───────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  >│0x40000000      ldr    x0, 0x40000018                                                                       │
   │0x40000004      mov    x1, xzr                                                                              │
   │0x40000008      mov    x2, xzr                                                                              │
   │0x4000000c      mov    x3, xzr                                                                              │
   │0x40000010      ldr    x4, 0x40000020                                                                       │
   │0x40000014      br     x4                                                                                   │
   │0x40000018      stxrh  w0, w0, [x0]                                                                         │
   │0x4000001c      .inst  0x00000000 ; undefined                                                               │
   └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
remote Thread 1.1 In:                                                                      L??   PC: 0x40000000
(gdb) 

 

__HEAD 까지 이동

아래 화면이 나타나는데 0x40200000 주소에 “ccmp   x18, #0x0, #0xd, pl” 명령과 “b 0x41d70000″이 보이는데 이들은 __HEAD로 시작하는 어셈블리 코드이다.

   ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  >│0x40200000      ccmp   x18, #0x0, #0xd, pl  // pl = nfrst                                                   │
   │0x40200004      b      0x41d70000                                                                           │
   │0x40200008      .inst  0x00000000 ; undefined                                                               │
   │0x4020000c      .inst  0x00000000 ; undefined                                                               │
   └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
remote Thread 1.1 In:                                                                      L??   PC: 0x40200000
(gdb) ni
0x0000000040000000 in ?? ()
(gdb) ni
0x0000000040000004 in ?? ()
(gdb) ni
0x0000000040000008 in ?? ()
(gdb) ni
0x000000004000000c in ?? ()
(gdb) ni
0x0000000040000010 in ?? ()
(gdb) ni
0x0000000040000014 in ?? ()
(gdb) ni
0x0000000040200000 in ?? ()
(gdb)

 

__HEAD 진입하자 마자 efi_signature_nop 매크로가 보이는데 이를 살펴보면 ccmp로 시작하는 명령이 진행됨을 알 수 있다.

arch/arm64/kernel/efi-header.S

        .macro  efi_signature_nop
#ifdef CONFIG_EFI
.L_head:
        /*
         * This ccmp instruction has no meaningful effect except that
         * its opcode forms the magic "MZ" signature required by UEFI.
         */
        ccmp    x18, #0, #0xd, pl
#else
        /*
         * Bootloaders may inspect the opcode at the start of the kernel
         * image to decide if the kernel is capable of booting via UEFI.
         * So put an ordinary NOP here, not the "MZ.." pseudo-nop above.
         */
        nop
#endif
        .endm

 

diff(가상주소 – 물리주소) 값 산출 및 offset 적용

그럼 __HEAD의 가상 주소에서 물리 주소인 0x40200000의 차이(diff)를 산출해둔다.
diff = 가상주소(0xffff800008000000) – 물리주소(0x40200000) = 0xffff7fffc7e00000

gdb로 살펴볼 코드 및 데이터 부분이 포함된 4 가지 섹션들의 물리 주소를 위에서 산출한 diff 값을 사용해서 산출해둔다. (필요한 만큼 추가한다)

  • .text
    • 0x40210000
  • .head.text
    • 0x40200000
  • .init.text
    • 0x41d70000
  • .bss
    • 0x42bdc000

 

위의 물리 주소를 사용한 심볼을 추가해본다. 추가하자마자 다음 화면과 같이 물리 주소 대신 심볼 주소가 보이기 시작한다.

   ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  >│0x40200000 <_text>      ccmp   x18, #0x0, #0xd, pl  // pl = nfrst                                           │
   │0x40200004 <_text+4>    b      0x41d70000 <primary_entry>                                                   │
   │0x40200008 <_text+8>    .inst  0x00000000 ; undefined                                                       │
   └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
remote Thread 1.1 In: _text                                                                L60   PC: 0x40200000
(gdb) add-symbol-file ~/workspace/linux/6.0/vmlinux 0x40210000 -s .head.text 0x40200000 -s .init.text 0x41d70000 -s .bss 0x42bdc000
add symbol table from file "~/workspace/linux/6.0/vmlinux" at
        .text_addr = 0x40210000
        .head.text_addr = 0x40200000
        .init.text_addr = 0x41d70000
        .bss_addr = 0x42bdc000
(y or n) y
Reading symbols from ~/workspace/linux/6.0/vmlinux...done.
(gdb)

 

추가된 심볼 주소에 대해 다시 한 번 “info target” 명령을 사용해보면 다음과 같이 4개의 섹션에 대해 물리주소가 추가되어 있음을 확인할 수 있다.

(gdb) info target
Symbols from "~/workspace/linux/6.0/vmlinux".
Remote serial target in gdb-specific protocol:
Debugging a target over a serial line.
        While running this, GDB does not access memory from...
Local exec file:
        `~/workspace/linux/6.0/vmlinux', file type elf64-littleaarch64.
        Entry point: 0xffff800008000000
        0xffff800008000000 - 0xffff800008010000 is .head.text
        0xffff800008010000 - 0xffff80000917c8d8 is .text
        0xffff80000917c8d8 - 0xffff80000917c8f0 is .got.plt
        0xffff800009180000 - 0xffff800009aeb5ae is .rodata
        0xffff800009aeb5b0 - 0xffff800009aedfe0 is .pci_fixup
        0xffff800009aedfe0 - 0xffff800009afde68 is __ksymtab
        0xffff800009afde68 - 0xffff800009b14e18 is __ksymtab_gpl
        0xffff800009b14e18 - 0xffff800009b55504 is __ksymtab_strings
        0xffff800009b55508 - 0xffff800009b59cc0 is __param
        0xffff800009b59cc0 - 0xffff800009b5a530 is __modver
        0xffff800009b5a530 - 0xffff800009b5d3e0 is __ex_table
        0xffff800009b5d3e0 - 0xffff800009b5d434 is .notes
        0xffff800009b5e000 - 0xffff800009b62000 is .hyp.rodata
        0xffff800009b62000 - 0xffff800009b66000 is .rodata.text
        0xffff800009b70000 - 0xffff800009bee668 is .init.text
        0xffff800009bee668 - 0xffff800009bf57ac is .exit.text
        0xffff800009bf57ac - 0xffff800009c31e84 is .altinstructions
        0xffff800009c43000 - 0xffff800009d6b6dd is .init.data
        0xffff800009d6c000 - 0xffff800009d7ffe8 is .data..percpu
        0xffff800009d80000 - 0xffff800009d81ee0 is .hyp.data..percpu
        0xffff800009d81ee0 - 0xffff800009d82138 is .hyp.reloc
        0xffff800009d82138 - 0xffff80000a5699b0 is .rela.dyn
        0xffff80000a570000 - 0xffff80000a9c2400 is .data
        0xffff80000a9c2400 - 0xffff80000a9dac4c is __bug_table
        0xffff80000a9db000 - 0xffff80000a9db008 is .mmuoff.data.write
        0xffff80000a9db800 - 0xffff80000a9db808 is .mmuoff.data.read
        0xffff80000a9db808 - 0xffff80000a9dba00 is .pecoff_edata_padding
        0xffff80000a9dc000 - 0xffff80000aa78fb8 is .bss
        0x0000000040200000 - 0x0000000040210000 is .head.text
        0x0000000040210000 - 0x000000004137c8d8 is .text
        0xffff80000917c8d8 - 0xffff80000917c8f0 is .got.plt
        0xffff800009180000 - 0xffff800009aeb5ae is .rodata
        0xffff800009aeb5b0 - 0xffff800009aedfe0 is .pci_fixup
        0xffff800009aedfe0 - 0xffff800009afde68 is __ksymtab
        0xffff800009afde68 - 0xffff800009b14e18 is __ksymtab_gpl
        0xffff800009b14e18 - 0xffff800009b55504 is __ksymtab_strings
        0xffff800009b55508 - 0xffff800009b59cc0 is __param
        0xffff800009b59cc0 - 0xffff800009b5a530 is __modver
        0xffff800009b5a530 - 0xffff800009b5d3e0 is __ex_table
        0xffff800009b5d3e0 - 0xffff800009b5d434 is .notes
        0xffff800009b5e000 - 0xffff800009b62000 is .hyp.rodata
        0xffff800009b62000 - 0xffff800009b66000 is .rodata.text
        0x0000000041d70000 - 0x0000000041dee668 is .init.text
        0xffff800009bee668 - 0xffff800009bf57ac is .exit.text
        0xffff800009bf57ac - 0xffff800009c31e84 is .altinstructions
        0xffff800009c43000 - 0xffff800009d6b6dd is .init.data
        0xffff800009d6c000 - 0xffff800009d7ffe8 is .data..percpu
        0xffff800009d80000 - 0xffff800009d81ee0 is .hyp.data..percpu
        0xffff800009d81ee0 - 0xffff800009d82138 is .hyp.reloc
        0xffff800009d82138 - 0xffff80000a5699b0 is .rela.dyn
        0xffff80000a570000 - 0xffff80000a9c2400 is .data
        0xffff80000a9c2400 - 0xffff80000a9dac4c is __bug_table
        0xffff80000a9db000 - 0xffff80000a9db008 is .mmuoff.data.write
        0xffff80000a9db800 - 0xffff80000a9db808 is .mmuoff.data.read
        0xffff80000a9db808 - 0xffff80000a9dba00 is .pecoff_edata_padding
        0x0000000042bdc000 - 0x0000000042c78fb8 is .bss

 

break point 설정

아래 화면과 같이 primary_entry에 브레이크 포인터를 위치하고 해당 위치까지 진행해본다. 그러기 위해선 primary_entry 에 위에서 산출한 diff 값을 뺀 주소에 브레이크 포인터를 위치하기 위해 다음 2가지 명령 중 하나의 방법을 선택한다.
  • b *primary_entry-0xffff7fffc7e00000
  • b *0x41d70000
   ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
B+>│0x41d70000 <primary_entry>              bl     0x41d70018 <preserve_boot_args>                              │
   │0x41d70004 <primary_entry+4>            bl     0x4137c000 <init_kernel_el>                                  │
   │0x41d70008 <primary_entry+8>            mov    x20, x0                                                      │
   │0x41d7000c <primary_entry+12>           bl     0x41d70098 <create_idmap>                                    │
   │0x41d70010 <primary_entry+16>           bl     0x4137c7d8 <__cpu_setup>                                     │
   └────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
remote Thread 1.1 In: primary_entry                                                        L89   PC: 0x41d70000
(gdb) b *primary_entry-0xffff7fffc7e00000
Breakpoint 1 at 0x41d70000: file arch/arm64/kernel/head.S, line 89.
(gdb) c
Continuing.

Breakpoint 1, primary_entry () at arch/arm64/kernel/head.S:89
(gdb)

 

(끝)

Enjoy your self ^^

 

참고

kernel/head.S – ARM64 (new for v6.0)

<kernel v6.0>

kernel/head.S – ARM64 (new for v6.0)

시스템의 부트로더로부터 커널을 주 메모리에 로드하고 커널이 최초로 호출되는 지점이 head.S의 _head: 레이블이다. 이 시작 코드에는 커널 이미지의 헤더 및 UEFI(Unified Extensible Firmware Interface) PE(Portable Executable) 헤더등을 포함하며 최초 C 언어로 처리하기 힘든 시스템 설정들을 여기 어셈블리에서 처리한 후 C 시작 루틴인 start_kernel()로 jump 하는 역할을 한다. 이 코드들은 물리 DRAM의 2M 단위로 정렬된 주소라면 어떠한 위치에 배치하여도 동작할 수 있도록 모든 코드가 position independent 코드들로 구성되어 있다.

 

다음 그림은 부트로더로부터 커널의 어셈블리 부분과 C 부분이 차례로 호출되는 과정을 보여준다.

 

부트로더가 하는 일 요약

커널을 로드하여 동작시키기 전까지 부트로더가 수행하는 일들은 다음과 같다.

  • 주 메모리의 초기화를 수행한다.
  • DTB(Device Tree Blob)를 주 메모리에 로드한다.
    • x0 레지스터에 DTB 물리 시작 주소를 담는다.
    • x1~x3 레지스터는 미래에 사용할 레지스터로 예약하였다.
  • 커널 이미지를 주 메모리에 로드한다.
  • (옵션) 압축된 커널 이미지(예: Image.gz 등)를 사용하는 경우 decompress를 수행한다.
    • 커널 이미지 헤더가 포함된 압축 풀린 커널 이미지는 2M 단위로 정렬하여야 한다.
  • 커널 이미지의 첫 주소로 jump하여 커널의 head.S 루틴을 시작한다.
  • DTB는 Device Tree 계층도를 Device Tree Script로 작성한 후 바이너리 형태로 변환한 형태며, 자세한 내용은 다음 문서를 참고한다.

 

커널 진입전 요구사항

커널 진입 전 부트로더는 다음 조건을 만족해야 한다.

  • cpu의 레지스터
    • x0: boot cpu인 경우 DTB 시작 물리 주소, boot cpu를 제외한 나머지 secondary cpu인 경우 0 (Reserved for Future)
    • x1~x3: 0 (Reserved for Future)
  • MMU 및 캐시 상태
    • MMU: off
    • D-Cache: off
    • 로드된 커널 이미지 영역에 대해 PoC까지 clean 상태여야 한다.
  • DMA 장치
    • 모든 DMA(Direct Memory Access) 장치들은 DMA 기능이 정지되어 있어야 한다.
  • CPU mode
    • 모든 cpu들의 PSTATE.DAIF는 모두 마스크되어야 한다. (디버그, SError, IRQ 및 FIQ의 마스크)
      모든 CPU는 EL2 또는 non-secure EL1에 있어야 하고 동일해야 한다.
  • 아키텍처 타이머
    • CNTFRQ 레지스터는 타이머 주파수로 프로그래밍해야 한다.
    • CNTVOFF 레지스터는 모든 CPU에서 동일한 값으로 프로그래밍해야 한다.
    • 커널이 EL1에서 동작할 경우 하이퍼바이저(el2)가 있는 경우 CNTHCTL_EL2.EL1PCTEN을 설정해야 한다.
  • 그 외 시스템 레지스터들
    • SCR_EL3.FIQ
      • 모든 cpu들에 동일한 값이 사용되어야 한다.
    • GICv3가 v3 모드에서 사용될 때
      • EL3 존재시
        • ICC_SRE_EL3.Enable=1
        • ICC_SRE_EL3.SRE=1
        • ICC_CTLR_EL3.PMHE 모든 cpu들에 동일한 값 사용
      • 커널이 EL1에서 동작시
        • ICC.SRE_EL2.Enable=1
        • ICC_SRE_EL2.SRE=1
      • Device Tree 또는 ACPI 테이블에 GICv3 인터럽트 컨트롤러에 대해 기술되어야 한다.
    • GICv3가 v2 호환모드에서 사용될 때
      • EL3 존재시
        • ICC_SRE_EL3.SRE=0
      • 커널이 EL1에서 동작시
        • ICC_SRE_EL2.SRE=0
      • Device Tree 또는 ACPI 테이블에 GICv2 인터럽트 컨트롤러에 대해 기술되어야 한다.
    • Pointer Authentication
    • Activity Monitors Unit v1

 

head.S가 하는일 요약

어셈블리 언어로 작성된 head.S 가 커널의 시작점이다. C 언어로 할 수 없는 아키텍처 등의 설정 등을 head.S에서 수행 한 후 이어서 C 언어로 작성된 첫 실행 지점인 start_kernel() 함수가 호출된다. 그런 후 곧바로 setup_arch() 함수에서 나머지 아케틱처 설정들을  C 언어로 수행한다. 다음은 head.S에서 어셈블리 언어로 수행하는 작업들이다.

  • 하이퍼 바이저 모드(el2)가 사용되는 경우 EL2 exception 벡터 준비
  • 부트 cpu 초기화 및 커널 운영체제용 EL1 exception 벡터 준비
  • 필요시 커널 가상 공간에 매핑될 커널 이미지의 위치를 랜덤하게 변경
  • 6 종류 종류별 페이지 테이블 중 init_pg_dir, init_idmap_pg_dir, idmap_pg_dir 페이지 테이블 생성 및 활성화
  • 커널용 스택 준비
  • MMU를 켜서 가상 주소 체제로 전환
  • 마지막으로 C로 작성된 커널 시작 함수인 start_kernel()로 점프

 


커널 이미지 위치

PAGE_OFFSET

arch/arm64/kernel/head.S

#if (PAGE_OFFSET & 0x1fffff) != 0
#error PAGE_OFFSET must be at least 2MB aligned
#endif

리니어(연속) 매핑이 시작되는 가상 주소이다. 이 영역에 물리 메모리를 리니어(연속) 매핑한다.

 

arch/arm64/include/asm/memory.h

/*
 * PAGE_OFFSET - the virtual address of the start of the linear map, at the
 *               start of the TTBR1 address space.
 * PAGE_END - the end of the linear map, where all other kernel mappings begin.
 * KIMAGE_VADDR - the virtual address of the start of the kernel image.
 * VA_BITS - the maximum number of bits for virtual addresses.
 */
#define VA_BITS                 (CONFIG_ARM64_VA_BITS)
#define _PAGE_OFFSET(va)        (-(UL(1) << (va)))
#define PAGE_OFFSET             (_PAGE_OFFSET(VA_BITS))

예) CONFIG_ARM64_VA_BITS=48로 설정

  • 가상 공간의 크기가 2^48 = 256TB로 결정된다.
  • 이 때 PAGE_OFFSET 값은 0xffff_0000_0000_0000

 

__PHYS_OFFSET

커널이 동작할 물리 시작 주소 offset 값으로, 실제 물리 주소를 알고자 할 때 adrp 명령과 함께 사용되었었는데, 커널 v6.0-rc1에서 제거되었다.

 

KERNEL_START & KERNEL_END

arch/arm64/include/asm/memory.h

#define KERNEL_START      _text
#define KERNEL_END        _end

커널 이미지의 코드(_text) 시작 주소가 KERNEL_START 이다. 그리고 커널 이미지의 끝이 _end로 .bss 섹션도 포함된다.

 

TEXT_OFFSET

기존 AArch64 커널(~v4.6까지)에서 2M 정렬된 커널에서 실제 커널 시작 코드가 배치되는 위치로 jump 하기 위해 TEXT_OFFSET을 사용했었다. 사용되는 값은 제조사가 정한 offset(기존 512K offset) 또는 랜덤 offset 등을 사용해왔었다. 그런데 KASLR 도입 과정에서 relocatable kernel 개념을 적용하여 TEXT_OFFSET이 의미 없어지면서 v5.8-rc1에서 0으로 변경하였다가, v5.8-rc2에서 완전히 제거되었다.

 

다음 그림은 커널 이미지가 2M 정렬되어 위치하는 모습을 보여준다.

 

가상 공간에 커널 이미지 배치

다음 그림은 가상 주소 공간에 배치될 때의 커널 이미지 위치를 보여준다.

  • KASLR(Kernel Address Sanitizer Location Randomization)을 사용하지 않는 경우이다.

 

KASLR(Kernel Address Sanitizer Location Randomization)

  • 보안 목적으로 커널 가상 주소 공간에서 커널 이미지 및 커널 모듈이 위치해 있는 곳을 알 수 없게 런타임에 랜덤 배치한다.이때 자동적으로  RELOCATABLE 커널 옵션이 활성화된다.
    • CONFIG_RANDOMIZE_BASE
      • 커널 이미지의 위치를 런타임에 랜덤하게 변경한다.
    • CONFIG_RANDOMIZE_MODULE_REGION_FULL
      • 커널 모듈의 위치를 런타임에 랜덤하게 변경한다.
    • 참고: arm64: add support for kernel ASLR (2016, v4.6-rc1)

 


EL(Exception Level)

 

시스템 전원이 켜지고 boot cpu가 처음 가동될 때 ARM64 시스템에 구현되어 가지고 있는 가장 최상의 EL(Exception Level)에서 동작한다. exception이 발생하였을 때 EL의 상승이 발생하고 exception 처리가 끝나면 다시 하강시킬 수 있다. 32비트 ARM 시스템에서는 하이퍼바이저나 Secure Monitor가 ARM SoC에 내장되지 않은채 출하된 경우도 있었지만, 64비트 ARM 시스템에서는 거의 대부분의 확률로 모두 탑재되어 있다. 따라서 대부분 boot cpu가 처음 동작하는 EL은 EL3가 된다.

 

다음 그림은 exception이 발생한 경우 EL0를 제외하고 해당 EL 또는 상위 EL로 이동하는 모습을 보여준다.

  • User application이 동작하는 EL0의 경우 privilidge 권한이 없어 exception 처리 루틴을 동작시킬 수 없다.
  • 인터럽트 exception이 발생하였을 경우 인터럽트 번호마다 어떠한 EL로 이동하여 처리할지 최상위 EL에서 설정해야 한다.

 

다음 그림은 명령을 통해 강제로 상위 EL을 호출하는 모습을 보여준다.

  • 명령들은 sync exception으로 분리한다.

 

다음 그림은 exception이 처리된 후 되돌아가는 경로를 보여준다.

  • 하위 EL 또는 동일한 EL로 돌아갈 수 있다. 그러나 상위 EL로 되돌아가는 방법은 없다.

 

VHE(Virtual Host Extension) 기능

부트로더로 부터 리눅스 커널로의 진입 시 boot cpu는 EL1 또는 EL2에 있을 수 있다. 즉 시스템이 처음 부팅하여 리눅스 커널이 하이퍼 바이저 용도로도 사용될 수 있는 상태라면 EL2로 진입하였을 것이고, 그렇지 못한 경우는 EL1으로 시작한다.

 

다음 그림은 boot cpu가 리눅스 커널에 진입할 때 EL1에서 시작한 경우를 보여준다.

  • Hypervisor(Xen 등) 또는 QEMU/KVM 등을 통해 가동되는 Guest용 리눅스 커널은 EL1에서 시작한다.

 

리눅스 커널이 EL2로 시작한 경우 리눅스 커널은 스스로 하이퍼 바이저 역할을 수행할 수 있다. 그런데 기존의 ARM 아키텍처에서는 EL2에서 리눅스 커널을 사용하기 위해서 한 가지 문제점이 있었다. EL2에서 동작하는 레지스터들을 사용하기 위해 방대한 코드들을 모두 변경해야 하는 부담이 있어서, 실제로 그렇게 하지 않고 대부분의 기능을 EL1으로 전환하여 동작시킨다. 즉 EL2로 excecption이 발생하면 일부 stub 코드만을 사용한 후 다시 EL1으로 변경하여 리눅스 커널 코드를 재활용하는 방법을 사용한다. 이렇게 하는 과정에서 EL 전환에 따른 성능 손실이 발생하였고 이를 해결하기 위해 VHE(Virtual Host Extension) 기능이 내장된 ARMv8.2 아키텍처가 나오게 되었다.

 

ARMv8.2-VHE extension을 가지고 있는 경우 EL2 모드에서 EL1 모드로 변경하지 않고 그대로 EL1 레지스터들을 호출하는 방법을 지원하게 되면서 EL 전환에 따른 성능 손실이 일어나지 않게 되었다.

 

다음 그림은 boot cpu가 리눅스 커널에 진입할 때 EL2에서 시작한 경우에 cpu의 VHE 기능 지원 여부에 따라 Host OS가 EL EL2에 위치(VHE)하여 동작하는 것과, 그렇지 않고 EL2에 위치(nVHE)하여 동작하는 방법과 를 보여준다.

  • 대부분의 최근 ARM64 SoC들은 Hypervisor extension과 Secure Monitor extension이 모두 탑재된 상태로 출하된다.

 

VHE 지원시 _ELx 레지스터 접미사 사용 규칙

VHE 기능이 활성화된 상태에서 EL2 모드에서 각 ELx의 레지스터 호출 방법은 다음과 같다.

  • _EL1 레지스터 사용
    • EL1 레지스터에 접근하지 않고 EL2 레지스터에 접근한다.
  • _EL2 레지스터 사용
    • 그대로 EL2 레지스터에 접근한다.
  • _EL12 레지스터 사용
    • EL1 레지스터에 접근한다.

 


Static 페이지 테이블

커널이 컴파일될 때 미리 준비되는 6개 페이지 테이블의 용도는 다음과 같다.

  • init_pg_dir
    • 원래 커널 페이지 테이블은 swapper 페이지 테이블만을 사용했었다. 그런데 보안 향상을 위해 swapper 페이지 테이블을 read-only로 운영하기 위해 별도로 분리하고, 커널 초기 부팅 중에만 잠시 사용하기 위해 read-write 가능한 상태로 init 페이지 테이블을 운영한다.
    • 초기 부팅 중에만 사용되므로 매핑에 사용할 페이지 테이블의 단계와 단계별 갯수는 커널 영역(text, data, bss 섹션)에 한정하여 컴파일 타임에 계산된다.
    • 정규 매핑 준비를 수행하는 paging_init() 후에 swapper_pg_dir로 전환을 수행한 후에는 이 init 페이지 테이블은 더 이상 운영하지 않으므로 할당 해제한다.
  • swapper_pg_dir
    • 커널 부트업 과정에서 정규 매핑이 가능해지는 순간부터 swapper 페이지 테이블이 커널 페이지 테이블로 사용된다.
    • 보안 향상을 위해 읽기 전용으로 매핑하여 사용하며, 매핑 변경을 위해 엔트리 값을 수정해야 하는 경우마다 잠깐씩 fixmap 가상 주소 영역에 읽고쓰기(read-write) 매핑하여 사용한다.
    • 정규 매핑이 가능해지면서 사용되므로 static으로 만들어지는 pgd 테이블을 제외하곤 필요시 동적으로 생성된다.
  • reserved_pg_dir
    • 보안 상향을 위해 copy_from_user() 등의 별도의 전용 API 사용을 제외하고 무단으로 커널 space에서 유저 공간에 접근 못하게 금지하는 SW 에뮬레이션 방식에서 필요한 zero 페이지 테이블이다.
    • ARMv8.0까지 사용되며, ARMv8.1-PAN HW 기능을 사용하면서 이 테이블은 사용하지 않는다.
  • tramp_pg_dir
    • 고성능 cpu를 가진 시스템에서 Speculation 공격을 회피하기 위한 보안 상향을 목적으로 유저 space로 복귀 시 커널 공간에 원천적으로 접근 못하게 하기 위해 별도의 trampoline 페이지 테이블을 운영한다.
    • 이 테이블에는 커널 매핑은 없고, 커널/유저 진출입시 사용되는 SDEI(Software Delegated Exception Interface)를 사용한 trampoline 코드만 매핑되어 사용된다.
  • idmap_pg_dir
    • 가상 주소와 물리 주소가 1:1로 매핑되어 사용될 때 필요한 테이블로 영구적으로 사용된다.
    • 예) MMU enable 시 사용
  • init_idmap_pg_dir

 

다음 그림은 컴파일 타임에 static하게 만들어지는 페이지 테이블의 용도를 보여준다.

  • 리눅스 커널은 이제 5단계(pgd -> p4d -> pud -> pmd -> pte) 테이블을 사용한다. 하지만 ARM64의 head.S 코드는 실제 ARM64 아키텍처가 4단계만 사용하므로 p4d 단계는 배제하고 구현되어 있다.
  • init_으로 시작하는 2개의 페이지 테이블은 컴파일 타임에 필요한 페이지 테이블이 모두 준비된다. pgd를 제외하고 나머지 테이블들은 1개 또는 그 이상으로 구성될 수 있다. 단 4K 페이지를 사용하는 커널 이미지의 매핑에는 2M 단위의 블럭 매핑을 사용하므로 마지막 pte 테이블을 사용하지 않는다.
  • 컴파일 타임에 pgd 테이블만 준비되는 4개의 테이블들은 런타임에 정규 매핑 준비 과정에서 pgd 이후 다음 단계의 페이지 테이블부터 dynamic 하게 생성된다.

 

다음 그림은 static 페이지 테이블들이 배치된 사례를 보여준다.

  • init_pg_dir & init_idmap_pg_dir
    • 4K 페이지, 2M 블럭 매핑을 사용하면서 pte 테이블을 사용하지 않고, 1단계 줄어 3단계로 구성된다.

 

섹션(블럭) 매핑

ARM64 시스템에서 4K 페이지를 사용하는 경우 2M 단위의 섹션(블럭) 매핑을 하여 필요한 페이지 테이블 단계를 1 단계 더 줄일 수 있다. 이 방법으로 init_pg_dir 및 idmap_pg_dir 역시 1 단계를 줄여 사용할 수 있다.

 

다음 그림은 init_pg_dir에서 기존 페이지 테이블 단계(4단계, 3단계)를 1 단계 더 줄여 2M 단위 섹션 (블럭) 매핑된 모습을 보여준다.

  • SWAPPER_PGTABLE_LEVELS가 PGTABLE_LEVELS 보다 1 단계 더 적다.
  • 섹션 블럭 매핑에서 각 단계의 명칭은 아래와 같이 표현하였다.
    • 좌측 그림: ARM64 아키텍처로 보면 lvl0 -> lvl1 -> lvl2 -> 2M이고, 매크로 코드를 공유하여 사용하므로 코드 관점에서 보면 pgd -> pmd -> pte -> 2M와 같이 표현해도 좋다.
    • 우측 그림: ARM64 아키텍처로 보면 lvl0 -> lvl1 -> 2M이고, 매크로 코드를 공유하여 사용하므로 코드 관점에서 보면 pgd -> pte -> 2M와 같이 표현해도 좋다.

 

Identity 매핑

물리 주소와 가상 주소가 동일하도록 매핑을 할 때 다음과 같은 3가지 상황이 발생한다.

 

다음 그림은 물리 주소의 idmap 코드 영역이 동일한 주소의 유저 가상 주소 공간에 배치 가능한 경우이다. 가장 일반적인 상황이다.

 

다음 그림은 물리 주소의 idmap 코드 영역이 동일한 주소의 유저 가상 주소 공간에 배치가 불가능할 때 페이지  테이블 단계를 증가시켜 유저 가상 주소 공간을 키워 매핑을 하게한 상황이다.

 

다음 그림은 물리 주소의 idmap 코드 영역이 동일한 주소의 유저 가상 주소 공간에 배치가 불가능하고, VA_BITS=48 공간을 최대치인 52 비트로 확장시킬 수 있는 방법이다.

 

52bit 유저 공간

커널 v5.0-rc1에서 52비트 유저 공간을 지원한다. (4 Peta Bytes)

 

52bit 커널 공간

커널 v5.4-rc1에서 52비트 커널 공간을 지원한다. (4 Peta Bytes)

  • ARMv8.2-LPA 기능을 지원하는 아키텍처
  • 64K 페이지 사용
  • 이 기능이 동작하면서 52bit 유저 공간만 지원하던 것이 이제 유저 및 커널 모두 같은 52bit 커널 공간으로 사용한다.
    • 즉 유저용은 52bit, 커널용은 48비트와 같이 나눠서 설정하는 번거로움을 아예 불가능하게 제거하였다.
  • 참고: arm64: mm: Introduce 52-bit Kernel VAs (2019, v5.4-rc1)

 


커널 및 유저 공간 분리

  • 유저에서 커널 공간의 분리
    • swapper 및 trampoline 두 커널 페이지 테이블을 사용한다.
  • 커널에서 유저 공간의 분리
    • ARMv8.1의 PAN(Privileged Access Never) 기능을 사용하거나, 이러한 기능이 없는 경우 소프트웨어 에뮬레이션 방법(CONFIG_ARM64_SW_TTBR0_PAN)을 사용한다.
  • 참고: KAISER: hiding the kernel from user space | LWN.net

 

다음 그림과 같이 ARM64 시스템에서 커널 공간을 담당하는 TTBR1과 유저 공간을 담당하는 TTBR1을 사용하여 각각의 커널 모드와 유저 모드에서 상대방의 영역을 사용하지 못하게 분리하는 방법을 보여준다.

 

ASID(Address Space IDentification )

mm 스위칭 후 TLB 캐시 및 명령 캐시에 대한 높은 비용의 플러시를 억제하기 위해 ASID를 이용한 가상 주소의 중복을 허용하게 하였다. 이를 이용하여 각각의 태스크 마다 유니크하게 식별할 수 있도록 ASID를 발급하여 구분한다. 그런데 이 ASID는 ARM32의 경우 8 bit 만을 허용하고, ARM64의 경우 8 bit 또는 16 bit를 지원한다. 이 때문에 리눅스 커널에서 태스크의 식별에 사용하는 pid를 사용하지 못하고 별도로 ASID 발급 관리를 수행한다

 

SDEI(Software Delegated Exception Interface)

펌웨어(Secure)가 OS 및 하이퍼바이저로 시스템 이벤트를 전달하기 위한 메커니즘이다.

  • 인터럽트 마스킹 및 critical section에 의해 지연되면 안되는 exception을 처리한다.
  • 주 사용 케이스
    • 시스템 에러 핸들링(RAS)
    • 시스템 감시(watchdog)
    • 커널 디버깅
    • 샘플 프로파일링
    • 유저 모드에서 trampoline 페이지 테이블을 사용한 커널 감추기
  • 참고: SDEI: Software Delegated Exception Interface | Trusted Firmware-A

 


커널(어셈블리) 시작 전 준비

시작하기에 앞서 몇 가지 어셈블리 명령 및 어셈블리 지시어등을 미리 알고 시작해야 분석에 소요되는 시간을 절약할 수 있다.

  • 어셈블리 명령들
    • b, bl
    • lda, sta, ldp, stp
    • mov, adr
    • add, sub, tst, cmp
    • mrs, msr
  • 어셈블리 지시어들
    • .macro, .endm
    • .align, .globl, .local, weak, .L
    • .size, .quad, .long
    • .if, .endif

 

위의 기본 명령과 지시어를 이해한 후에는 다음 항목들에 대해서 필요할 때마다 확인한다.

  • 명령 뒤에 붙는 conditional branch 명령들
  • 주소 참조에 사용되는 addressing mode들

 

참고할 어셈블리 명령

adrp 명령

  • AArch64:
    • adrp Xd, label
  • Address of 4KB page at a PC-relative offset.
  • 현재 주소(pc)로부터 +-4G 주소까지의 label 주소를 알아와서 Xd 레지스터에 저장한다.
  • 참고: Addressing Mode (AArch64) | 문c

 

eret 명령 – Exception Layer 이동

  • AArch64:
    • eret
  • 현재 Exception Level을 읽어오는 방법은 Read Only인 CurrentEL 레지스터를 읽어오면 알 수 있다.  eret 명령을 사용하여 exception 발생 전으로 돌아갈 수 있는데 돌아갈 EL 모드 정보가 포함된 PSTATE 값을 pspr_elx 레지스터에 저장하고, 복귀할 주소가 담겨 있는 lr 레지스터의 값을 elr_elx 레지스터에 저장한 후 eret 명령을 사용하면 원하는 EL 및 주소로 이동이 가능하다. 단 상위 레이어로의 이동은 불가능하다.
  • eret 명령은 실제 exception과 pair로만 사용되는 것이 아니라, exception 없이 EL 전환과 특정 주소로 이동시킬 수 있다.
  • 참고: AArch64 Exception Levels | Mike’s

 

csel(condition select) 명령

  • AArch64:
    • csel Xd, Xn, Xm, #cond
  • 컨디션(#cond)이 true인 경우 Xn을 선택하여 Xd에 대입하고, false인 경우 Xm을 선택한 후 Xd에 대입한다.
    • Xd = #cond ? Xn : Xm

 

ubfx 명령

  • AArch64:
    • ubfx Xd, Xn, #lsb, #width
  • Xn에서 #lsb 비트 위치 부터 msb 방향으로 #width 비트 수만큼 읽어 Xd에 대입한다.
    • 예) Xn=0x1234_5678_9abc_def0, #lsb=4, #width=8
      • Xd=0xef

 

bic(BitwIse Clear)

  • AArch64:
    • bic Xd, Xn, #op2
  • Xn에서 #op2 비트들을 클리어하여 Xd에 대입한다.
  • Xd = Xn & ~op2
    • 예) Xn=0x1234_5678_9abc_deef0, #op2=0xfff
      • Xd=0x1234_5678_9abc_d000

 

bfi(Bit Field Insert) 명령

  • AArch64:
    • bfi Xd, Xn, #lsb, #width
  • Xn 레지스터 값에서 하위 #width 비트 수 만큼의 값을 Xd의 #lsb 비트 위치에 끼워넣는다.
    • 예) Xd=0x1111_2222_3333_4444, Xn=0x1234_5678_9abc_def0, #lsb=4, #width=8
      • Xd=0x1111_2222_3333_4f04

 

clz(Count Leading Zero) 명령

  • AArch64:
    • clz Xd, Xn
  • 0으로 시작하는 비트가 몇 개인지 센다.
    • 예) Xn=0x0000_00f1_1234_0000
      • Xd = 24

 

PC 상대(PC-relative) 주소 지정 매크로

현재 위치 PC 레지스터로부터 +- 4G 주소 범위 이내에 위치한 심볼 위치에 접근할 때 사용되는 매크로 3개를 알아본다.

 

adr_l 매크로

arch/arm64/include/asm/assembler.h

/*
 * Pseudo-ops for PC-relative adr/ldr/str <reg>, <symbol> where
 * <symbol> is within the range +/- 4 GB of the PC.
 */
        /*
         * @dst: destination register (64 bit wide)
         * @sym: name of the symbol
         */
.       .macro  adr_l, dst, sym
        adrp    \dst, \sym
        add     \dst, \dst, :lo12:\sym
        .endm

현재 주소에서 +-4G 이내 범위에 위치한 심볼 주소 @sym에 대한 주소를 @dst 레지스터에 알아온다.

 

ldr_l 매크로

arch/arm64/include/asm/assembler.h

.       /*
         * @dst: destination register (32 or 64 bit wide)
         * @sym: name of the symbol
         * @tmp: optional 64-bit scratch register to be used if <dst> is a
         *       32-bit wide register, in which case it cannot be used to hold
         *       the address
         */
.       .macro  ldr_l, dst, sym, tmp=
        .ifb    \tmp
        adrp    \dst, \sym
        ldr     \dst, [\dst, :lo12:\sym]
        .else
        adrp    \tmp, \sym
        ldr     \dst, [\tmp, :lo12:\sym]
        .endif
        .endm

현재 주소에서 +-4G 이내범위에 위치한 심볼 @sym 주소의 값을 32비트 또는 64비트 @dst 레지스터에 담아온다. 만일 @dst 레지스터가 32비트인 경우 @tmp에 64비트 레지스터를 지정해야 한다. @tmp 레지스터는 사용 후 파손된다.

 

str_l 매크로

arch/arm64/include/asm/assembler.h

.       /*
         * @src: source register (32 or 64 bit wide)
         * @sym: name of the symbol
         * @tmp: mandatory 64-bit scratch register to calculate the address
         *       while <src> needs to be preserved.
         */
.       .macro  str_l, src, sym, tmp
        adrp    \tmp, \sym
        str     \src, [\tmp, :lo12:\sym]
        .endm

현재 주소에서 +-4G 이내 범위에 위치한 심볼 @sym 주소에 32비트 또는 64비트 @dst 레지스터 값을 기록한다.

 

mov_q 매크로

include/asm/assembler.h

/*
 * mov_q - move an immediate constant into a 64-bit register using
 *         between 2 and 4 movz/movk instructions (depending on the
 *         magnitude and sign of the operand)
 */
.macro  mov_q, reg, val
        .if (((\val) >> 31) == 0 || ((\val) >> 31) == 0x1ffffffff)
            movz    \reg, :abs_g1_s:\val
        .else
            .if (((\val) >> 47) == 0 || ((\val) >> 47) == 0x1ffff)
                movz    \reg, :abs_g2_s:\val
            .else
                movz    \reg, :abs_g3:\val
                movk    \reg, :abs_g2_nc:\val
            .endif
            movk    \reg, :abs_g1_nc:\val
        .endif
        movk    \reg, :abs_g0_nc:\val
.endm

상수값 @val을 64비트 레지스터인 @reg에 대입한다. 한 개의 어셈블리 코드로 모든 64비트 상수를 대입시킬 수 없으므로, 상수를 16비트씩 나누어 최소 2회에서 최대 4회에 걸쳐 대입한다.

 

예) 다음 명령을 수행하는 경우 다음과 같이 4회에 걸쳐 mov 명령을 사용하도록 어셈블된다.

  • mov_q x2, 0x4000300020001
mov     x2, #0x4000000000000            // x2 <- 0x4_0003_0002_0001
movk    x2, #0x3, lsl #32
movk    x2, #0x2, lsl #16
movk    x2, #0x1

 

:abs_g1_s: & :abs_g1:

  • _s 접미사가 붙으면 절대 값 g1 영역의 signed 값을 의미한다.
  • 3 개의 g0 ~ g3 영역은 16비트씩 사용되며 다음과 같이 구분한다.
    • g0=bits[15:0]
    • g1=bits[31:16]
    • g2=bits[47..32]
    • g3=bits[63..48]
  • 참고: Assembly expressions | ARM

 

:lo12:

  • 하위 12비트 값을 의미한다.

 


커널(어셈블리) 시작

_head:

arch/arm64/kernel/head.S

/*
 * Kernel startup entry point.
 * ---------------------------
 *
 * The requirements are:
 *   MMU = off, D-cache = off, I-cache = on or off,
 *   x0 = physical address to the FDT blob.
 *
 * Note that the callee-saved registers are used for storing variables
 * that are useful before the MMU is enabled. The allocations are described
 * in the entry routines.
 */
        __HEAD
        /*
         * DO NOT MODIFY. Image header expected by Linux boot-loaders.
         */
        efi_signature_nop                       // special NOP to identity as PE/COFF executable
        b       primary_entry                   // branch to kernel start, magic
        .quad   0                               // Image load offset from start of RAM, little-endian
        le64sym _kernel_size_le                 // Effective size of kernel image, little-endian
        le64sym _kernel_flags_le                // Informative flags, little-endian
        .quad   0                               // reserved
        .quad   0                               // reserved
        .quad   0                               // reserved
        .ascii  ARM64_IMAGE_MAGIC               // Magic number
        .long   .Lpe_header_offset              // Offset to the PE header.

        __EFI_PE_HEADER

부트 로더로 부터 처음 진입되는 커널 코드 시작점이다.

  • 부트 로더로 부터 커널 코드 시작인 _head에 진입하기 전에 다음 규칙이 적용된다.
    • MMU는 off 되어 있어야 한다.
    • I-Cache는 on/off 상관 없다.
    • x0 레지스터에는 DTB 시작 물리 주소가 담겨 있어야 한다.
    • x0~x3까지의 레지스터를 커널(callee)이 보존해야 한다.
    • 커널 진입 전 부트 로더 등에서 처리하는 일에 대해 다음을 참고한다.
  • 코드 라인 1에서 이후의 코드가 a(allocation) 및 x(execution) 속성을 가진 섹션 “.head.text”에 위치하도록 컴파일러에 지시한다.
  • 코드 라인 5에서 ccmp(conditional compare) 명령이 처음 시작되지만 이 명령의 결과는 전혀 의미가 없는 nop 처럼 사용된다. 이를 통해 실제 목표는 UEFI 지원 커널인지 여부를 알아내는 식별자(처음 2바이트가 “MZ” 아스키 코드)로 사용된다.
  • 코드 라인 6에서 실제 코드가 있는 primary_entry 레이블로 이동한다.
  • 코드 라인 7~16에서 커널 이미지 정보이다.

 

__HEAD

include/linux/init.h

#define __HEAD          .section        ".head.text","ax"

이후의 코드가 a(allocation) 및 x(execution) 속성을 가진 섹션 “.head.text”에 위치하도록 컴파일러에 지시한다.

 

efi_signature_nop 매크로

        .macro  efi_signature_nop
#ifdef CONFIG_EFI
.L_head:
        /*
         * This ccmp instruction has no meaningful effect except that
         * its opcode forms the magic "MZ" signature required by UEFI.
         */
        ccmp    x18, #0, #0xd, pl
#else
        /*
         * Bootloaders may inspect the opcode at the start of the kernel
         * image to decide if the kernel is capable of booting via UEFI.
         * So put an ordinary NOP here, not the "MZ.." pseudo-nop above.
         */
        nop
#endif
        .endm

ccmp(conditional compare) 명령이 처음 시작되지만 이 명령의 결과는 전혀 의미가 없는 nop 처럼 사용된다. 이를 통해 실제 목표는 UEFI 지원 커널인지 여부(DOS 헤더 포함)를 알아내는 식별자(처음 2바이트가 “MZ” 아스키 코드)로 사용된다.

  • 코드 라인 2~8에서 CONFIG_EFI 커널 옵션을 사용하여 빌드하면 UEFI BIOS를 지원하게 되는데, 커널 이미지의 시작 위치 2바이트에 DOS 헤더를 알리는 “MZ” 아스키 코드가 있어야 한다. 따라서 MZ 문자가 들어가면서 특별히 시스템에 영향을 주지 않는 명령을 골라 사용한 결과로 ccmp를 사용하였다.
    • arm 및 arm64 모두 아키텍처가 빅 엔디안 모드와 리틀 엔디안 모드가 지원되는데, 위의 ccmp 명령은 0xfa40_5a4d이며 이를 디폴트 설정인 리틀 엔디안으로 빌드한 이미지에서 최초 4바이트는 다음과 같이 거꾸로 표현한다.
      • 4d 5a 40 fa (4d 5a가 각각 ‘M’과 ‘Z’ 아스키 문자)
  • 코드 라인 9~16에서 CONFIG_EFI 커널 옵션을 사용하지 않는 경우 처음 4바이트 명령에 아무 결과도 수행하지 않고 명령 사이클만 소모시키는 nop 명령으로 시작한다.

 

커널 이미지 헤더

압축 해제 상태의 커널 이미지는 다음과 같이 64바이트의 커널 이미지 헤더이고, DOS 헤더 형태와 호환되도록 최초 2 바이트를 “MZ” 아스키 문자열을 사용하여 만들어졌고, 리틀 엔디안 포맷으로 구성되어 있다.

  u32 code0;                    /* Executable code */
  u32 code1;                    /* Executable code */
  u64 text_offset;              /* Image load offset, little endian */
  u64 image_size;               /* Effective Image size, little endian */
  u64 flags;                    /* kernel flags, little endian */
  u64 res2      = 0;            /* reserved */
  u64 res3      = 0;            /* reserved */
  u64 res4      = 0;            /* reserved */
  u32 magic     = 0x644d5241;   /* Magic number, little endian, "ARM\x64" */
  u32 res5;                     /* reserved (used for PE COFF offset) */
  • code0/code1
    • stext로의 jump 코드가 있다.
      • 예) ccmp x18, #0, #0xd, pl 또는 add x13, x18, #0x16
      •        b primary_entry
    • 시스템에 UEFI 펌웨어가 있는 경우 이 코드는 skip 하며, UEFI의 PE 헤더에 포함된 entry 포인터(efi_stub_entry)로 jump 한다. 그 후 다시 code0 위치로 jump 한다.
  • text_offset
  • flags
    • bit0: 커널의 엔디안 (1=BE, 0=LE)
    • bit1~2: 커널 페이지 사이즈 (0=Unspecified, 1=4K, 2=16K, 3=64K)
    • bit3: 2M 정렬된 커널 이미지의 커널 물리 위치(Kernel Physical Placement) (0=DRAM의 시작 위치로 부터 근접, 1=DRAM의 모든 영역)
  • image_size
    • 이미지 사이즈 (v3.17 이전에는 0 값이 기록되어 있다.)
  • magic
    • AMR64 이미지임을 나타내는 식별 문자열로 “ARMd“이다.

 

다음과 같이 커널(vmlinux)을 덤프해본다. ELF 헤더 + DOS 헤더 + UEFI PE 헤더등으로 시작한다.

  • ELF 헤더 (64 바이트)
    • ELF
      • ELF(Excutable and Linkable Format)
      • 커널 이미지의 첫 부분에는 ELF 헤더가 있고, 이를 식별할 수 있도록 “ELF” 아스크코드 문자열을 볼 수 있다.
  • DOS 헤더 (64 바이트)
    • MZ
      • vmlinux 파일은 위의 ELF를 포함하고 0x10000 offset을 가지므로, UEFI를 지원하는 커널인 경우 아래와 같이 0x10000 주소만큼 떨어진 위치에서 “MZ” 아스키코드 문자열을 볼 수 있다.
      • MZ 문자열로 시작하는데 DOS 호환을 위해 사용되었다.
    • ARMd
      • ARM64 커널 이미지라는 것을 알 수 있도록 0x10038 주소에서 “ARMd” 아스키 코드 문자열을 볼 수 있다.
  • EFI PE 헤더
    • PE
      • EFI(Extensible Firmware Interface) PE(Portable Excutable)
      • UEFI 헤더를 식별할 수 있도록 “PE” 아스키 코드 문자열을 볼 수 있다.
$ hexdump -C vmlinux
00000000  7f 45 4c 46 02 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  03 00 b7 00 01 00 00 00  00 00 00 10 00 80 ff ff  |................|
00000020  40 00 00 00 00 00 00 00  50 cb cf 01 00 00 00 00  |@.......P.......|
00000030  00 00 00 00 40 00 38 00  03 00 40 00 1c 00 1b 00  |....@.8...@.....|
...
*
00010000  4d 5a 00 91 ff bf 51 14  00 00 00 00 00 00 00 00  |MZ....Q.........|
00010010  00 00 d8 01 00 00 00 00  0a 00 00 00 00 00 00 00  |................|
00010020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00010030  00 00 00 00 00 00 00 00  41 52 4d 64 40 00 00 00  |........ARMd@...|
00010040  50 45 00 00 64 aa 02 00  00 00 00 00 00 00 00 00  |PE..d...........|
00010050  00 00 00 00 a0 00 06 02  0b 02 02 14 00 00 55 01  |..............U.|
00010060  00 00 86 00 00 00 00 00  fc 5e 51 01 00 00 01 00  |.........^Q.....|
00010070  00 00 00 00 00 00 00 00  00 00 01 00 00 02 00 00  |................|

 

ELF 파일이 아닌 Image 파일을 덤프해본다. ELF 헤더가 제외되고, DOS 헤더 + UEFI 헤더등으로 시작하는 것을 알 수 있다.

$ hexdump -C arch/arm64/boot/Image
00000000  4d 5a 00 91 ff bf 51 14  00 00 00 00 00 00 00 00  |MZ....Q.........|
00000010  00 00 d8 01 00 00 00 00  0a 00 00 00 00 00 00 00  |................|
00000020  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000030  00 00 00 00 00 00 00 00  41 52 4d 64 40 00 00 00  |........ARMd@...|
00000040  50 45 00 00 64 aa 02 00  00 00 00 00 00 00 00 00  |PE..d...........|
00000050  00 00 00 00 a0 00 06 02  0b 02 02 14 00 00 55 01  |..............U.|
00000060  00 00 86 00 00 00 00 00  fc 5e 51 01 00 00 01 00  |.........^Q.....|
00000070  00 00 00 00 00 00 00 00  00 00 01 00 00 02 00 00  |................|

 

ELF Header

 

DOS Header

실제 DOS Header 규격은 다음과 같다.

typedef struct _IMAGE_DOS_HEADER
{
                        // Cumulative size:
     WORD e_magic;      // 2
     WORD e_cblp;       // 4
     WORD e_cp;         // 6
     WORD e_crlc;       // 8
     WORD e_cparhdr;    // 10
     WORD e_minalloc;   // 12
     WORD e_maxalloc;   // 14
     WORD e_ss;         // 16
     WORD e_sp;         // 18
     WORD e_csum;       // 20
     WORD e_ip;         // 22
     WORD e_cs;         // 24
     WORD e_lfarlc;     // 26
     WORD e_ovno;       // 28
     WORD e_res[4];     // 36
     WORD e_oemid;      // 38
     WORD e_oeminfo;    // 40
     WORD e_res2[10];   // 60
     LONG e_lfanew;     // 64
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

 

UEFI(Unified Extensible Firmware Interface)

  • ARM64 시스템에서도 UEFI 펌웨어가 내장된 서버들이 있다. 이러한 커널에서는 CONFIG_EFI가 반드시 필요하다.
  • UEFI 펌웨어는 디바이스 정보를 자동으로 인식하거나 수동 설정된 내용을 ACPI 테이블로 변환하여 부트로더 및 커널에 전달한다. 부트 로더 및 커널은 이 정보를 가지고 시스템을 초기화한다. 이렇게 UEFI가 전달하는 ACPI 테이블이 없는 임베디드 시스템들은 Device Tree 스크립트를 작성하여 컴파일한 FDT/DTB(Flattened Device Tree / Device Tree Blob) 스타일로 디바이스 정보를 전달한다. 최근엔 주요 정보는 ACPI로 전달하고, FDT/DTB로 추가 전달하는 경우도 있다.

 


primary_entry:

arch/arm64/kernel/head.S

        __INIT

        /*
         * The following callee saved general purpose registers are used on the
         * primary lowlevel boot path:
         *
         *  Register   Scope                      Purpose
         *  x20        primary_entry() .. __primary_switch()    CPU boot mode
         *  x21        primary_entry() .. start_kernel()        FDT pointer passed at boot in x0
         *  x22        create_idmap() .. start_kernel()         ID map VA of the DT blob
         *  x23        primary_entry() .. start_kernel()        physical misalignment/KASLR offset
         *  x24        __primary_switch()                       linear map KASLR seed
         *  x25        primary_entry() .. start_kernel()        supported VA size
         *  x28        create_idmap()                           callee preserved temp register
         */
SYM_CODE_START(primary_entry)
        bl      preserve_boot_args
        bl      init_kernel_el                  // w0=cpu_boot_mode
        mov     x20, x0
        bl      create_idmap

        /*
         * The following calls CPU setup code, see arch/arm64/mm/proc.S for
         * details.
         * On return, the CPU will be ready for the MMU to be turned on and
         * the TCR will have been set.
         */
#if VA_BITS > 48
        mrs_s   x0, SYS_ID_AA64MMFR2_EL1
        tst     x0, #0xf << ID_AA64MMFR2_LVA_SHIFT
        mov     x0, #VA_BITS
        mov     x25, #VA_BITS_MIN
        csel    x25, x25, x0, eq
        mov     x0, x25
#endif
        bl      __cpu_setup                     // initialise processor
        b       __primary_switch
SYM_CODE_END(primary_entry)

커널 코드가 처음 시작되는 .init.text 섹션이다. 어셈블리 코드를 통해 임시 페이지 매핑을 수행한 후 boot cpu의 MMU 장치를 켜서 가상 주소 환경이 동작되도록 페이징 활성화한다. 그 후 C 함수로 작성된 커널의 시작 위치인 start_kernel() 함수로 진입한다. 참고로 boot cpu가 아닌 나머지 cpu 코어들은 secondary core라고 불리고 이들은 아직 시작하지 않는 상태로 있다. 시스템마다 조금씩 다르지만 특정 주소에서 스핀(spin)하며 대기 중에 있거나 또는 전원이 꺼져있는 상태로 시작한다.

  • 코드 라인 1에서기존에 stext 라는 레이블을 사용했었는데 primary_entry 레이블로 변경되었다. 헤더들을 빼면 진정한 커널 시작점이라고 할 수 있다.
  • 코드 라인 2에서 부트로더가 전달해준 x0 ~ x3 레지스터들을 boot_args 위치에 보관해둔다.
    • setup_arch() 마지막 부분에서 저장된 boot_args[] 값들 중 x1~x3에 해당하는 값이 0이 아닌 값이 있는 경우 다음과 같은 경고 메시지를 출력한다.
      • WARNING: x1-x3 nonzero in violation of boot protocol: …
  • 코드 라인 3에서 리눅스 커널이 el2로 부팅한 경우 하이퍼 바이저에 관련된 설정들을 수행한다.
  • 코드 라인 4~5에서 DTB 물리 주소를 x20에 옮긴 후, 커널에 대해 임시로 사용할 init_idmap 및 init 페이지 테이블을 생성한다.
  • 코드 라인 13~20에서 52비트 가상 주소를 지원하는 커널인 경우 코드를 통해 mmfr2_el1 레지스터에서 LVA(Large Virtual Address) 기능이 지원되는 것을 확인해본다. 만일 확인된 경우 유저 가상 주소를 표현하는 비트 수를 52로하여 x0에 대입한다. 그렇지 않은 경우 VA_BITS_MIN을 사용한다.
    • 즉 52비트를 지원하는 커널에서 cpu가 52비트를 지원하는지 여부를 알아보고 다음 둘 중 하나를 선택한다.
      • 52비트 VA를 지원하는 아키텍처인 경우 VA_BITS(52)를 선택한다.
      • 52비트 VA를 지원하지 아키텍처인 경우 VA_BITS_MIN(48)을 선택한다.
  • 코드 라인 21에서 프로세서를 초기화한다.
  • 코드 라인 22에서 MMU를 활성화시킨 후 start_kernel() 함수로 점프한다.

 

__INIT

include/linux/init.h

#define __INIT          .section        ".init.text","ax"

이후의 코드가 a(allocation) 및 x(execution) 속성을 가진 섹션 “.init.text”에 위치하도록 컴파일러에 지시한다.

  • .init 섹션에 위치한 코드 및 데이터는 커널이 부팅한 후 더이상 필요 없으므로 추후 커널의 물리 메모리를 관리하는 버디 시스템으로 되돌려 활용하는 것으로 메모리를 절약한다.

 

VA_BITS vs VA_BITS_MIN

64비트 주소 버스를 사용하는 ARMv8 아키텍처의 경우 MMU를 사용하여 가상 주소를 사용할 수 있는데 그 크기는 ARMv8 아키텍처가 지원하는 몇 가지 크기가 제공된다. 그 크기의 조절은 ARMv8 아키텍처가 지원하는 PAGE_SIZE(4K, 16K, 64K)와 페이지 테이블 레벨(2, 3, 4)의 조합으로 VA_BITS=36, 39, 42, 47, 48, 52 중 하나를 선택하여 사용할 수 있다.

 

최초 ARMv8 아키텍처의 경우 64비트 주소 중 최대 48비트만을 가상 주소 영역에 사용할 수 있었는데 그 경우 최대 지원 가능한 가상 주소 영역의 크기는 2^48 = 256TB 크기 였다.

 

그 후, 더 큰 가상 주소 영역을 지원하기 위해 52비트의 LVA(Large Virtual Address) 기능을 탑재한 새 ARMv8.2 아키텍처가 만들어졌다. 이러한 경우 VA_BITS=52로 커널 이미지를 빌드하여 만들 수 있는데, 이렇게 빌드되어 만들어진 52비트 커널 이미지는 최대 가상 주소 영역에 48비트만을 사용하는 구형 아키텍처에서도 동작시킬 수도 있고, 52비트를 지원하는 최신 아키텍처에 둘 다에서 동작시킬 수 있다. 이러한 이유로 52비트에서 동작하는 커널을 만드는 경우 VA_BITS=52로 하되 VA_BITS_MIN=48로 값을 다르게하여 빌드 타임이 아닌 커널이 부트되는 런타임에 구형 아키텍처 및 신형 아키텍처를 판별하여 둘 중 하나를 선택하도록 코드를 구성하였다.

 

결국 VA_BITS 및 VA_BITS_MIN을 살펴보면 다음과 같이 VA_BITS=48비트까지는 VA_BITS_MIN 값도 동일하다. 그렇지만 VA_BITS=52비트인 경우 VA_BITS_MIN=48과 같이 두 값이 달라지는 것을 알 수 있다.

  • VA_BITS=36, VA_BITS_MIN=36
  • VA_BITS=39, VA_BITS_MIN=39
  • VA_BITS=42, VA_BITS_MIN=42
  • VA_BITS=47, VA_BITS_MIN=47
  • VA_BITS=48, VA_BITS_MIN=48
  • VA_BITS=52, VA_BITS_MIN=48

 


부트 시 전달된 인자(x0~x3) 저장

preserve_boot_args()

arch/arm64/kernel/head.S

/*
 * Preserve the arguments passed by the bootloader in x0 .. x3
 */
SYM_CODE_START_LOCAL(preserve_boot_args)
        mov     x21, x0                         // x21=FDT

        adr_l   x0, boot_args                   // record the contents of
        stp     x21, x1, [x0]                   // x0 .. x3 at kernel entry
        stp     x2, x3, [x0, #16]

        dmb     sy                              // needed before dc ivac with
                                                // MMU off

        add     x1, x0, #0x20                   // 4 x 8 bytes
        b       dcache_inval_poc                // tail call
SYM_CODE_END(preserve_boot_args)

부트로더가 전달해준 x0 ~ x3 레지스터들을 boot_args 위치에 보관해둔다. x0 레지스터는 DTB 주소로 사용되고, 나머지는 추후 사용하기 위해 예약되었다.

  • 코드 라인 2~6에서 부트로더가 전달해준 x0 ~ x3 레지스터들을 boot_args 위치에 보관해둔다.
  • 코드 라인 8에서 데이터 캐시를 invalidate하여 제거하기 전에 위의 stp 명령이 내부 버퍼에서 완전하게 마무리되도록 메모리 베리어를 사용한다.
    • MMU가 꺼진 상태는 캐시를 사용하지 않지만 버퍼는 사용가능한 상태이다.
      • 참고로 MMU가 꺼져 있어도 버퍼를 통해 predictive 로딩은 가능한 상태이다.
    • MMU가 꺼진 상태에서 캐시에 쓰레기 값이 존재할 수 있다. 따라서 해당 주소의 캐시는 비워두는 것이 추후 안전하다.
      • 쓰레기 캐시 값이 남아 있게되면 나중에 MMU가 켜진 후 캐시 라인을 clean 하거나 또는 자동으로 evict되는 경우 캐시에 있었던 쓰레기 값을 메모리에 덮어 기록하면서 의도하지 않는 일이 발생할 수 있다.
  • 코드 라인 11~12에서 위에서 설명한 이유로 추후 잘못된 값이 읽히지 않도록 PoC 레벨까지 모든 cpu의 invalidate D-cache를 수행한다.

 

다음 두 그림을 통해 mmu-off 상태에서 메모리를 기록한 후 dmb와 dc ivac 명령을 사용하여 잠재적인 캐시 코히런스 문제를 제거한다.

 

 


하이퍼 바이저 지원 코드 설정

init_kernel_el()

arch/arm64/kernel/head.S

/*
 * end early head section, begin head code that is also used for
 * hotplug and needs to have the same protections as the text region
 */
        .section ".idmap.text","awx"

/*
 * Starting from EL2 or EL1, configure the CPU to execute at the highest
 * reachable EL supported by the kernel in a chosen default state. If dropping
 * from EL2 to EL1, configure EL2 before configuring EL1.
 *
 * Since we cannot always rely on ERET synchronizing writes to sysregs (e.g. if
 * SCTLR_ELx.EOS is clear), we place an ISB prior to ERET.
 *
 * Returns either BOOT_CPU_MODE_EL1 or BOOT_CPU_MODE_EL2 in x0 if
 * booted in EL1 or EL2 respectively, with the top 32 bits containing
 * potential context flags. These flags are *not* stored in __boot_cpu_mode.
 */
SYM_FUNC_START(init_kernel_el)
        mrs     x0, CurrentEL
        cmp     x0, #CurrentEL_EL2
        b.eq    init_el2

el1 또는 el2 cpu가 현재 어떤 el 레벨로 커널에 진입했는지 알아오기 위해 CurrentEL 레지스터를 읽어온다. 그 후 레벨에 따라 init_el1 과 init_el2로 분기한다.

  • 코드 라인 2~3에서 CurrentEL 값을 읽어 el2 레벨인지 비교한다.
    • 커널 진입 시점의 boot cpu에서 CurrentEL 레지스터를 읽은 값은 CurrentEL_EL1(4) 또는 CurrentEL_EL2(8)이다.
  • 코드 라인 4에서 만일 el2인 경우 init_el2로 점프하고, 그렇지 않은 경우 el1이므로 다음 행에 있는 init_el1: 레이블을 진행한다.

 

init_el1:

arch/arm64/kernel/head.S

SYM_INNER_LABEL(init_el1, SYM_L_LOCAL)
        mov_q   x0, INIT_SCTLR_EL1_MMU_OFF
        msr     sctlr_el1, x0
        isb
        mov_q   x0, INIT_PSTATE_EL1
        msr     spsr_el1, x0
        msr     elr_el1, lr
        mov     w0, #BOOT_CPU_MODE_EL1
        eret
  • 코드 라인 2~3에서 MMU가 꺼진 상태 값으로 시스템 컨트롤 레지스터인 sctlr_el1 을 설정한다.
  • 코드 라인 4에서 시스템 컨트롤 레지스터의 내용을 변경한 경우에는 isb 명령을 사용하여 파이프라인을 모두 비워야 한다.
  • 코드 라인 5~6에서 EL1 전환을 위해 EL1 pstate 초깃값을 spsr_el1 레지스터에 지정한다.
    • INTI_PSTATE_EL1 값에는 DAIF 플래그를 마스크하고 EL1 모드로 변경되도록 값이 지정되어 있다.
  • 코드 라인 7에서 복귀할 주소를 지정하기 위해 lr 레지스터에 담긴 복귀 주소를 elr_el1 레지스터에 저장한다.
  • 코드 라인 8에서 el1으로 복귀하는 것을 호출한 함수에서 알 수 있도록 w0 레지스터에 담는다. (반환 값은 el1을 의미하라고 0xe11)
  • 코드 라인 9에서 exception return 명령을 통해 복귀한다.

 

INIT_PSTATE_EL1 & INIT_PSTATE_EL2 값
#define INIT_PSTATE_EL1 \
        (PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL1h)
#define INIT_PSTATE_EL2 \
        (PSR_D_BIT | PSR_A_BIT | PSR_I_BIT | PSR_F_BIT | PSR_MODE_EL2h)

EL1 및 EL2의 PSTATE 초깃값으로 DAIF 플래그를 모두 마스크한 상태와 해당 EL 모드를 포함한다.

 

init_el2:

arch/arm64/kernel/head.S

SYM_INNER_LABEL(init_el2, SYM_L_LOCAL)
        mov_q   x0, HCR_HOST_NVHE_FLAGS
        msr     hcr_el2, x0
        isb

        init_el2_state

        /* Hypervisor stub */
        adr_l   x0, __hyp_stub_vectors
        msr     vbar_el2, x0
        isb

        mov_q   x1, INIT_SCTLR_EL1_MMU_OFF

        /*
         * Fruity CPUs seem to have HCR_EL2.E2H set to RES1,
         * making it impossible to start in nVHE mode. Is that
         * compliant with the architecture? Absolutely not!
         */
        mrs     x0, hcr_el2
        and     x0, x0, #HCR_E2H
        cbz     x0, 1f

        /* Set a sane SCTLR_EL1, the VHE way */
        msr_s   SYS_SCTLR_EL12, x1
        mov     x2, #BOOT_CPU_FLAG_E2H
        b       2f

1:
        msr     sctlr_el1, x1
        mov     x2, xzr
2:
        msr     elr_el2, lr
        mov     w0, #BOOT_CPU_MODE_EL2
        orr     x0, x0, x2
        eret
SYM_FUNC_END(init_kernel_el)

el2 에서 동작할 코드 및 exception stub 벡터들을 준비하고 el2 레지스터들을 초기화한다.

  • 코드 라인 2~4에서 하이퍼바이저 설정 레지스터를 nVHE 모드로 설정한다.
  • 코드 라인 6에서 nVHE를 위해 각종 EL2 레지스터들을 초기화한다.
  • 코드 라인 9~11에서 nVHE를 위해 호스트 커널은 EL1으로 전환하여 동작하여야 한다. 따라서 EL2에 하이퍼 바이저 용도로만 사용할 stub 벡터가 필요하므로 이를 설치한다.
  • 코드 라인 13에서 잠시 x1 레지스터에 EL1용 시스템 콘트롤레지스터에 저장할 초기값(mmu disable 포함)을 준비한다.
  • 코드 라인 20~22에서 하이퍼바이저 설정 레지스터를 읽어와서 E2H 필드 값을 읽어와서 0인 경우 VHE를 지원하지 않으므로 1: 레이블로 이동한다.
  • 코드 라인 25~27에서 아키텍처가 VHE 기능을 지원함을 확인했다. 조금 전에 x1 레지스터에 저장해두었던 값을 EL1용 시스템 컨트롤 레지스터에 저장하여 MMU를 확실하게 disable 한다. 그리고 VHE 기능을 활성화 하도록 E2H 비트를 x2레지스터에 담아 2: 레이블로 이동한다.
    • VHE 모드에서 _el12 접미사를 사용하는 레지스터는 실제 el1 레지스터를 의미한다.
  • 코드 라인 29~31에서 1: 레이블이다. 조금 전에 x1 레지스터에 저장해두었던 값을 EL1용 시스템 콘트롤레지스터에 저장하여 MMU를 확실하게 disable 한다.
  • 코드 라인 32~36에서 2: 레이블이다. el2 부팅되었음을 알리는 값을 x0 레지스터를 통해 반환한다. (반환값: el2를 의미하라고 0xe12)
  • 코드 라인 37에서 현재 EL2 모드에 있고, eret 명령을 사용하여 EL1 또는 EL2(같은 모드로도 가능) 모드로 전환하면서 elr_el2 레지스터에 저장한 복귀할 주소로 점프한다.
    • primary_entry에서 bl init_kernel_el 명령을 사용했었었다. 이렇게 bl 명령을 사용하면 돌아올 위치를 lr 레지스터에 담아두었다는 것을 기억해야 한다.

 

init_el2_state 매크로

arch/arm64/include/asm/el2_setup.h

/**
 * Initialize EL2 registers to sane values. This should be called early on all
 * cores that were booted in EL2. Note that everything gets initialised as
 * if VHE was not evailable. The kernel context will be upgraded to VHE
 * if possible later on in the boot process
 *
 * Regs: x0, x1 and x2 are clobbered.
 */
.macro init_el2_state
        __init_el2_sctlr
        __init_el2_timers
        __init_el2_debug
        __init_el2_lor
        __init_el2_stage2
        __init_el2_gicv3
        __init_el2_hstr
        __init_el2_nvhe_idregs
        __init_el2_nvhe_cptr
        __init_el2_nvhe_sve
        __init_el2_fgt
        __init_el2_nvhe_prepare_eret
.endm

EL2로 진입한 경우 먼저 VHE 지원 여부와 관계없이 먼저 각각의 EL2 레지스터들을 초기화한다.

  • 이렇게 초기화하면 nVHE에서 유효한 설정이 되는 것이고, 그 후 VHE를 지원하는 시스템에서는 나중에 추가 작업을 수행한다.

 

__init_el2_sctlr 매크로 – MMU disable

arch/arm64/include/asm/el2_setup.h

.macro __init_el2_sctlr
        mov_q   x0, INIT_SCTLR_EL2_MMU_OFF
        msr     sctlr_el2, x0
        isb
.endm

하이퍼 바이저용 시스템 콘트롤 레지스터에 MMU off 상태의 디폴트 설정으로 기록한다.

 

__init_el2_timers 매크로 – EL1 물리 타이머 enable

arch/arm64/include/asm/el2_setup.h

/*
 * Allow Non-secure EL1 and EL0 to access physical timer and counter.
 * This is not necessary for VHE, since the host kernel runs in EL2,
 * and EL0 accesses are configured in the later stage of boot process.
 * Note that when HCR_EL2.E2H == 1, CNTHCTL_EL2 has the same bit layout
 * as CNTKCTL_EL1, and CNTKCTL_EL1 accessing instructions are redefined
 * to access CNTHCTL_EL2. This allows the kernel designed to run at EL1
 * to transparently mess with the EL0 bits via CNTKCTL_EL1 access in
 * EL2.
 */
.macro __init_el2_timers
        mov     x0, #3                          // Enable EL1 physical timers
        msr     cnthctl_el2, x0
        msr     cntvoff_el2, xzr                // Clear virtual offset
.endm

el1 물리 타이머를 enable 하고, virtual offset을 클리어한다.

  • 코드 라인 2~3에서 하이퍼 바이저 카운터-타이머 컨트롤(cnthctl_el2) 레지스터의 EL1 물리 카운터와 (el1pcen)와 EL1 물리 타이머(el1pcten)에 해당하는 비트들을 설정하여 el0 및 el1에서 이들을 사용가능하도록 한다.
  • 코드 라인 4에서 virtual offset 레지스터를 0으로 클리어한다.

 

__init_el2_debug 매크로 – hw debuger enable

arch/arm64/include/asm/el2_setup.h

.macro __init_el2_debug
        mrs     x1, id_aa64dfr0_el1
        sbfx    x0, x1, #ID_AA64DFR0_PMUVER_SHIFT, #4
        cmp     x0, #1
        b.lt    .Lskip_pmu_\@                   // Skip if no PMU present
        mrs     x0, pmcr_el0                    // Disable debug access traps
        ubfx    x0, x0, #11, #5                 // to EL2 and allow access to
.Lskip_pmu_\@:
        csel    x2, xzr, x0, lt                 // all PMU counters from EL1

        /* Statistical profiling */
        ubfx    x0, x1, #ID_AA64DFR0_PMSVER_SHIFT, #4
        cbz     x0, .Lskip_spe_\@               // Skip if SPE not present

        mrs_s   x0, SYS_PMBIDR_EL1              // If SPE available at EL2,
        and     x0, x0, #(1 << SYS_PMBIDR_EL1_P_SHIFT)
        cbnz    x0, .Lskip_spe_el2_\@           // then permit sampling of physical
        mov     x0, #(1 << SYS_PMSCR_EL2_PCT_SHIFT | \
                      1 << SYS_PMSCR_EL2_PA_SHIFT)
        msr_s   SYS_PMSCR_EL2, x0               // addresses and physical counter
.Lskip_spe_el2_\@:
        mov     x0, #(MDCR_EL2_E2PB_MASK << MDCR_EL2_E2PB_SHIFT)
        orr     x2, x2, x0                      // If we don't have VHE, then
                                                // use EL1&0 translation.

PMU(Process Monitoring Unit)이 내장된 아키텍처에서 디버거를 사용할 수 있게, el2 트랩하지 않도록 한다. (자세한 설명은 skip)

 

__init_el2_lor 매크로 – LORegion 기능 disable

arch/arm64/include/asm/el2_setup.h

/* LORegions */
.macro __init_el2_lor
        mrs     x1, id_aa64mmfr1_el1
        ubfx    x0, x1, #ID_AA64MMFR1_LOR_SHIFT, 4
        cbz     x0, .Lskip_lor_\@
        msr_s   SYS_LORC_EL1, xzr
.Lskip_lor_\@:
.endm

LORegion 기능을 disable 한다.

  • 코드 라인 3~4에서 id_aa64mmfr1_el1 레지스터의 LOR 필드를 읽어온다.
  • 코드 라인 5~6에서 LORegions 기능이 있는 경우 LORC_EL1 레지스터를 클리어하여 LORegion 기능을 disable 한다.

 

__init_el2_stage2 매크로 – vttbr_el2 클리어

arch/arm64/include/asm/el2_setup.h

/* Stage-2 translation */
.macro __init_el2_stage2
        msr     vttbr_el2, xzr
.endm

vttbr_el2 레지스터를 클리어한다.

 

__init_el2_gicv3 매크로 – GICv3 초기화

arch/arm64/include/asm/el2_setup.h

/* GICv3 system register access */
.macro __init_el2_gicv3
        mrs     x0, id_aa64pfr0_el1
        ubfx    x0, x0, #ID_AA64PFR0_GIC_SHIFT, #4
        cbz     x0, .Lskip_gicv3_\@

        mrs_s   x0, SYS_ICC_SRE_EL2
        orr     x0, x0, #ICC_SRE_EL2_SRE        // Set ICC_SRE_EL2.SRE==1
        orr     x0, x0, #ICC_SRE_EL2_ENABLE     // Set ICC_SRE_EL2.Enable==1
        msr_s   SYS_ICC_SRE_EL2, x0
        isb                                     // Make sure SRE is now set
        mrs_s   x0, SYS_ICC_SRE_EL2             // Read SRE back,
        tbz     x0, #0, 1f                      // and check that it sticks
        msr_s   SYS_ICH_HCR_EL2, xzr            // Reset ICC_HCR_EL2 to defaults
.Lskip_gicv3_\@:
.endm

GICv3를 초기화한다.

  • 코드 라인 3~5에서 id_aa64pfr0_el1 레지스터에서 gic 필드를 읽어 gicv3가 구현되지 않은 경우 GICv3 설정을 skip 한다.
  • 코드 라인 7~11에서 icc_sre_el2.sre를 1로 설정하여시스템 레지스터를 enable하고,  icc_sre_el2.enable을 1로 설정하여 non-secure el1에서 icc_sre_el1을 사용하도록 설정한다.
  • 코드 라인 12~14에서 icc_sre_el2 레지스터를 다시 읽고, 63번 비트가 1로 설정된 경우 문제가 있다고 판단하여 sys_ich_hcr_el2 레지스터를 클리어하여 리셋시킨다.

 

__init_el2_hstr 매크로 – CP15 enable

arch/arm64/include/asm/el2_setup.h

.macro __init_el2_hstr
        msr     hstr_el2, xzr                   // Disable CP15 traps to EL2
.endm

hstr_el2 레지스터를 클리어하여 cp15 레지스터를 읽을 수 있도록 허용한다.

 

__init_el2_nvhe_idregs 매크로 – Virtual CPU ID 등록

arch/arm64/include/asm/el2_setup.h

/* Virtual CPU ID registers */
.macro __init_el2_nvhe_idregs
        mrs     x0, midr_el1
        mrs     x1, mpidr_el1
        msr     vpidr_el2, x0
        msr     vmpidr_el2, x1
.endm

midr_el1 레지스터의 설정값을 읽어 vpidr_el2 레지스터에 그대로 적용하고, mpidr_el1 레지스터도 vmpidr_el2 레지스터에 적용한다.

 

__init_el2_nvhe_cptr 매크로 – Enable FP & SVE trap

arch/arm64/include/asm/el2_setup.h

/* Coprocessor traps */
.macro __init_el2_nvhe_cptr
        mov     x0, #0x33ff
        msr     cptr_el2, x0                    // Disable copro. traps to EL2
.endm

Floating Point 장치나 SVE 명령을 사용시 일단 trap 되도록 설정한다.

 

__init_el2_nvhe_sve 매크로 – SVE 명령 사용 허가

arch/arm64/include/asm/el2_setup.h

/* SVE register access */
.macro __init_el2_nvhe_sve
        mrs     x1, id_aa64pfr0_el1
        ubfx    x1, x1, #ID_AA64PFR0_SVE_SHIFT, #4
        cbz     x1, .Lskip_sve_\@

        bic     x0, x0, #CPTR_EL2_TZ            // Also disable SVE traps
        msr     cptr_el2, x0                    // Disable copro. traps to EL2
        isb
        mov     x1, #ZCR_ELx_LEN_MASK           // SVE: Enable full vector
        msr_s   SYS_ZCR_EL2, x1                 // length for EL1.
.Lskip_sve_\@:
.endm

아키텍처에 SVE 기능이 내장된 경우 SVE 명령 사용 허가

  • 코드 라인 3~11에서 id_aa64pfr0_el1 레지스터에서 sve 기능이 있는 경우 cptr_el2.tz을 0으로 클리어하여 SVE 명령을 사용하여도 trap을 발생시키지 않도록 한다. 그런 후 SVE 컨트롤 레지스터 zcr_el2.len의 4비트를 모두 1로 채워 가장 큰 벡터 길이로 활성화한다.
    • 벡터 길이 = (len + 1) * 128bits
    • RAZ/WI에 해당하는 비트들은 어떠한 값을 기록해도 무시되고 0으로 읽힌다.

 

__init_el2_fgt 매크로

arch/arm64/include/asm/el2_setup.h

/* Disable any fine grained traps */
.macro __init_el2_fgt
        mrs     x1, id_aa64mmfr0_el1
        ubfx    x1, x1, #ID_AA64MMFR0_FGT_SHIFT, #4
        cbz     x1, .Lskip_fgt_\@

        mov     x0, xzr
        mrs     x1, id_aa64dfr0_el1
        ubfx    x1, x1, #ID_AA64DFR0_PMSVER_SHIFT, #4
        cmp     x1, #3
        b.lt    .Lset_fgt_\@
        /* Disable PMSNEVFR_EL1 read and write traps */
        orr     x0, x0, #(1 << 62)

.Lset_fgt_\@:
        msr_s   SYS_HDFGRTR_EL2, x0
        msr_s   SYS_HDFGWTR_EL2, x0
        msr_s   SYS_HFGRTR_EL2, xzr
        msr_s   SYS_HFGWTR_EL2, xzr
        msr_s   SYS_HFGITR_EL2, xzr

        mrs     x1, id_aa64pfr0_el1             // AMU traps UNDEF without AMU
        ubfx    x1, x1, #ID_AA64PFR0_AMU_SHIFT, #4
        cbz     x1, .Lskip_fgt_\@

        msr_s   SYS_HAFGRTR_EL2, xzr
.Lskip_fgt_\@:
.endm

statistical profiling 설정 관련 설명은 생략한다.

 

__init_el2_nvhe_prepare_eret 매크로

arch/arm64/include/asm/el2_setup.h

.macro __init_el2_nvhe_prepare_eret
        mov     x0, #INIT_PSTATE_EL1
        msr     spsr_el2, x0
.endm

일단 nVHE 상태로 spsr_el2 레지스터를 초기화한다.

 

__hyp_stub_vectors – for nVHE

arch/arm64/kernel/hyp-stub.S

        .text
        .pushsection    .hyp.text, "ax"

        .align 11

SYM_CODE_START(__hyp_stub_vectors)
        ventry  el2_sync_invalid                // Synchronous EL2t
        ventry  el2_irq_invalid                 // IRQ EL2t
        ventry  el2_fiq_invalid                 // FIQ EL2t
        ventry  el2_error_invalid               // Error EL2t

        ventry  elx_sync                        // Synchronous EL2h
        ventry  el2_irq_invalid                 // IRQ EL2h
        ventry  el2_fiq_invalid                 // FIQ EL2h
        ventry  el2_error_invalid               // Error EL2h

        ventry  elx_sync                        // Synchronous 64-bit EL1
        ventry  el1_irq_invalid                 // IRQ 64-bit EL1
        ventry  el1_fiq_invalid                 // FIQ 64-bit EL1
        ventry  el1_error_invalid               // Error 64-bit EL1

        ventry  el1_sync_invalid                // Synchronous 32-bit EL1
        ventry  el1_irq_invalid                 // IRQ 32-bit EL1
        ventry  el1_fiq_invalid                 // FIQ 32-bit EL1
        ventry  el1_error_invalid               // Error 32-bit EL1
SYM_CODE_END(__hyp_stub_vectors)
  • 호스트 OS에서 발생하는 sync exception만 처리하며 elx_sync 레이블이 호출된다.
  • 참고: Exception -7- (ARM64 Vector) | 문c

 


페이지 테이블 생성

create_idmap()

arch/arm64/kernel/head.S -1/2-

SYM_FUNC_START_LOCAL(create_idmap)
        mov     x28, lr
        /*
         * The ID map carries a 1:1 mapping of the physical address range
         * covered by the loaded image, which could be anywhere in DRAM. This
         * means that the required size of the VA (== PA) space is decided at
         * boot time, and could be more than the configured size of the VA
         * space for ordinary kernel and user space mappings.
         *
         * There are three cases to consider here:
         * - 39 <= VA_BITS < 48, and the ID map needs up to 48 VA bits to cover
         *   the placement of the image. In this case, we configure one extra
         *   level of translation on the fly for the ID map only. (This case
         *   also covers 42-bit VA/52-bit PA on 64k pages).
         *
         * - VA_BITS == 48, and the ID map needs more than 48 VA bits. This can
         *   only happen when using 64k pages, in which case we need to extend
         *   the root level table rather than add a level. Note that we can
         *   treat this case as 'always extended' as long as we take care not
         *   to program an unsupported T0SZ value into the TCR register.
         *
         * - Combinations that would require two additional levels of
         *   translation are not supported, e.g., VA_BITS==36 on 16k pages, or
         *   VA_BITS==39/4k pages with 5-level paging, where the input address
         *   requires more than 47 or 48 bits, respectively.
         */
#if (VA_BITS < 48)
#define IDMAP_PGD_ORDER (VA_BITS - PGDIR_SHIFT)
#define EXTRA_SHIFT     (PGDIR_SHIFT + PAGE_SHIFT - 3)

        /*
         * If VA_BITS < 48, we have to configure an additional table level.
         * First, we have to verify our assumption that the current value of
         * VA_BITS was chosen such that all translation levels are fully
         * utilised, and that lowering T0SZ will always result in an additional
         * translation level to be configured.
         */
#if VA_BITS != EXTRA_SHIFT
#error "Mismatch between VA_BITS and page size/number of translation levels"
#endif
#else
#define IDMAP_PGD_ORDER (PHYS_MASK_SHIFT - PGDIR_SHIFT)
#define EXTRA_SHIFT
        /*
         * If VA_BITS == 48, we don't have to configure an additional
         * translation level, but the top-level table has more entries.
         */
#endif
        adrp    x0, init_idmap_pg_dir
        adrp    x3, _text
        adrp    x6, _end + MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
        mov     x7, SWAPPER_RX_MMUFLAGS

        map_memory x0, x1, x3, x6, x7, x3, IDMAP_PGD_ORDER, x10, x11, x12, x13, x14, EXTRA_SHIFT

메모리에 로드된 커널 이미지 및 FDT 영역을 물리 주소와 가상 주소를 1:1(VA = PA) 동일하게 변환되도록, Read Only 속성으로 init_idmap_pg_dir 테이블에 매핑한다. 그런 후 커널 이미지 내에 있는 init_pg_dir 페이지 테이블 영역과 커널 이미지 위에 위치한 FDT 영역은 별도록 다시 한 번 Read/Write 속성으로 재매핑을 한다.

VA_BITS는 유저 및 커널이 사용할 가상 주소의 크기에 사용할 비트 수 이다. IDMAP 테이블도 이와 동일하게 만들어야 하지만 DRAM에 로드된 커널이미지의 끝 물리 주소가 VA_BITS로 인해 커버할 가상 주소가 공간보다 더 높은 주소에 위치하면 사용자가 원하는 VA_BITS를 IDMAP에 그대로 사용할 수 없다. 때문에 이러한 경우를 위해 IDMAP에 사용할 페이지 테이블 레벨을 한 단계 더 끌어올려 사용할 수도 있다. (현재 커널 코드는 IDMAP 생성 시 페이지 테이블 레벨을 1단계 더 올리는 것은 가능하지만 2단계 이상은 처리하지 못한다.)

  • 39 <= VA_BITS < 48
    • 예) VA=39(4K 페이지, 3 레벨 페이지 테이블), PA=48 -> va_actual=48
    • 예) VA=42(64K 페이지, 3 레벨 페이지 테이블), PA=52 -> va_actual=52
  • VA_BITS == 48
    • 예) VA=48(64K 페이지, 3레벨 페이지 테이블), PA=52 -> va_actual=52

 

  • 코드 라인 2에서 일단 복귀할 주소를 담은 lr을 x28로 옮긴다.
  • 코드 라인 27~48에서 다음과 같이 VA_BITS가 48 미만인 경우와 이상인 경우에 따라 IDMAP_PGD_ORDER 및 EXTRA_SHIFT를 컴파일 타임에 결정한다.
    • VA_BITS < 48
      • IDMAP_PGD_ORDER
        • 사용할 VA_BITS – PGD에 사용할 비트
      • EXTRA_SHIFT
        • PGD에 사용할 비트 + 추가 페이지 테이블 비트
    • VA_BITS >= 48
      • IDMAP_PGD_ORDER
        • 사용할 PA_BITS – PGD에 사용할 비트
      • EXTRA_SHIFT
        • <지정된 값 없음>
  • 코드 라인 49에서 init_idmap_pg_dir의 주소 중 4K 페이지 단위로 내림 정렬한 주소를 알아와서 x0 레지스터에 대입한다.
    • 예) init_idmap_pg_dir=0x2000_4124
      • 16진수 주소의 뒷부분 3자리를 0으로 자르면 x0 = 0x2000_4000
  • 코드 라인 50~54에서 물리 주소와 가상 주소가 1:1 동일하게 준비한 후 map_memory 매크로를 통해 커널 이미지와 FDT 모두를 Read Only 매핑을 수행한다.
    • 참고로 커널의 시작 위치는 2M 단위로 정렬되어 있지만, 최대 사이즈 2M의 FDT는 커널에서 8 바이트 정렬된 주소를 사용하고 있다. FDT는 조금 뒤에 나오는 코드에서 Read/Write로 재매핑을 해야 하는데 FDT의 끝부분이 재매핑에서 잘릴 수 있으므로  미리 SWAPPER_BLOCK_SIZE 만큼 추가하여 매핑을 해둔다.
    • 주의: FDT 규격은 4 바이트 정렬을 사용한다. 그러나 커널에 지정해주는 시작 주소는 8 바이트 정렬된 주소를 사용해야 한다.
    • 커널 코드 시작 부분의 주소를 x3 레지스터에 대입한다.
    • 커널 코드 끝 주소에 fdt 최대 사이즈(2M)와 swapper 블럭 사이즈(pmd or pte)를 추가한 주소를 x6 레지스터에 대입한다.
    • 매핑에 사용할 플래그 값들을 x7에 대입한다.

 

arch/arm64/kernel/head.S -2/2-

        /* Remap the kernel page tables r/w in the ID map */
        adrp    x1, _text
        adrp    x2, init_pg_dir
        adrp    x3, init_pg_end
        bic     x4, x2, #SWAPPER_BLOCK_SIZE - 1
        mov     x5, SWAPPER_RW_MMUFLAGS
        mov     x6, #SWAPPER_BLOCK_SHIFT
        bl      remap_region

        /* Remap the FDT after the kernel image */
        adrp    x1, _text
        adrp    x22, _end + SWAPPER_BLOCK_SIZE
        bic     x2, x22, #SWAPPER_BLOCK_SIZE - 1
        bfi     x22, x21, #0, #SWAPPER_BLOCK_SHIFT              // remapped FDT address
        add     x3, x2, #MAX_FDT_SIZE + SWAPPER_BLOCK_SIZE
        bic     x4, x21, #SWAPPER_BLOCK_SIZE - 1
        mov     x5, SWAPPER_RW_MMUFLAGS
        mov     x6, #SWAPPER_BLOCK_SHIFT
        bl      remap_region

        /*
         * Since the page tables have been populated with non-cacheable
         * accesses (MMU disabled), invalidate those tables again to
         * remove any speculatively loaded cache lines.
         */
        dmb     sy

        adrp    x0, init_idmap_pg_dir
        adrp    x1, init_idmap_pg_end
        bl      dcache_inval_poc
        ret     x28
SYM_FUNC_END(create_idmap)
  • 코드 라인 2~8에서 초기 커널 페이지 테이블 용도로 사용해야 할 init_pg_dir 영역에 대해서 Read/Write 매핑으로 리매핑한다.
  • 코드 라인 11~19에서 FDT 영역도 Read/Write 매핑으로 리매핑한다. 이 과정에서 FDT 영역이 매핑단위로 align 되어 있지 않으므로 매핑 단위인 SWAPPER_BLOCK_SIZE 만큼 더해 매핑해야 FDT 영역의 전체 영역이 Read/Write 가능하다.
    • 주의: FDT 영역은 물리 주소에서 커널 이미지 공간과 다른 위치에 있을 수 있다. 처음 시스템이 부팅될 때 부트로더가 x0 레지스터에 FDT 위치를 넘겨주는 이유이다. 따라서 이 공간은 가상 주소에 매핑할 때 커널 이미지 위에 위치하도록 매핑한다. 즉 FDT 공간 만큼은 1:1 idendity 매핑을 수행하지 않는다.
  • 코드 라인 26~31에서 메모리 배리어를 수행하여 메모리에 완전하게 기록을 완료 시킨 후 init_idmap_pg_dir 전체 영역에 대해 데이터 캐시를 invalidate 한다. (해당 이유는 본문 앞 부분에서 거론하였으므로 설명은 생략)

 

다음 그림은 커널 이미지 + FDT 영역에 대해 init_idmap_pg_dir 페이지 테이블에 R/O 속성으로 1:1 identity 매핑한 모습을 보여준다.

 

 

다음 그림은 커널 이미지 내부에 있는 init_pg_dir 페이지 테이블 영역에 대해 init_idmap_pg_dir 페이지 테이블에 R/W 속성으로 재매핑하는 모습을 보여준다.

 

 

다음 그림은 커널 이미지 외부에 있는 FDT 영역에 대해 init_idmap_pg_dir 페이지 테이블에 R/W 속성으로 재매핑하는 모습을 보여준다.

  • 주의: FDT는 커널 이미지와 떨어져 있기 때문에 init_idmap_pg_dir 페이지 테이블을 사용하여도 1:1 identity 매핑을 하지 않는것을 알 수 있다.

 


페이지 테이블 관련 매크로

map_memory 매크로

arch/arm64/kernel/head.S

/*
 * Map memory for specified virtual address range. Each level of page table needed supports
 * multiple entries. If a level requires n entries the next page table level is assumed to be
 * formed from n pages.
 *
 *      tbl:    location of page table
 *      rtbl:   address to be used for first level page table entry (typically tbl + PAGE_SIZE)
 *      vstart: virtual address of start of range
 *      vend:   virtual address of end of range - we map [vstart, vend - 1]
 *      flags:  flags to use to map last level entries
 *      phys:   physical address corresponding to vstart - physical memory is contiguous
 *      order:  #imm 2log(number of entries in PGD table)
 *
 * If extra_shift is set, an extra level will be populated if the end address does
 * not fit in 'extra_shift' bits. This assumes vend is in the TTBR0 range.
 *
 * Temporaries: istart, iend, tmp, count, sv - these need to be different registers
 * Preserves:   vstart, flags
 * Corrupts:    tbl, rtbl, vend, istart, iend, tmp, count, sv
 */
        .macro map_memory, tbl, rtbl, vstart, vend, flags, phys, order, istart, iend, tmp, count, sv, extra_shift
        sub \vend, \vend, #1
        add \rtbl, \tbl, #PAGE_SIZE
        mov \count, #0

        .ifnb   \extra_shift
        tst     \vend, #~((1 << (\extra_shift)) - 1)
        b.eq    .L_\@
        compute_indices \vstart, \vend, #\extra_shift, #(PAGE_SHIFT - 3), \istart, \iend, \count
        mov \sv, \rtbl
        populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
        mov \tbl, \sv
        .endif
.L_\@:
        compute_indices \vstart, \vend, #PGDIR_SHIFT, #\order, \istart, \iend, \count
        mov \sv, \rtbl
        populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
        mov \tbl, \sv

#if SWAPPER_PGTABLE_LEVELS > 3
        compute_indices \vstart, \vend, #PUD_SHIFT, #(PAGE_SHIFT - 3), \istart, \iend, \count
        mov \sv, \rtbl
        populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
        mov \tbl, \sv
#endif

#if SWAPPER_PGTABLE_LEVELS > 2
        compute_indices \vstart, \vend, #SWAPPER_TABLE_SHIFT, #(PAGE_SHIFT - 3), \istart, \iend, \count
        mov \sv, \rtbl
        populate_entries \tbl, \rtbl, \istart, \iend, #PMD_TYPE_TABLE, #PAGE_SIZE, \tmp
        mov \tbl, \sv
#endif

        compute_indices \vstart, \vend, #SWAPPER_BLOCK_SHIFT, #(PAGE_SHIFT - 3), \istart, \iend, \count
        bic \rtbl, \phys, #SWAPPER_BLOCK_SIZE - 1
        populate_entries \tbl, \rtbl, \istart, \iend, \flags, #SWAPPER_BLOCK_SIZE, \tmp
        .endm

pgd 테이블 @tbl에 가상 주소 영역 [@vstart, @vend-1]을 필요한 전체 단계의 테이블에 매핑한다. 4K 페이지를 지원하는 경우 2M 단위로 블럭 매핑한다.

  • 코드 라인 2에서 @vend-1까지 매핑을 해야 하므로 @vend 값을 1 감소 시킨다.
  • 코드 라인 3에서 @rtbl은 다음 1레벨 페이지 테이블을 가리키도록 @tbl + #PAGE_SIZE를 대입한다.
  • 코드 라인 4에서 다음 레벨 매핑을 위해 이전 레벨에서 extra 매핑한 엔트리 수를 담는 @count를 0으로 클리어한다.
    • 다음 단계에서 사용할 기본 테이블 1개를 제외하고 추가로 필요로하는 테이블 수가 담긴다. (@count 변수 명을 @extra_count라고 생각하면 쉽다)
  • 코드 라인 6~8에서 .ifnb(if not blank)에서 @extra-shift가 지정된 경우 @vend가@extra_shift 커버 범위를 초과하지 않아 extra 페이지 테이블을 만들 필요가 없으면,즉 idmap을 위해 1단계의 추가 테이블이 필요 없으면 .L_\@ 레이블로이동한다.
    • extra_shift는 (VA_BITS < 48)에서만 (PGDIR_SHIFT + PAGE_SHIFT – 3) 값으로 지정되었었다.
  • 코드 라인 9~12에서 원래 처음 만들어야 할 pgd의 앞인 extra용 페이지 테이블에 [@vstart, @vend-1] 가상 주소에 해당하는 인덱스 엔트리를 매핑하여 연결한다.
  • 코드 라인 15~18에서 다음 단계의 pgd 페이지 테이블에 [@vstart, @vend-1] 가상 주소에 해당하는 인덱스 엔트리를 매핑하여 연결한다.
  • 코드 라인 20~25에서 다음 단계의 pud 페이지 테이블에 [@vstart, @vend-1] 가상 주소에 해당하는 인덱스 엔트리를 매핑하여 연결한다.
    • SWAPPER_PGTABLE_LEVELS이 4단계 이상에서만 pud 테이블을 사용한다.
  • 코드 라인 27~32에서 다음 단계의 페이지 테이블을 pmd 테이블에 [@vstart, @vend-1] 가상 주소에 해당하는 인덱스 엔트리에 연결한다.
    • SWAPPER_PGTABLE_LEVELS이 3단계 이상에서만 pmd 테이블을 사용한다.
  • 코드 라인 34~37에서 페이지 또는 2M 섹션(블럭)을 SWAPPER_BLOCK_SIZE 단위로 절삭하여 pte 테이블의 [@vstart, @vend-1] 가상 주소에 해당하는 인덱스 엔트리에 매핑할 때 @flags 속성을 추가하여 매핑한다.
    • 마지막 테이블 레벨의 매핑은 커널 설정에 따라 둘 중 하나의 방식을 사용한다.
      • 1) PAGE_SIZE(16K or 64K) 단위로 페이지 매핑을 사용하는 경우
      • 2) 4K 페이지 설정을 사용하여 PMD(2M) 단위의 블럭 매핑을 사용하는 경우
        • PMD 매핑을 사용하는 경우 엔트리 수에 PTRS_PER_PTE 상수가 보여 이상하다고 느낄것이다. 왜 PTRS_PER_PMD를 사용하지 않고 PTRS_PER_PTE를 사용했을까라는 의심이 있을것이다. 그냥 커널 옵션 설정에 따라 이 코드에서 PTE 매핑과 PMD 매핑 둘 다 같은 코드를 사용하고 있으므로 단지 마지막 단위의 PTRS_PER_PTE 를 사용하고 있다. 참고로 이렇게 할 수 있는 이유는 PTRS_PER_PTE와 PTRS_PER_PMD가 같은 값을 사용하기 때문이다.
    • 마지막 pupulate에 사용할 @count는 더 이상 count 용도로 사용하지 않고, 매핑할 물리 주소를 담아사용한다.

 

다음 그림은 테이블을 1 레벨 확장하여 사용하는 방법을 보여준다.

  • 확장하여 extra 페이지 테이블을 사용하는 경우 extra -> pgd -> pud -> pmd 순으로 페이지 테이블을 생성한다.

 

다음 그림은 커널 이미지 영역을 map_memory 매크로를 통해 init_idmap_pg_dir에 매핑하는 모습을 보여준다.

  • 참고로 다음 그림은 extra 페이지 테이블을 사용하지 않을 때의 사례이다.

 

compute_indices 매크로

arch/arm64/kernel/head.S

/*
 * Compute indices of table entries from virtual address range. If multiple entries
 * were needed in the previous page table level then the next page table level is assumed
 * to be composed of multiple pages. (This effectively scales the end index).
 *
 *      vstart: virtual address of start of range
 *      vend:   virtual address of end of range - we map [vstart, vend]
 *      shift:  shift used to transform virtual address into index
 *      order:  #imm 2log(number of entries in page table)
 *      istart: index in table corresponding to vstart
 *      iend:   index in table corresponding to vend
 *      count:  On entry: how many extra entries were required in previous level, scales
 *                        our end index.
 *              On exit: returns how many extra entries required for next page table level
 *
 * Preserves:   vstart, vend
 * Returns:     istart, iend, count
 */
.       .macro compute_indices, vstart, vend, shift, order, istart, iend, count
        ubfx    \istart, \vstart, \shift, \order
        ubfx    \iend, \vend, \shift, \order
        add     \iend, \iend, \count, lsl \order
        sub     \count, \iend, \istart
        .endm

페이지 테이블에서 가상 주소 범위 [@vstart, vend]에 해당하는 인덱스 번호 [@istart, @iend]를 산출한다. @count는 입출력 인자로 입력시에는 전단계에서 산출된 추가 필요 테이블 수를 담아오고, 출력시에는 다음 단계에서 사용할 기본 테이블 1개를 제외하고 추가로 필요로하는 테이블 수가 담긴다. (@count 변수 명을 @extra_count라고 생각하면 쉽다)

  • 코드 라인 2에서 가상 주소 @vstart를 @shift 비트 부터 msb 방향으로 @order 비트 수 만큼 값을 잘라 @istart에 대입한다.
  • 코드 라인 3에서 가상 주소 @vend를 @shift 비트 부터 msb 방향으로 @order 비트 수 만큼 값을 잘라 @iend에 대입한다.
  • 코드 라인 4에서 그런 후 @iend 값에 extra 엔트리 수인 @count << @order 한 값을 추가한다.
  • 코드 라인 5에서 @iend – @istart 값을 @count에 대입한다.

 

다음 그림은 compute_indices가 init_idmap 페이지 테이블에 대해 단계별로 3번 호출되는 모습을 보여준다.

 

다음 그림은 compute_indices가 init_idmap 페이지 테이블에 대해 단계별로 3번 호출되며 3개의 테이블이 더 추가된 모습을 보여준다.

 

다음 그림은 compute_indices가 작은 크기의 init_idmap 페이지 테이블에 대해 단계별로 3번 호출되며 2개의 테이블이 더 추가된 모습을 보여준다.

  • 주의: 실제 사이즈와 다르게 극단적인 가상의 사례로 8K 밖에 안되는 메모리를 매핑하였다.

 

populate_entries 매크로

arch/arm64/kernel/head.S

/*
 * Macro to populate page table entries, these entries can be pointers to the next level
 * or last level entries pointing to physical memory.
 *
 *      tbl:    page table address
 *      rtbl:   pointer to page table or physical memory
 *      index:  start index to write
 *      eindex: end index to write - [index, eindex] written to
 *      flags:  flags for pagetable entry to or in
 *      inc:    increment to rtbl between each entry
 *      tmp1:   temporary variable
 *
 * Preserves:   tbl, eindex, flags, inc
 * Corrupts:    index, tmp1
 * Returns:     rtbl
 */
        .macro populate_entries, tbl, rtbl, index, eindex, flags, inc, tmp1
.Lpe\@: phys_to_pte \tmp1, \rtbl
        orr     \tmp1, \tmp1, \flags    // tmp1 = table entry
        str     \tmp1, [\tbl, \index, lsl #3]
        add     \rtbl, \rtbl, \inc      // rtbl = pa next level
        add     \index, \index, #1
        cmp     \index, \eindex
        b.ls    .Lpe\@
        .endm

@tbl 페이지 테이블의 [@index, @eindex] 범위까지 다음 단계 테이블 또는 메모리(페이지 or 블럭)인 @rtbl에 속성 @flags를 mix하여 만든 pte 엔트리 값으로 매핑한다.

  • 코드 라인 3~4에서 @rtbl 물리 주소로 pte 엔트리 값으로 변환하고 속성 값 @flags를 추가하여 pte 엔트리 값을 구한다.
  • 코드 라인 5에서 pte 엔트리 값을 @tbl 페이지 테이블의 @index*8 주소 위치에 저장하여 매핑한다.
  • 코드 라인 6~8에서 다음 매핑할 물리 주소를 산출하기 위해 @inc를 더하고, @eindex 까지 반복한다.

 

다음 그림은 페이지 테이블이 static하게 연속된 페이지 다음 단계 테이블들에 연결되는 모습을 보여준다.

  • [index, eindex] 엔트리들이 다음 단계 페이지 테이블들로 연결된다.

 

phys_to_pte 매크로

arch/arm64/include/asm/assembler.h

        .macro  phys_to_pte, pte, phys
#ifdef CONFIG_ARM64_PA_BITS_52
        /*
         * We assume \phys is 64K aligned and this is guaranteed by only
         * supporting this configuration with 64K pages.
         */
        orr     \pte, \phys, \phys, lsr #36
        and     \pte, \pte, #PTE_ADDR_MASK
#else
        mov     \pte, \phys
#endif
        .endm

물리 주소 @phys를 사용하여 @pte 엔트리 값을 구성한다. (속성 값은 아직 더하지 않은 상태이다)

  • 코드 라인 7~8에서 52비트 물리 주소를 지원하는 경우 @phys 값에 36비트 우측 시프트한 @phys 값을 더한 후 필요 주소 영역(bits[47:12])만 사용할 수 있도록 마스크하여 @pte에 저장한다. 저장되는 @pte 값은 다음과 같이 구성된다.
    • @pte bits[47:16] <– 물리 주소 @phys bits[47:16]
    • @pte bits[15:12] <– 물리 주소 @phys bits[51:48]
  • 코드 라인 10에서 52비트 물리 주소를 사용하지 않는 경우 @phys 값을 @pte로 그대로 사용한다.

 

다음 그림은 연결될 물리 주소 phys를 사용하여 pte 엔트리로 변경된 모습을 보여준다.

  • VABITS=52를 사용하는 경우 phys의 bits[55:48]이 bits[15:12] 위치로 이동한다.

 

remap_region()

arch/arm64/kernel/head.S

/*
 * Remap a subregion created with the map_memory macro with modified attributes
 * or output address. The entire remapped region must have been covered in the
 * invocation of map_memory.
 *
 * x0: last level table address (returned in first argument to map_memory)
 * x1: start VA of the existing mapping
 * x2: start VA of the region to update
 * x3: end VA of the region to update (exclusive)
 * x4: start PA associated with the region to update
 * x5: attributes to set on the updated region
 * x6: order of the last level mappings
 */
SYM_FUNC_START_LOCAL(remap_region)
        sub     x3, x3, #1              // make end inclusive

        // Get the index offset for the start of the last level table
        lsr     x1, x1, x6
        bfi     x1, xzr, #0, #PAGE_SHIFT - 3

        // Derive the start and end indexes into the last level table
        // associated with the provided region
        lsr     x2, x2, x6
        lsr     x3, x3, x6
        sub     x2, x2, x1
        sub     x3, x3, x1

        mov     x1, #1
        lsl     x6, x1, x6              // block size at this level

        populate_entries x0, x4, x2, x3, x5, x6, x7
        ret
SYM_FUNC_END(remap_region)

요청한 영역을 다시 다른 속성으로 변경하여 리매핑을 수행한다.

  • x0: map_memory 수행 후 반환된 마지막 레벨 페이지 테이블 주소
  • x1: 기존 매핑된 시작 가상 주소
  • x2: 갱신할 영역의 시작 가상 주소
  • x3: 갱신할 영역의 끝 가상 주소
  • x4: 갱신할 영역에 연결될 시작 물리 주소
  • x5: 갱신할 영역에 설정할 속성
  • x6: 마지막 레벨 매핑 order

 

  • 코드 라인 2에서 갱신할 테이블의 끝 부분의 주소를 -1을 한다.
  • 코드 라인 5~6에서 x1 레지스터 값을 블럭(or 페이지) 매핑에 사용한 order(x6) 만큼 우측 shift 하여 인덱스 표현에 필요 없는 하위 비트들을 버린다. 그런 후 이 값에서 다시 하위 PAGE_SHIFT-3 비트 수 만큼을 clear 하여 마지막 테이블의 인덱스만을 꺼내기 위한 기준값(x1)을 만들어둔다.
  • 코드 라인 10~13에서 갱신할 영역의 시작(x2)과 끝(x3)을 대상으로 각각 order(x6) 만큼 우측 시프트하고 기준값(x1)을 빼면 시작 인덱스(sindex)와 끝 인덱스(eindex)가 산출된다.
  • 코드 라인 15~16에서 매핑에 사용할 메모리(블럭 or 페이지)사이즈를 x6에 대입한다.
  • 코드 라인 18~19에서 populate_entries  매크로를 통해 매핑을 수행한다.
    • @tbl(x0): 매핑에 사용할 테이블 주소
    • @rtbl(x4): 매핑될 물리 메모리(페이지 or 블럭)
    • @index(x2): 매핑에 사용할 테이블 내 시작 인덱스
    • @eindex(x3): 매핑에 사용할 테이블 내 끝 인덱스
    • @flags(x5): 매핑에 사용할 속성
    • @inc(x6): 인덱스 변경 시 마다 증가할 사이즈
    • @tmp1(x7): 임시 레지스터

 

다음과 같은 조건에서 init_pg_dir 테이블을 R/W 속성으로 재매핑을 하는 과정을 알아본다.

예) 4K 페이지, VA_BITS=48, 시작주소, 커널 이미지 40M

  • 주요 가상 주소((컴파일하여 산출된 심볼 가상 주소) 및 물리 주소는 다음과 같다고 가정한다. idmap 방식으로 매핑할때에는 PA 주소와 동일하게 VA를 사용한다는 점에 주의해야 한다.
    • _text
      • 심볼 VA=0xffff800010000000
      • VA=PA=0x4020_0000
    • _end
      • 심볼 VA=0xffff8000128f0000
      • VA=PA=0x42a0_0000
    • init_pg_dir
      • 심볼 VA=0xffff8000128e3000
      • VA=PA=0x429f_3000
    • init_pg_end
      • 심볼 VA=0xffff8000128e6000
      • VA=PA=0x429f_6000
  • 4K 페이지를 선택하였으므로 다음과 같은 매핑 사이즈를 갖는다.
    • PMD(2M) 단위의 블럭매핑 사용
    • SWAPPER_BLOCK_SHIFT=21
    • SWAPPER_BLOCK_SIZE=2M
    • 매핑할 전체 사이즈
      • 40M 커널 이미지 + MAX_FDT_SIZE(2M) + SWAPPER_BLOCK_SIZE(2M) = 44M 이다.
  • remap_region()에 사용된 인자
    • x0:
      • last level table address (returned in first argument to map_memory)
      • map_memory()가 init_idmap_pg_dir의 매핑(pgd->pud->pmd)에 사용한 마지막 테이블(pmd 테이블 시작)을 지정한다.
    • x1:
      • 기존 매핑에 사용한 시작 가상 주소
      • idmap에 사용하였으니 VA=PA=__pa(_text)=0x4020_0000
    • x2:
      • start VA of the region to update
      • 다시 매핑할 영역의 시작 VA=PA=__pa(init_pg_dir)=0x429f_3000
    • x3:
      • end VA of the region to update (exclusive)
      • 다시 매핑할 영역의 끝 VA=PA=__pa(init_pg_end)=0x429f_6000
    • x4:
      • start PA associated with the region to update
      • init_pg_dir 주소를 2M 단위로 매핑하므로 2M 단위 절삭한 물리 주소=0x4280_0000
    • x5:
      • attributes to set on the updated region
      • 갱신할 속성은 RW가 가능하도록 SWAPPER_RW_MMUFLAGS를 사용한다.
    • x6:
      • order of the last level mappings
      • 마지막 레벨에 사용하는 매핑 order = SWAPPER_BLOCK_SHIFT = 21
  • remap_region()이 진행하면서 산출되는 값들
    • x3:
      • 끝 주소에서 1을 뺀 값=__pa(init_pg_dir-1)=0x429f_2fff
    • x1:
      • 기존 매핑에 사용한 시작 가상 주소 >> 21 하고 하위 9비트를 clear 한다. (x1 레지스터는 임시 변수로 사용된다)
      • x1 = 0x4020_0000 >> 21 & ~0x1ff = 0x201 & ~0x1ff = 0x200
    • x2:
      • (0x429f_3000 >> 21) – x1 = 0x214 – 0x200 = 0x14
    • x3:
      • (0x429f_6000 >> 21) – x1 = 0x214 – 0x200 = 0x14
    • x6:
      • 1 << 21 = 0x20_0000
  • populate_entries()로 전달되는 값
    • @tbl(x0):
      • 매핑에 사용할 테이블 주소 =
    • @rtbl(x4):
      • 매핑할 메모리 시작 블럭 주소
      • init_pg_dir 주소를 2M 단위로 매핑하므로 2M 단위 절삭한 주소 = 0x4280_0000
    • @index(x2):
      • 매핑에 사용할 테이블 내 시작 인덱스 = 0x14
    • @eindex(x3):
      • 매핑에 사용할 테이블 내 끝 인덱스 = 0x14
    • @flags(x5): 매핑에 사용할 속성
      • SWAPPER_RW_MMUFLAGS
    • @inc(x6): 인덱스 변경 시 마다 증가할 사이즈
      • 0x20_0000
    • @tmp1(x7): 임시 레지스터

 


CPU 설정

__cpu_setup()

arch/arm64/mm/proc.S -1/3-

/*
 *      __cpu_setup
 *
 *      Initialise the processor for turning the MMU on.
 *
 * Input:
 *      x0 - actual number of VA bits (ignored unless VA_BITS > 48)
 * Output:
 *      Return in x0 the value of the SCTLR_EL1 register.
 */
        .pushsection ".idmap.text", "awx"
SYM_FUNC_START(__cpu_setup)
        tlbi    vmalle1                         // Invalidate local TLB
        dsb     nsh

        mov     x1, #3 << 20
        msr     cpacr_el1, x1                   // Enable FP/ASIMD
        mov     x1, #1 << 12                    // Reset mdscr_el1 and disable
        msr     mdscr_el1, x1                   // access to the DCC from EL0
        isb                                     // Unmask debug exceptions now,
        enable_dbg                              // since this is per-cpu
        reset_pmuserenr_el0 x1                  // Disable PMU access from EL0
        reset_amuserenr_el0 x1                  // Disable AMU access from EL0

        /*
         * Default values for VMSA control registers. These will be adjusted
         * below depending on detected CPU features.
         */
        mair    .req    x17
        tcr     .req    x16
        mov_q   mair, MAIR_EL1_SET
        mov_q   tcr, TCR_TxSZ(VA_BITS) | TCR_CACHE_FLAGS | TCR_SMP_FLAGS | \
                        TCR_TG_FLAGS | TCR_KASLR_FLAGS | TCR_ASID16 | \
                        TCR_TBI0 | TCR_A1 | TCR_KASAN_SW_FLAGS

MMU를 켜기 위해 현재 cpu를 초기화한다. (x0 레지스터에 vabits_actual 값이 지정되며, 단 VA_BITS > 48 이상에서만 유효하다)

  • 코드 라인 3~4에서 local cpu의 TLB 캐시를 모두 invalidate 하고 dsb 명령을 통해 TLB 캐시 invalidate 작업이 완료될 때까지 기다린다.
    • cold 부팅한 cpu의 TLB 캐시안에 있는 쓰레기 값을 모두 제거한 후 MMU를 켜야 한다.
  • 코드 라인 6~7에서 CPACR_EL1의 bit[20:21]에 0b11 값을 지정하여 FP/ASIMD 명령 사용 시 커널로 trap 되지 않도록 enable 한다.
  • 코드 라인 8~9에서 EL0 즉 사용자 공간에서 디버그 동작을 수행하도록 mdscr_el1 레지스터의 DCC(Debug Communication Channel) 비트를 on하여 커널로 trap 한다.
  • 코드 라인 10~11에서 isb 명령을 통해 명령 파이프를 비우고, DAIF 플래그 중 A 플래그를 unmask 하는 것으로 지금 이후 부터 곧바로 debug exceptions이 가능하도록 한다.
    • enable_dbg 매크로는 daifclr에 #8 값을 기록하여 A 플래그를 unmask 한다.
  • 코드 라인 12~13에서 유저 영역에서 PMU 및 AMU 레지스터의 사용을 금지시킨다.
  • 코드 라인 19~20에서 as 디렉티브 .req를 사용하여 x17 및 x16레지스터 이름을 mair 및 tcr을 사용할 수 있게 어셈블러에 지시 한다.
  • 코드 라인 21에서 MAIR(Memory Attribute Indirection Register) 레지스터에 기록할 메모리 페이지의 간접 매핑 속성들을  mail(x17)에 대입한다.
    • MAIR 레지스터는 최대 8개의 속성을 담아둘 수 있고, 현재 커널이 5개의 속성을 지정하여 사용한다.
    • 아직 부팅한 시스템이 MTE(Memory Tagging Extension) 기능을 가지고 있는지 확인이 안되었으므로 디폴트 설정에서는 mte 메모리 매핑 속성 자리에 normal 메모리 매핑 속성 값을 대신 사용한다.
  • 코드 라인 22에서 TCR(Translation Control Register) 레지스터에 기록할 디폴트 설정을 tcr(x16)에 대입한다.

 

arch/arm64/mm/proc.S -2/3-

#ifdef CONFIG_ARM64_MTE
        /*
         * Update MAIR_EL1, GCR_EL1 and TFSR*_EL1 if MTE is supported
         * (ID_AA64PFR1_EL1[11:8] > 1).
         */
        mrs     x10, ID_AA64PFR1_EL1
        ubfx    x10, x10, #ID_AA64PFR1_MTE_SHIFT, #4
        cmp     x10, #ID_AA64PFR1_MTE
        b.lt    1f

        /* Normal Tagged memory type at the corresponding MAIR index */
        mov     x10, #MAIR_ATTR_NORMAL_TAGGED
        bfi     mair, x10, #(8 *  MT_NORMAL_TAGGED), #8

        mov     x10, #KERNEL_GCR_EL1
        msr_s   SYS_GCR_EL1, x10

        /*
         * If GCR_EL1.RRND=1 is implemented the same way as RRND=0, then
         * RGSR_EL1.SEED must be non-zero for IRG to produce
         * pseudorandom numbers. As RGSR_EL1 is UNKNOWN out of reset, we
         * must initialize it.
         */
        mrs     x10, CNTVCT_EL0
        ands    x10, x10, #SYS_RGSR_EL1_SEED_MASK
        csinc   x10, x10, xzr, ne
        lsl     x10, x10, #SYS_RGSR_EL1_SEED_SHIFT
        msr_s   SYS_RGSR_EL1, x10

        /* clear any pending tag check faults in TFSR*_EL1 */
        msr_s   SYS_TFSR_EL1, xzr
        msr_s   SYS_TFSRE0_EL1, xzr

        /* set the TCR_EL1 bits */
        mov_q   x10, TCR_MTE_FLAGS
        orr     tcr, tcr, x10
1:
#endif
        tcr_clear_errata_bits tcr, x9, x5

ARMv8.2-MTE 기능이 탑재된 경우 유저 영역에서 할당한 메모리에 대해 color 태그를 부여하여 다른 영역에 액세스하는 것을 방지할 수 있다. 이러한 것을 커널이 지원하기 위해 관련된 레지스터들을 설정한다.

  • 코드 라인 6~9에서 AArch64 아키텍처에는 해당 cpu가 지원하는 기능들의 지원여부를 알아보기 위한 ID 레지스터들이 여러 개가 존재한다. 그 중 ID_AA64PFR1_EL1 레지스터의 MTE 필드를 조사하여 MTE(Memory Tagging Extension) 기능을 지원하지 않으면 1: 레이블로 이동한다.
  • 코드 라인  12~13에서 MTE 기능을 지원하는 것을 확인하였기 때문에 MTE 속성을 지원하도록  해당 속성위치의 값을 MAIR_ATTR_NORMAL_TAGGED 값으로 변경한다.
  • 코드 라인 15~16에서 GCR_EL1 (Tag Control Register) 레지스터에 디폴트 설정을 기록한다.
  • 코드 라인 24~28에서 의사 난수를 생성하기 위해 Virtual 카운터(CNTVCT) 레지스터의 하위 16비트 값을 읽어와서 RGSR_EL1 레지스터의 SEED 필드 값으로 설정한다. 단 SEED 값은 0을 사용하면 안되므로 이 때엔 SEED 값은 1을 사용한다.
    • ands(Bitwise AND Setting flags) 명령어
      • and 연산도 하고, tst 명령과 같이 그 결과에 따라 NZCV 플래그들을 설정한다.
    • csinc(conditional select increment)  명령어
      • ne(not equal)인 경우 x10값을 그대로 사용하고  그렇지 않은 경우 xzr + 1, 즉 1을 대입한다.
  • 코드 라인 31~32에서 EL1과 EL0에서 pending 되었을 수도 있는 tag check faults를 클리어하기 위해 TFSR_EL1(Tag Fault Status Register) 및 TFSRE0_EL1 레지스터를 0으로 초기화한다.
  • 코드 라인 35~36에서 tcr(x16)에 TBI1 및 TBID1 필드 값들을 1로 설정한다.
    • TBI1(Top Byte Ignore for TTBR1_EL1 region)
      • 1=EL1에서 주소 계산에 Top Byte Ignore를 사용한다.
      • 0=EL1에서 주소 계산에 Top Byte도 사용한다.
    • TBID1
      • 1=위의 TBI1 기능 사용시 데이터 주소에만 적용하도록 한다.
      • 0=위의 TBI1 기능 사용시 명령 주소와 데이터 주소 모두에 적용하도록 한다.
  • 코드 라인 39에서 대형 클러스터 시스템 구축에 상요된 Fujitsu-A64 칩을 위한 errata 코드이다.
    • Fujitsu-A64FX erratum E#010001: Undefined fault may occur wrongly

 

arch/arm64/mm/proc.S -3/3-

#ifdef CONFIG_ARM64_VA_BITS_52
        sub             x9, xzr, x0
        add             x9, x9, #64
        tcr_set_t1sz    tcr, x9
#else
        idmap_get_t0sz  x9
#endif
        tcr_set_t0sz    tcr, x9

        /*
         * Set the IPS bits in TCR_EL1.
         */
        tcr_compute_pa_size tcr, #TCR_IPS_SHIFT, x5, x6
#ifdef CONFIG_ARM64_HW_AFDBM
        /*
         * Enable hardware update of the Access Flags bit.
         * Hardware dirty bit management is enabled later,
         * via capabilities.
         */
        mrs     x9, ID_AA64MMFR1_EL1
        and     x9, x9, #0xf
        cbz     x9, 1f
        orr     tcr, tcr, #TCR_HA               // hardware Access flag update
1:
#endif  /* CONFIG_ARM64_HW_AFDBM */
        msr     mair_el1, mair
        msr     tcr_el1, tcr
        /*
         * Prepare SCTLR
         */
        mov_q   x0, INIT_SCTLR_EL1_MMU_ON
        ret                                     // return to head.S

        .unreq  mair
        .unreq  tcr
SYM_FUNC_END(__cpu_setup)
  • 코드 라인 1~8에서 idmap 페이지 테이블을 위해 TCR 레지스터의 t0sz와 t1sz 필드 값을 지정한다.
    • 이 값을 통해 idmap 페이지 테이블이 관리할 가상 주소 공간의 크기가 결정된다.
    • 52비트 커널을 사용하도록 빌드된 커널 이미지인 경우 x0 값에는 실제 va 비트 수가 담기므로 48 또는 52로 이 함수에 진입한다. 이 값을 TCR 레지스터의 T1SZ에 저장해야 하므로 64 – x0 한 값을 사용한다.
    • 그 외 52비트 커널을 사용한 경우가 아니라면 실제 커널의 끝 주소와 사용할 VA 공간과의 비트 차이만큼을 고려하여 TCR 레지스터의 T0SZ 필드에 대입한다.
  • 코드 라인 13에서 아키텍처가 지원하는 최대 PA 사이즈와 커널빌드시 지정한 최대 PA 사이즈 중 가장 작은 값으로 최대 PA를 최대한 줄여 최적화한 값을 사용하도록 @tcr의 @pos 위치에 있는 3비트의 IPS 필드 값을 갱신한다.
  • 코드 라인 20~23에서 ID_AA64MMFR1_EL1 레지스터의 하위 4비트 HAFDBS(Hardware updates to Access flag and Dirty state) 필드 값을 읽어 0이 아니면 HA 기능이 지원되므로 @tcr의 HA(Hardware Access flags update) 비트를 설정하여 El0와 EL1에 대한 stage 1 변환 내에서 HA 비트 갱신 기능이 가능하도록 한다.
  • 코드 라인 26~27에서 지금까지 준비한 mair(x17) 값을 MAIR_EL1 레지스터에 저장하고, tcr(x16) 레지스터 값도 TCR_EL1 레지스터에 저장한다.
  • 코드 라인 31~32에서 x0 레지스터에 mmu on을 하기 위한 SCTLR 레지스터용 초깃값을 대입하고, 복귀한다.
  • 코드 라인 34~35에서 “.req”로 정의한 mair 및 tcr 레지스터 이름을 취소한다.

 

tcr_set_t0sz 매크로

arch/arm64/include/asm/assembler.h

/*
 * tcr_set_t0sz - update TCR.T0SZ so that we can load the ID map
 */
.       .macro  tcr_set_t0sz, valreg, t0sz
        bfi     \valreg, \t0sz, #TCR_T0SZ_OFFSET, #TCR_TxSZ_WIDTH
        .endm

TCR 레지스터에 기록하기 위한 @valreg 값 중 T0SZ 필드에 @t0sz 값을 채운다.

  • @valreg에 @t0sz 값을 #TCR_T0SZ_OFFSET(0) 비트 위치부터 #TCR_TxSZ_WIDTH(6) 비트 수만큼 채운다.

 

tcr_set_t1sz 매크로

arch/arm64/include/asm/assembler.h

/*
 * tcr_set_t1sz - update TCR.T1SZ
 */
.       .macro  tcr_set_t1sz, valreg, t1sz
        bfi     \valreg, \t1sz, #TCR_T1SZ_OFFSET, #TCR_TxSZ_WIDTH
        .endm

TCR 레지스터에 기록하기 위한 @valreg 값 중 T1SZ 필드에 @t1sz 값을 채운다.

  • @valreg에 @t1sz 값을 #TCR_T1SZ_OFFSET(16) 비트 위치부터 #TCR_TxSZ_WIDTH(6) 비트 수만큼 채운다.

 

idmap_get_t0sz 매크로

arch/arm64/include/asm/assembler.h

/*
 * idmap_get_t0sz - get the T0SZ value needed to cover the ID map
 *
 * Calculate the maximum allowed value for TCR_EL1.T0SZ so that the
 * entire ID map region can be mapped. As T0SZ == (64 - #bits used),
 * this number conveniently equals the number of leading zeroes in
 * the physical address of _end.
 */
.       .macro  idmap_get_t0sz, reg
        adrp    \reg, _end
        orr     \reg, \reg, #(1 << VA_BITS_MIN) - 1
        clz     \reg, \reg
        .endm

@reg에 idmap을 커버할 T0SZ 값을 구해온다.

  • 커널 이미지의 끝 주소(_end)를 VA_BITS_MIN 만큼 쉬프트하고 쉬프트한 비트들은 1로 채운다. 그런 후 bit63부터 시작하여 처음 발견되는 0의 개수를 알아온다.
    • 예) _end = 0x1234_5678,VA_BITS_MIN=39
      • @reg = 0x1234_5000 | 0x0000_007f_ffff_ffff = 0x0000_007f_ffff_ffff
      • 최종 @reg = 25
    • 예) _end = 0x0000_1234_5678_9ab0, VA_BITS_MIN=39
      • @reg = 0x0000_1234_5678_9000 | 0x0000_007f_ffff_ffff = 0x0000_127f_ffff_ffff
      • 죄종 @reg = 19
  • TCR_EL1.T0SZ에 사용할 값은 64 – 매핑에 사용할 VA 비트 값이다.

 

MAIR_EL1_SET 매크로 상수

arch/arm64/mm/proc.S

/*
 * Default MAIR_EL1. MT_NORMAL_TAGGED is initially mapped as Normal memory and
 * changed during __cpu_setup to Normal Tagged if the system supports MTE.
 */
#define MAIR_EL1_SET                                                    \
        (MAIR_ATTRIDX(MAIR_ATTR_DEVICE_nGnRnE, MT_DEVICE_nGnRnE) |      \
         MAIR_ATTRIDX(MAIR_ATTR_DEVICE_nGnRE, MT_DEVICE_nGnRE) |        \
         MAIR_ATTRIDX(MAIR_ATTR_NORMAL_NC, MT_NORMAL_NC) |              \
         MAIR_ATTRIDX(MAIR_ATTR_NORMAL, MT_NORMAL) |                    \
         MAIR_ATTRIDX(MAIR_ATTR_NORMAL, MT_NORMAL_TAGGED))

MAIR_EL1 레지스터에는 커널이 사용할 5가지 디폴트 매핑 설정이 지정된다. 단 MT_NORMAL_TAGGED (1) 속성 자리에는 tagged 메모리 매핑 속성인 MAIR_ATTR_NORMAL_TAGGED 대신 일반 메모리 매핑 속성인 MAIR_ATTR_NORMAL 값이 사용되고 있으므로 기본 설정만으로는 tagged 메모리 속성을 지원하지 않는다.

 

tcr_compute_pa_size 매크로

arch/arm64/include/asm/assembler.h

/*
 * tcr_compute_pa_size - set TCR.(I)PS to the highest supported
 * ID_AA64MMFR0_EL1.PARange value
 *
 *      tcr:            register with the TCR_ELx value to be updated
 *      pos:            IPS or PS bitfield position
 *      tmp{0,1}:       temporary registers
 */
.       .macro  tcr_compute_pa_size, tcr, pos, tmp0, tmp1
        mrs     \tmp0, ID_AA64MMFR0_EL1
        // Narrow PARange to fit the PS field in TCR_ELx
        ubfx    \tmp0, \tmp0, #ID_AA64MMFR0_PARANGE_SHIFT, #3
        mov     \tmp1, #ID_AA64MMFR0_PARANGE_MAX
        cmp     \tmp0, \tmp1
        csel    \tmp0, \tmp1, \tmp0, hi
        bfi     \tcr, \tmp0, \pos, #3
        .endm

아키텍처가 지원하는 최대 PA 사이즈와 커널빌드시 지정한 최대 PA 사이즈 중 가장 작은 값으로 최대 PA를 최대한 줄여 최적화한 값을 사용하도록 @tcr의 @pos 위치에 있는 3비트의 IPS 필드 값을 갱신한다.

  • 코드 라인 2~4에서 AA64MMFR0_EL1 레지스터의 PARANGE 필드값 3비트를 @tmp0에 읽어온다.
  • 코드 라인 5~7에서 이 값을 최대 PA 비트(ID_AA64MMFR0_PARANGE_MAX=6 or 5)가 담긴 @tmp1과 비교하여 두 값중 더 작은 값을 @tmp0로 사용한다.
  • 코드 라인 8에서 산출된 가장 작은 PA 비트 값에 해당하는 @tcr 레지스터의 @pos 위치에 3비트를 기록한다.

 

TCR 레지스터의 IPS(Intermediate Physical address Size)  값

최대 물리 주소 표현에 사용하는 비트 수

  • 0=32bits, 4GB
  • 1=36bits, 64GB
  • 2=40bits, 1TB
  • 3=42bits, 4TB
  • 4=44bits, 16TB
  • 5=48bits, 256TB
  • 6=52bits, 4PB

 


부트 CPU 스위치

 

다음 그림은 head.S가 진행되는 흐름을 보여준다. __primary_switch: 이후 __primary_switched:로의 전환이 커널 v6.0부터 일부 바뀌었다.

 

MMU 스위치 전

__primary_switch:

arch/arm64/kernel/head.S

SYM_FUNC_START_LOCAL(__primary_switch)
        adrp    x1, reserved_pg_dir
        adrp    x2, init_idmap_pg_dir
        bl      __enable_mmu
#ifdef CONFIG_RELOCATABLE
        adrp    x23, KERNEL_START
        and     x23, x23, MIN_KIMG_ALIGN - 1
#ifdef CONFIG_RANDOMIZE_BASE
        mov     x0, x22
        adrp    x1, init_pg_end
        mov     sp, x1
        mov     x29, xzr
        bl      __pi_kaslr_early_init
        and     x24, x0, #SZ_2M - 1             // capture memstart offset seed
        bic     x0, x0, #SZ_2M - 1
        orr     x23, x23, x0                    // record kernel offset
#endif
#endif
        bl      clear_page_tables
        bl      create_kernel_mapping

        adrp    x1, init_pg_dir
        load_ttbr1 x1, x1, x2
#ifdef CONFIG_RELOCATABLE
        bl      __relocate_kernel
#endif
        ldr     x8, =__primary_switched
        adrp    x0, KERNEL_START                // __pa(KERNEL_START)
        br      x8
SYM_FUNC_END(__primary_switch)

MMU를 활성화하고 [커널 랜덤 위치를 결정한 후] 초기 커널용 페이지 테이블을 매핑하고, [커널 심볼 재배치]를 수행한다. 그런 후 __primary_switched로 점프한다. []안은 커널 옵션에 따라 수행한다.

  • 코드 라인 2~4에서 init_idmap_pg_dir 페이지 테이블을 사용하여 mmu를 활성화한다.
  • 코드 라인 5~7에서 커널 심볼 재배치 설정이 있는 경우 x23에 커널이 시작되는 시작 주소(MMU는 켜있지만 여전히 VA=PA)를 대입하고, 2M 단위로 내림 정렬한다.
  • 코드 라인 8~13에서 KASLR(커널 랜덤 위치) 옵션이 지정된 경우 커널의 랜덤 위치를 결정하는데 사용할 랜덤 seed 값을 디바이스 트리(DTB)에서 /chosen 노드의 kaslr-seed= 속성에 지정된 값을 읽어온다. 그 후 이를 기반으로 랜덤값을 산출하고 이 랜덤값으로 커널이 위치할 랜덤 주소 offset 값을 알아온다.
    • x0에 DTB 주소를 담고, x1에는 init_pg_dir의 끝 주소를 담는다. 이 주소는 sp 에도 저장해두고, x29에 0을 대입한다.
    • 예) kaslr-seed = <0xfeedbeef 0xc0def00d>;
    • 이후 C 루틴으로 동작하는 최초 커널 함수인 start_kernel()부터 랜덤 가상 주소에서 실행된다.
  • 코드 라인 14~16에서 커널이 위치할 랜덤 주소 offset 값을 알아왔으면 이 주소0 값을 2M 단위로 내림정렬한 후 x24에 대입한다. x23에 커널 랜덤 시작 위치를 대입하기 위해 __pi_kaslr_early_init에서 산출한 랜덤 offset 주소(x0)를 기존 커널 시작 주소(x23)에 더한다.
  • 코드 라인 19에서 초기 커널 페이지 테이블 용도로 사용할 init_pg_dir을 모두 0으로 클리어한다.
  • 코드 라인 20에서 초기 커널 페이지 테이블 init_pg_dir에 전체 커널 이미지(_text ~ _end)를 랜덤 주소부터 RW 속성으 매핑한다.
  • 코드 라인 22~23에서 init_pg_dir 페이지 테이블을 ttbr1에 지정한다.
  • 코드 라인 24~26에서 커널 심볼 재배치 설정이 있는 경우 커널 심볼들을 재배치한다.
    • 재배치 정보를 담고 있는 .rela.dyn 섹션에 위치한 엔트리들을 옮긴다.
  • 코드 라인 27~29에서x0에 커널 이미지의 물리 주소 위치(offset 구간 포함)가 담긴 주소를 담고 __primary_switched로 점프한다.
    • __primary_switched() 함수로 점프할 때부터 init_pg_dir 페이지 테이블을 사용한다.

 

MMU 활성화

__enable_mmu()

arch/arm64/kernel/head.S

/*
 * Enable the MMU.
 *
 *  x0  = SCTLR_EL1 value for turning on the MMU.
 *  x1  = TTBR1_EL1 value
 *  x2  = ID map root table address
 *
 * Returns to the caller via x30/lr. This requires the caller to be covered
 * by the .idmap.text section.
 *
 * Checks if the selected granule size is supported by the CPU.
 * If it isn't, park the CPU
 */
SYM_FUNC_START(__enable_mmu)
        mrs     x3, ID_AA64MMFR0_EL1
        ubfx    x3, x3, #ID_AA64MMFR0_TGRAN_SHIFT, 4
        cmp     x3, #ID_AA64MMFR0_TGRAN_SUPPORTED_MIN
        b.lt    __no_granule_support
        cmp     x3, #ID_AA64MMFR0_TGRAN_SUPPORTED_MAX
        b.gt    __no_granule_support
        phys_to_ttbr x2, x2
        msr     ttbr0_el1, x2                   // load TTBR0
        load_ttbr1 x1, x1, x3

        set_sctlr_el1   x0

        ret
SYM_FUNC_END(__enable_mmu)

TTBR0 레지스터가 x2(init_idmap or idmap용) 테이블, 그리고 TTBR1 레지스터가 x1(reserved or swapper용) 테이블을 가리키게 하고 MMU를 켠다.

  • 이 함수가 각 cpu 마다 호출될 때 동작은 다음과 같다.
    • primary cpu에서 이 함수 호출
      • x1=reserved_pg_dir, x2=reserved_pg_dir 이 담긴채로 진입
      • MMU를 enable 하는 순간 PC(Program Counter)는 주소가 낮은 물리 주소에서 동작하므로 init_idmap_pg_dir 페이지 테이블을 기반으로 먼저 동작한다.
    • seconcary cpu에서 이 함수 호출
      • x1=swapper_pg_dir, x2=idmap_pg_dir이 담긴채로 진입
      • MMU를 enable 하는 순간 PC(Program Counter)는 주소가 낮은 물리 주소에서 동작하므로 idmap_pg_dir을 기반으로 동작한다.
  • 코드 라인2-7에서 MMFR0_EL1 (Memory Model Feature Register 0 Register – EL1)의 TGRAN 필드값을 읽어와서 커널이 설정한 페이지 단위(4K, 16K, 64K)의 각각의 범위가 지원하는지 확인하고, 지원하지 않는 경우 __no_granule_support 레이블로 이동한다.
  • 코드 라인 8~9에서 TTBR0_EL1 레지스터에 x2 테이블(init_idmap_pg_dir or idmap_pg_dir)을 지정하여 사용할 수 있도록 설정한다.
  • 코드 라인 10에서 TTBR1_EL1 레지스터에 x1 테이블(reserved_pg_dir or swapper_pg_dir)을 지정하여 사용할 수 있도록 설정한다.
    • reserved_pg_dir 는 블랭크 테이블이므로 MMU가 켜진 이후부터 커널 공간의 주소를 액세스할 수 없게 한다.
  • 코드 라인 12에서 드디어 SCTLR_EL1 레지스터를 사용하여 MMU를 활성화한다.

 

phys_to_ttbr 매크로

arch/arm64/include/asm/assembler.h

/*
 * Arrange a physical address in a TTBR register, taking care of 52-bit
 * addresses.
 *
 *      phys:   physical address, preserved
 *      ttbr:   returns the TTBR value
 */
        .macro  phys_to_ttbr, ttbr, phys
#ifdef CONFIG_ARM64_PA_BITS_52
        orr     \ttbr, \phys, \phys, lsr #46
        and     \ttbr, \ttbr, #TTBR_BADDR_MASK_52
#else
        mov     \ttbr, \phys
#endif
        .endm

테이블의 물리주소 @phys를 TTBR 레지스터 포맷으로 변경한다.

  • 52bit VA를 지원하는 커널에서는 주소의 일부 비트들을 이동시켜야 한다.

 

load_ttbr1 매크로

arch/arm64/include/asm/assembler.h

/*
 * load_ttbr1 - install @pgtbl as a TTBR1 page table
 * pgtbl preserved
 * tmp1/tmp2 clobbered, either may overlap with pgtbl
 */
.       .macro          load_ttbr1, pgtbl, tmp1, tmp2
        phys_to_ttbr    \tmp1, \pgtbl
        offset_ttbr1    \tmp1, \tmp2
        msr             ttbr1_el1, \tmp1
        isb
        .endm

TTBR1 레지스터가 페이지 테이블 @pgtbl 의 주소를 가리키도록 저장한다. (idmap에서 pgd 테이블 엔트리가 확장되어 사용되는 경우 이를 반영하는 주소로 지정된다)

  • 코드 라인 2에서 페이지 테이블 주소 @pgtb을 먼저 임시 레지스터 @tmp1 에 대입한다.
  • 코드 라인 3에서 다시 offset 적용이 필요한 상황에서 offset을 적용한 주소를 @tmp1으로 재산출한다. (@tmp2 는 임시 레지스터로 활용한다)
  • 코드 라인 4에서 TTBR1_EL1 레지스터에 @tmp1을 저장한다.
  • 코드 라인 5에서 명령 파이프를 비운다.

 

offset_ttbr1 매크로

arch/arm64/include/asm/assembler.h

/*
 * Offset ttbr1 to allow for 48-bit kernel VAs set with 52-bit PTRS_PER_PGD.
 * orr is used as it can cover the immediate value (and is idempotent).
 * In future this may be nop'ed out when dealing with 52-bit kernel VAs.
 *      ttbr: Value of ttbr to set, modified.
 */
        .macro  offset_ttbr1, ttbr, tmp
#ifdef CONFIG_ARM64_VA_BITS_52
        mrs_s   \tmp, SYS_ID_AA64MMFR2_EL1
        and     \tmp, \tmp, #(0xf << ID_AA64MMFR2_LVA_SHIFT)
        cbnz    \tmp, .Lskipoffs_\@
        orr     \ttbr, \ttbr, #TTBR1_BADDR_4852_OFFSET
.Lskipoffs_\@ :
#endif
        .endm

커널 이미지를 VA=52로 사용하는데 LVA(VA=52 support)가 지원되지 않는 시스템의 경우 어쩔 수 없이 VA=48로 운영해야하는데 PGD 테이블 위치를 offset(0x1E00) 만큼 더한 주소를 적용시킨다.

  • 이렇게 더한 주소를 사용하는 경우 리눅스의 pgd_index()등의 함수가 유저 페이지 테이블이든 커널 페이지 테이블이든 1개의 API로 통일하여 사용할 수 있는 잇점이 있다.
  • ttbr offset

 

다음 그림은 64K 페이지, 3 단계 페이지 테이블을 사용하는 시스템에서 VA=48 및 VA=52 구성인 경우 각 페이지 테이블에 사용하는 엔트리 수를 보여준다.

  • VA_BITS=48
    • PGDIR_SHIFT=42
    • PTRS_PER_PGD=64
  • VA_BITS=52
    • PGDIR_SHIFT=42
    • PTRS_PER_PGD=1024

 

다음 그림은 64K, 3 단계 페이지 테이블을 사용하는 시스템의 3 가지 시스템 구성에서 pgd_index()를 공통적으로 사용할 수 있도록 일부 구성에서 offset이 적용된 모습을 보여준다.

 

초기 커널 페이지 테이블 생성

clear_page_tables()

arch/arm64/kernel/head.S

SYM_FUNC_START_LOCAL(clear_page_tables)
        /*
         * Clear the init page tables.
         */
        adrp    x0, init_pg_dir
        adrp    x1, init_pg_end
        sub     x2, x1, x0
        mov     x1, xzr
        b       __pi_memset                     // tail call
SYM_FUNC_END(clear_page_tables)

init_pg_dir 페이지테이블 내용을 0 값으로 클리어한다.

 

create_kernel_mapping()

arch/arm64/kernel/head.S

SYM_FUNC_START_LOCAL(create_kernel_mapping)
        adrp    x0, init_pg_dir
        mov_q   x5, KIMAGE_VADDR                // compile time __va(_text)
#ifdef CONFIG_RELOCATABLE
        add     x5, x5, x23                     // add KASLR displacement
#endif
 
        adrp    x6, _end                        // runtime __pa(_end)
        adrp    x3, _text                       // runtime __pa(_text)
        sub     x6, x6, x3                      // _end - _text
        add     x6, x6, x5                      // runtime __va(_end)
        mov     x7, SWAPPER_RW_MMUFLAGS

        map_memory x0, x1, x5, x6, x7, x3, (VA_BITS - PGDIR_SHIFT), x10, x11, x12, x13, x14

        dsb     ishst                           // sync with page table walker
        ret
SYM_FUNC_END(create_kernel_mapping)

1init_pg_dir 테이블의 랜덤 주소 적용된 커널 가상 주소(__va(texst) + random offset)에 해당하는 엔트리들에 커널 이미지의 물리 주소 _text ~ _end 까지를 R/W 속성으로 매핑한다.

 

다음 그림은 KASLR 옵션에 따라 커널 이미지가 위치할 랜덤 가상 주소가 결정된 후 init_pg_dir 페이지 테이블에 R/W 속성으로 매핑하는 모습을 보여준다.

 


재배치 엔트리 리로케이션

__relocate_kernel()

arch/arm64/kernel/head.S -1/2-

#ifdef CONFIG_RELOCATABLE
SYM_FUNC_START_LOCAL(__relocate_kernel)
        /*
         * Iterate over each entry in the relocation table, and apply the
         * relocations in place.
         */
        adr_l   x9, __rela_start
        adr_l   x10, __rela_end
        mov_q   x11, KIMAGE_VADDR               // default virtual offset
        add     x11, x11, x23                   // actual virtual offset

0:      cmp     x9, x10
        b.hs    1f
        ldp     x12, x13, [x9], #24
        ldr     x14, [x9, #-8]
        cmp     w13, #R_AARCH64_RELATIVE
        b.ne    0b
        add     x14, x14, x23                   // relocate
        str     x14, [x12, x23]
        b       0b

커널 코드의 재배치가 일어나는 경우 재배치 정보를 담고 있는 .rela.dyn 섹션에 위치한 엔트리 정보들을 사용해 엔트리가 상대 주소(#R_AARCH64_RELATIVE)를 사용하는 타입인 경우 이들이 가리키는 주소의 값을 변경된 offset 만큼 추가하여 변경한다.

  • 코드 라인 7~8에서 x9 레지스터에 __rela_start(.relr.dyn 섹션) 주소와 x10 레지스터에 __rela_end 주소를 대입한다.
  • 코드 라인 9~10에서 컴파일 타임에 생성된 커널 이미지의 시작 가상 주소를 x11에 대입하고, virtual offset을 더한 실제 커널 가상 주소를 x11에 대입한다.
  • 코드 라인 12~13에서 0: 레이블이다. 루프 비교 조건을 수행한다. x9 레지스터 값이 끝 주소 이상이면 리로케이션이 모두 완료되었으므로 1: 레이블로 전진하여 함수를 빠져나간다.
  • 코드 라인 14~15에서 24바이트로 구성된 다이나믹 리로케이션 엔트리를 읽어온다.
    • x9주소의 16바이트를 Offset(x12)와 인포타입(x13) 레지스터로 읽고, 다음 엔트리를 위해 x9 주소에 #24를 더 한다. 그리고 x9  – 8 주소 위치의 값을 Addend 값(x14) 레지스터로 읽는다.
    • ldp x12, x13, [x9], #24 의 경우 post-indexed 어드레싱을 사용했다.
    • ldr x14, [x9, #-8]의 경우 offset 어드레싱을 사용했다.
  • 코드 라인 16~17에서 인포 타입(w13) 레지스터의 값이 #R_AARCH64_RELATIVE가 아닌 경우 skip 하고 다시 0 레이블로 반복한다.
  • 코드 라인 18~20에서 다이나믹 리로케이션 엔트리의 Offset(x12) + relocation offset(x23)가 가리키는 주소에 엔트리의 Addend 값(x14) + relocation offset(x23) 값으로 갱신하고 0레이블로 이동하여 반복한다.

 

다음 그림은 하나의 R_AARCH64_RELATIVE 엔트리의 offset + KASLR offset이 가리키는 주소의 8바이트 값을 addend 값과 KASLR offset이 더한 값으로 교체하는 과정을 보여준다.

 

arch/arm64/kernel/head.S -2/2-

1:
#ifdef CONFIG_RELR
        /*
         * Apply RELR relocations.
         *
         * RELR is a compressed format for storing relative relocations. The
         * encoded sequence of entries looks like:
         * [ AAAAAAAA BBBBBBB1 BBBBBBB1 ... AAAAAAAA BBBBBB1 ... ]
         *
         * i.e. start with an address, followed by any number of bitmaps. The
         * address entry encodes 1 relocation. The subsequent bitmap entries
         * encode up to 63 relocations each, at subsequent offsets following
         * the last address entry.
         *
         * The bitmap entries must have 1 in the least significant bit. The
         * assumption here is that an address cannot have 1 in lsb. Odd
         * addresses are not supported. Any odd addresses are stored in the RELA
         * section, which is handled above.
         *
         * Excluding the least significant bit in the bitmap, each non-zero
         * bit in the bitmap represents a relocation to be applied to
         * a corresponding machine word that follows the base address
         * word. The second least significant bit represents the machine
         * word immediately following the initial address, and each bit
         * that follows represents the next word, in linear order. As such,
         * a single bitmap can encode up to 63 relocations in a 64-bit object.
         *
         * In this implementation we store the address of the next RELR table
         * entry in x9, the address being relocated by the current address or
         * bitmap entry in x13 and the address being relocated by the current
         * bit in x14.
         */
        adr_l   x9, __relr_start
        adr_l   x10, __relr_end

2:      cmp     x9, x10
        b.hs    7f
        ldr     x11, [x9], #8
        tbnz    x11, #0, 3f                     // branch to handle bitmaps
        add     x13, x11, x23
        ldr     x12, [x13]                      // relocate address entry
        add     x12, x12, x23
        str     x12, [x13], #8                  // adjust to start of bitmap
        b       2b

3:      mov     x14, x13
4:      lsr     x11, x11, #1
        cbz     x11, 6f
        tbz     x11, #0, 5f                     // skip bit if not set
        ldr     x12, [x14]                      // relocate bit
        add     x12, x12, x23
        str     x12, [x14]

5:      add     x14, x14, #8                    // move to next bit's address
        b       4b

6:      /*
         * Move to the next bitmap's address. 8 is the word size, and 63 is the
         * number of significant bits in a bitmap entry.
         */
        add     x13, x13, #(8 * 63)
        b       2b

7:
#endif
        ret

SYM_FUNC_END(__relocate_kernel)
#endif
  • CONFIG_RELR 커널 옵션을 사용하는 경우 커널 이미지의 크기를 줄일 수 있는 RELR 재배치 기능을 지원한다.

 

.rela.dyn 섹션

먼저 .rela.dyn 섹션에서 offset 값을 알아본다.

$ readelf -S vmlinux
Section Headers:
  [Nr] Name              Type             Address           Offset
       Size              EntSize          Flags  Link  Info  Align
  [ 0]                   NULL             0000000000000000  00000000
       0000000000000000  0000000000000000           0     0     0
  [ 1] .head.text        PROGBITS         ffff000010080000  00010000
       0000000000001000  0000000000000000  AX       0     0     4096
  [ 2] .text             PROGBITS         ffff000010081000  00011000
       0000000000a9b7e8  0000000000000008  AX       0     0     2048
(...생략...)
  [16] .init.data        PROGBITS         ffff000011136000  010c6000
       000000000008e5f0  0000000000000000  WA       0     0     256
  [17] .data..percpu     PROGBITS         ffff0000111c5000  01155000
       000000000000db18  0000000000000000  WA       0     0     64
  [18] .rela.dyn         RELA             ffff0000111d2b18  01162b18
       00000000003231a8  0000000000000018   A       0     0     8
  [19] .data             PROGBITS         ffff000011500000  01490000
       000000000017e240  0000000000000000  WA       0     0     4096
(...생략...) 

 

다음과 같이 .relay.dyn 섹션에 위치한 137,063개의 리로케이션 엔트리들을 볼 수 있다.

  • 다이나믹 리로케이션 엔트리당 24 바이트이며, 다음과 같이 구성되어 있다.
    • Offset 주소(8 바이트) + Info(8 바이트) + Addend 값(8 바이트)
      • Info는 심볼 인덱스(4 바이트) + 타입(4 바이트)으로 구성되어 있다.
$ readelf -r vmlinux
Relocation section '.rela.dyn' at offset 0x1162b18 contains 137063 entries:
  Offset          Info           Type           Sym. Value    Sym. Name + Addend
ffff0000100aed68  000000000403 R_AARCH64_RELATIV                    -ffffeff4e7f4
ffff0000100aed70  000000000403 R_AARCH64_RELATIV                    -ffffeff4e7dc
ffff0000100fbbc8  000000000403 R_AARCH64_RELATIV                    -ffffeef9da40
ffff00001015e658  000000000403 R_AARCH64_RELATIV                    -ffffef24c520
ffff00001015e660  000000000403 R_AARCH64_RELATIV                    -ffffef107fa8
(...생략...)

 

위의 엔트리들의 실제 덤프 값을 확인해본다.

  • 엔트리 하나 당 24 바이트임을 알 수 있다.
$ xxd -s 0x01162b18 -l 0x78 -g 8 -e vmlinux
01162b18: ffff0000100aed68 0000000000000403  h...............
01162b28: ffff0000100b180c ffff0000100aed70  ........p.......
01162b38: 0000000000000403 ffff0000100b1824  ........$.......
01162b48: ffff0000100fbbc8 0000000000000403  ................
01162b58: ffff0000110625c0 ffff00001015e658  .%......X.......
01162b68: 0000000000000403 ffff000010db3ae0  .........:......
01162b78: ffff00001015e660 0000000000000403  `...............
01162b88: ffff000010ef8058                   X.......

 


부트 CPU MMU 스위치 후

__primary_switched:

arch/arm64/kernel/head.S

/*
 * The following fragment of code is executed with the MMU enabled.
 *
 *   x0 = __pa(KERNEL_START)
 */
SYM_FUNC_START_LOCAL(__primary_switched)
        adr_l   x4, init_task
        init_cpu_task x4, x5, x6

        adr_l   x8, vectors                     // load VBAR_EL1 with virtual
        msr     vbar_el1, x8                    // vector table address
        isb

        stp     x29, x30, [sp, #-16]!
        mov     x29, sp

        str_l   x21, __fdt_pointer, x5          // Save FDT pointer

        ldr_l   x4, kimage_vaddr                // Save the offset between
        sub     x4, x4, x0                      // the kernel virtual and
        str_l   x4, kimage_voffset, x5          // physical mappings

        mov     x0, x20
        bl      set_cpu_boot_mode_flag

        // Clear BSS
        adr_l   x0, __bss_start
        mov     x1, xzr
        adr_l   x2, __bss_stop
        sub     x2, x2, x0
        bl      __pi_memset
        dsb     ishst                           // Make zero page visible to PTW

#if VA_BITS > 48
        adr_l   x8, vabits_actual               // Set this early so KASAN early init
        str     x25, [x8]                       // ... observes the correct value
        dc      civac, x8                       // Make visible to booting secondaries
#endif

#ifdef CONFIG_RANDOMIZE_BASE
        adrp    x5, memstart_offset_seed        // Save KASLR linear map seed
        strh    w24, [x5, :lo12:memstart_offset_seed]
#endif
#if defined(CONFIG_KASAN_GENERIC) || defined(CONFIG_KASAN_SW_TAGS)
        bl      kasan_early_init
#endif
        mov     x0, x21                         // pass FDT address in x0
        bl      early_fdt_map                   // Try mapping the FDT early
        mov     x0, x20                         // pass the full boot status
        bl      init_feature_override           // Parse cpu feature overrides
        mov     x0, x20
        bl      finalise_el2                    // Prefer VHE if possible
        ldp     x29, x30, [sp], #16
        bl      start_kernel
        ASM_BUG()
SYM_FUNC_END(__primary_switched)

MMU를 켠 후 동작하는 코드로 커널용 스택과 벡터 포인터를 지정하고 BSS 영역을 클리어한 후 start_kernel() 함수로 점프한다.

  • 코드 라인 2~3에서 최초 커널용 태스크인 init_task의 주소를 SP_EL0에 저장하고 , 초기 커널 스택 위치를 SP 레지스터에 대입하는 등 초기화를 수행한다.
    • sp_el0는 유저 공간으로 context switch 된 후 유저용 스택 위치를 가리키는 용도로 사용된다.
    • 그러나 커널(el1)에서는 사용하지 않는 스크래치 레지스터 용도일 뿐이므로 이를 활용하여 thread_info를 가리키는 레지스터로 사용한다.
  • 코드 라인 5~7에서 vbar_el1 레지스터에 vector 위치를 지정한 후 isb를 수행하여 이후 실행되는 명령이 isb 전에 변경한 컨텍스트가 적용되어 동작하도록 한다.
  • 코드 라인 9~10에서 x29 및 x30(lr) 레지스터 내용을 스택에 보관하고, x29 레지스터를 sp 레지스터에 대입한다.
    • stp x29, x30, [sp, #-16]! 는 pre-index 방식의 어드레싱 모드를 사용하고 있다. 따라서 sp += -16을 먼저 연산한 후 sp가 가리키는 주소에 x29와 x30 레지스터를 저장한다.
  • 코드 라인 12에서 fdt 시작 물리 주소를 담고 있는 x21 레지스터를 변수 __fdt_pointer에 저장한다.
  • 코드 라인 14~16에서 커널 시작 가상 주소(x4)에서 커널 시작 물리 주소(x0=__PHYS_OFFSET)를 뺀 offset을 변수 kimage_voffset에 저장한다.
    • x0에는 이 루틴이 호출되기 전에 __PHYS_OFFSET이 담겨 호출된다.
    • 예) kimage_vaddr=0xffff_0000_1000_0000, __PHYS_OFFSET=0x4000_0000
      • kimage_voffset=0xfffe_ffff_d000_0000
  • 코드 라인 18~19에서 커널 부팅 시 boot 모드 값(EL1 또는 EL2 여부)를 저장한다.
  • 코드 라인 22~27에서 BSS 영역을 0으로 모두 클리어한 후 기록된 0 값이 다른 inner-share 영역의 cpu들이 볼 수 있도록 반영한다.
  • 코드 라인 29~33에서 VA=52를 사용하는 커널인 경우 vabits_actual 값을 읽어 x25에 대입한다. MMU가 on된 이후 vabits_actual에 저장된 값이 캐시 라인에 있을 수 있으므로 이 값이 다른 cpu에서도 메모리를 통해 정확히 읽힐 수 있도록 캐시라인을 clean & invalidate 한다.
  • 코드 라인 35~38에서 CONFIG_RANDOMIZE_BASE 커널 옵션을 사용한 경우 memstart_offset_seed 변수에 저장된 값을 w24에 대입한다.
  • 코드 라인 39~41에서 KASAN 초기화를 수행한다. (KASAN 디버그 생략)
  • 코드 라인 42~43에서 FDT 물리 주소를 인자로 전달하여 FDT를 fixmap 가상 공간을 이용하여 매핑한다.
  • 코드 라인 44~45에서 init_feature_override() 함수를 호출하여 몇 개 레지스터들(mmfr1, &pfr0, …)의 overide 영역의 캐시를 PoC 영역까지 clean & invalidate 한다.
  • 코드 라인 46~47에서 EL2 모드의 사용을 위해 마지막 준비를 수행한다. (생략)
  • 코드 라인 48~49에서 조금 전에 백업해 두었던 x29와 x30(lr) 레지스터를 원위치시키고, start_kernel() 함수로 점프한다.

 

init_cpu_task 매크로

arch/arm64/kernel/head.S

.       /*
         * Initialize CPU registers with task-specific and cpu-specific context.
         *
         * Create a final frame record at task_pt_regs(current)->stackframe, so
         * that the unwinder can identify the final frame record of any task by
         * its location in the task stack. We reserve the entire pt_regs space
         * for consistency with user tasks and kthreads.
         */
.       .macro  init_cpu_task tsk, tmp1, tmp2
        msr     sp_el0, \tsk

        ldr     \tmp1, [\tsk, #TSK_STACK]
        add     sp, \tmp1, #THREAD_SIZE
        sub     sp, sp, #PT_REGS_SIZE

        stp     xzr, xzr, [sp, #S_STACKFRAME]
        add     x29, sp, #S_STACKFRAME

        scs_load \tsk

        adr_l   \tmp1, __per_cpu_offset
        ldr     w\tmp2, [\tsk, #TSK_TI_CPU]
        ldr     \tmp1, [\tmp1, \tmp2, lsl #3]
        set_this_cpu_offset \tmp1
        .endm

태스크 @tsk의 주소를 SP_EL0에 저장하고 , @tsk->stack 위치를 SP 레지스터에 대입하는 등 초기화를 수행한다.

커널 태스크와 관련 스택들을 초기화한다.

  • 코드 라인 2에서 MMU를 enable 한 후에는 먼저 유저용 스택 레지스터인 sp_el0에 active task인 @tsk를 대입해야 한다.
  • 코드 라인 4~5에서 또한 현재 스택(sp) 레지스터에는 active task @tsk의 stack을 대입해야 한다.
    • @tsk->stack이 가리키는 스택 주소에서 + THREAD_SIZE(스택 사이즈)를 가리켜 빈 스택을 가리키게 한다.
    • arm/arm64 아키텍처는 top-down 방식의 스택을 사용하므로 스택의 가장 높은 위치로 스택포인터를 지정한다.
    • init_task.stack이 가리키는 주소는 &init_stack 이다.
  • 코드 라인 6에서 스택에 PT_REGS_SIZE(pt_regs 구조체 크기)를 확보한다.
  • 코드 라인 8~9에서 확보한 pt_regs->stackframe[2]을 0으로 클리어하고, x29 레지스터에 이 위치를 저장해 둔다.
  • 코드 라인 11에서 CONFIG_SHADOW_CALL_STACK 커널 옵션을 사용하는 경우에만 @tsk->thread_info.scs_sp에 저장된 값을 임시로 x18 레지스터에 로드해둔다.
    • CONFIG_SHADOW_CALL_STACK
      • Shadow Call Stack은 스택을 사본으로 복제한 후 Stack buffer overflow 공격을 막기 위해 사용된다.
        • 예) 사본에 저장된 리턴 주소(return address)가 변경되어 오염된 경우 이를 막기 위해 사용된다.
        • 참고: Shadow Stack | WIKIPEDIA
  • 코드 라인 13~16에서 현재 cpu에 대한 per-cpu offset을 tpidr 레지스터에 저장한다.
    • boot cpu의 offset은 0이다.

 

set_this_cpu_offset 매크로

arch/arm64/include/asm/assembler.h

        .macro  set_this_cpu_offset, src
alternative_if_not ARM64_HAS_VIRT_HOST_EXTN
        msr     tpidr_el1, \src
alternative_else
        msr     tpidr_el2, \src
alternative_endif
        .endm

TPIDR_EL1 레지스터에 @src를 저장한ㄷ. 이 레지스터는 리눅스 커널에서 per-cpu 자료구조의 cpu별 offset을 담아 사용한다.

  • 코드 라인 2~3에서 VHE 기능이 없는 경우 tpidr_el1 레지스터에 @src를 저장한다.
  • 코드 라인 4~6에서 VHE 기능이 있는 경우 코드에서 tpidr_el2에 액세스하여도 실제로는 tpidr_el1에 액세스한다.

 

__primary_switch에서 __primary_switched로 전환

다음 3개의 영역별로 섹션과 매핑 그리고 pc에 대한 관계를 살펴본다.

  • primary_entry
    • 섹션: .init.text
    • 매핑: 없음
    • pc: 물리 주소에서 시작 (예: 0x416b_0000)
  • __primary_switch
    • 섹션: .idmap
    • 매핑: .idmap 섹션 코드들을 idmap_pg_dir 페이지 테이블에 va=pa 1:1 매핑
    • pc: 물리주소를 그대로 가상주소로 사용 (예: 0x40f7_8310)
  • __primary_switched
    • 섹션: .init.text
    • 매핑: .text 섹션 코드 및 데이터를 init_pg_dir 페이지 테이블 사용하여 매핑
    • pc: 커널 가상 주소가 본격적으로 사용 (예: 0xffff_8000_114b_0330)

 

근거리 및 원거리 점프 방법에 대해 알아본다.

  • 근거리 점프를 위해서는 pc + relative offset 방식을 사용하는 b 및 bl 명령들을 사용할 수 있다.
  • 원거리 점프를 위해서는 레지스터를 이용한 br 및 blr 명령을 사용할 수 있다.
    • 예) ldr <Rd>, =<label>과 같은 특수한 pesudo instruction을 사용
      • 이 명령은 실제 존재하는 명령이 아니라 매크로와 같은 명령으로 컴파일러가 12바이트의 코드를 만들어낸다.
      • 4바이트는 ldr <Rd>, <코드 인근 주소 레이블>과 같은 명령을 사용하고,
      • 8바이트에는 컴파일 타임에 <label>에 대한 주소를 저장한다.
      • 결국 컴파일 타임에 저장해둔 8바이트 주소를 레지스터에 읽어들이는 코드를 생성한다.

 

__primary_switch에서 pc가 0x4xxx_xxxx 주소를 사용할 때에는 TTBR0_EL1과 연결된 idmap_pg_dir을 사용하다, __primary_switched에 해당하는 0xffff_8000_1xxx_xxxx 주소를 사용할 때에는 TTBR1_EL1에 연결된 init_pg_dir 페이지 테이블을 사용하는 식으로 자연스럽게 전환되므로 page fault는 발생하지 않는다.

 

다음은 디버거를 이용하여 런타임에 사용된 주소들을 보여준다.

_head:                                               ; KERNEL_START
   0x0000000040200000:  add     x13, x18, #0x16      ; 
   0x0000000040200004:  b       0x416b0000           ; primary_entry

primary_entry:
   0x00000000416b0000:  bl      0x416b0020           ; preserve_boot_args
   0x00000000416b0004:  bl      0x40f78000           ; el2_setup
   0x00000000416b0008:  adrp    x23, 0x40200000    
   0x00000000416b000c:  and     x23, x23, #0x1fffff
   0x00000000416b0010:  bl      0x40f78180           ; set_cpu_boot_mode_flag
   0x00000000416b0014:  bl      0x416b0040           ; __create_page_tables
   0x00000000416b0018:  bl      0x40f7860c           ; __cpu_setup
   0x00000000416b001c:  b       0x40f78310           ; __primary_switch


__primary_switch:
   0x0000000040f78310:  adrp    x1, 0x41fbb000      
   0x0000000040f78314:  bl      0x40f78248           ; __enable_mmu
   0x0000000040f78318:  bl      0x40f782c8           ; __relocate_kernel
   0x0000000040f7831c:  ldr     x8, 0x40f78338       ; =__primary_switched
   0x0000000040f78320:  adrp    x0, 0x40200000       ; __PHYS_OFFSET
   0x0000000040f78324:  br      x8
   ...
   0x0000000040f78338:  .inst   0x114b0330      
   0x0000000040f78338:  .inst   0xffff8000

__primary_switched:
   0xffff8000114b0330 <+0>:     adrp    x4, 0xffff800011a70000
   0xffff8000114b0334 <+4>:     add     sp, x4, #0x4, lsl #12
   0xffff8000114b0338 <+8>:     adrp    x5, 0xffff800011a83000 <envp_init+104>
   0xffff8000114b033c <+12>:    add     x5, x5, #0x300
   0xffff8000114b0340 <+16>:    msr     sp_el0, x5
   0xffff8000114b0344 <+20>:    adrp    x8, 0xffff800010010000 <dw_apb_ictl_handle_irq>
   0xffff8000114b0348 <+24>:    add     x8, x8, #0x800
   0xffff8000114b034c <+28>:    msr     vbar_el1, x8
   0xffff8000114b0350 <+32>:    isb
   0xffff8000114b0354 <+36>:    stp     xzr, x30, [sp, #-16]!
   0xffff8000114b0358 <+40>:    mov     x29, sp
   0xffff8000114b035c <+44>:    adrp    x5, 0xffff800011561000 <tmp_cmdline.61939+2040>
   0xffff8000114b0360 <+48>:    str     x21, [x5, #904]
   0xffff8000114b0364 <+52>:    adrp    x4, 0xffff800010d80000 <kimage_vaddr>
   0xffff8000114b0368 <+56>:    ldr     x4, [x4]
   0xffff8000114b036c <+60>:    sub     x4, x4, x0
   0xffff8000114b0370 <+64>:    adrp    x5, 0xffff80001143e000
   0xffff8000114b0374 <+68>:    str     x4, [x5, #3888]
   0xffff8000114b0378 <+72>:    adrp    x0, 0xffff800011d2c000 <__boot_cpu_mode>
   0xffff8000114b037c <+76>:    add     x0, x0, #0xa00
   0xffff8000114b0380 <+80>:    mov     x1, xzr
   0xffff8000114b0384 <+84>:    adrp    x2, 0xffff800011dba000 <write_buf.76616+30304>
   0xffff8000114b0388 <+88>:    add     x2, x2, #0xd3c
   0xffff8000114b038c <+92>:    sub     x2, x2, x0
   0xffff8000114b0390 <+96>:    bl      0xffff800010477840 <memset>
   0xffff8000114b0394 <+100>:   dsb     ishst
   0xffff8000114b0398 <+104>:   add     sp, sp, #0x10
   0xffff8000114b039c <+108>:   mov     x29, #0x0                       // #0
   0xffff8000114b03a0 <+112>:   mov     x30, #0x0                       // #0
   0xffff8000114b03a4 <+116>:   b       0xffff8000114b0a3c <start_kernel>

 

kimage_vaddr & kimage_voffset

kimage_vaddr 변수

arch/arm64/kernel/head.S

        .pushsection ".rodata", "a"
SYM_DATA_START(kimage_vaddr)
        .quad           _text
SYM_DATA_END(kimage_vaddr)
EXPORT_SYMBOL(kimage_vaddr)
        .popsection

kimage_vaddr은 MMU 상태와 관계없이 근거리내에서 해당 레이블을 읽어야 하므로 .idmap.text 섹션에 위치해 있고, 컴파일 타임에 커널 시작 가상 주소 _text의 주소가 저장되어 있다. 단 랜더마이즈에 의해 리로케이션이 진행되면 이 심볼 값도 랜더마이즈된 커널 이미지 시작 주소로 변경된다.

 

kimage_voffset 변수

arch/arm64/mm/mmu.c

u64 kimage_voffset __ro_after_init;
EXPORT_SYMBOL(kimage_voffset);

부트업 타임에 다음과 같은 값으로 저장된 후 읽기 전용으로 사용된다.

  • kimage_voffset = kimage_vaddr – 커널 물리 시작 주소

 

다음 그림은 이미지의 가상 주소에서 물리 주소의 차이를 kimage_voffset 값에 담았고, 이 값을 통해 이미지의 가상 주소와 물리 주소의 변환 API에 활용되는 모습을 보여준다.

 

kimage_voffset 저장 과정

primary_switched 내부에는 아래와 같이 kimage_voffset를 저장하는 루틴이 있다.

  • ldr_l x4, kimage_vaddr
    • kimage_vaddr에는 리로케이션된 커널 시작 가상 주소가 담긴다.
  • sub x4, x4, x0
    • x0에는 런타임 커널 시작 주소(1st: 0x4020_0000)가 담겨있다.
  • str_l x4, kimage_voffset, x5
    • kimage_voffset = 랜더마이즈한 커널 시작 주소 – 물리 시작 주소 값

 

kimage_voffset 값은 커널 이미지의 가상 주소 vs 물리 주소와의 변환에 다음 API를 통해 사용한다.

  • __kimg_to_phys()
  • __phys_to_kimg()

 

__kimg_to_phys()

arch/arm64/include/asm/memory.h

#define __kimg_to_phys(addr) ((addr) - kimage_voffset)

커널 이미지 가상 주소를 커널 이미지 물리 주소로 변환하여 알아온다.

 

__phys_to_kimg()

arch/arm64/include/asm/memory.h

#define __phys_to_kimg(x)       ((unsigned long)((x) + kimage_voffset))

커널 이미지 물리 주소를 커널 이미지 가상 주소로 변환하여 알아온다.

 

커널 스택 크기

arch/arm64/include/asm/memory.h

#define THREAD_SIZE             (UL(1) << THREAD_SHIFT)

커널 스택 사이즈는 디폴트 커널 설정(4K 페이지)에서 16K를 사용한다.

 

arch/arm64/include/asm/memory.h

/*
 * VMAP'd stacks are allocated at page granularity, so we must ensure that such
 * stacks are a multiple of page size.
 */
#if defined(CONFIG_VMAP_STACK) && (MIN_THREAD_SHIFT < PAGE_SHIFT)
#define THREAD_SHIFT            PAGE_SHIFT
#else
#define THREAD_SHIFT            MIN_THREAD_SHIFT
#endif

페이지 사이즈로 64K로 선택한 경우 vmap(디폴트)을 사용한 커널 스택은 1 개의 64K 페이지를 사용한다. 그렇지 않은 경우 MIN_THREAD_SHIFT 단위의 커널 스택을 사용한다.

 

arch/arm64/include/asm/memory.h

#define MIN_THREAD_SHIFT        (14 + KASAN_THREAD_SHIFT)

커널 스택 최소 단위는 16K이며 KASAN을 사용하는 경우 32K를 사용하고, KASAN Extra를 사용하는 경우 64K를 사용한다.

 

부트 cpu 모드 저장

set_cpu_boot_mode_flag:

arch/arm64/kernel/head.S

/*
 * Sets the __boot_cpu_mode flag depending on the CPU boot mode passed
 * in w0. See arch/arm64/include/asm/virt.h for more info.
 */
SYM_FUNC_START_LOCAL(set_cpu_boot_mode_flag)
        adr_l   x1, __boot_cpu_mode
        cmp     w0, #BOOT_CPU_MODE_EL2
        b.ne    1f
        add     x1, x1, #4
1:      str     w0, [x1]                        // Save CPU boot mode
        ret
SYM_FUNC_END(set_cpu_boot_mode_flag)

커널 부트 진입 시 cpu 모드(el1 ~ el2)를 파악하여 변수 __boot_cpu_mode[0~1]에 저장한다.

  • 코드 라인 2~7에 첫 번째 인자 w0 값이 el2 모드가 아닌 경우 w0를 __boot_cpu_mode[0]에 저장하고, el2 모드로 부팅한 경우 __boot_cpu_mode[1]에 w0를 저장한다.

 

__boot_cpu_mode[]

arch/arm64/include/asm/virt.h

/*
 * __boot_cpu_mode records what mode CPUs were booted in.
 * A correctly-implemented bootloader must start all CPUs in the same mode:
 * In this case, both 32bit halves of __boot_cpu_mode will contain the
 * same value (either 0 if booted in EL1, BOOT_CPU_MODE_EL2 if booted in EL2).
 *
 * Should the bootloader fail to do this, the two values will be different.
 * This allows the kernel to flag an error when the secondaries have come up.
 */

arch/arm64/mm/mmu.c

u32 __boot_cpu_mode[] = { BOOT_CPU_MODE_EL2, BOOT_CPU_MODE_EL1 };

__boot_cpu_mode[]의 초기 값은 다음과 같이 두 개 값이 담겨있다.

  • BOOT_CPU_MODE_EL2=0xe12
  • BOOT_CPU_MODE_EL1=0xe11

 


Secondary CPU 부팅

secondary_entry:

arch/arm64/kernel/head.S

        /*
         * Secondary entry point that jumps straight into the kernel. Only to
         * be used where CPUs are brought online dynamically by the kernel.
         */
SYM_FUNC_START(secondary_entry)
        bl      init_kernel_el                  // w0=cpu_boot_mode
        b       secondary_startup
SYM_FUNC_END(secondary_entry)

부트 cpu를 제외한 나머지 cpu들이 깨어날 때 수행될 루틴들이다. 하이퍼 바이저 설정 코드를 수행하고, 부트 cpu 모드를 저장한 후 secondary_startup 레이블로 이동하여 계속 처리한다.

  • 잠들어 있는 cpu들은 wfe 동작과 같은 상태로 클럭이 멈춰있는 상태이고 부트가 되어야 할지 여부가 기록된 스핀 테이블 내용이 변경되지 않는 한 루프를 돌며 다시 wfe 상태가 된다.

 

secondary_startup:

arch/arm64/kernel/head.S

SYM_FUNC_START_LOCAL(secondary_startup)
        /*
         * Common entry point for secondary CPUs.
         */
        mov     x20, x0                         // preserve boot mode
        bl      finalise_el2
        bl      __cpu_secondary_check52bitva
#if VA_BITS > 48
        ldr_l   x0, vabits_actual
#endif
        bl      __cpu_setup                     // initialise processor
        adrp    x1, swapper_pg_dir
        adrp    x2, idmap_pg_dir
        bl      __enable_mmu
        ldr     x8, =__secondary_switched
        br      x8
SYM_FUNC_END(secondary_startup)

프로세서를 초기화하고 MMU를 켠 후 __secondary_switched: 루틴으로 점프한다.

  • 코드 라인 4~5에서 x20에 x0 레지스터를 보관한 후 EL2 모드의 사용을 위해 마지막 준비를 수행한다. (생략)
  • 코드 라인 6에서 부트 cpu가 52bit 유저 가상 주소를 사용한 경우 secondary cpu가 이를 지원하는지 여부를 체크하는데, 지원하지 않는 경우 stuck한다. 이 함수를 빠져나오면 x0에 시스템이 지원하는 vabits_actual 비트가 담겨있다.
  • 코드 라인 7~9에서 52비트 VA를 지원하는 커널인 경우 시스템이 실제 운영가능한  vabits_actual(48 or 52) 값을 x0에 대입한다.
  • 코드 라인 10에서 MMU를 켜기 위해 현재 cpu를 초기화한다. (x0 레지스터에 vabits_actual 값이 지정되며, 단 VA_BITS > 48 이상에서만 유효하다)
  • 코드 라인 11~13에서 TTBR1 레지스터가 swapper_pg_dir을 가리키고, TTBR0 레지스터가 idmap_pg_dir을 가리키게한 후 MMU를 켠다.
    • MMU가 enable하는 순간에는 물리 주소에서 PC(Program Counter)가 동작하고 있었기 때문에 처음에는 idmap_pg_dir을 기반으로 동작하게 된다.
  • 코드 라인 14~15에서 __secondary_switched 루틴으로 점프한다.

 

__cpu_secondary_check52bitva:

arch/arm64/kernel/head.S

SYM_FUNC_START(__cpu_secondary_check52bitva)
#if VA_BITS > 48
        ldr_l   x0, vabits_actual
        cmp     x0, #52
        b.ne    2f

        mrs_s   x0, SYS_ID_AA64MMFR2_EL1
        and     x0, x0, #(0xf << ID_AA64MMFR2_LVA_SHIFT)
        cbnz    x0, 2f

        update_early_cpu_boot_status \
                CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_52_BIT_VA, x0, x1
1:      wfe
        wfi
        b       1b

#endif
2:      ret
SYM_FUNC_END(__cpu_secondary_check52bitva)

secondary cpu의 52 비트 가상 주소 지원 여부를 체크한다. 지원하지 않는 cpu는 부팅되지 않고 stuck한다.

  • 코드 라인 3~5에서 부트 cpu에서 저장한 변수 vabits_actual에 담긴 값이 52가 아니면 함수를 빠져나간다.
  • 코드 라인 7~9에서 mmfr2_el1 레지스터의 VARange 필드 값이 1이면 유저 가상 주소로 52비트를 지원하는 것이므로 함수를 빠져나간다.
  • 코드 라인 11~15에서 52bit를 지원하지 않아 변수 __early_cpu_boot_status에 0x102를 저장하고 cpu가 정지(stuck)한다.

 

update_early_cpu_boot_status 매크로

arch/arm64/kernel/head.S

/*
 * The booting CPU updates the failed status @__early_cpu_boot_status,
 * with MMU turned off.
 *
 * update_early_cpu_boot_status tmp, status
 *  - Corrupts tmp1, tmp2
 *  - Writes 'status' to __early_cpu_boot_status and makes sure
 *    it is committed to memory.
 */
        .macro  update_early_cpu_boot_status status, tmp1, tmp2
        mov     \tmp2, #\status
        adr_l   \tmp1, __early_cpu_boot_status
        str     \tmp2, [\tmp1]
        dmb     sy
        dc      ivac, \tmp1                     // Invalidate potentially stale cache line
        .endm

변수 __early_cpu_boot_status에 부트 상태 @status를 저장한다.

  • @tmp1과 @tmp2에는 파괴되도 상관이 없는 임시 레지스터를 지정한다.
  • 주석 내용에서 인자가 잘못되어 있음을 확인할 수 있다.

 

__secondary_switched:

arch/arm64/kernel/head.S

SYM_FUNC_START_LOCAL(__secondary_switched)
        mov     x0, x20
        bl      set_cpu_boot_mode_flag
        str_l   xzr, __early_cpu_boot_status, x3
        adr_l   x5, vectors
        msr     vbar_el1, x5
        isb

        adr_l   x0, secondary_data
        ldr     x2, [x0, #CPU_BOOT_TASK]
        cbz     x2, __secondary_too_slow

        init_cpu_task x2, x1, x3

#ifdef CONFIG_ARM64_PTR_AUTH
        ptrauth_keys_init_cpu x2, x3, x4, x5
#endif

        bl      secondary_start_kernel
        ASM_BUG()
SYM_FUNC_END(__secondary_switched)

커널용 벡터 포인터와 스택을 지정한 후 C 루틴인 secondary_start_kernel() 루틴으로 점프한다.

 


CPU stuck 시 Reason 코드 확인

__early_cpu_boot_status 변수

arch/arm64/kernel/head.S

/*
 * The booting CPU updates the failed status @__early_cpu_boot_status,
 * with MMU turned off.
 */
SYM_DATA_START(__early_cpu_boot_status)
        .quad
SYM_DATA_END(__early_cpu_boot_status)

다음과 같이 하위 8비트는 cpu boot 상태를 표시하고, 상위 비트에서 stuck 이유를 담는다.

 

arch/arm64/include/asm/smp.h

/* Values for secondary_data.status */
#define CPU_STUCK_REASON_SHIFT          (8)
#define CPU_BOOT_STATUS_MASK            ((UL(1) << CPU_STUCK_REASON_SHIFT) - 1)

#define CPU_MMU_OFF                     (-1)
#define CPU_BOOT_SUCCESS                (0)
/* The cpu invoked ops->cpu_die, synchronise it with cpu_kill */
#define CPU_KILL_ME                     (1)
/* The cpu couldn't die gracefully and is looping in the kernel */
#define CPU_STUCK_IN_KERNEL             (2)
/* Fatal system error detected by secondary CPU, crash the system */
#define CPU_PANIC_KERNEL                (3)

#define CPU_STUCK_REASON_52_BIT_VA      (UL(1) << CPU_STUCK_REASON_SHIFT)
#define CPU_STUCK_REASON_NO_GRAN        (UL(2) << CPU_STUCK_REASON_SHIFT)

 

__no_granule_support:

arch/arm64/kernel/head.S

SYM_FUNC_START_LOCAL(__no_granule_support)
        /* Indicate that this CPU can't boot and is stuck in the kernel */
        update_early_cpu_boot_status \
                CPU_STUCK_IN_KERNEL | CPU_STUCK_REASON_NO_GRAN, x1, x2
1:
        wfe
        wfi
        b       1b
SYM_FUNC_END(__no_granule_support)

커널이 설정한 페이지 테이블 단위를 해당 cpu의 아키텍처가 지원하지 않아 stuck 한다.

 

참고

 

 

4. 추천 학습 순서 (Step by Step Code)

4. 추천 학습 순서 (Step by Step Code)

 

1) 코드 분석 전

 

사전 이해도

  • 네트워크 C 프로그래밍 중급

앞으로 익숙해져야 할 툴

  • Linux bash shell
  • GCC
  • Git
  • Vim

코드 분석 전 읽어야 할 이론서

  • 간단한 커널 이론서
  • ARM 및 ARM64 아키텍처에 관한 이론서

2) 어셈블리 코드 분석 시작

 


3) C 코드 분석 시작

 

 


4) 아키텍처 설정

 

 


5) 페이징 및 메모리 설정

 

 


6) 클럭, 타이머 및 인터럽트

 

 


7) 스케줄러, mutex 및 RCU

 

 


8) 디바이스 드라이버

 

 


9. 네트워크 시스템 및 파일 시스템

각자 도생 ^^;

 

DTB – 라즈베리파이 4b

<kernel v5.15>

DTB – 라즈베리파이 4b

사용한 디바이스 트리 파일은 다음과 같고, 괄호안의 번호 순서대로 보여준다.

  • bcm2711-rpi-4-b.dts (6~7)
    •  bcm2711.dtsi (2)
      • bcm283x.dtsi (1)
    • bcm2711-rpi.dtsi (4)
      • bcm2835-rpi.dtsi (3)
    • bcm283x-rpi-usb-peripheral.dtsi (5)

 

(1) bcm283x.dtsi

arch/arm/boot/dts/bcm283x.dtsi

#include <dt-bindings/pinctrl/bcm2835.h>
#include <dt-bindings/clock/bcm2835.h>
#include <dt-bindings/clock/bcm2835-aux.h>
#include <dt-bindings/gpio/gpio.h>
#include <dt-bindings/interrupt-controller/irq.h>
#include <dt-bindings/soc/bcm2835-pm.h>

/* firmware-provided startup stubs live here, where the secondary CPUs are
 * spinning.
 */
/memreserve/ 0x00000000 0x00001000;

/* This include file covers the common peripherals and configuration between
 * bcm2835 and bcm2836 implementations, leaving the CPU configuration to
 * bcm2835.dtsi and bcm2836.dtsi.
 */

/ {
        compatible = "brcm,bcm2835";
        model = "BCM2835";
        #address-cells = <1>;
        #size-cells = <1>;

        aliases {
                serial0 = &uart0;
                serial1 = &uart1;
        };

        chosen {
                stdout-path = "serial0:115200n8";
        };

        rmem: reserved-memory {
                #address-cells = <1>;
                #size-cells = <1>;
                ranges;

                cma: linux,cma {
                        compatible = "shared-dma-pool";
                        size = <0x4000000>; /* 64MB */
                        reusable;
                        linux,cma-default;
                };
        };

        thermal-zones {
                cpu_thermal: cpu-thermal {
                        polling-delay-passive = <0>;
                        polling-delay = <1000>;

                        trips {
                                cpu-crit {
                                        temperature     = <90000>;
                                        hysteresis      = <0>;
                                        type            = "critical";
                                };
                        };

                        cooling-maps {
                        };
                };
        };

        soc {
                compatible = "simple-bus";
                #address-cells = <1>;
                #size-cells = <1>;

                system_timer: timer@7e003000 {
                        compatible = "brcm,bcm2835-system-timer";
                        reg = <0x7e003000 0x1000>;
                        interrupts = <1 0>, <1 1>, <1 2>, <1 3>;
                        /* This could be a reference to BCM2835_CLOCK_TIMER,
                         * but we don't have the driver using the common clock
                         * support yet.
                         */
                        clock-frequency = <1000000>;
                };

                txp: txp@7e004000 {
                        compatible = "brcm,bcm2835-txp";
                        reg = <0x7e004000 0x20>;
                        interrupts = <1 11>;
                };

                clocks: cprman@7e101000 {
                        compatible = "brcm,bcm2835-cprman";
                        #clock-cells = <1>;
                        reg = <0x7e101000 0x2000>;

                        /* CPRMAN derives almost everything from the
                         * platform's oscillator.  However, the DSI
                         * pixel clocks come from the DSI analog PHY.
                         */
                        clocks = <&clk_osc>,
                                <&dsi0 0>, <&dsi0 1>, <&dsi0 2>,
                                <&dsi1 0>, <&dsi1 1>, <&dsi1 2>;
                };

                mailbox: mailbox@7e00b880 {
                        compatible = "brcm,bcm2835-mbox";
                        reg = <0x7e00b880 0x40>;
                        interrupts = <0 1>;
                        #mbox-cells = <0>;
                };

                gpio: gpio@7e200000 {
                        compatible = "brcm,bcm2835-gpio";
                        reg = <0x7e200000 0xb4>;
                        /*
                         * The GPIO IP block is designed for 3 banks of GPIOs.
                         * Each bank has a GPIO interrupt for itself.
                         * There is an overall "any bank" interrupt.
                         * In order, these are GIC interrupts 17, 18, 19, 20.
                         * Since the BCM2835 only has 2 banks, the 2nd bank
                         * interrupt output appears to be mirrored onto the
                         * 3rd bank's interrupt signal.
                         * So, a bank0 interrupt shows up on 17, 20, and
                         * a bank1 interrupt shows up on 18, 19, 20!
                         */
                        interrupts = <2 17>, <2 18>, <2 19>, <2 20>;

                        gpio-controller;
                        #gpio-cells = <2>;

                        interrupt-controller;
                        #interrupt-cells = <2>;

                        /* Defines common pin muxing groups
                         *
                         * While each pin can have its mux selected
                         * for various functions individually, some
                         * groups only make sense to switch to a
                         * particular function together.
                         */
                        dpi_gpio0: dpi_gpio0 {
                                brcm,pins = <0 1 2 3 4 5 6 7 8 9 10 11
                                             12 13 14 15 16 17 18 19
                                             20 21 22 23 24 25 26 27>;
                                brcm,function = <BCM2835_FSEL_ALT2>;
                        };
                        emmc_gpio22: emmc_gpio22 {
                                brcm,pins = <22 23 24 25 26 27>;
                                brcm,function = <BCM2835_FSEL_ALT3>;
                        };
                        emmc_gpio34: emmc_gpio34 {
                                brcm,pins = <34 35 36 37 38 39>;
                                brcm,function = <BCM2835_FSEL_ALT3>;
                                brcm,pull = <BCM2835_PUD_OFF
                                             BCM2835_PUD_UP
                                             BCM2835_PUD_UP
                                             BCM2835_PUD_UP
                                             BCM2835_PUD_UP
                                             BCM2835_PUD_UP>;
                        };
                        emmc_gpio48: emmc_gpio48 {
                                brcm,pins = <48 49 50 51 52 53>;
                                brcm,function = <BCM2835_FSEL_ALT3>;
                        };

                        gpclk0_gpio4: gpclk0_gpio4 {
                                brcm,pins = <4>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        gpclk1_gpio5: gpclk1_gpio5 {
                                brcm,pins = <5>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        gpclk1_gpio42: gpclk1_gpio42 {
                                brcm,pins = <42>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        gpclk1_gpio44: gpclk1_gpio44 {
                                brcm,pins = <44>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        gpclk2_gpio6: gpclk2_gpio6 {
                                brcm,pins = <6>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        gpclk2_gpio43: gpclk2_gpio43 {
                                brcm,pins = <43>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                                brcm,pull = <BCM2835_PUD_OFF>;
                        };

                        i2c0_gpio0: i2c0_gpio0 {
                                brcm,pins = <0 1>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        i2c0_gpio28: i2c0_gpio28 {
                                brcm,pins = <28 29>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        i2c0_gpio44: i2c0_gpio44 {
                                brcm,pins = <44 45>;
                                brcm,function = <BCM2835_FSEL_ALT1>;
                        };
                        i2c1_gpio2: i2c1_gpio2 {
                                brcm,pins = <2 3>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        i2c1_gpio44: i2c1_gpio44 {
                                brcm,pins = <44 45>;
                                brcm,function = <BCM2835_FSEL_ALT2>;
                        };

                        jtag_gpio22: jtag_gpio22 {
                                brcm,pins = <22 23 24 25 26 27>;
                                brcm,function = <BCM2835_FSEL_ALT4>;
                        };

                        pcm_gpio18: pcm_gpio18 {
                                brcm,pins = <18 19 20 21>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        pcm_gpio28: pcm_gpio28 {
                                brcm,pins = <28 29 30 31>;
                                brcm,function = <BCM2835_FSEL_ALT2>;
                        };

                        sdhost_gpio48: sdhost_gpio48 {
                                brcm,pins = <48 49 50 51 52 53>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };

                        spi0_gpio7: spi0_gpio7 {
                                brcm,pins = <7 8 9 10 11>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        spi0_gpio35: spi0_gpio35 {
                                brcm,pins = <35 36 37 38 39>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        spi1_gpio16: spi1_gpio16 {
                                brcm,pins = <16 17 18 19 20 21>;
                                brcm,function = <BCM2835_FSEL_ALT4>;
                        };
                        spi2_gpio40: spi2_gpio40 {
                                brcm,pins = <40 41 42 43 44 45>;
                                brcm,function = <BCM2835_FSEL_ALT4>;
                        };

                        uart0_gpio14: uart0_gpio14 {
                                brcm,pins = <14 15>;
                                brcm,function = <BCM2835_FSEL_ALT0>;
                        };
                        /* Separate from the uart0_gpio14 group
                         * because it conflicts with spi1_gpio16, and
                         * people often run uart0 on the two pins
                         * without flow control.
                         */
                        uart0_ctsrts_gpio16: uart0_ctsrts_gpio16 {
                                brcm,pins = <16 17>;
                                brcm,function = <BCM2835_FSEL_ALT3>;
                        };
                        uart0_ctsrts_gpio30: uart0_ctsrts_gpio30 {
                                brcm,pins = <30 31>;
                                brcm,function = <BCM2835_FSEL_ALT3>;
                                brcm,pull = <BCM2835_PUD_UP BCM2835_PUD_OFF>;
                        };
                        uart0_gpio32: uart0_gpio32 {
                                brcm,pins = <32 33>;
                                brcm,function = <BCM2835_FSEL_ALT3>;
                                brcm,pull = <BCM2835_PUD_OFF BCM2835_PUD_UP>;
                        };
                        uart0_gpio36: uart0_gpio36 {
                                brcm,pins = <36 37>;
                                brcm,function = <BCM2835_FSEL_ALT2>;
                        };
                        uart0_ctsrts_gpio38: uart0_ctsrts_gpio38 {
                                brcm,pins = <38 39>;
                                brcm,function = <BCM2835_FSEL_ALT2>;
                        };

                        uart1_gpio14: uart1_gpio14 {
                                brcm,pins = <14 15>;
                                brcm,function = <BCM2835_FSEL_ALT5>;
                        };
                        uart1_ctsrts_gpio16: uart1_ctsrts_gpio16 {
                                brcm,pins = <16 17>;
                                brcm,function = <BCM2835_FSEL_ALT5>;
                        };
                        uart1_gpio32: uart1_gpio32 {
                                brcm,pins = <32 33>;
                                brcm,function = <BCM2835_FSEL_ALT5>;
                        };
                        uart1_ctsrts_gpio30: uart1_ctsrts_gpio30 {
                                brcm,pins = <30 31>;
                                brcm,function = <BCM2835_FSEL_ALT5>;
                        };
                        uart1_gpio40: uart1_gpio40 {
                                brcm,pins = <40 41>;
                                brcm,function = <BCM2835_FSEL_ALT5>;
                        };
                        uart1_ctsrts_gpio42: uart1_ctsrts_gpio42 {
                                brcm,pins = <42 43>;
                                brcm,function = <BCM2835_FSEL_ALT5>;
                        };
                };

                uart0: serial@7e201000 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201000 0x200>;
                        interrupts = <2 25>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                };

                sdhost: mmc@7e202000 {
                        compatible = "brcm,bcm2835-sdhost";
                        reg = <0x7e202000 0x100>;
                        interrupts = <2 24>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        status = "disabled";
                };

                i2s: i2s@7e203000 {
                        compatible = "brcm,bcm2835-i2s";
                        reg = <0x7e203000 0x24>;
                        clocks = <&clocks BCM2835_CLOCK_PCM>;
                        status = "disabled";
                };

                spi: spi@7e204000 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204000 0x200>;
                        interrupts = <2 22>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                i2c0: i2c@7e205000 {
                        compatible = "brcm,bcm2835-i2c";
                        reg = <0x7e205000 0x200>;
                        interrupts = <2 21>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                dpi: dpi@7e208000 {
                        compatible = "brcm,bcm2835-dpi";
                        reg = <0x7e208000 0x8c>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>,
                                 <&clocks BCM2835_CLOCK_DPI>;
                        clock-names = "core", "pixel";
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                dsi0: dsi@7e209000 {
                        compatible = "brcm,bcm2835-dsi0";
                        reg = <0x7e209000 0x78>;
                        interrupts = <2 4>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        #clock-cells = <1>;

                        clocks = <&clocks BCM2835_PLLA_DSI0>,
                                 <&clocks BCM2835_CLOCK_DSI0E>,
                                 <&clocks BCM2835_CLOCK_DSI0P>;
                        clock-names = "phy", "escape", "pixel";

                        clock-output-names = "dsi0_byte",
                                             "dsi0_ddr2",
                                             "dsi0_ddr";

                        status = "disabled";
                };

                aux: aux@7e215000 {
                        compatible = "brcm,bcm2835-aux";
                        #clock-cells = <1>;
                        reg = <0x7e215000 0x8>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                };

                uart1: serial@7e215040 {
                        compatible = "brcm,bcm2835-aux-uart";
                        reg = <0x7e215040 0x40>;
                        interrupts = <1 29>;
                        clocks = <&aux BCM2835_AUX_CLOCK_UART>;
                        status = "disabled";
                };

                spi1: spi@7e215080 {
                        compatible = "brcm,bcm2835-aux-spi";
                        reg = <0x7e215080 0x40>;
                        interrupts = <1 29>;
                        clocks = <&aux BCM2835_AUX_CLOCK_SPI1>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                spi2: spi@7e2150c0 {
                        compatible = "brcm,bcm2835-aux-spi";
                        reg = <0x7e2150c0 0x40>;
                        interrupts = <1 29>;
                        clocks = <&aux BCM2835_AUX_CLOCK_SPI2>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                pwm: pwm@7e20c000 {
                        compatible = "brcm,bcm2835-pwm";
                        reg = <0x7e20c000 0x28>;
                        clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clock-rates = <10000000>;
                        #pwm-cells = <2>;
                        status = "disabled";
                };

                sdhci: mmc@7e300000 {
                        compatible = "brcm,bcm2835-sdhci";
                        reg = <0x7e300000 0x100>;
                        interrupts = <2 30>;
                        clocks = <&clocks BCM2835_CLOCK_EMMC>;
                        status = "disabled";
                };

                hvs@7e400000 {
                        compatible = "brcm,bcm2835-hvs";
                        reg = <0x7e400000 0x6000>;
                        interrupts = <2 1>;
                };

                dsi1: dsi@7e700000 {
                        compatible = "brcm,bcm2835-dsi1";
                        reg = <0x7e700000 0x8c>;
                        interrupts = <2 12>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        #clock-cells = <1>;

                        clocks = <&clocks BCM2835_PLLD_DSI1>,
                                 <&clocks BCM2835_CLOCK_DSI1E>,
                                 <&clocks BCM2835_CLOCK_DSI1P>;
                        clock-names = "phy", "escape", "pixel";

                        clock-output-names = "dsi1_byte",
                                             "dsi1_ddr2",
                                             "dsi1_ddr";

                        status = "disabled";
                };

                i2c1: i2c@7e804000 {
                        compatible = "brcm,bcm2835-i2c";
                        reg = <0x7e804000 0x1000>;
                        interrupts = <2 21>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                usb: usb@7e980000 {
                        compatible = "brcm,bcm2835-usb";
                        reg = <0x7e980000 0x10000>;
                        interrupts = <1 9>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        clocks = <&clk_usb>;
                        clock-names = "otg";
                        phys = <&usbphy>;
                        phy-names = "usb2-phy";
                };
        };

        clocks {
                /* The oscillator is the root of the clock tree. */
                clk_osc: clk-osc {
                        compatible = "fixed-clock";
                        #clock-cells = <0>;
                        clock-output-names = "osc";
                        clock-frequency = <19200000>;
                };

                clk_usb: clk-usb {
                        compatible = "fixed-clock";
                        #clock-cells = <0>;
                        clock-output-names = "otg";
                        clock-frequency = <480000000>;
                };
        };

        usbphy: phy {
                compatible = "usb-nop-xceiv";
                #phy-cells = <0>;
        };
};

 

(2) bcm2711.dtsi

arch/arm/boot/dts/bcm2711.dtsi

// SPDX-License-Identifier: GPL-2.0
#include "bcm283x.dtsi"

#include <dt-bindings/interrupt-controller/arm-gic.h>
#include <dt-bindings/soc/bcm2835-pm.h>

/ {
        compatible = "brcm,bcm2711";

        #address-cells = <2>;
        #size-cells = <1>;

        interrupt-parent = <&gicv2>;

        vc4: gpu {
                compatible = "brcm,bcm2711-vc5";
                status = "disabled";
        };

        clk_27MHz: clk-27M {
                #clock-cells = <0>;
                compatible = "fixed-clock";
                clock-frequency = <27000000>;
                clock-output-names = "27MHz-clock";
        };

        clk_108MHz: clk-108M {
                #clock-cells = <0>;
                compatible = "fixed-clock";
                clock-frequency = <108000000>;
                clock-output-names = "108MHz-clock";
        };

        soc {
                /*
                 * Defined ranges:
                 *   Common BCM283x peripherals
                 *   BCM2711-specific peripherals
                 *   ARM-local peripherals
                 */
                ranges = <0x7e000000  0x0 0xfe000000  0x01800000>,
                         <0x7c000000  0x0 0xfc000000  0x02000000>,
                         <0x40000000  0x0 0xff800000  0x00800000>;
                /* Emulate a contiguous 30-bit address range for DMA */
                dma-ranges = <0xc0000000  0x0 0x00000000  0x40000000>;

                /*
                 * This node is the provider for the enable-method for
                 * bringing up secondary cores.
                 */
                local_intc: local_intc@40000000 {
                        compatible = "brcm,bcm2836-l1-intc";
                        reg = <0x40000000 0x100>;
                };

                gicv2: interrupt-controller@40041000 {
                        interrupt-controller;
                        #interrupt-cells = <3>;
                        compatible = "arm,gic-400";
                        reg =   <0x40041000 0x1000>,
                                <0x40042000 0x2000>,
                                <0x40044000 0x2000>,
                                <0x40046000 0x2000>;
                        interrupts = <GIC_PPI 9 (GIC_CPU_MASK_SIMPLE(4) |
                                                 IRQ_TYPE_LEVEL_HIGH)>;
                };

                avs_monitor: avs-monitor@7d5d2000 {
                        compatible = "brcm,bcm2711-avs-monitor",
                                     "syscon", "simple-mfd";
                        reg = <0x7d5d2000 0xf00>;

                        thermal: thermal {
                                compatible = "brcm,bcm2711-thermal";
                                #thermal-sensor-cells = <0>;
                        };
                };

                dma: dma@7e007000 {
                        compatible = "brcm,bcm2835-dma";
                        reg = <0x7e007000 0xb00>;
                        interrupts = <GIC_SPI 80 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 81 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 82 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 83 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 84 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 85 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 86 IRQ_TYPE_LEVEL_HIGH>,
                                     /* DMA lite 7 - 10 */
                                     <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 87 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 88 IRQ_TYPE_LEVEL_HIGH>;
                        interrupt-names = "dma0",
                                          "dma1",
                                          "dma2",
                                          "dma3",
                                          "dma4",
                                          "dma5",
                                          "dma6",
                                          "dma7",
                                          "dma8",
                                          "dma9",
                                          "dma10";
                        #dma-cells = <1>;
                        brcm,dma-channel-mask = <0x07f5>;
                };

                pm: watchdog@7e100000 {
                        compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt";
                        #power-domain-cells = <1>;
                        #reset-cells = <1>;
                        reg = <0x7e100000 0x114>,
                              <0x7e00a000 0x24>,
                              <0x7ec11000 0x20>;
                        clocks = <&clocks BCM2835_CLOCK_V3D>,
                                 <&clocks BCM2835_CLOCK_PERI_IMAGE>,
                                 <&clocks BCM2835_CLOCK_H264>,
                                 <&clocks BCM2835_CLOCK_ISP>;
                        clock-names = "v3d", "peri_image", "h264", "isp";
                        system-power-controller;
                };

                rng@7e104000 {
                        compatible = "brcm,bcm2711-rng200";
                        reg = <0x7e104000 0x28>;
                };

                uart2: serial@7e201400 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201400 0x200>;
                        interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                        status = "disabled";
                };

                uart3: serial@7e201600 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201600 0x200>;
                        interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                        status = "disabled";
                };

                uart4: serial@7e201800 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201800 0x200>;
                        interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                        status = "disabled";
                };

                uart5: serial@7e201a00 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201a00 0x200>;
                        interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_UART>,
                                 <&clocks BCM2835_CLOCK_VPU>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x00241011>;
                        status = "disabled";
                };

                spi3: spi@7e204600 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204600 0x0200>;
                        interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                spi4: spi@7e204800 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204800 0x0200>;
                        interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                spi5: spi@7e204a00 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204a00 0x0200>;
                        interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                spi6: spi@7e204c00 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204c00 0x0200>;
                        interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                i2c3: i2c@7e205600 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205600 0x200>;
                        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                i2c4: i2c@7e205800 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205800 0x200>;
                        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                i2c5: i2c@7e205a00 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205a00 0x200>;
                        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                i2c6: i2c@7e205c00 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205c00 0x200>;
                        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2835_CLOCK_VPU>;
                        #address-cells = <1>;
                        #size-cells = <0>;
                        status = "disabled";
                };

                pixelvalve0: pixelvalve@7e206000 {
                        compatible = "brcm,bcm2711-pixelvalve0";
                        reg = <0x7e206000 0x100>;
                        interrupts = <GIC_SPI 109 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };

                pixelvalve1: pixelvalve@7e207000 {
                        compatible = "brcm,bcm2711-pixelvalve1";
                        reg = <0x7e207000 0x100>;
                        interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };

                pixelvalve2: pixelvalve@7e20a000 {
                        compatible = "brcm,bcm2711-pixelvalve2";
                        reg = <0x7e20a000 0x100>;
                        interrupts = <GIC_SPI 101 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };

                pwm1: pwm@7e20c800 {
                        compatible = "brcm,bcm2835-pwm";
                        reg = <0x7e20c800 0x28>;
                        clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clocks = <&clocks BCM2835_CLOCK_PWM>;
                        assigned-clock-rates = <10000000>;
                        #pwm-cells = <2>;
                        status = "disabled";
                };

                pixelvalve4: pixelvalve@7e216000 {
                        compatible = "brcm,bcm2711-pixelvalve4";
                        reg = <0x7e216000 0x100>;
                        interrupts = <GIC_SPI 110 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };

                hvs: hvs@7e400000 {
                        compatible = "brcm,bcm2711-hvs";
                        interrupts = <GIC_SPI 97 IRQ_TYPE_LEVEL_HIGH>;
                };

                pixelvalve3: pixelvalve@7ec12000 {
                        compatible = "brcm,bcm2711-pixelvalve3";
                        reg = <0x7ec12000 0x100>;
                        interrupts = <GIC_SPI 106 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };

                vec: vec@7ec13000 {
                        compatible = "brcm,bcm2711-vec";
                        reg = <0x7ec13000 0x1000>;
                        clocks = <&clocks BCM2835_CLOCK_VEC>;
                        interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";
                };

                dvp: clock@7ef00000 {
                        compatible = "brcm,brcm2711-dvp";
                        reg = <0x7ef00000 0x10>;
                        clocks = <&clk_108MHz>;
                        #clock-cells = <1>;
                        #reset-cells = <1>;
                };

                aon_intr: interrupt-controller@7ef00100 {
                        compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc";
                        reg = <0x7ef00100 0x30>;
                        interrupts = <GIC_SPI 96 IRQ_TYPE_LEVEL_HIGH>;
                        interrupt-controller;
                        #interrupt-cells = <1>;
                };

                hdmi0: hdmi@7ef00700 {
                        compatible = "brcm,bcm2711-hdmi0";
                        reg = <0x7ef00700 0x300>,
                              <0x7ef00300 0x200>,
                              <0x7ef00f00 0x80>,
                              <0x7ef00f80 0x80>,
                              <0x7ef01b00 0x200>,
                              <0x7ef01f00 0x400>,
                              <0x7ef00200 0x80>,
                              <0x7ef04300 0x100>,
                              <0x7ef20000 0x100>;
                        reg-names = "hdmi",
                                    "dvp",
                                    "phy",
                                    "rm",
                                    "packet",
                                    "metadata",
                                    "csc",
                                    "cec",
                                    "hd";
                        clock-names = "hdmi", "bvb", "audio", "cec";
                        resets = <&dvp 0>;
                        interrupt-parent = <&aon_intr>;
                        interrupts = <0>, <1>, <2>,
                                     <3>, <4>, <5>;
                        interrupt-names = "cec-tx", "cec-rx", "cec-low",
                                          "wakeup", "hpd-connected", "hpd-removed";
                        ddc = <&ddc0>;
                        dmas = <&dma 10>;
                        dma-names = "audio-rx";
                        status = "disabled";
                };

                ddc0: i2c@7ef04500 {
                        compatible = "brcm,bcm2711-hdmi-i2c";
                        reg = <0x7ef04500 0x100>, <0x7ef00b00 0x300>;
                        reg-names = "bsc", "auto-i2c";
                        clock-frequency = <97500>;
                        status = "disabled";
                };

                hdmi1: hdmi@7ef05700 {
                        compatible = "brcm,bcm2711-hdmi1";
                        reg = <0x7ef05700 0x300>,
                              <0x7ef05300 0x200>,
                              <0x7ef05f00 0x80>,
                              <0x7ef05f80 0x80>,
                              <0x7ef06b00 0x200>,
                              <0x7ef06f00 0x400>,
                              <0x7ef00280 0x80>,
                              <0x7ef09300 0x100>,
                              <0x7ef20000 0x100>;
                        reg-names = "hdmi",
                                    "dvp",
                                    "phy",
                                    "rm",
                                    "packet",
                                    "metadata",
                                    "csc",
                                    "cec",
                                    "hd";
                        ddc = <&ddc1>;
                        clock-names = "hdmi", "bvb", "audio", "cec";
                        resets = <&dvp 1>;
                        interrupt-parent = <&aon_intr>;
                        interrupts = <8>, <7>, <6>,
                                     <9>, <10>, <11>;
                        interrupt-names = "cec-tx", "cec-rx", "cec-low",
                                          "wakeup", "hpd-connected", "hpd-removed";
                        dmas = <&dma 17>;
                        dma-names = "audio-rx";
                        status = "disabled";
                };

                ddc1: i2c@7ef09500 {
                        compatible = "brcm,bcm2711-hdmi-i2c";
                        reg = <0x7ef09500 0x100>, <0x7ef05b00 0x300>;
                        reg-names = "bsc", "auto-i2c";
                        clock-frequency = <97500>;
                        status = "disabled";
                };
        };

        /*
         * emmc2 has different DMA constraints based on SoC revisions. It was
         * moved into its own bus, so as for RPi4's firmware to update them.
         * The firmware will find whether the emmc2bus alias is defined, and if
         * so, it'll edit the dma-ranges property below accordingly.
         */
        emmc2bus: emmc2bus {
                compatible = "simple-bus";
                #address-cells = <2>;
                #size-cells = <1>;

                ranges = <0x0 0x7e000000  0x0 0xfe000000  0x01800000>;
                dma-ranges = <0x0 0xc0000000  0x0 0x00000000  0x40000000>;

                emmc2: mmc@7e340000 {
                        compatible = "brcm,bcm2711-emmc2";
                        reg = <0x0 0x7e340000 0x100>;
                        interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>;
                        clocks = <&clocks BCM2711_CLOCK_EMMC2>;
                        status = "disabled";
                };
        };

        arm-pmu {
                compatible = "arm,cortex-a72-pmu", "arm,armv8-pmuv3";
                interrupts = <GIC_SPI 16 IRQ_TYPE_LEVEL_HIGH>,
                        <GIC_SPI 17 IRQ_TYPE_LEVEL_HIGH>,
                        <GIC_SPI 18 IRQ_TYPE_LEVEL_HIGH>,
                        <GIC_SPI 19 IRQ_TYPE_LEVEL_HIGH>;
                interrupt-affinity = <&cpu0>, <&cpu1>, <&cpu2>, <&cpu3>;
        };

        timer {
                compatible = "arm,armv8-timer";
                interrupts = <GIC_PPI 13 (GIC_CPU_MASK_SIMPLE(4) |
                                          IRQ_TYPE_LEVEL_LOW)>,
                             <GIC_PPI 14 (GIC_CPU_MASK_SIMPLE(4) |
                                          IRQ_TYPE_LEVEL_LOW)>,
                             <GIC_PPI 11 (GIC_CPU_MASK_SIMPLE(4) |
                                          IRQ_TYPE_LEVEL_LOW)>,
                             <GIC_PPI 10 (GIC_CPU_MASK_SIMPLE(4) |
                                          IRQ_TYPE_LEVEL_LOW)>;
                /* This only applies to the ARMv7 stub */
                arm,cpu-registers-not-fw-configured;
        };


        cpus: cpus {
                #address-cells = <1>;
                #size-cells = <0>;
                enable-method = "brcm,bcm2836-smp"; // for ARM 32-bit

                cpu0: cpu@0 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <0>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0x000000d8>;
                };

                cpu1: cpu@1 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <1>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0x000000e0>;
                };

                cpu2: cpu@2 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <2>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0x000000e8>;
                };

                cpu3: cpu@3 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <3>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0x000000f0>;
                };
        };

        scb {
                compatible = "simple-bus";
                #address-cells = <2>;
                #size-cells = <1>;

                ranges = <0x0 0x7c000000  0x0 0xfc000000  0x03800000>,
                         <0x6 0x00000000  0x6 0x00000000  0x40000000>;

                pcie0: pcie@7d500000 {
                        compatible = "brcm,bcm2711-pcie";
                        reg = <0x0 0x7d500000 0x9310>;
                        device_type = "pci";
                        #address-cells = <3>;
                        #interrupt-cells = <1>;
                        #size-cells = <2>;
                        interrupts = <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 148 IRQ_TYPE_LEVEL_HIGH>;
                        interrupt-names = "pcie", "msi";
                        interrupt-map-mask = <0x0 0x0 0x0 0x7>;
                        interrupt-map = <0 0 0 1 &gicv2 GIC_SPI 143
                                                        IRQ_TYPE_LEVEL_HIGH>;
                        msi-controller;
                        msi-parent = <&pcie0>;

                        ranges = <0x02000000 0x0 0xf8000000 0x6 0x00000000
                                  0x0 0x04000000>;
                        /*
                         * The wrapper around the PCIe block has a bug
                         * preventing it from accessing beyond the first 3GB of
                         * memory.
                         */
                        dma-ranges = <0x02000000 0x0 0x00000000 0x0 0x00000000
                                      0x0 0xc0000000>;
                        brcm,enable-ssc;
                };

                genet: ethernet@7d580000 {
                        compatible = "brcm,bcm2711-genet-v5";
                        reg = <0x0 0x7d580000 0x10000>;
                        #address-cells = <0x1>;
                        #size-cells = <0x1>;
                        interrupts = <GIC_SPI 157 IRQ_TYPE_LEVEL_HIGH>,
                                     <GIC_SPI 158 IRQ_TYPE_LEVEL_HIGH>;
                        status = "disabled";

                        genet_mdio: mdio@e14 {
                                compatible = "brcm,genet-mdio-v5";
                                reg = <0xe14 0x8>;
                                reg-names = "mdio";
                                #address-cells = <0x1>;
                                #size-cells = <0x0>;
                        };
                };
        };
};

&clk_osc {
        clock-frequency = <54000000>;
};

&clocks {
        compatible = "brcm,bcm2711-cprman";
};

&cpu_thermal {
        coefficients = <(-487) 410040>;
        thermal-sensors = <&thermal>;
};

&dsi0 {
        interrupts = <GIC_SPI 100 IRQ_TYPE_LEVEL_HIGH>;
};

&dsi1 {
        interrupts = <GIC_SPI 108 IRQ_TYPE_LEVEL_HIGH>;
        compatible = "brcm,bcm2711-dsi1";
};

&gpio {
        compatible = "brcm,bcm2711-gpio";
        interrupts = <GIC_SPI 113 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 114 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 115 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 116 IRQ_TYPE_LEVEL_HIGH>;

        gpclk0_gpio49: gpclk0_gpio49 {
                pin-gpclk {
                        pins = "gpio49";
                        function = "alt1";
                        bias-disable;
                };
        };
        gpclk1_gpio50: gpclk1_gpio50 {
                pin-gpclk {
                        pins = "gpio50";
                        function = "alt1";
                        bias-disable;
                };
        };
        gpclk2_gpio51: gpclk2_gpio51 {
                pin-gpclk {
                        pins = "gpio51";
                        function = "alt1";
                        bias-disable;
                };
        };

        i2c0_gpio46: i2c0_gpio46 {
                pin-sda {
                        function = "alt0";
                        pins = "gpio46";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt0";
                        pins = "gpio47";
                        bias-disable;
                };
        };
        i2c1_gpio46: i2c1_gpio46 {
                pin-sda {
                        function = "alt1";
                        pins = "gpio46";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt1";
                        pins = "gpio47";
                        bias-disable;
                };
        };
        i2c3_gpio2: i2c3_gpio2 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio2";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio3";
                        bias-disable;
                };
        };
        i2c3_gpio4: i2c3_gpio4 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio4";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio5";
                        bias-disable;
                };
        };
        i2c4_gpio6: i2c4_gpio6 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio6";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio7";
                        bias-disable;
                };
        };
        i2c4_gpio8: i2c4_gpio8 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio8";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio9";
                        bias-disable;
                };
        };
        i2c5_gpio10: i2c5_gpio10 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio10";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio11";
                        bias-disable;
                };
        };
        i2c5_gpio12: i2c5_gpio12 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio12";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio13";
                        bias-disable;
                };
        };
        i2c6_gpio0: i2c6_gpio0 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio0";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio1";
                        bias-disable;
                };
        };
        i2c6_gpio22: i2c6_gpio22 {
                pin-sda {
                        function = "alt5";
                        pins = "gpio22";
                        bias-pull-up;
                };
                pin-scl {
                        function = "alt5";
                        pins = "gpio23";
                        bias-disable;
                };
        };
        i2c_slave_gpio8: i2c_slave_gpio8 {
                pins-i2c-slave {
                        pins = "gpio8",
                               "gpio9",
                               "gpio10",
                               "gpio11";
                        function = "alt3";
                };
        };

        jtag_gpio48: jtag_gpio48 {
                pins-jtag {
                        pins = "gpio48",
                               "gpio49",
                               "gpio50",
                               "gpio51",
                               "gpio52",
                               "gpio53";
                        function = "alt4";
                };
        };

        mii_gpio28: mii_gpio28 {
                pins-mii {
                        pins = "gpio28",
                               "gpio29",
                               "gpio30",
                               "gpio31";
                        function = "alt4";
                };
        };
        mii_gpio36: mii_gpio36 {
                pins-mii {
                        pins = "gpio36",
                               "gpio37",
                               "gpio38",
                               "gpio39";
                        function = "alt5";
                };
        };

        pcm_gpio50: pcm_gpio50 {
                pins-pcm {
                        pins = "gpio50",
                               "gpio51",
                               "gpio52",
                               "gpio53";
                        function = "alt2";
                };
        };

        pwm0_0_gpio12: pwm0_0_gpio12 {
                pin-pwm {
                        pins = "gpio12";
                        function = "alt0";
                        bias-disable;
                };
        };
        pwm0_0_gpio18: pwm0_0_gpio18 {
                pin-pwm {
                        pins = "gpio18";
                        function = "alt5";
                        bias-disable;
                };
        };
        pwm1_0_gpio40: pwm1_0_gpio40 {
                pin-pwm {
                        pins = "gpio40";
                        function = "alt0";
                        bias-disable;
                };
        };
        pwm0_1_gpio13: pwm0_1_gpio13 {
                pin-pwm {
                        pins = "gpio13";
                        function = "alt0";
                        bias-disable;
                };
        };
        pwm0_1_gpio19: pwm0_1_gpio19 {
                pin-pwm {
                        pins = "gpio19";
                        function = "alt5";
                        bias-disable;
                };
        };
        pwm1_1_gpio41: pwm1_1_gpio41 {
                pin-pwm {
                        pins = "gpio41";
                        function = "alt0";
                        bias-disable;
                };
        };
        pwm0_1_gpio45: pwm0_1_gpio45 {
                pin-pwm {
                        pins = "gpio45";
                        function = "alt0";
                        bias-disable;
                };
        };
        pwm0_0_gpio52: pwm0_0_gpio52 {
                pin-pwm {
                        pins = "gpio52";
                        function = "alt1";
                        bias-disable;
                };
        };
        pwm0_1_gpio53: pwm0_1_gpio53 {
                pin-pwm {
                        pins = "gpio53";
                        function = "alt1";
                        bias-disable;
                };
        };

        rgmii_gpio35: rgmii_gpio35 {
                pin-start-stop {
                        pins = "gpio35";
                        function = "alt4";
                };
                pin-rx-ok {
                        pins = "gpio36";
                        function = "alt4";
                };
        };
        rgmii_irq_gpio34: rgmii_irq_gpio34 {
                pin-irq {
                        pins = "gpio34";
                        function = "alt5";
                };
        };
        rgmii_irq_gpio39: rgmii_irq_gpio39 {
                pin-irq {
                        pins = "gpio39";
                        function = "alt4";
                };
        };
        rgmii_mdio_gpio28: rgmii_mdio_gpio28 {
                pins-mdio {
                        pins = "gpio28",
                               "gpio29";
                        function = "alt5";
                };
        };
        rgmii_mdio_gpio37: rgmii_mdio_gpio37 {
                pins-mdio {
                        pins = "gpio37",
                               "gpio38";
                        function = "alt4";
                };
        };

        spi0_gpio46: spi0_gpio46 {
                pins-spi {
                        pins = "gpio46",
                               "gpio47",
                               "gpio48",
                               "gpio49";
                        function = "alt2";
                };
        };
        spi2_gpio46: spi2_gpio46 {
                pins-spi {
                        pins = "gpio46",
                               "gpio47",
                               "gpio48",
                               "gpio49",
                               "gpio50";
                        function = "alt5";
                };
        };
        spi3_gpio0: spi3_gpio0 {
                pins-spi {
                        pins = "gpio0",
                               "gpio1",
                               "gpio2",
                               "gpio3";
                        function = "alt3";
                };
        };
        spi4_gpio4: spi4_gpio4 {
                pins-spi {
                        pins = "gpio4",
                               "gpio5",
                               "gpio6",
                               "gpio7";
                        function = "alt3";
                };
        };
        spi5_gpio12: spi5_gpio12 {
                pins-spi {
                        pins = "gpio12",
                               "gpio13",
                               "gpio14",
                               "gpio15";
                        function = "alt3";
                };
        };
        spi6_gpio18: spi6_gpio18 {
                pins-spi {
                        pins = "gpio18",
                               "gpio19",
                               "gpio20",
                               "gpio21";
                        function = "alt3";
                };
        };

        uart2_gpio0: uart2_gpio0 {
                pin-tx {
                        pins = "gpio0";
                        function = "alt4";
                        bias-disable;
                };
                pin-rx {
                        pins = "gpio1";
                        function = "alt4";
                        bias-pull-up;
                };
        };
        uart2_ctsrts_gpio2: uart2_ctsrts_gpio2 {
                pin-cts {
                        pins = "gpio2";
                        function = "alt4";
                        bias-pull-up;
                };
                pin-rts {
                        pins = "gpio3";
                        function = "alt4";
                        bias-disable;
                };
        };
        uart3_gpio4: uart3_gpio4 {
                pin-tx {
                        pins = "gpio4";
                        function = "alt4";
                        bias-disable;
                };
                pin-rx {
                        pins = "gpio5";
                        function = "alt4";
                        bias-pull-up;
                };
        };
        uart3_ctsrts_gpio6: uart3_ctsrts_gpio6 {
                pin-cts {
                        pins = "gpio6";
                        function = "alt4";
                        bias-pull-up;
                };
                pin-rts {
                        pins = "gpio7";
                        function = "alt4";
                        bias-disable;
                };
        };
        uart4_gpio8: uart4_gpio8 {
                pin-tx {
                        pins = "gpio8";
                        function = "alt4";
                        bias-disable;
                };
                pin-rx {
                        pins = "gpio9";
                        function = "alt4";
                        bias-pull-up;
                };
        };
        uart4_ctsrts_gpio10: uart4_ctsrts_gpio10 {
                pin-cts {
                        pins = "gpio10";
                        function = "alt4";
                        bias-pull-up;
                };
                pin-rts {
                        pins = "gpio11";
                        function = "alt4";
                        bias-disable;
                };
        };
        uart5_gpio12: uart5_gpio12 {
                pin-tx {
                        pins = "gpio12";
                        function = "alt4";
                        bias-disable;
                };
                pin-rx {
                        pins = "gpio13";
                        function = "alt4";
                        bias-pull-up;
                };
        };
        uart5_ctsrts_gpio14: uart5_ctsrts_gpio14 {
                pin-cts {
                        pins = "gpio14";
                        function = "alt4";
                        bias-pull-up;
                };
                pin-rts {
                        pins = "gpio15";
                        function = "alt4";
                        bias-disable;
                };
        };
};

&rmem {
        #address-cells = <2>;
};

&cma {
        /*
         * arm64 reserves the CMA by default somewhere in ZONE_DMA32,
         * that's not good enough for the BCM2711 as some devices can
         * only address the lower 1G of memory (ZONE_DMA).
         */
        alloc-ranges = <0x0 0x00000000 0x40000000>;
};

&i2c0 {
        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
};

&i2c1 {
        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
        interrupts = <GIC_SPI 117 IRQ_TYPE_LEVEL_HIGH>;
};

&mailbox {
        interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
};

&sdhci {
        interrupts = <GIC_SPI 126 IRQ_TYPE_LEVEL_HIGH>;
};

&sdhost {
        interrupts = <GIC_SPI 120 IRQ_TYPE_LEVEL_HIGH>;
};

&spi {
        interrupts = <GIC_SPI 118 IRQ_TYPE_LEVEL_HIGH>;
};

&spi1 {
        interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
};

&spi2 {
        interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
};

&system_timer {
        interrupts = <GIC_SPI 64 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 65 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 66 IRQ_TYPE_LEVEL_HIGH>,
                     <GIC_SPI 67 IRQ_TYPE_LEVEL_HIGH>;
};

&txp {
        interrupts = <GIC_SPI 75 IRQ_TYPE_LEVEL_HIGH>;
};

&uart0 {
        interrupts = <GIC_SPI 121 IRQ_TYPE_LEVEL_HIGH>;
};

&uart1 {
        interrupts = <GIC_SPI 93 IRQ_TYPE_LEVEL_HIGH>;
};

&usb {
        interrupts = <GIC_SPI 73 IRQ_TYPE_LEVEL_HIGH>;
};

&vec {
        compatible = "brcm,bcm2711-vec";
        interrupts = <GIC_SPI 123 IRQ_TYPE_LEVEL_HIGH>;
};

 

(3) bcm2835-rpi.dtsi

arch/arm/boot/dts/bcm2835-rpi.dtsi

#include <dt-bindings/power/raspberrypi-power.h>

/ {
        leds {
                compatible = "gpio-leds";

                led-act {
                        label = "ACT";
                        default-state = "keep";
                        linux,default-trigger = "heartbeat";
                };
        };

        soc {
                firmware: firmware {
                        compatible = "raspberrypi,bcm2835-firmware", "simple-mfd";
                        #address-cells = <1>;
                        #size-cells = <1>;

                        mboxes = <&mailbox>;
                        dma-ranges;
                };

                power: power {
                        compatible = "raspberrypi,bcm2835-power";
                        firmware = <&firmware>;
                        #power-domain-cells = <1>;
                };

                vchiq: mailbox@7e00b840 {
                        compatible = "brcm,bcm2835-vchiq";
                        reg = <0x7e00b840 0x3c>;
                        interrupts = <0 2>;
                };
        };
};

&gpio {
        pinctrl-names = "default";

        gpioout: gpioout {
                brcm,pins = <6>;
                brcm,function = <BCM2835_FSEL_GPIO_OUT>;
        };

        alt0: alt0 {
                brcm,pins = <4 5 7 8 9 10 11>;
                brcm,function = <BCM2835_FSEL_ALT0>;
        };
};

&i2c0 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c0_gpio0>;
        status = "okay";
        clock-frequency = <100000>;
};

&i2c1 {
        pinctrl-names = "default";
        pinctrl-0 = <&i2c1_gpio2>;
        status = "okay";
        clock-frequency = <100000>;
};

&usb {
        power-domains = <&power RPI_POWER_DOMAIN_USB>;
};

&vec {
        power-domains = <&power RPI_POWER_DOMAIN_VEC>;
        status = "okay";
};

&dsi0 {
        power-domains = <&power RPI_POWER_DOMAIN_DSI0>;
};

&dsi1 {
        power-domains = <&power RPI_POWER_DOMAIN_DSI1>;
};

 

(4) bcm2711-rpi.dtsi

arch/arm/boot/dts/bcm2711-rpi.dtsi

// SPDX-License-Identifier: GPL-2.0
#include "bcm2835-rpi.dtsi"

#include <dt-bindings/reset/raspberrypi,firmware-reset.h>

/ {
        /* Will be filled by the bootloader */
        memory@0 {
                device_type = "memory";
                reg = <0 0 0>;
        };

        aliases {
                emmc2bus = &emmc2bus;
                ethernet0 = &genet;
                pcie0 = &pcie0;
                blconfig = &blconfig;
        };
};

&firmware {
        firmware_clocks: clocks {
                compatible = "raspberrypi,firmware-clocks";
                #clock-cells = <1>;
        };

        expgpio: gpio {
                compatible = "raspberrypi,firmware-gpio";
                gpio-controller;
                #gpio-cells = <2>;
                status = "okay";
        };

        reset: reset {
                compatible = "raspberrypi,firmware-reset";
                #reset-cells = <1>;
        };
};

&hdmi0 {
        clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 0>, <&clk_27MHz>;
        clock-names = "hdmi", "bvb", "audio", "cec";
        wifi-2.4ghz-coexistence;
};

&hdmi1 {
        clocks = <&firmware_clocks 13>, <&firmware_clocks 14>, <&dvp 1>, <&clk_27MHz>;
        clock-names = "hdmi", "bvb", "audio", "cec";
        wifi-2.4ghz-coexistence;
};

&hvs {
        clocks = <&firmware_clocks 4>;
};

&rmem {
        /*
         * RPi4's co-processor will copy the board's bootloader configuration
         * into memory for the OS to consume. It'll also update this node with
         * its placement information.
         */
        blconfig: nvram@0 {
                compatible = "raspberrypi,bootloader-config", "nvmem-rmem";
                #address-cells = <1>;
                #size-cells = <1>;
                reg = <0x0 0x0 0x0>;
                no-map;
                status = "disabled";
        };
};

&vchiq {
        interrupts = <GIC_SPI 34 IRQ_TYPE_LEVEL_HIGH>;
};

 

(5) bcm283x-rpi-usb-peripheral.dtsi

arch/arm/boot/dts/bcm283x-rpi-usb-peripheral.dtsi

// SPDX-License-Identifier: GPL-2.0
&usb {
        dr_mode = "peripheral";
        g-rx-fifo-size = <256>;
        g-np-tx-fifo-size = <32>;
        g-tx-fifo-size = <256 256 512 512 512 768 768>;
};

 

(6) bcm2711-rpi-4-b.dts

arch/arm/boot/dts/bcm2711-rpi-4-b.dts

// SPDX-License-Identifier: GPL-2.0
/dts-v1/;
#include "bcm2711.dtsi"
#include "bcm2711-rpi.dtsi"
#include "bcm283x-rpi-usb-peripheral.dtsi"

/ {
        compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
        model = "Raspberry Pi 4 Model B";

        chosen {
                /* 8250 auxiliary UART instead of pl011 */
                stdout-path = "serial1:115200n8";
        };

        leds {
                led-act {
                        gpios = <&gpio 42 GPIO_ACTIVE_HIGH>;
                };

                led-pwr {
                        label = "PWR";
                        gpios = <&expgpio 2 GPIO_ACTIVE_LOW>;
                        default-state = "keep";
                        linux,default-trigger = "default-on";
                };
        };

        wifi_pwrseq: wifi-pwrseq {
                compatible = "mmc-pwrseq-simple";
                reset-gpios = <&expgpio 1 GPIO_ACTIVE_LOW>;
        };

        sd_io_1v8_reg: sd_io_1v8_reg {
                compatible = "regulator-gpio";
                regulator-name = "vdd-sd-io";
                regulator-min-microvolt = <1800000>;
                regulator-max-microvolt = <3300000>;
                regulator-boot-on;
                regulator-always-on;
                regulator-settling-time-us = <5000>;
                gpios = <&expgpio 4 GPIO_ACTIVE_HIGH>;
                states = <1800000 0x1>,
                         <3300000 0x0>;
                status = "okay";
        };

        sd_vcc_reg: sd_vcc_reg {
                compatible = "regulator-fixed";
                regulator-name = "vcc-sd";
                regulator-min-microvolt = <3300000>;
                regulator-max-microvolt = <3300000>;
                regulator-boot-on;
                enable-active-high;
                gpio = <&expgpio 6 GPIO_ACTIVE_HIGH>;
        };
};

&ddc0 {
        status = "okay";
};

&ddc1 {
        status = "okay";
};

expgpio {
        gpio-line-names = "BT_ON",
                          "WL_ON",
                          "PWR_LED_OFF",
                          "GLOBAL_RESET",
                          "VDD_SD_IO_SEL",
                          "CAM_GPIO",
                          "SD_PWR_ON",
                          "";
};

&gpio {
        /*
         * Parts taken from rpi_SCH_4b_4p0_reduced.pdf and
         * the official GPU firmware DT blob.
         *
         * Legend:
         * "FOO" = GPIO line named "FOO" on the schematic
         * "FOO_N" = GPIO line named "FOO" on schematic, active low
         */
        gpio-line-names = "ID_SDA",
                          "ID_SCL",
                          "SDA1",
                          "SCL1",
                          "GPIO_GCLK",
                          "GPIO5",
                          "GPIO6",
                          "SPI_CE1_N",
                          "SPI_CE0_N",
                          "SPI_MISO",
                          "SPI_MOSI",
                          "SPI_SCLK",
                          "GPIO12",
                          "GPIO13",
                          /* Serial port */
                          "TXD1",
                          "RXD1",
                          "GPIO16",
                          "GPIO17",
                          "GPIO18",
                          "GPIO19",
                          "GPIO20",
                          "GPIO21",
                          "GPIO22",
                          "GPIO23",
                          "GPIO24",
                          "GPIO25",
                          "GPIO26",
                          "GPIO27",
                          "RGMII_MDIO",
                          "RGMIO_MDC",
                          /* Used by BT module */
                          "CTS0",
                          "RTS0",
                          "TXD0",
                          "RXD0",
                          /* Used by Wifi */
                          "SD1_CLK",
                          "SD1_CMD",
                          "SD1_DATA0",
                          "SD1_DATA1",
                          "SD1_DATA2",
                          "SD1_DATA3",
                          /* Shared with SPI flash */
                          "PWM0_MISO",
                          "PWM1_MOSI",
                          "STATUS_LED_G_CLK",
                          "SPIFLASH_CE_N",
                          "SDA0",
                          "SCL0",
                          "RGMII_RXCLK",
                          "RGMII_RXCTL",
                          "RGMII_RXD0",
                          "RGMII_RXD1",
                          "RGMII_RXD2",
                          "RGMII_RXD3",
                          "RGMII_TXCLK",
                          "RGMII_TXCTL",
                          "RGMII_TXD0",
                          "RGMII_TXD1",
                          "RGMII_TXD2",
                          "RGMII_TXD3";
};

&hdmi0 {
        status = "okay";
};

&hdmi1 {
        status = "okay";
};

&pixelvalve0 {
        status = "okay";
};

&pixelvalve1 {
        status = "okay";
};

&pixelvalve2 {
        status = "okay";
};

&pixelvalve4 {
        status = "okay";
};

&pwm1 {
        pinctrl-names = "default";
        pinctrl-0 = <&pwm1_0_gpio40 &pwm1_1_gpio41>;
        status = "okay";
};

/* SDHCI is used to control the SDIO for wireless */
&sdhci {
        #address-cells = <1>;
        #size-cells = <0>;
        pinctrl-names = "default";
        pinctrl-0 = <&emmc_gpio34>;
        bus-width = <4>;
        non-removable;
        mmc-pwrseq = <&wifi_pwrseq>;
        status = "okay";

        brcmf: wifi@1 {
                reg = <1>;
                compatible = "brcm,bcm4329-fmac";
        };
};

/* EMMC2 is used to drive the SD card */
&emmc2 {
        vqmmc-supply = <&sd_io_1v8_reg>;
        vmmc-supply = <&sd_vcc_reg>;
        broken-cd;
        status = "okay";
};

&genet {
        phy-handle = <&phy1>;
        phy-mode = "rgmii-rxid";
        status = "okay";
};

&genet_mdio {
        phy1: ethernet-phy@1 {
                /* No PHY interrupt */
                reg = <0x1>;
        };
};

&pcie0 {
        pci@0,0 {
                device_type = "pci";
                #address-cells = <3>;
                #size-cells = <2>;
                ranges;

                reg = <0 0 0 0 0>;

                usb@0,0 {
                        reg = <0 0 0 0 0>;
                        resets = <&reset RASPBERRYPI_FIRMWARE_RESET_ID_USB>;
                };
        };
};

/* uart0 communicates with the BT module */
&uart0 {
        pinctrl-names = "default";
        pinctrl-0 = <&uart0_ctsrts_gpio30 &uart0_gpio32>;
        uart-has-rtscts;
        status = "okay";

        bluetooth {
                compatible = "brcm,bcm43438-bt";
                max-speed = <2000000>;
                shutdown-gpios = <&expgpio 0 GPIO_ACTIVE_HIGH>;
        };
};

/* uart1 is mapped to the pin header */
&uart1 {
        pinctrl-names = "default";
        pinctrl-0 = <&uart1_gpio14>;
        status = "okay";
};

&vc4 {
        status = "okay";
};

&vec {
        status = "disabled";
};

 

(7) bcm2711-rpi-4-b.dts

arch/arm64/boot/dts/bcm2711-rpi-4-b.dts

// SPDX-License-Identifier: GPL-2.0
#include "arm/bcm2711-rpi-4-b.dts"

 


통합 Script

위의 디바이스 트리들을 dtc로 컴파일하면 하나의 스크립트로 병합시킨 모습을 보여줄 수 있다.

  • dtc bcm2711-rpi-4-b.dts -O dtb > a.dtsi

 

/dts-v1/;

/memreserve/    0x0000000000000000 0x0000000000001000;
/ {
        compatible = "raspberrypi,4-model-b", "brcm,bcm2711";
        model = "Raspberry Pi 4 Model B";
        #address-cells = <0x2>;
        #size-cells = <0x1>;
        interrupt-parent = <0x1>;

        aliases {
                serial0 = "/soc/serial@7e201000";
                serial1 = "/soc/serial@7e215040";
                emmc2bus = "/emmc2bus";
                ethernet0 = "/scb/ethernet@7d580000";
                pcie0 = "/scb/pcie@7d500000";
                blconfig = "/reserved-memory/nvram@0";
        };

        chosen {
                stdout-path = "serial1:115200n8";
        };

        reserved-memory {
                #address-cells = <0x2>;
                #size-cells = <0x1>;
                ranges;

                linux,cma {
                        compatible = "shared-dma-pool";
                        size = <0x4000000>;
                        reusable;
                        linux,cma-default;
                        alloc-ranges = <0x0 0x0 0x40000000>;
                };

                nvram@0 {
                        compatible = "raspberrypi,bootloader-config", "nvmem-rmem";
                        #address-cells = <0x1>;
                        #size-cells = <0x1>;
                        reg = <0x0 0x0 0x0>;
                        no-map;
                        status = "disabled";
                };
        };

        thermal-zones {

                cpu-thermal {
                        polling-delay-passive = <0x0>;
                        polling-delay = <0x3e8>;
                        coefficients = <0xfffffe19 0x641b8>;
                        thermal-sensors = <0x2>;

                        trips {

                                cpu-crit {
                                        temperature = <0x15f90>;
                                        hysteresis = <0x0>;
                                        type = "critical";
                                };
                        };

                        cooling-maps {
                        };
                };
        };

        soc {
                compatible = "simple-bus";
                #address-cells = <0x1>;
                #size-cells = <0x1>;
                ranges = <0x7e000000 0x0 0xfe000000 0x1800000 0x7c000000 0x0 0xfc000000 0x2000000 0x40000000 0x0 0xff800000 0x800000>;
                dma-ranges = <0xc0000000 0x0 0x0 0x40000000>;

                timer@7e003000 {
                        compatible = "brcm,bcm2835-system-timer";
                        reg = <0x7e003000 0x1000>;
                        interrupts = <0x0 0x40 0x4 0x0 0x41 0x4 0x0 0x42 0x4 0x0 0x43 0x4>;
                        clock-frequency = <0xf4240>;
                };

                txp@7e004000 {
                        compatible = "brcm,bcm2835-txp";
                        reg = <0x7e004000 0x20>;
                        interrupts = <0x0 0x4b 0x4>;
                };

                cprman@7e101000 {
                        compatible = "brcm,bcm2711-cprman";
                        #clock-cells = <0x1>;
                        reg = <0x7e101000 0x2000>;
                        clocks = <0x3 0x4 0x0 0x4 0x1 0x4 0x2 0x5 0x0 0x5 0x1 0x5 0x2>;
                        phandle = <0x6>;
                };

                mailbox@7e00b880 {
                        compatible = "brcm,bcm2835-mbox";
                        reg = <0x7e00b880 0x40>;
                        interrupts = <0x0 0x21 0x4>;
                        #mbox-cells = <0x0>;
                        phandle = <0x1d>;
                };

                gpio@7e200000 {
                        compatible = "brcm,bcm2711-gpio";
                        reg = <0x7e200000 0xb4>;
                        interrupts = <0x0 0x71 0x4 0x0 0x72 0x4 0x0 0x73 0x4 0x0 0x74 0x4>;
                        gpio-controller;
                        #gpio-cells = <0x2>;
                        interrupt-controller;
                        #interrupt-cells = <0x2>;
                        pinctrl-names = "default";
                        gpio-line-names = "ID_SDA", "ID_SCL", "SDA1", "SCL1", "GPIO_GCLK", "GPIO5", "GPIO6", "SPI_CE1_N", "SPI_CE0_N", "SPI_MISO", "SPI_MOSI", "SPI_SCLK", "GPIO12", "GPIO13", "TXD1", "RXD1", "GPIO16", "GPIO17", "GPIO18", "GPIO19", "GPIO20", "GPIO21", "GPIO22", "GPIO23", "GPIO24", "GPIO25", "GPIO26", "GPIO27", "RGMII_MDIO", "RGMIO_MDC", "CTS0", "RTS0", "TXD0", "RXD0", "SD1_CLK", "SD1_CMD", "SD1_DATA0", "SD1_DATA1", "SD1_DATA2", "SD1_DATA3", "PWM0_MISO", "PWM1_MOSI", "STATUS_LED_G_CLK", "SPIFLASH_CE_N", "SDA0", "SCL0", "RGMII_RXCLK", "RGMII_RXCTL", "RGMII_RXD0", "RGMII_RXD1", "RGMII_RXD2", "RGMII_RXD3", "RGMII_TXCLK", "RGMII_TXCTL", "RGMII_TXD0", "RGMII_TXD1", "RGMII_TXD2", "RGMII_TXD3";
                        phandle = <0x28>;

                        dpi_gpio0 {
                                brcm,pins = <0x0 0x1 0x2 0x3 0x4 0x5 0x6 0x7 0x8 0x9 0xa 0xb 0xc 0xd 0xe 0xf 0x10 0x11 0x12 0x13 0x14 0x15 0x16 0x17 0x18 0x19 0x1a 0x1b>;
                                brcm,function = <0x6>;
                        };

                        emmc_gpio22 {
                                brcm,pins = <0x16 0x17 0x18 0x19 0x1a 0x1b>;
                                brcm,function = <0x7>;
                        };

                        emmc_gpio34 {
                                brcm,pins = <0x22 0x23 0x24 0x25 0x26 0x27>;
                                brcm,function = <0x7>;
                                brcm,pull = <0x0 0x2 0x2 0x2 0x2 0x2>;
                                phandle = <0xe>;
                        };

                        emmc_gpio48 {
                                brcm,pins = <0x30 0x31 0x32 0x33 0x34 0x35>;
                                brcm,function = <0x7>;
                        };

                        gpclk0_gpio4 {
                                brcm,pins = <0x4>;
                                brcm,function = <0x4>;
                        };

                        gpclk1_gpio5 {
                                brcm,pins = <0x5>;
                                brcm,function = <0x4>;
                        };

                        gpclk1_gpio42 {
                                brcm,pins = <0x2a>;
                                brcm,function = <0x4>;
                        };

                        gpclk1_gpio44 {
                                brcm,pins = <0x2c>;
                                brcm,function = <0x4>;
                        };

                        gpclk2_gpio6 {
                                brcm,pins = <0x6>;
                                brcm,function = <0x4>;
                        };

                        gpclk2_gpio43 {
                                brcm,pins = <0x2b>;
                                brcm,function = <0x4>;
                                brcm,pull = <0x0>;
                        };

                        i2c0_gpio0 {
                                brcm,pins = <0x0 0x1>;
                                brcm,function = <0x4>;
                                phandle = <0xa>;
                        };

                        i2c0_gpio28 {
                                brcm,pins = <0x1c 0x1d>;
                                brcm,function = <0x4>;
                        };

                        i2c0_gpio44 {
                                brcm,pins = <0x2c 0x2d>;
                                brcm,function = <0x5>;
                        };

                        i2c1_gpio2 {
                                brcm,pins = <0x2 0x3>;
                                brcm,function = <0x4>;
                                phandle = <0x11>;
                        };

                        i2c1_gpio44 {
                                brcm,pins = <0x2c 0x2d>;
                                brcm,function = <0x6>;
                        };

                        jtag_gpio22 {
                                brcm,pins = <0x16 0x17 0x18 0x19 0x1a 0x1b>;
                                brcm,function = <0x3>;
                        };

                        pcm_gpio18 {
                                brcm,pins = <0x12 0x13 0x14 0x15>;
                                brcm,function = <0x4>;
                        };

                        pcm_gpio28 {
                                brcm,pins = <0x1c 0x1d 0x1e 0x1f>;
                                brcm,function = <0x6>;
                        };

                        sdhost_gpio48 {
                                brcm,pins = <0x30 0x31 0x32 0x33 0x34 0x35>;
                                brcm,function = <0x4>;
                        };

                        spi0_gpio7 {
                                brcm,pins = <0x7 0x8 0x9 0xa 0xb>;
                                brcm,function = <0x4>;
                        };

                        spi0_gpio35 {
                                brcm,pins = <0x23 0x24 0x25 0x26 0x27>;
                                brcm,function = <0x4>;
                        };

                        spi1_gpio16 {
                                brcm,pins = <0x10 0x11 0x12 0x13 0x14 0x15>;
                                brcm,function = <0x3>;
                        };

                        spi2_gpio40 {
                                brcm,pins = <0x28 0x29 0x2a 0x2b 0x2c 0x2d>;
                                brcm,function = <0x3>;
                        };

                        uart0_gpio14 {
                                brcm,pins = <0xe 0xf>;
                                brcm,function = <0x4>;
                        };

                        uart0_ctsrts_gpio16 {
                                brcm,pins = <0x10 0x11>;
                                brcm,function = <0x7>;
                        };

                        uart0_ctsrts_gpio30 {
                                brcm,pins = <0x1e 0x1f>;
                                brcm,function = <0x7>;
                                brcm,pull = <0x2 0x0>;
                                phandle = <0x7>;
                        };

                        uart0_gpio32 {
                                brcm,pins = <0x20 0x21>;
                                brcm,function = <0x7>;
                                brcm,pull = <0x0 0x2>;
                                phandle = <0x8>;
                        };

                        uart0_gpio36 {
                                brcm,pins = <0x24 0x25>;
                                brcm,function = <0x6>;
                        };

                        uart0_ctsrts_gpio38 {
                                brcm,pins = <0x26 0x27>;
                                brcm,function = <0x6>;
                        };

                        uart1_gpio14 {
                                brcm,pins = <0xe 0xf>;
                                brcm,function = <0x2>;
                                phandle = <0xd>;
                        };

                        uart1_ctsrts_gpio16 {
                                brcm,pins = <0x10 0x11>;
                                brcm,function = <0x2>;
                        };

                        uart1_gpio32 {
                                brcm,pins = <0x20 0x21>;
                                brcm,function = <0x2>;
                        };

                        uart1_ctsrts_gpio30 {
                                brcm,pins = <0x1e 0x1f>;
                                brcm,function = <0x2>;
                        };

                        uart1_gpio40 {
                                brcm,pins = <0x28 0x29>;
                                brcm,function = <0x2>;
                        };

                        uart1_ctsrts_gpio42 {
                                brcm,pins = <0x2a 0x2b>;
                                brcm,function = <0x2>;
                        };

                        gpclk0_gpio49 {

                                pin-gpclk {
                                        pins = "gpio49";
                                        function = "alt1";
                                        bias-disable;
                                };
                        };

                        gpclk1_gpio50 {

                                pin-gpclk {
                                        pins = "gpio50";
                                        function = "alt1";
                                        bias-disable;
                                };
                        };

                        gpclk2_gpio51 {

                                pin-gpclk {
                                        pins = "gpio51";
                                        function = "alt1";
                                        bias-disable;
                                };
                        };

                        i2c0_gpio46 {

                                pin-sda {
                                        function = "alt0";
                                        pins = "gpio46";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt0";
                                        pins = "gpio47";
                                        bias-disable;
                                };
                        };

                        i2c1_gpio46 {

                                pin-sda {
                                        function = "alt1";
                                        pins = "gpio46";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt1";
                                        pins = "gpio47";
                                        bias-disable;
                                };
                        };

                        i2c3_gpio2 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio2";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio3";
                                        bias-disable;
                                };
                        };

                        i2c3_gpio4 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio4";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio5";
                                        bias-disable;
                                };
                        };

                        i2c4_gpio6 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio6";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio7";
                                        bias-disable;
                                };
                        };

                        i2c4_gpio8 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio8";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio9";
                                        bias-disable;
                                };
                        };

                        i2c5_gpio10 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio10";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio11";
                                        bias-disable;
                                };
                        };

                        i2c5_gpio12 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio12";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio13";
                                        bias-disable;
                                };
                        };

                        i2c6_gpio0 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio0";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio1";
                                        bias-disable;
                                };
                        };

                        i2c6_gpio22 {

                                pin-sda {
                                        function = "alt5";
                                        pins = "gpio22";
                                        bias-pull-up;
                                };

                                pin-scl {
                                        function = "alt5";
                                        pins = "gpio23";
                                        bias-disable;
                                };
                        };

                        i2c_slave_gpio8 {

                                pins-i2c-slave {
                                        pins = "gpio8", "gpio9", "gpio10", "gpio11";
                                        function = "alt3";
                                };
                        };

                        jtag_gpio48 {

                                pins-jtag {
                                        pins = "gpio48", "gpio49", "gpio50", "gpio51", "gpio52", "gpio53";
                                        function = "alt4";
                                };
                        };

                        mii_gpio28 {

                                pins-mii {
                                        pins = "gpio28", "gpio29", "gpio30", "gpio31";
                                        function = "alt4";
                                };
                        };

                        mii_gpio36 {

                                pins-mii {
                                        pins = "gpio36", "gpio37", "gpio38", "gpio39";
                                        function = "alt5";
                                };
                        };

                        pcm_gpio50 {

                                pins-pcm {
                                        pins = "gpio50", "gpio51", "gpio52", "gpio53";
                                        function = "alt2";
                                };
                        };

                        pwm0_0_gpio12 {

                                pin-pwm {
                                        pins = "gpio12";
                                        function = "alt0";
                                        bias-disable;
                                };
                        };

                        pwm0_0_gpio18 {

                                pin-pwm {
                                        pins = "gpio18";
                                        function = "alt5";
                                        bias-disable;
                                };
                        };

                        pwm1_0_gpio40 {
                                phandle = <0x14>;

                                pin-pwm {
                                        pins = "gpio40";
                                        function = "alt0";
                                        bias-disable;
                                };
                        };

                        pwm0_1_gpio13 {

                                pin-pwm {
                                        pins = "gpio13";
                                        function = "alt0";
                                        bias-disable;
                                };
                        };

                        pwm0_1_gpio19 {

                                pin-pwm {
                                        pins = "gpio19";
                                        function = "alt5";
                                        bias-disable;
                                };
                        };

                        pwm1_1_gpio41 {
                                phandle = <0x15>;

                                pin-pwm {
                                        pins = "gpio41";
                                        function = "alt0";
                                        bias-disable;
                                };
                        };

                        pwm0_1_gpio45 {

                                pin-pwm {
                                        pins = "gpio45";
                                        function = "alt0";
                                        bias-disable;
                                };
                        };

                        pwm0_0_gpio52 {

                                pin-pwm {
                                        pins = "gpio52";
                                        function = "alt1";
                                        bias-disable;
                                };
                        };

                        pwm0_1_gpio53 {

                                pin-pwm {
                                        pins = "gpio53";
                                        function = "alt1";
                                        bias-disable;
                                };
                        };

                        rgmii_gpio35 {

                                pin-start-stop {
                                        pins = "gpio35";
                                        function = "alt4";
                                };

                                pin-rx-ok {
                                        pins = "gpio36";
                                        function = "alt4";
                                };
                        };

                        rgmii_irq_gpio34 {

                                pin-irq {
                                        pins = "gpio34";
                                        function = "alt5";
                                };
                        };

                        rgmii_irq_gpio39 {

                                pin-irq {
                                        pins = "gpio39";
                                        function = "alt4";
                                };
                        };

                        rgmii_mdio_gpio28 {

                                pins-mdio {
                                        pins = "gpio28", "gpio29";
                                        function = "alt5";
                                };
                        };

                        rgmii_mdio_gpio37 {

                                pins-mdio {
                                        pins = "gpio37", "gpio38";
                                        function = "alt4";
                                };
                        };

                        spi0_gpio46 {

                                pins-spi {
                                        pins = "gpio46", "gpio47", "gpio48", "gpio49";
                                        function = "alt2";
                                };
                        };

                        spi2_gpio46 {

                                pins-spi {
                                        pins = "gpio46", "gpio47", "gpio48", "gpio49", "gpio50";
                                        function = "alt5";
                                };
                        };

                        spi3_gpio0 {

                                pins-spi {
                                        pins = "gpio0", "gpio1", "gpio2", "gpio3";
                                        function = "alt3";
                                };
                        };

                        spi4_gpio4 {

                                pins-spi {
                                        pins = "gpio4", "gpio5", "gpio6", "gpio7";
                                        function = "alt3";
                                };
                        };

                        spi5_gpio12 {

                                pins-spi {
                                        pins = "gpio12", "gpio13", "gpio14", "gpio15";
                                        function = "alt3";
                                };
                        };

                        spi6_gpio18 {

                                pins-spi {
                                        pins = "gpio18", "gpio19", "gpio20", "gpio21";
                                        function = "alt3";
                                };
                        };

                        uart2_gpio0 {

                                pin-tx {
                                        pins = "gpio0";
                                        function = "alt4";
                                        bias-disable;
                                };

                                pin-rx {
                                        pins = "gpio1";
                                        function = "alt4";
                                        bias-pull-up;
                                };
                        };

                        uart2_ctsrts_gpio2 {

                                pin-cts {
                                        pins = "gpio2";
                                        function = "alt4";
                                        bias-pull-up;
                                };

                                pin-rts {
                                        pins = "gpio3";
                                        function = "alt4";
                                        bias-disable;
                                };
                        };

                        uart3_gpio4 {

                                pin-tx {
                                        pins = "gpio4";
                                        function = "alt4";
                                        bias-disable;
                                };

                                pin-rx {
                                        pins = "gpio5";
                                        function = "alt4";
                                        bias-pull-up;
                                };
                        };

                        uart3_ctsrts_gpio6 {

                                pin-cts {
                                        pins = "gpio6";
                                        function = "alt4";
                                        bias-pull-up;
                                };

                                pin-rts {
                                        pins = "gpio7";
                                        function = "alt4";
                                        bias-disable;
                                };
                        };

                        uart4_gpio8 {

                                pin-tx {
                                        pins = "gpio8";
                                        function = "alt4";
                                        bias-disable;
                                };

                                pin-rx {
                                        pins = "gpio9";
                                        function = "alt4";
                                        bias-pull-up;
                                };
                        };

                        uart4_ctsrts_gpio10 {

                                pin-cts {
                                        pins = "gpio10";
                                        function = "alt4";
                                        bias-pull-up;
                                };

                                pin-rts {
                                        pins = "gpio11";
                                        function = "alt4";
                                        bias-disable;
                                };
                        };

                        uart5_gpio12 {

                                pin-tx {
                                        pins = "gpio12";
                                        function = "alt4";
                                        bias-disable;
                                };

                                pin-rx {
                                        pins = "gpio13";
                                        function = "alt4";
                                        bias-pull-up;
                                };
                        };

                        uart5_ctsrts_gpio14 {

                                pin-cts {
                                        pins = "gpio14";
                                        function = "alt4";
                                        bias-pull-up;
                                };

                                pin-rts {
                                        pins = "gpio15";
                                        function = "alt4";
                                        bias-disable;
                                };
                        };

                        gpioout {
                                brcm,pins = <0x6>;
                                brcm,function = <0x1>;
                        };

                        alt0 {
                                brcm,pins = <0x4 0x5 0x7 0x8 0x9 0xa 0xb>;
                                brcm,function = <0x4>;
                        };
                };

                serial@7e201000 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201000 0x200>;
                        interrupts = <0x0 0x79 0x4>;
                        clocks = <0x6 0x13 0x6 0x14>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x241011>;
                        pinctrl-names = "default";
                        pinctrl-0 = <0x7 0x8>;
                        uart-has-rtscts;
                        status = "okay";

                        bluetooth {
                                compatible = "brcm,bcm43438-bt";
                                max-speed = <0x1e8480>;
                                shutdown-gpios = <0x9 0x0 0x0>;
                        };
                };

                mmc@7e202000 {
                        compatible = "brcm,bcm2835-sdhost";
                        reg = <0x7e202000 0x100>;
                        interrupts = <0x0 0x78 0x4>;
                        clocks = <0x6 0x14>;
                        status = "disabled";
                };

                i2s@7e203000 {
                        compatible = "brcm,bcm2835-i2s";
                        reg = <0x7e203000 0x24>;
                        clocks = <0x6 0x1f>;
                        status = "disabled";
                };

                spi@7e204000 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204000 0x200>;
                        interrupts = <0x0 0x76 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                i2c@7e205000 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205000 0x200>;
                        interrupts = <0x0 0x75 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <0xa>;
                        clock-frequency = <0x186a0>;
                };

                dpi@7e208000 {
                        compatible = "brcm,bcm2835-dpi";
                        reg = <0x7e208000 0x8c>;
                        clocks = <0x6 0x14 0x6 0x2c>;
                        clock-names = "core", "pixel";
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                dsi@7e209000 {
                        compatible = "brcm,bcm2835-dsi0";
                        reg = <0x7e209000 0x78>;
                        interrupts = <0x0 0x64 0x4>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        #clock-cells = <0x1>;
                        clocks = <0x6 0x20 0x6 0x2f 0x6 0x31>;
                        clock-names = "phy", "escape", "pixel";
                        clock-output-names = "dsi0_byte", "dsi0_ddr2", "dsi0_ddr";
                        status = "disabled";
                        power-domains = <0xb 0x11>;
                        phandle = <0x4>;
                };

                aux@7e215000 {
                        compatible = "brcm,bcm2835-aux";
                        #clock-cells = <0x1>;
                        reg = <0x7e215000 0x8>;
                        clocks = <0x6 0x14>;
                        phandle = <0xc>;
                };

                serial@7e215040 {
                        compatible = "brcm,bcm2835-aux-uart";
                        reg = <0x7e215040 0x40>;
                        interrupts = <0x0 0x5d 0x4>;
                        clocks = <0xc 0x0>;
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <0xd>;
                };

                spi@7e215080 {
                        compatible = "brcm,bcm2835-aux-spi";
                        reg = <0x7e215080 0x40>;
                        interrupts = <0x0 0x5d 0x4>;
                        clocks = <0xc 0x1>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                spi@7e2150c0 {
                        compatible = "brcm,bcm2835-aux-spi";
                        reg = <0x7e2150c0 0x40>;
                        interrupts = <0x0 0x5d 0x4>;
                        clocks = <0xc 0x2>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                pwm@7e20c000 {
                        compatible = "brcm,bcm2835-pwm";
                        reg = <0x7e20c000 0x28>;
                        clocks = <0x6 0x1e>;
                        assigned-clocks = <0x6 0x1e>;
                        assigned-clock-rates = <0x989680>;
                        #pwm-cells = <0x2>;
                        status = "disabled";
                };

                mmc@7e300000 {
                        compatible = "brcm,bcm2835-sdhci";
                        reg = <0x7e300000 0x100>;
                        interrupts = <0x0 0x7e 0x4>;
                        clocks = <0x6 0x1c>;
                        status = "okay";
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        pinctrl-names = "default";
                        pinctrl-0 = <0xe>;
                        bus-width = <0x4>;
                        non-removable;
                        mmc-pwrseq = <0xf>;

                        wifi@1 {
                                reg = <0x1>;
                                compatible = "brcm,bcm4329-fmac";
                        };
                };

                hvs@7e400000 {
                        compatible = "brcm,bcm2711-hvs";
                        reg = <0x7e400000 0x6000>;
                        interrupts = <0x0 0x61 0x4>;
                        clocks = <0x10 0x4>;
                };

                dsi@7e700000 {
                        compatible = "brcm,bcm2711-dsi1";
                        reg = <0x7e700000 0x8c>;
                        interrupts = <0x0 0x6c 0x4>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        #clock-cells = <0x1>;
                        clocks = <0x6 0x23 0x6 0x30 0x6 0x32>;
                        clock-names = "phy", "escape", "pixel";
                        clock-output-names = "dsi1_byte", "dsi1_ddr2", "dsi1_ddr";
                        status = "disabled";
                        power-domains = <0xb 0x12>;
                        phandle = <0x5>;
                };

                i2c@7e804000 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e804000 0x1000>;
                        interrupts = <0x0 0x75 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <0x11>;
                        clock-frequency = <0x186a0>;
                };

                usb@7e980000 {
                        compatible = "brcm,bcm2835-usb";
                        reg = <0x7e980000 0x10000>;
                        interrupts = <0x0 0x49 0x4>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        clocks = <0x12>;
                        clock-names = "otg";
                        phys = <0x13>;
                        phy-names = "usb2-phy";
                        power-domains = <0xb 0x6>;
                        dr_mode = "peripheral";
                        g-rx-fifo-size = <0x100>;
                        g-np-tx-fifo-size = <0x20>;
                        g-tx-fifo-size = <0x100 0x100 0x200 0x200 0x200 0x300 0x300>;
                };

                local_intc@40000000 {
                        compatible = "brcm,bcm2836-l1-intc";
                        reg = <0x40000000 0x100>;
                };

                interrupt-controller@40041000 {
                        interrupt-controller;
                        #interrupt-cells = <0x3>;
                        compatible = "arm,gic-400";
                        reg = <0x40041000 0x1000 0x40042000 0x2000 0x40044000 0x2000 0x40046000 0x2000>;
                        interrupts = <0x1 0x9 0xf04>;
                        phandle = <0x1>;
                };

                avs-monitor@7d5d2000 {
                        compatible = "brcm,bcm2711-avs-monitor", "syscon", "simple-mfd";
                        reg = <0x7d5d2000 0xf00>;

                        thermal {
                                compatible = "brcm,bcm2711-thermal";
                                #thermal-sensor-cells = <0x0>;
                                phandle = <0x2>;
                        };
                };

                dma@7e007000 {
                        compatible = "brcm,bcm2835-dma";
                        reg = <0x7e007000 0xb00>;
                        interrupts = <0x0 0x50 0x4 0x0 0x51 0x4 0x0 0x52 0x4 0x0 0x53 0x4 0x0 0x54 0x4 0x0 0x55 0x4 0x0 0x56 0x4 0x0 0x57 0x4 0x0 0x57 0x4 0x0 0x58 0x4 0x0 0x58 0x4>;
                        interrupt-names = "dma0", "dma1", "dma2", "dma3", "dma4", "dma5", "dma6", "dma7", "dma8", "dma9", "dma10";
                        #dma-cells = <0x1>;
                        brcm,dma-channel-mask = <0x7f5>;
                        phandle = <0x1a>;
                };

                watchdog@7e100000 {
                        compatible = "brcm,bcm2835-pm", "brcm,bcm2835-pm-wdt";
                        #power-domain-cells = <0x1>;
                        #reset-cells = <0x1>;
                        reg = <0x7e100000 0x114 0x7e00a000 0x24 0x7ec11000 0x20>;
                        clocks = <0x6 0x15 0x6 0x1d 0x6 0x17 0x6 0x16>;
                        clock-names = "v3d", "peri_image", "h264", "isp";
                        system-power-controller;
                };

                rng@7e104000 {
                        compatible = "brcm,bcm2711-rng200";
                        reg = <0x7e104000 0x28>;
                };

                serial@7e201400 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201400 0x200>;
                        interrupts = <0x0 0x79 0x4>;
                        clocks = <0x6 0x13 0x6 0x14>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x241011>;
                        status = "disabled";
                };

                serial@7e201600 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201600 0x200>;
                        interrupts = <0x0 0x79 0x4>;
                        clocks = <0x6 0x13 0x6 0x14>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x241011>;
                        status = "disabled";
                };

                serial@7e201800 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201800 0x200>;
                        interrupts = <0x0 0x79 0x4>;
                        clocks = <0x6 0x13 0x6 0x14>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x241011>;
                        status = "disabled";
                };

                serial@7e201a00 {
                        compatible = "arm,pl011", "arm,primecell";
                        reg = <0x7e201a00 0x200>;
                        interrupts = <0x0 0x79 0x4>;
                        clocks = <0x6 0x13 0x6 0x14>;
                        clock-names = "uartclk", "apb_pclk";
                        arm,primecell-periphid = <0x241011>;
                        status = "disabled";
                };

                spi@7e204600 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204600 0x200>;
                        interrupts = <0x0 0x76 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                spi@7e204800 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204800 0x200>;
                        interrupts = <0x0 0x76 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                spi@7e204a00 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204a00 0x200>;
                        interrupts = <0x0 0x76 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                spi@7e204c00 {
                        compatible = "brcm,bcm2835-spi";
                        reg = <0x7e204c00 0x200>;
                        interrupts = <0x0 0x76 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                i2c@7e205600 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205600 0x200>;
                        interrupts = <0x0 0x75 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                i2c@7e205800 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205800 0x200>;
                        interrupts = <0x0 0x75 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                i2c@7e205a00 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205a00 0x200>;
                        interrupts = <0x0 0x75 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                i2c@7e205c00 {
                        compatible = "brcm,bcm2711-i2c", "brcm,bcm2835-i2c";
                        reg = <0x7e205c00 0x200>;
                        interrupts = <0x0 0x75 0x4>;
                        clocks = <0x6 0x14>;
                        #address-cells = <0x1>;
                        #size-cells = <0x0>;
                        status = "disabled";
                };

                pixelvalve@7e206000 {
                        compatible = "brcm,bcm2711-pixelvalve0";
                        reg = <0x7e206000 0x100>;
                        interrupts = <0x0 0x6d 0x4>;
                        status = "okay";
                };

                pixelvalve@7e207000 {
                        compatible = "brcm,bcm2711-pixelvalve1";
                        reg = <0x7e207000 0x100>;
                        interrupts = <0x0 0x6e 0x4>;
                        status = "okay";
                };

                pixelvalve@7e20a000 {
                        compatible = "brcm,bcm2711-pixelvalve2";
                        reg = <0x7e20a000 0x100>;
                        interrupts = <0x0 0x65 0x4>;
                        status = "okay";
                };

                pwm@7e20c800 {
                        compatible = "brcm,bcm2835-pwm";
                        reg = <0x7e20c800 0x28>;
                        clocks = <0x6 0x1e>;
                        assigned-clocks = <0x6 0x1e>;
                        assigned-clock-rates = <0x989680>;
                        #pwm-cells = <0x2>;
                        status = "okay";
                        pinctrl-names = "default";
                        pinctrl-0 = <0x14 0x15>;
                };

                pixelvalve@7e216000 {
                        compatible = "brcm,bcm2711-pixelvalve4";
                        reg = <0x7e216000 0x100>;
                        interrupts = <0x0 0x6e 0x4>;
                        status = "okay";
                };

                pixelvalve@7ec12000 {
                        compatible = "brcm,bcm2711-pixelvalve3";
                        reg = <0x7ec12000 0x100>;
                        interrupts = <0x0 0x6a 0x4>;
                        status = "disabled";
                };

                vec@7ec13000 {
                        compatible = "brcm,bcm2711-vec";
                        reg = <0x7ec13000 0x1000>;
                        clocks = <0x6 0x18>;
                        interrupts = <0x0 0x7b 0x4>;
                        status = "disabled";
                        power-domains = <0xb 0x7>;
                };

                clock@7ef00000 {
                        compatible = "brcm,brcm2711-dvp";
                        reg = <0x7ef00000 0x10>;
                        clocks = <0x16>;
                        #clock-cells = <0x1>;
                        #reset-cells = <0x1>;
                        phandle = <0x17>;
                };

                interrupt-controller@7ef00100 {
                        compatible = "brcm,bcm2711-l2-intc", "brcm,l2-intc";
                        reg = <0x7ef00100 0x30>;
                        interrupts = <0x0 0x60 0x4>;
                        interrupt-controller;
                        #interrupt-cells = <0x1>;
                        phandle = <0x18>;
                };

                hdmi@7ef00700 {
                        compatible = "brcm,bcm2711-hdmi0";
                        reg = <0x7ef00700 0x300 0x7ef00300 0x200 0x7ef00f00 0x80 0x7ef00f80 0x80 0x7ef01b00 0x200 0x7ef01f00 0x400 0x7ef00200 0x80 0x7ef04300 0x100 0x7ef20000 0x100>;
                        reg-names = "hdmi", "dvp", "phy", "rm", "packet", "metadata", "csc", "cec", "hd";
                        clock-names = "hdmi", "bvb", "audio", "cec";
                        resets = <0x17 0x0>;
                        interrupt-parent = <0x18>;
                        interrupts = <0x0 0x1 0x2 0x3 0x4 0x5>;
                        interrupt-names = "cec-tx", "cec-rx", "cec-low", "wakeup", "hpd-connected", "hpd-removed";
                        ddc = <0x19>;
                        dmas = <0x1a 0xa>;
                        dma-names = "audio-rx";
                        status = "okay";
                        clocks = <0x10 0xd 0x10 0xe 0x17 0x0 0x1b>;
                        wifi-2.4ghz-coexistence;
                };

                i2c@7ef04500 {
                        compatible = "brcm,bcm2711-hdmi-i2c";
                        reg = <0x7ef04500 0x100 0x7ef00b00 0x300>;
                        reg-names = "bsc", "auto-i2c";
                        clock-frequency = <0x17cdc>;
                        status = "okay";
                        phandle = <0x19>;
                };

                hdmi@7ef05700 {
                        compatible = "brcm,bcm2711-hdmi1";
                        reg = <0x7ef05700 0x300 0x7ef05300 0x200 0x7ef05f00 0x80 0x7ef05f80 0x80 0x7ef06b00 0x200 0x7ef06f00 0x400 0x7ef00280 0x80 0x7ef09300 0x100 0x7ef20000 0x100>;
                        reg-names = "hdmi", "dvp", "phy", "rm", "packet", "metadata", "csc", "cec", "hd";
                        ddc = <0x1c>;
                        clock-names = "hdmi", "bvb", "audio", "cec";
                        resets = <0x17 0x1>;
                        interrupt-parent = <0x18>;
                        interrupts = <0x8 0x7 0x6 0x9 0xa 0xb>;
                        interrupt-names = "cec-tx", "cec-rx", "cec-low", "wakeup", "hpd-connected", "hpd-removed";
                        dmas = <0x1a 0x11>;
                        dma-names = "audio-rx";
                        status = "okay";
                        clocks = <0x10 0xd 0x10 0xe 0x17 0x1 0x1b>;
                        wifi-2.4ghz-coexistence;
                };

                i2c@7ef09500 {
                        compatible = "brcm,bcm2711-hdmi-i2c";
                        reg = <0x7ef09500 0x100 0x7ef05b00 0x300>;
                        reg-names = "bsc", "auto-i2c";
                        clock-frequency = <0x17cdc>;
                        status = "okay";
                        phandle = <0x1c>;
                };

                firmware {
                        compatible = "raspberrypi,bcm2835-firmware", "simple-mfd";
                        #address-cells = <0x1>;
                        #size-cells = <0x1>;
                        mboxes = <0x1d>;
                        dma-ranges;
                        phandle = <0x1e>;

                        clocks {
                                compatible = "raspberrypi,firmware-clocks";
                                #clock-cells = <0x1>;
                                phandle = <0x10>;
                        };

                        gpio {
                                compatible = "raspberrypi,firmware-gpio";
                                gpio-controller;
                                #gpio-cells = <0x2>;
                                status = "okay";
                                gpio-line-names = "BT_ON", "WL_ON", "PWR_LED_OFF", "GLOBAL_RESET", "VDD_SD_IO_SEL", "CAM_GPIO", "SD_PWR_ON", "";
                                phandle = <0x9>;
                        };

                        reset {
                                compatible = "raspberrypi,firmware-reset";
                                #reset-cells = <0x1>;
                                phandle = <0x26>;
                        };
                };

                power {
                        compatible = "raspberrypi,bcm2835-power";
                        firmware = <0x1e>;
                        #power-domain-cells = <0x1>;
                        phandle = <0xb>;
                };

                mailbox@7e00b840 {
                        compatible = "brcm,bcm2835-vchiq";
                        reg = <0x7e00b840 0x3c>;
                        interrupts = <0x0 0x22 0x4>;
                };
        };

        clocks {

                clk-osc {
                        compatible = "fixed-clock";
                        #clock-cells = <0x0>;
                        clock-output-names = "osc";
                        clock-frequency = <0x337f980>;
                        phandle = <0x3>;
                };

                clk-usb {
                        compatible = "fixed-clock";
                        #clock-cells = <0x0>;
                        clock-output-names = "otg";
                        clock-frequency = <0x1c9c3800>;
                        phandle = <0x12>;
                };
        };

        phy {
                compatible = "usb-nop-xceiv";
                #phy-cells = <0x0>;
                phandle = <0x13>;
        };

        gpu {
                compatible = "brcm,bcm2711-vc5";
                status = "okay";
        };

        clk-27M {
                #clock-cells = <0x0>;
                compatible = "fixed-clock";
                clock-frequency = <0x19bfcc0>;
                clock-output-names = "27MHz-clock";
                phandle = <0x1b>;
        };

        clk-108M {
                #clock-cells = <0x0>;
                compatible = "fixed-clock";
                clock-frequency = <0x66ff300>;
                clock-output-names = "108MHz-clock";
                phandle = <0x16>;
        };

        emmc2bus {
                compatible = "simple-bus";
                #address-cells = <0x2>;
                #size-cells = <0x1>;
                ranges = <0x0 0x7e000000 0x0 0xfe000000 0x1800000>;
                dma-ranges = <0x0 0xc0000000 0x0 0x0 0x40000000>;

                mmc@7e340000 {
                        compatible = "brcm,bcm2711-emmc2";
                        reg = <0x0 0x7e340000 0x100>;
                        interrupts = <0x0 0x7e 0x4>;
                        clocks = <0x6 0x33>;
                        status = "okay";
                        vqmmc-supply = <0x1f>;
                        vmmc-supply = <0x20>;
                        broken-cd;
                };
        };

        arm-pmu {
                compatible = "arm,cortex-a72-pmu", "arm,armv8-pmuv3";
                interrupts = <0x0 0x10 0x4 0x0 0x11 0x4 0x0 0x12 0x4 0x0 0x13 0x4>;
                interrupt-affinity = <0x21 0x22 0x23 0x24>;
        };

        timer {
                compatible = "arm,armv8-timer";
                interrupts = <0x1 0xd 0xf08 0x1 0xe 0xf08 0x1 0xb 0xf08 0x1 0xa 0xf08>;
                arm,cpu-registers-not-fw-configured;
        };

        cpus {
                #address-cells = <0x1>;
                #size-cells = <0x0>;
                enable-method = "brcm,bcm2836-smp";

                cpu@0 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <0x0>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0xd8>;
                        phandle = <0x21>;
                };

                cpu@1 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <0x1>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0xe0>;
                        phandle = <0x22>;
                };

                cpu@2 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <0x2>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0xe8>;
                        phandle = <0x23>;
                };

                cpu@3 {
                        device_type = "cpu";
                        compatible = "arm,cortex-a72";
                        reg = <0x3>;
                        enable-method = "spin-table";
                        cpu-release-addr = <0x0 0xf0>;
                        phandle = <0x24>;
                };
        };

        scb {
                compatible = "simple-bus";
                #address-cells = <0x2>;
                #size-cells = <0x1>;
                ranges = <0x0 0x7c000000 0x0 0xfc000000 0x3800000 0x6 0x0 0x6 0x0 0x40000000>;

                pcie@7d500000 {
                        compatible = "brcm,bcm2711-pcie";
                        reg = <0x0 0x7d500000 0x9310>;
                        device_type = "pci";
                        #address-cells = <0x3>;
                        #interrupt-cells = <0x1>;
                        #size-cells = <0x2>;
                        interrupts = <0x0 0x94 0x4 0x0 0x94 0x4>;
                        interrupt-names = "pcie", "msi";
                        interrupt-map-mask = <0x0 0x0 0x0 0x7>;
                        interrupt-map = <0x0 0x0 0x0 0x1 0x1 0x0 0x8f 0x4>;
                        msi-controller;
                        msi-parent = <0x25>;
                        ranges = <0x2000000 0x0 0xf8000000 0x6 0x0 0x0 0x4000000>;
                        dma-ranges = <0x2000000 0x0 0x0 0x0 0x0 0x0 0xc0000000>;
                        brcm,enable-ssc;
                        phandle = <0x25>;

                        pci@0,0 {
                                device_type = "pci";
                                #address-cells = <0x3>;
                                #size-cells = <0x2>;
                                ranges;
                                reg = <0x0 0x0 0x0 0x0 0x0>;

                                usb@0,0 {
                                        reg = <0x0 0x0 0x0 0x0 0x0>;
                                        resets = <0x26 0x0>;
                                };
                        };
                };

                ethernet@7d580000 {
                        compatible = "brcm,bcm2711-genet-v5";
                        reg = <0x0 0x7d580000 0x10000>;
                        #address-cells = <0x1>;
                        #size-cells = <0x1>;
                        interrupts = <0x0 0x9d 0x4 0x0 0x9e 0x4>;
                        status = "okay";
                        phy-handle = <0x27>;
                        phy-mode = "rgmii-rxid";

                        mdio@e14 {
                                compatible = "brcm,genet-mdio-v5";
                                reg = <0xe14 0x8>;
                                reg-names = "mdio";
                                #address-cells = <0x1>;
                                #size-cells = <0x0>;

                                ethernet-phy@1 {
                                        reg = <0x1>;
                                        phandle = <0x27>;
                                };
                        };
                };
        };

        leds {
                compatible = "gpio-leds";

                led-act {
                        label = "ACT";
                        default-state = "keep";
                        linux,default-trigger = "heartbeat";
                        gpios = <0x28 0x2a 0x0>;
                };

                led-pwr {
                        label = "PWR";
                        gpios = <0x9 0x2 0x1>;
                        default-state = "keep";
                        linux,default-trigger = "default-on";
                };
        };

        memory@0 {
                device_type = "memory";
                reg = <0x0 0x0 0x0>;
        };

        wifi-pwrseq {
                compatible = "mmc-pwrseq-simple";
                reset-gpios = <0x9 0x1 0x1>;
                phandle = <0xf>;
        };

        sd_io_1v8_reg {
                compatible = "regulator-gpio";
                regulator-name = "vdd-sd-io";
                regulator-min-microvolt = <0x1b7740>;
                regulator-max-microvolt = <0x325aa0>;
                regulator-boot-on;
                regulator-always-on;
                regulator-settling-time-us = <0x1388>;
                gpios = <0x9 0x4 0x0>;
                states = <0x1b7740 0x1 0x325aa0 0x0>;
                status = "okay";
                phandle = <0x1f>;
        };

        sd_vcc_reg {
                compatible = "regulator-fixed";
                regulator-name = "vcc-sd";
                regulator-min-microvolt = <0x325aa0>;
                regulator-max-microvolt = <0x325aa0>;
                regulator-boot-on;
                enable-active-high;
                gpio = <0x9 0x6 0x0>;
                phandle = <0x20>;
        };
};

 

참고