parse_early_param()

<kernel v5.10>

커멘드 라인 파라미터 파싱

부트 커멘드라인 문자열을 파싱하여 해당 파라미터가 early 파라미터인 경우 해당 설정 함수를 호출한다. 호환을 위해 “console” 파라미터는 earlycon으로 동작한다.

boot_command_line(이하 cmdline 또는 커멘드 라인) 파라미터로 요청받은 문자열을 토큰으로 parsing 하여 이에 대응하는 설정 함수를 setup_param 테이블에서 찾고 해당 항목이 early로 설정되어 있는 경우 이를 호출하여 실행한다. 또한 요청 토큰이 “console”인 경우 setup_param 테이블에서 “earlycon”을 찾아 해당 설정 함수를 호출한다. DTB 내부에서도 chosen 노드의  stdout-path 속성값에 해당하는 console  디바이스를 earlycon_of_table 에서 찾은 경우 해당 설정 함수를 동작시킨다.

  • setup_param 테이블에서 early_console 함수를 찾아 등록된 초기화 함수 수행
    • __setup_start 부터 __setup_end 영역
    • obs_kernel_param 구조체 엔트리들
  • 다음 조건에 해당되는 커널 파라미터를 발견하면 해당 커널 파라미터에 등록된 함수를 호출한다.
    • early 파라미터 호출
      • 요청한 파라미터가 등록된 커널 파라미터 문자열과 같으면서 early 설정이 된 경우
    • earlycon 호출
      • 요청한 파라미터가 “console”이고 등록된 커널 파라미터는 “earlycon”인 경우
  • cmdline 문자열 중 “console” 을 발견 시 콘솔 디바이스명으로 모든 earlycon으로 등록된 모든 초기화 함수를 수행
  • 예) rpi2: earlycon=xxx를 설정한 경우:
    • pl011_setup_earlycon(“xxx”) → setup_earlycon() → pl011_early_console_setup(“xxx”)
    • uart_setup_earlycon(“xxx”) → setup_earlycon() → early_serial8250_setup(“xxx”)
    • uart8250_setup_earlycon(“xxx”) → 상동
    • setup_of_earlycon(“xxx”) → early_init_dt_scan_chosen_serial()
      • 디바이스 트리에서 /chosen의 stdout-path를 찾아냄
  • 예) rpi2는 두 개의 드라이버를 console로 지정한다.
    • dwc_otg.lpm_enable=0 console=ttyAMA0,115200 console=tty1 root=/dev/mmcblk0p6 rootfstype=ext4 elevator=deadline rootwait
      • ttyAMA는 ARM에서 제공하는 pl011 UART 드라이버이다.
      • tty는 리눅스 기본 tty 드라이버이다.
    • earlycon이 필요 시
      • “earlycon=pl011,0x3f201000,115200n8”
    • earlyprintk가 필요 시
      • “earlyprintk”
    • earlycon과 earlyprintk와 동시 사용 시
      • “earlycon=pl011,0x3f201000,115200n8 earlyprintk”

 

다음 그림은 매치되는 early 파라미터들의 셋업 함수를 호출한다.

  • 예) rpi2에 등록된 earlycon 관련 항목들
    • EARLYCON_DECLARE(pl011, pl011_early_console_setup);
      • 연결함수: pl011_setup_earlycon() -> setup_earlycon() -> pl011_early_console_setup()
    • EARLYCON_DECLARE(uart, early_serial8250_setup);
      • 연결함수: uart_setup_earlycon() -> setup_earlycon() -> early_serial8250_setup()
    • EARLYCON_DECLARE(uart8250, early_serial8250_setup);
      • 연결함수: uart8250_setup_earlycon() -> setup_earlycon() -> early_serial8250_setup()
    • early_param(“earlycon”, setup_of_earlycon);
      • 연결함수: setup_of_earlycon() -> early_init_dt_scan_chosen_serial() -> of_setup_earlycon() -> 예) pl011_early_console_setup()
  • 예) rpi2에 등록된 earlyprintk 관련 항목
    • early_param(“earlyprintk”, setup_early_printk);

 

parse_early_param()

init/main.c

/* Arch code calls this early on, or if not, just before other parsing. */
void __init parse_early_param(void)
{
        static int done __initdata;
        static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;

        if (done)
                return;

        /* All fall through to do_early_param. */
        strlcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
        parse_early_options(tmp_cmdline);
        done = 1; 
}

커멘트 라인 파라미터들 중 early 파라미터에 해당하는 설정 함수를 호출한다.

  • 코드 라인 10에서 전역 변수 boot_command_line의 내용을 tmp_cmdline에 복사한다.
    • boot_command_line
      • A) 부트로더가 다음 중 하나를 전달해 준다.
        • ATAG _CMDLINE 문자열 – ARM32 only
        • DTB의 “/chosen” 노드의 “bootargs” 속성 값
      • B) 커널에서도 준비한 문자열
        • 커널 옵션으로 입력한 커맨드라인 문자열 CONFIG_CMDLINE이 준비된다.
      • 위의 A) 및 B)를 아래 옵션에 따라 조합하여 사용한다.
        • 1) CONFIG_CMDLINE_EXTEND
          • A) DTB(or ATAG)와 B) 커널 cmdline을 합쳐서 사용한다.
        • 2) CONFIG_CLDLINE_FORCE
          • B) 커널 cmdline을 사용한다.
        • 3) no option (default)
          • A) DTB(or ATAG)를 사용한다.
  • 코드 라인 12에서 early 파라미터에 해당하는 설정 함수를 호출한다.

 

parse_early_options()

init/main.c

void __init parse_early_options(char *cmdline)
{
        parse_args("early options", cmdline, NULL, 0, 0, 0, NULL,
                   do_early_param);
}

do_early_param() 함수 주소를 인수로 parse_args() 함수를 호출한다.

  • 파라미터 블록, 개수, 범위가 지정되는 경우 그 파라미터 범위에 해당하는 토큰과 매치되는 경우 해당 파라미터에 값을 대입한다.
  • 그러나 파라미터 블록, 개수 및 범위가 0으로 전달되는 경우 각 토큰을 파싱하게 되면 param과 val 값을 가지고 항상 unknown handler인 do_early_param() 함수가 호출된다.

 

parse_args()

kernel/params.c

/* Args looks like "foo=bar,bar2 baz=fuz wiz". */
char *parse_args(const char *doing,
                 char *args,
                 const struct kernel_param *params,
                 unsigned num,
                 s16 min_level,
                 s16 max_level,
                 void *arg,
                 int (*unknown)(char *param, char *val,
                                const char *doing, void *arg))
{
        char *param, *val, *err = NULL;

        /* Chew leading spaces */
        args = skip_spaces(args);

        if (*args)
                pr_debug("doing %s, parsing ARGS: '%s'\n", doing, args);

        while (*args) {
                int ret;
                int irq_was_disabled;

                args = next_arg(args, &param, &val);
                /* Stop at -- */
                if (!val && strcmp(param, "--") == 0)
                        return err ?: args;
                irq_was_disabled = irqs_disabled();
                ret = parse_one(param, val, doing, params, num,
                                min_level, max_level, arg, unknown);
                if (irq_was_disabled && !irqs_disabled())
                        pr_warn("%s: option '%s' enabled irq's!\n",
                                doing, param);

                switch (ret) {
                case 0:
                        continue;
                case -ENOENT:
                        pr_err("%s: Unknown parameter `%s'\n", doing, param);
                        break;
                case -ENOSPC:
                        pr_err("%s: `%s' too large for parameter `%s'\n",
                               doing, val ?: "", param);
                        break;
                default:
                        pr_err("%s: `%s' invalid for parameter `%s'\n",
                               doing, val ?: "", param);
                        break;
                }

                err = ERR_PTR(ret);
        }

        return err;
}

커멘드 라인 파라미터를 파싱한 토큰과 값으로 파라미터 블럭 @params에서 @min_level ~ @max_level 범위의 파라미터 테이블을 검색하여 일치하는 토큰이 있는 경우 해당 파라미터에 값을 대입한다. 만일 매치되는 조건이 없으면 @unknown 함수를 호출한다.

  • 코드 라인 14~17에서 args 인수에는 cmdline의 주소를 담고 있는데 space(0x20, 탭, 라인피드 등) 인 경우 skip 한다.
    • 예) ”  console=xxx” -> “console=xxx”
  • 코드 라인 19~23에서 parsing할 문자열이 남아 있는 동안 루프를 돌며 토큰을 “=” 문자로 분리하여 param과 val로 담는다.
    • (abc=1)
    • space 문자로 토큰과 토큰을 분리한다.
      • (aaa=1 bbb=2 ccc=3)
    • 쌍 따옴표(“)가 사용된 경우 중간에 space가 있어도 토큰을 분리하지 않는다.
      • (aaa=”1″ bbb=”2 2″ ccc=”3″)
        • param=(aaa), val=(1)
        • param=(bbb), val=(2 2)
        • param=(ccc), val=(3)
  • 코드 라인 25~26에서 val 값이 없으면서 param 값이 “–” 인 경우 더 이상 파싱을 하지 않고 함수를 종료한다.
  • 코드 라인 27에서 SCTLR의 인터럽트 마스크(“I”, 1번 비트)가 disable된 상태인지 알아온다.
    • true = irq disabled status, false = irq enabled status
  • 코드 라인 28~29에서 파싱된 토큰의 param과 val 그리고 doing(“early options”메시지 출력용) 및 do_early_param() 함수 포인터를 인자로 parse_one()을 호출한다.
    • 파라미터 블록, 개수, 범위가 지정되는 경우 그 파라미터 범위에 해당하는 토큰과 매치되는 경우 해당 파라미터에 값을 대입한다
    • 그러나 파라미터 블록, 개수 및 범위가 0으로 전달되는 경우 각 토큰을 파싱하게 되면 param과 val 값을 가지고 항상 unknown handler인 do_early_param() 함수가 호출된다.
  • 코드 라인 30~32에서 irq 설정 상태가 바뀌었으면 어떤 파라미터 옵션에서 바뀌었는지 warning을 출력한다.
    • 파라미터로 인해 특정 코드(디바이스 드라이버 등)에서 irq를 enable하고 나오는지를 확인하기 위해 경고 출력을 위한 디버그 코드이다.
    • 커널이 초기 설정 중에는 인터럽트가 마스크되어 동작하지 않고 있는데 갑자기 커널 파라미터로 인해 인터럽트가 발생되면 안되기 때문에 이를 확인하기 위함이다.
  • 코드 라인 34~48에서 파싱한 결과가 에러인 경우 에러 메시지를 출력하고 리턴한다. 성공인 경우  다음 토큰을 위해 루프를 계속 진행한다.

 

do_early_param()

init/main.c

/* Check for early params. */
static int __init do_early_param(char *param, char *val, 
                                 const char *unused, void *arg)
{                
        const struct obs_kernel_param *p;

        for (p = __setup_start; p < __setup_end; p++) {
                if ((p->early && parameq(param, p->str)) ||
                    (strcmp(param, "console") == 0 &&
                     strcmp(p->str, "earlycon") == 0)
                ) {
                        if (p->setup_func(val) != 0)
                                pr_warn("Malformed early option '%s'\n", param);
                }
        }
        /* We accept everything at this stage. */
        return 0;
}

다음 조건에 해당되는 early 커널 파라미터를 발견하면 @val 값 인수를 가지고 해당 커널 파라미터에 등록된 함수를 호출한다.

  • 요청한 커멘드 라인 파라미터가 early 커널 셋업 파라미터와 매치된 경우
    • 예) “earlyprintk”
  • 요청한 커멘드 라인 파라미터가 “console”로 시작한 경우
    • 예) “console=pl011”
  • early_param(“earlycon”)으로 등록한 셋업 함수
    • 현재 param_setup_earlycon() 함수 하나만 사용되고 있다.

 

  • 코드 라인 6에서 __setup_start 주소 영역 부터 __setup_end 주소 영역까지에 여러 개의 커널 파라미터인 obs_kernel_param 구조체가 있는데 순서대로 검색하여 p를 대입한다.
  • 코드 라인 7~10에서 p->early 항목이 설정되어 있으면서 p->str이 인수로 받은 param 문자열과 같은 경우이거나 p->str이 “earlycon”이면서 인수로 받은 param 문자열이 “console”인 경우
  • 코드 라인 11~12에서 p->setup_func()을 호출하여 에러가 있는 경우 경고 메시지를 출력한다.

 


디바이스 트리를 통한 파라미터 지정

디바이스 트리를 통해 파라미터 및 earlycon을 지정할 수 있다.

 

다음과 같이 DTB의 chosen 노드에 stdout-path를 지정하여 earlycon에 사용할 수 있고, bootargs를 통해 커멘드 라인 파라미터를 지정할 수 있다.

        chosen {
                bootargs = "console=ttyS0,115200n8 earlyprintk";
                stdout-path = "serial0:115200n8";
        };

 

디바이스 트리를 통한 earlycon 지정

커멘드 라인 파라메터에서 “earlycon” 또는 “console”이 지정될 때 디바이스 트리의 chosen 노드에서 “stdout-path”를 early 콘솔 디바이스로 지정한다. “console” 뒤에 디바이스가 지정되면 안된다.

early_init_dt_scan_chosen_stdout()

drivers/of/fdt.c

int __init early_init_dt_scan_chosen_stdout(void)
{
        int offset;
        const char *p, *q, *options = NULL;
        int l;
        const struct earlycon_id **p_match;
        const void *fdt = initial_boot_params;

        offset = fdt_path_offset(fdt, "/chosen");
        if (offset < 0)
                offset = fdt_path_offset(fdt, "/chosen@0");
        if (offset < 0)
                return -ENOENT;

        p = fdt_getprop(fdt, offset, "stdout-path", &l);
        if (!p)
                p = fdt_getprop(fdt, offset, "linux,stdout-path", &l);
        if (!p || !l)
                return -ENOENT;

        q = strchrnul(p, ':');
        if (*q != '\0')
                options = q + 1;
        l = q - p;

        /* Get the node specified by stdout-path */
        offset = fdt_path_offset_namelen(fdt, p, l);
        if (offset < 0) {
                pr_warn("earlycon: stdout-path %.*s not found\n", l, p);
                return 0;
        }

        for (p_match = __earlycon_table; p_match < __earlycon_table_end;
             p_match++) {
                const struct earlycon_id *match = *p_match;

                if (!match->compatible[0])
                        continue;

                if (fdt_node_check_compatible(fdt, offset, match->compatible))
                        continue;

                if (of_setup_earlycon(match, offset, options) == 0)
                        return 0;
        }
        return -ENODEV;
}

이 함수는 CONFIG_SERIAL_EARLYCON이 설정되어 있는 경우 DTB에서 /chosen 노드의 stdout-path 속성에 지정된 compatible(디바이스명)을 알아와서 earlycon 테이블에 등록한 모든 earlycon 디바이스의 이름과 같은 디바이스의 설정 함수를 호출한다.

  • 코드 라인 9~13에서 “/chosen” 또는 “/chosen@0” 노드를 검색한다.
  • 코드 라인 15~19에서 발견한 노드에서 “stdout-path” 또는 “linux,stdout-path” 속성을 검색한다.
  • 코드 라인 21~31에서 발견한 속성에서 디바이스명과 옵션을 분리한다.
    • 예) “serial0:115200n8” 에서 offset은 “serial0″를 의미하고, options는 “115200n8″을 의미한다.
  • 코드 라인33~38에서 __earlycon_of_table 주소 부터 compatible[0]이 있는 동안 검색한다.
  • 코드 라인40~41에서 compatible 속성 값에서 디바이스명(match->compatible)이 매치되지 않으면 skip 한다.
    • fdt_node_check_compatible()
      • compatible 속성 값에서 문자열 비교: 0=match, 1=non match, 길이=”compatible” 속성이 발견되지 않는 경우
  • 코드 라인 43~44에서 early console 디바이스를 셋업한다.
    • match->data에는 디바이스의 setup 함수 주소가 담겨있다.
      • rpi2 예) match->data = pl011_early_console_setup()

 

예) DTB용 콘솔 디바이스 드라이버 소스 – earlycon_of_table 섹션에 저장된다.

  • OF_EARLYCON_DECLARE(pl011, “arm,pl011”, pl011_early_console_setup);

 

of_setup_earlycon()

drivers/tty/serial/earlycon.c

int __init of_setup_earlycon(const struct earlycon_id *match,
                             unsigned long node,
                             const char *options)
{
        int err;
        struct uart_port *port = &early_console_dev.port;
        const __be32 *val;
        bool big_endian;
        u64 addr;

        spin_lock_init(&port->lock);
        port->iotype = UPIO_MEM;
        addr = of_flat_dt_translate_address(node);
        if (addr == OF_BAD_ADDR) {
                pr_warn("[%s] bad address\n", match->name);
                return -ENXIO;
        }
        port->mapbase = addr;

        val = of_get_flat_dt_prop(node, "reg-offset", NULL);
        if (val)
                port->mapbase += be32_to_cpu(*val);
        port->membase = earlycon_map(port->mapbase, SZ_4K);

        val = of_get_flat_dt_prop(node, "reg-shift", NULL);
        if (val)
                port->regshift = be32_to_cpu(*val);
        big_endian = of_get_flat_dt_prop(node, "big-endian", NULL) != NULL ||
                (IS_ENABLED(CONFIG_CPU_BIG_ENDIAN) &&
                 of_get_flat_dt_prop(node, "native-endian", NULL) != NULL);
        val = of_get_flat_dt_prop(node, "reg-io-width", NULL);
        if (val) {
                switch (be32_to_cpu(*val)) {
                case 1:
                        port->iotype = UPIO_MEM;
                        break;
                case 2:
                        port->iotype = UPIO_MEM16;
                        break;
                case 4:
                        port->iotype = (big_endian) ? UPIO_MEM32BE : UPIO_MEM32;
                        break;
                default:
                        pr_warn("[%s] unsupported reg-io-width\n", match->name);
                        return -EINVAL;
                }
        }

        val = of_get_flat_dt_prop(node, "current-speed", NULL);
        if (val)
                early_console_dev.baud = be32_to_cpu(*val);

        val = of_get_flat_dt_prop(node, "clock-frequency", NULL);
        if (val)
                port->uartclk = be32_to_cpu(*val);

        if (options) {
                early_console_dev.baud = simple_strtoul(options, NULL, 0);
                strlcpy(early_console_dev.options, options,
                        sizeof(early_console_dev.options));
        }
        earlycon_init(&early_console_dev, match->name);
        err = match->setup(&early_console_dev, options);
        if (err < 0)
                return err;
        if (!early_console_dev.con->write)
                return -ENODEV;

        register_console(early_console_dev.con);
        return 0;
}

디바이스 트리를 통해 early 콘솔 디바이스 속성 값들을 읽어온 후 디바이스의 설정 함수를 호출한 후 콘솔 디바이스로 등록한다.

  • 코드 라인 12~18에서 레지스터 주소를 알아온다.
    • UPIO_MEM
      • 유저 스페이스 노출가능한 mmio(주소 지정된 io)로 처리 bit 및 endian이 지정되지 않은 driver-specific mmio이다.
      • 참고로 8250은 8bit 리틀 엔디안 이고 pl011은 16bit 리틀 엔디안이다.
  • 코드 라인 20~22에서 “reg-offset” 속성이 있는 경우 해당 값 만큼 offset을 주소에 더한다.
  • 코드 라인 23에서 early 콘솔 디바이스를 4K 페이지만큼 매핑한다.
  • 코드 라인 25~27에서 “reg-shift” 속성이 있는 경우 regshift 값에 대입해둔다.
  • 코드 라인 28~30에서 빅 엔디안 요청이 있는지 여부를 확인한다. 다음 2가지 경우로 판단한다.
    • “big-endian” 속성이 있는 경우
    • cpu가 big-endian으로 동작하면서 “native-endian” 속성이 있는 경우
  • 코드 라인 31~47에서 콘솔 버스 크기를 알아와서 크기에 따라 다음과 같이 포트 타입을 지정한다.
    • 1 바이트 크기인 경우 포트 타입을 UPIO_MEM으로 한다.
    • 2 바이트 크기인 경우 포트 타입을 UPIO_MEM16으로 한다.
    • 4 바이트 크기인 경우 포트 타입을 UPIO_MEM32BE 또는 UPIO_MEM32로 한다.
  • 코드 라인 49~51에서 “current-speed” 값을 읽어 시리얼 속도를 지정한다.
  • 코드 라인 53~55에서 “clock-frequency” 값을 읽어 클럭 값을 지정한다.
  • 코드 라인 57~61에서 콘솔 디바이스에 옵션(콘솔명 + ‘:’ + 옵션)이 지정된 경우 시리얼 속도를 지정하고, 옵션도 지정해둔다.
  • 코드 라인 62에서 early 콘솔 디바이스의 이름과 데이터를 지정한 후, 설정 정보를 출력한다.
    • 예) earlycon: pl11 at MMIO 0x0000000009000000 (options ”)
  • 코드 라인 63~65에서 매치된 디바이스의 설정 함수를 호출한다.
  • 코드 라인 66~67에서 early 콘솔 드라이버에 출력 후크 함수가 구현되지 않은 경우 -ENODEV 에러를 반환한다.
  • 코드 라인 69에서 콘솔 디바이스로 등록한다.

 

참고

 

setup_machine_tags()

arch 번호로 태그테이블에서 machine을 검색하여 machine_desc 구조체 포인터를 찾고 ATAG를 디바이스 트리 구조로 변경한다.

setup_machine_tags

 

setup_machine_tags()

  • for_each_machine_desc()
    • __arch_info_begin ~ __arch_info_end 영역에 위치한 machine_desc 구조체 배열에서 머신 번호가 같은 경우를 찾는다.
    • machine_desc 구조체 배열은 .arch.info.init 섹션에 위치한다.
  • 만일 machine을 검색하여 찾지 못한 경우 machine table을 덤프하고 정지한다.
  • CONFIG_DEPRECATED_PARAM_STRUCT
    • ATAG 사용 하기 전에는 PARAM_STRUCT를 사용했다.
    •  convert_to_tag_list()
      • 태그의 처음이 ATAG_CORE가 아니면 PARAM_STRUCT 방식이라고 판단하여 ATAG 구조로 변환한다.
  • 처음 태그가 ATAG_CORE가 아닌 경우 “Warning: Neither atags nor dtb found” 경고 메시지를  출력하고 default 태그 구조체를 사용한다.
  • fixup 콜백함수가 null이 아닌 경우 fixup 콜백 함수를 수행한다.
    • 펌웨어에 문제가 있는 경우를 패치하기 위한 함수가 존재하는 경우 호출
    • 예) mach-msm/board-msm7x30.c – msm7x30_fixup() 참고
  • 태그가 ATAG_CORE 인 경우
    • 물리 메모리 사이즈가 이미 존재하는 경우 태그 정보를 무시하기 위해 squash_mem_tags()를 호출하여 ATAG_MEM을 ATAG_NONE으로 변경한다.
      • memblock_phys_mem_size = memblock.memory.total_size
    • save_atags()
      • 전역 변수 atags_copy 문자열 배열에 태그를 저장
    • parse_tags()
      • __tagtable_begin 부터 __tagtable_end 위치에 존재하는 태그 테이블에서 하나 씩 비교하여 동일한 태그인 경우 해당 태그의 parse 루틴을 호출한 후 리턴한다.
        • 태그 테이블은 .taglist.init 섹션에 위치한다.
      • parsing이 실패하면 “Ignoring unrecognised tag”라고 경고 출력한다.
      • 각 태그에 대한 파싱 함수 목록
        • ATAG_CORE: parse_tag_core()
        • ATAG_MEM: parse_tag_mem32()
        • ATAG_CMDLINE: parse_tag_cmdline
        • ATAG_INITRD: parse_tag_initrd()
        • ATAG_INITRD2, parse_tag_initrd2()
        • ATAG_VIDEOTEXT: parse_tag_videotext()
        • ATAG_RAMDIST: parse_tag_ramdisk()
        • ATAG_SERAIL: parse_tag_serialnr()
        • ATAG_REVISION: parse_tag_revision()
  • 마지막으로 전역 변수 boot_command_line에 default_cmd_line 값을 대입한다.
    • default_cmd_line은 컴파일 시 초기 설정된 값이 있고 커널 파라메터 설정에 따라 parse_tag_cmdline()을 수행하고 난 후 변경될 수 있다.
const struct machine_desc * __init
setup_machine_tags(phys_addr_t __atags_pointer, unsigned int machine_nr)
{
        struct tag *tags = (struct tag *)&default_tags;
        const struct machine_desc *mdesc = NULL, *p;
        char *from = default_command_line;

        default_tags.mem.start = PHYS_OFFSET;

        /*
         * locate machine in the list of supported machines.
         */
        for_each_machine_desc(p)
                if (machine_nr == p->nr) {
                        pr_info("Machine: %s\n", p->name);
                        mdesc = p;
                        break;
                }

        if (!mdesc) {
                early_print("\nError: unrecognized/unsupported machine ID"
                            " (r1 = 0x%08x).\n\n", machine_nr);
                dump_machine_table(); /* does not return */
        }

        if (__atags_pointer)
                tags = phys_to_virt(__atags_pointer);
        else if (mdesc->atag_offset)
                tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);

#if defined(CONFIG_DEPRECATED_PARAM_STRUCT)
        /*
         * If we have the old style parameters, convert them to
         * a tag list.
         */
        if (tags->hdr.tag != ATAG_CORE)
                convert_to_tag_list(tags);
#endif
        if (tags->hdr.tag != ATAG_CORE) {
                early_print("Warning: Neither atags nor dtb found\n");
                tags = (struct tag *)&default_tags;
        }

        if (mdesc->fixup)
                mdesc->fixup(tags, &from);

        if (tags->hdr.tag == ATAG_CORE) {
                if (memblock_phys_mem_size())
                        squash_mem_tags(tags);
                save_atags(tags);
                parse_tags(tags);
        }

        /* parse_early_param needs a boot_command_line */
        strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

        return mdesc;
}

 

ATAG용 Machine 정보

MACHINE_START()

  • ATAG용 machine_desc 구조체 선언 매크로
    • ATAG용에서는 nr로 검색하므로 nr 값이 중요하다.
      • arm용 머신 번호는 arch/arm/tools/mach-types 화일을 참고한다.
    • DTB용은 DT_MACHINE_START() 매크로를 사용하고 name으로 검색한다.
  • __used를 사용하여 이 객체가 참조되지 않아도 컴파일러가 제거하지 않도록 한다.
  • MACHINE_END()와 쌍으로 사용한다.

arch/arm/include/asm/mach/arch.h

/*
 * Set of macros to define architecture features.  This is built into
 * a table by the linker.
 */
#define MACHINE_START(_type,_name)                      \
static const struct machine_desc __mach_desc_##_type    \
 __used                                                 \
 __attribute__((__section__(".arch.info.init"))) = {    \
        .nr             = MACH_TYPE_##_type,            \
        .name           = _name,

#define MACHINE_END                             \
};

 

machine_desc 구조체

arch/arm/include/asm/mach/arch.h

struct machine_desc {
        unsigned int            nr;             /* architecture number  */
        const char              *name;          /* architecture name    */
        unsigned long           atag_offset;    /* tagged list (relative) */
        const char *const       *dt_compat;     /* array of device tree
                                                 * 'compatible' strings */

        unsigned int            nr_irqs;        /* number of IRQs */

#ifdef CONFIG_ZONE_DMA
        phys_addr_t             dma_zone_size;  /* size of DMA-able area */
#endif

        unsigned int            video_start;    /* start of video RAM   */
        unsigned int            video_end;      /* end of video RAM     */

        unsigned char           reserve_lp0 :1; /* never has lp0        */
        unsigned char           reserve_lp1 :1; /* never has lp1        */
        unsigned char           reserve_lp2 :1; /* never has lp2        */
        enum reboot_mode        reboot_mode;    /* default restart mode */
        unsigned                l2c_aux_val;    /* L2 cache aux value   */
        unsigned                l2c_aux_mask;   /* L2 cache aux mask    */
        void                    (*l2c_write_sec)(unsigned long, unsigned);
        struct smp_operations   *smp;           /* SMP operations       */
        bool                    (*smp_init)(void);
        void                    (*fixup)(struct tag *, char **);
        void                    (*dt_fixup)(void);
        void                    (*init_meminfo)(void);
        void                    (*reserve)(void);/* reserve mem blocks  */
        void                    (*map_io)(void);/* IO mapping function  */
        void                    (*init_early)(void);
        void                    (*init_irq)(void);
        void                    (*init_time)(void);
        void                    (*init_machine)(void);
        void                    (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
        void                    (*handle_irq)(struct pt_regs *);
#endif
        void                    (*restart)(enum reboot_mode, const char *);
};

 

라즈베리파이 1 & 2 MACHINE 구조체 선언

arch/arm/mach-bcm2709/bcm2709.c

static const char * const bcm2709_compat[] = {
        "brcm,bcm2709",
        "brcm,bcm2708", /* Could use bcm2708 in a pinch */
        NULL
};

MACHINE_START(BCM2709, "BCM2709")
    /* Maintainer: Broadcom Europe Ltd. */
#ifdef CONFIG_SMP
        .smp            = smp_ops(bcm2709_smp_ops),
#endif
        .map_io = bcm2709_map_io,
        .init_irq = bcm2709_init_irq,
        .init_time = bcm2709_timer_init,
        .init_machine = bcm2709_init,
        .init_early = bcm2709_init_early,
        .reserve = board_reserve,
        .restart        = bcm2709_restart,
        .dt_compat = bcm2709_compat,
MACHINE_END

MACHINE_START(BCM2708, "BCM2709")
    /* Maintainer: Broadcom Europe Ltd. */
#ifdef CONFIG_SMP
        .smp            = smp_ops(bcm2709_smp_ops),
#endif
        .map_io = bcm2709_map_io,
        .init_irq = bcm2709_init_irq,
        .init_time = bcm2709_timer_init,
        .init_machine = bcm2709_init,
        .init_early = bcm2709_init_early,
        .reserve = board_reserve,
        .restart        = bcm2709_restart,
        .dt_compat = bcm2709_compat,
MACHINE_END

 

ATAG Parsing

tag 및 tagtable 구조체

arch/arm/include/uapi/asm/setup.h

struct tag {
        struct tag_header hdr;
        union {
                struct tag_core         core;
                struct tag_mem32        mem;
                struct tag_videotext    videotext;
                struct tag_ramdisk      ramdisk;
                struct tag_initrd       initrd;
                struct tag_serialnr     serialnr;
                struct tag_revision     revision;
                struct tag_videolfb     videolfb;
                struct tag_cmdline      cmdline;

                /*
                 * Acorn specific
                 */
                struct tag_acorn        acorn;

                /*
                 * DC21285 specific
                 */
                struct tag_memclk       memclk;
        } u;
};

struct tagtable {
        __u32 tag;
        int (*parse)(const struct tag *);
};

 

 

parse_tag()

  •  태그 영역에 저장된 태그들 중 아키텍처 번호가 같은 태그들에 연결된 parse 콜백 함수를  호출한다.
/*
 * Scan the tag table for this tag, and call its parse function.
 * The tag table is built by the linker from all the __tagtable
 * declarations.
 */
static int __init parse_tag(const struct tag *tag)
{
        extern struct tagtable __tagtable_begin, __tagtable_end;
        struct tagtable *t;

        for (t = &__tagtable_begin; t < &__tagtable_end; t++)
                if (tag->hdr.tag == t->tag) {
                        t->parse(tag);
                        break;
                }

        return t < &__tagtable_end;
}

 

 

parse_tag_core()

  • 전역 변수 root_mountflags에 루트 마운트 플래그 속성에서 MS_RDONLY를 제거하고 저장
  • 전역 변수 ROOT_DEV에 디바이스 번호를 저장한다.

arch/arm/kernel/atags_parse.c

static int __init parse_tag_core(const struct tag *tag)
{
        if (tag->hdr.size > 2) {
                if ((tag->u.core.flags & 1) == 0)
                        root_mountflags &= ~MS_RDONLY;
                ROOT_DEV = old_decode_dev(tag->u.core.rootdev);
        }
        return 0;
}

__tagtable(ATAG_CORE, parse_tag_core);

 

parse_tag_mem32()

  • arm_add_memory() 함수를 사용하여 메모리 영역을 추가한다.
static int __init parse_tag_mem32(const struct tag *tag)
{
        return arm_add_memory(tag->u.mem.start, tag->u.mem.size);
}

__tagtable(ATAG_MEM, parse_tag_mem32);

 

parse_tag_videotext()

  • screen_info 구조체에 파라메터 값들을 저장한다.
#if defined(CONFIG_VGA_CONSOLE) || defined(CONFIG_DUMMY_CONSOLE)
static int __init parse_tag_videotext(const struct tag *tag)
{
        screen_info.orig_x            = tag->u.videotext.x;
        screen_info.orig_y            = tag->u.videotext.y;
        screen_info.orig_video_page   = tag->u.videotext.video_page;
        screen_info.orig_video_mode   = tag->u.videotext.video_mode;
        screen_info.orig_video_cols   = tag->u.videotext.video_cols;
        screen_info.orig_video_ega_bx = tag->u.videotext.video_ega_bx;
        screen_info.orig_video_lines  = tag->u.videotext.video_lines;
        screen_info.orig_video_isVGA  = tag->u.videotext.video_isvga;
        screen_info.orig_video_points = tag->u.videotext.video_points;
        return 0;
}

__tagtable(ATAG_VIDEOTEXT, parse_tag_videotext);
#endif

 

parse_tag_ramdisk()

  • 전역 변수 rd_image_start에 램디스크 시작 주소를 저장한다.
  • 전역 변수 rd_doload와 rd_prompt에 플래그 상태를 저장한다.
  • 전역 변수 rd_size에 램디스크 사이즈를 저장한다.
#ifdef CONFIG_BLK_DEV_RAM
static int __init parse_tag_ramdisk(const struct tag *tag)
{
        extern int rd_size, rd_image_start, rd_prompt, rd_doload;

        rd_image_start = tag->u.ramdisk.start;
        rd_doload = (tag->u.ramdisk.flags & 1) == 0;
        rd_prompt = (tag->u.ramdisk.flags & 2) == 0;

        if (tag->u.ramdisk.size)
                rd_size = tag->u.ramdisk.size;

        return 0;
}

__tagtable(ATAG_RAMDISK, parse_tag_ramdisk);
#endif

 

parse_tag_serialnr()

  • 전역 변수 system_serial_low와 system_serial_high에 시리얼 low 값과 high 값을 저장한다.
static int __init parse_tag_serialnr(const struct tag *tag)
{
        system_serial_low = tag->u.serialnr.low;
        system_serial_high = tag->u.serialnr.high;
        return 0;
}

__tagtable(ATAG_SERIAL, parse_tag_serialnr);

 

parse_tag_revision()

  •  전역 변수 system_rev에 리비전 정보를 저장한다.
static int __init parse_tag_revision(const struct tag *tag)
{
        system_rev = tag->u.revision.rev;
        return 0;
}

__tagtable(ATAG_REVISION, parse_tag_revision);

 

parse_tag_cmdline()

  • 다음 3가지 case에 대해 수행한다.
    • CONFIG_CMDLINE_EXTEND
      • default_command_line에 ATAG가 전달한 cmdline을 추가한다.
    • CONFIG_CMDLINE_FORCE
      • ATAG가 전달한 cmdline을 무시하고 default_command_line을 사용한다.
    • cmdline 관련 옵션이 없는 경우
      • default_command_line에 ATAG가 전달한 cmdline을 겹쳐 쓴다.
static int __init parse_tag_cmdline(const struct tag *tag)
{
#if defined(CONFIG_CMDLINE_EXTEND)
        strlcat(default_command_line, " ", COMMAND_LINE_SIZE);
        strlcat(default_command_line, tag->u.cmdline.cmdline,
                COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
        pr_warn("Ignoring tag cmdline (using the default kernel command line)\n");
#else
        strlcpy(default_command_line, tag->u.cmdline.cmdline,
                COMMAND_LINE_SIZE);
#endif
        return 0;
}

__tagtable(ATAG_CMDLINE, parse_tag_cmdline);

 

기타 함수

arm_add_memory()

arch/arm/kernel/setup.c

int __init arm_add_memory(u64 start, u64 size)
{
        u64 aligned_start;

        /*   
         * Ensure that start/size are aligned to a page boundary.
         * Size is rounded down, start is rounded up.
         */
        aligned_start = PAGE_ALIGN(start);
        if (aligned_start > start + size)
                size = 0; 
        else 
                size -= aligned_start - start;

#ifndef CONFIG_ARCH_PHYS_ADDR_T_64BIT
        if (aligned_start > ULONG_MAX) {
                pr_crit("Ignoring memory at 0x%08llx outside 32-bit physical address space\n",
                        (long long)start);
                return -EINVAL;
        }

        if (aligned_start + size > ULONG_MAX) {
                pr_crit("Truncating memory at 0x%08llx to fit in 32-bit physical address space\n",
                        (long long)start);
                /*   
                 * To ensure bank->start + bank->size is representable in
                 * 32 bits, we use ULONG_MAX as the upper limit rather than 4GB.
                 * This means we lose a page after masking.
                 */
                size = ULONG_MAX - aligned_start;
        }    
#endif

        if (aligned_start < PHYS_OFFSET) {
                if (aligned_start + size <= PHYS_OFFSET) {
                        pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n",
                                aligned_start, aligned_start + size);
                        return -EINVAL;
                }

                pr_info("Ignoring memory below PHYS_OFFSET: 0x%08llx-0x%08llx\n",
                        aligned_start, (u64)PHYS_OFFSET);

                size -= PHYS_OFFSET - aligned_start;
                aligned_start = PHYS_OFFSET;
        }

        start = aligned_start;
        size = size & ~(phys_addr_t)(PAGE_SIZE - 1);

        /*   
         * Check whether this memory region has non-zero size or
         * invalid node number.
         */
        if (size == 0)
                return -EINVAL;

        memblock_add(start, size);
        return 0;
}
  • aligned_start = PAGE_ALIGN(start);
    • start 주소에 대해 4K round up 한다.
  • if (aligned_start > start + size)
    • 4K round up 한 aligned_start 주소가 start + size를 초과하는 경우 size를 0으로 변경하고 그렇지 않은 경우 4K round up으로 인해 발생한 그 차이만큼 size에서 뺀다.
    • 결국 4K align 되어 남는 하위 메모리는 버리게 된다.
    • 예) arm_add_memory(0x1234_5678, 0x1000_0000)
      • 물리 메모리 주소 0x1234_5678 부터 256M 크기의 메모리를 추가하라는 요청
      • 수행 후 물리 메모리 주소 0x1234_5000 부터 (256M – 0x678) 크기의 메모리를 추가
  • CONFIG_ARCH_PHYS_ADDR_T_64BIT
    • LPAE 설정 시 사용된다.
  • if (aligned_start + size > ULONG_MAX) {
    • 추가할 메모리 영역이 32비트 주소의 끝을 초과하는 경우 size를 32비트 이내에 들어갈 수 있도록 조정한다.
    • 예) arm_add_memory(0xf000_0000, 0x2000_0000)
      • 물리 메모리 주소 0xf000_0000 부터 512M 크기의 메모리를 추가하라는 요청
      • 수행 후 물리 메모리 주소 0xf000_0000 부터 0x0fff_ffff 크기의 메모리를 추가
  • if (aligned_start < PHYS_OFFSET) {
    • 요청한 시작 주소가 물리 메모리 시작 주소보다 작은 경우
  • if (aligned_start + size <= PHYS_OFFSET) {
    • size 까지 합친 요청 영역이 물리 메모리 시작 주소보다 작아 범위를 아예 벗어난 경우 에러를 경고하고 함수를 리턴한다.
    • 요청한 구간이 물리 메모리 이하에서 시작하였다는 것을 경고 출력하고 물리 메모리 시작 주소 이하의 요청 메모리를 제거한 범위를 시작 주소와 사이즈를 재 조정한다.
    • 예) arm_add_memory(0x1F00_0000, 0x1000_0000) 이 때 PHYS_OFFSET=0x2000_0000
      • aligned_start = 0x2000_0000
      • size = 0x0f00_000
  • size = size & ~(phys_addr_t)(PAGE_SIZE – 1);
    • 사이즈 또한 align 되어 있지 않으면 round down 하여 버린다.
    • 예) arm_add_memory(0x2000_0000, 0x1234_5678)
      • size = 0x1234_5000
  •  if (size == 0)
    • 추가 할 사이즈가 0이면 함수를 빠져나간다.
  • memblock_add(start, size);
    • memory memblock 에 메모리 영역을 추가한다.

 

참고

setup_machine_fdt()

<kernel v5.0>

머신 설정

시스템을 설정하는 방법은 다음과 같이 두 가지로 나뉜다.

  • Legacy
    • 머신 디스크립터를 사용하여 아키텍처 또는 머신 specific한 코드를 사용한다.
    • 기존 ARM32를 사용한 대부분의 임베디드 SoC 업체들이 사용한 방법이다.
    • ARM64에서는 사용하지 않는다.
  • Device Tree
    • 발빠른 ARM32 SOC 업체들은 디바이스 트리를 지원하기 시작하였다.
    • ARM32에서는 DT_MACHINE_START() 매크로를 사용한 머신 디스크립터를 사용한다.
      • ARM64는 머신 디스크립터를 사용하지 않는다.
    • ARM64는 디바이스 트리만 지원한다. 즉 디바이스 트리 설정에 따라 시스템을 초기화하는 방식을 사용한다.
      • 드라이버 개발자들은 디바이스 트리의 코어 운용 기준을 정확히 알아야 하고 이에 따라 드라이버를 구성해야 한다.

 

인수로 받은 DTB 물리 주소를 사용하여 Machine 테이블에서 name으로 검색한 후 machine_desc 구조체 포인터로 리턴한다. – ARM32

setup_machine_fdt_1a

 

멀티플랫폼(ARCH_MULTIPLATFORM) – ARM32

  • 컴파일된 커널이 미리 지정된 여러 개의 플랫폼을 지원할 수 있게하였다. 부팅 중 플랫폼을 선택하여 동작한다.
  • 2012년 ARM에서 추가를 시도했으나 개별 플랫폼의 코드를 통합하는 것에 난이도가 높아 잘 사용되지 않을 전망이다.
  • 관련 설정
    • ARCH_MULTIPLATFORM
    • ARCH_MULTI_CPU_AUTO
    • MULTI_IRQ_HANDLER
    • ARCH_MULTI_V4
    • ARCH_MULTI_V4T
    • ARCH_MULTI_V4_V5
    • ARCH_MULTI_V5
    • ARCH_MULTI_V6
    • ARCH_MULTI_V6_V7
    • ARCH_MULTI_V7
  • 참고: ARM: initial multiplatform support | LWN.net

 

DTB용 Machine 정보

DT_MACHINE_START() 매크로 – ARM32

arch/arm/include/asm/mach/arch.h

#define DT_MACHINE_START(_name, _namestr)               \
static const struct machine_desc __mach_desc_##_name    \
 __used                                                 \
 __attribute__((__section__(".arch.info.init"))) = {    \
        .nr             = ~0,                           \
        .name           = _namestr,

#endif
  • .arch.info.init 섹션에 const 객체를 만들어 둔다.
  • DTB 기반 Machine 테이블을 선언한다.
    • 검색시 MACHINE_START() 매크로로 선언된 ATAG 기반 Machine 정보와 다르게 번호가 아닌 name으로 검색한다.
    • nr을 -1로 한다.
  • __used를 사용하여 이 객체가 참조되지 않아도 컴파일러가 제거하지 않도록 한다.

 

machine_desc 및 map_desc 구조체 – ARM32

  • 라즈베리파이2: bcm2709

setup_machine_fdt_2

 

setup_machine_fdt() – ARM32

arch/arm/kernel/devtree.c

/**
 * setup_machine_fdt - Machine setup when an dtb was passed to the kernel
 * @dt_phys: physical address of dt blob
 *
 * If a dtb was passed to the kernel in r2, then use it to choose the
 * correct machine_desc and to setup the system.
 */
const struct machine_desc * __init setup_machine_fdt(unsigned int dt_phys)
{
        const struct machine_desc *mdesc, *mdesc_best = NULL;

#if defined(CONFIG_ARCH_MULTIPLATFORM) || defined(CONFIG_ARM_SINGLE_ARMV7M)
        DT_MACHINE_START(GENERIC_DT, "Generic DT based system")
                .l2c_aux_val = 0x0,
                .l2c_aux_mask = ~0x0,
        MACHINE_END

        mdesc_best = &__mach_desc_GENERIC_DT;
#endif

        if (!dt_phys || !early_init_dt_verify(phys_to_virt(dt_phys)))
                return NULL;

        mdesc = of_flat_dt_match_machine(mdesc_best, arch_get_next_mach);

        if (!mdesc) {
                const char *prop;
                int size;
                unsigned long dt_root;

                early_print("\nError: unrecognized/unsupported "
                            "device tree compatible list:\n[ ");

                dt_root = of_get_flat_dt_root();
                prop = of_get_flat_dt_prop(dt_root, "compatible", &size);
                while (size > 0) {
                        early_print("'%s' ", prop);
                        size -= strlen(prop) + 1;
                        prop += strlen(prop) + 1;
                }
                early_print("]\n\n");

                dump_machine_table(); /* does not return */
        }

        /* We really don't want to do this, but sometimes firmware provides buggy data */
        if (mdesc->dt_fixup)
                mdesc->dt_fixup();

        early_init_dt_scan_nodes();

        /* Change machine number to match the mdesc we're using */
        __machine_arch_type = mdesc->nr;

        return mdesc;
}

인수로 전달받은 디바이스 트리(fdt)가 물리 주소에 있는지 검색하여 해당 machine을 찾고 machine_desc 구조체 포인터로 알아온다. 또한 디바이스 트리로부터 초기 부트업 과정에 미리(early) 설정해야할 정보를 얻어온 다. 초기 정보에는 커멘드 라인 정보나 메모리 정보 등이 있다.

  • 코드 라인 5~12에서 멀티(2개 이상의 cpu가 다른 아키텍처) 플랫폼 또는 single ARMv7M을 사용한 시스템인 경우 빈 데이터 상태의 머신 구조체 정보를 사용한다.
  • 코드 라인 15~16에서 디바이스 트리(fdt)의 물리 주소를 검사하여 이상이 있는 경우 null을 가지고 그냥 빠져나간다.
  • 코드 라인 18~39에서 machine을 찾아 machine_desc 구조체 포인터를 알아온다. 못 찾은 경우 machine 테이블을 덤프하고 시스템을 정지한다.
  • 코드 라인 42~43에서 만일 fixup 함수가 준비되어 있으면 동작시킨다.
    • dt_fixup 멤버 변수는 보통 null이며 특별히 펌웨어에 문제가 있어 수정되어야 하는 경우 이 멤버 변수에 fixup 관련 함수를 등록하여 사용한다.
    • 사용 예:
      • arch/arm/mach-exynos/exynos.c – dt_fixup에 exynos_dt_fixup() 함수가 연결되어 있다.
  • early_init_dt_scan_nodes()
    • 노드를 스캔하여 early하게 초기화 해놓아야 할 항목들을 수행한다.
      • boot_commmand_line, dt_root_size_cells, dt_root_addr_cells, early하게 추가해야 할 가용메모리 정보등이 있다.

 

setup_machine_fdt() – ARM64

arch/arm64/kernel/setup.c

static void __init setup_machine_fdt(phys_addr_t dt_phys)
{
        void *dt_virt = fixmap_remap_fdt(dt_phys);
        const char *name;

        if (!dt_virt || !early_init_dt_scan(dt_virt)) {
                pr_crit("\n"
                        "Error: invalid device tree blob at physical address %pa (virtual address 0xx
%p)\n"
                        "The dtb must be 8-byte aligned and must not exceed 2 MB in size\n"
                        "\nPlease check your bootloader.",
                        &dt_phys, dt_virt);

                while (true)
                        cpu_relax();
        }

        name = of_flat_dt_get_machine_name();
        if (!name)
                return;

        pr_info("Machine model: %s\n", name);
        dump_stack_set_arch_desc("%s (DT)", name);
}

디바이스 트리(fdt)를 읽어들여 fixmap에 매핑하고 초기 부트업 과정에 미리(early) 설정해야할 정보를 얻어온 다. 초기 정보에는 커멘드 라인 정보나 메모리 정보 등이 있다.

  • 코드 라인 3에서 디바이스 트리(fdt)를 읽어 fixmap에 매핑한다.
  • 코드 라인 6~16에서 노드를 스캔하여 다음과 같이 먼저(early) 초기화할 항목들을 수행한다. 디바이스 트리를 발견할 수 없거나  디바이스트리 물리 주소를 검사하여 이상이 있는 경우 에러 메시지를 출력하고 동작을 멈춘다.
    • boot_commmand_line 설정
    • initrd_start, initrd_end 설정
    • dt_root_size_cells, dt_root_addr_cells 설정
    • memory memblock1에 메모리 등록
  • 코드 라인 18~22에서 머신명을 알아와서 모델명을 출력한다. 루트 노드의 “model” 속성을 머신명으로 사용하며 속성이 발견되지 않는 경우 “compatible” 속성을 사용한다.
  • 코드 라인 23에서 dump_stack_print_info() 함수가 호출될 때 사용할 머신 모델 명을 미리 저장해둔다.

 

early_init_dt_scan_nodes()

drivers/of/fdt.c

void __init early_init_dt_scan_nodes(void)
{
        int rc = 0;

        /* Retrieve various information from the /chosen node */
        rc = of_scan_flat_dt(early_init_dt_scan_chosen, boot_command_line);
        if (!rc)
                pr_warn("No chosen node found, continuing without\n");

        /* Initialize {size,address}-cells info */
        of_scan_flat_dt(early_init_dt_scan_root, NULL);

        /* Setup memory, calling early_init_dt_add_memory_arch */
        of_scan_flat_dt(early_init_dt_scan_memory, NULL);
}

DTB 노드를 스캔하여 먼저(early) 초기화해놓아야 할 항목들을 수행한다.

  • 코드 라인 6~8에서 “/chosen” 노드를 스캔하여 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 읽어와서 전역 initrd_start와 initrd_end에 저장한다. 또한 “bootargs” 속성 값을 읽어와서 전역 변수 boot_command_line에 저장한다.
    • initrd는 커널 부트 로드 과정에 필요한 초기 램디스크로, 이 램디스크는 보통 일시적으로 루트 파일 시스템으로 마운트되고 처음 유저 모드에서 초기화 코드(sysvinit, systemd, …)가 호출된다. 이 과정에서 다른 정규 파일 시스템이 마운트되고 루트 파일 시스템이 교체되는 과정이 진행된다. 유저 모드에서 실행하는 처음 초기화 코드를 통해 두 과정으로 진행하는데, 하나는 커널이 필요로 하는 최소의 드라이버들을 로드하고 그 후 나머지 추가적인 모듈들이 로드된다.
    • CONFIG_CMDLINE 커널 옵션을 사용하는 경우에는 “bootargs” 속성 값이 없다면 커널 빌드 시 주어진 디폴트 CONFIG_CMDLINE을 사용한다. 단, CONFIG_CMDLINE_FORCE 커널 옵션도 사용하는 경우 “bootargs” 속성 값의 유무와 상관없이 무조건 디폴트 CONFIG_CMDLINE을 사용한다.
  • 코드 라인 11에서 루트 노드를 스캔하여 “#size-cells” 속성 값과 “#address-cells” 속성 값을 읽어와서 전역 변수 dt_root_size_cells와 dt_root_addr_cells에 저장한다.
  • 코드 라인 14에서 “memory” 노드를 스캔하여 “reg” 속성 값을 읽어와서 파싱한 물리 메모리 시작 주소 및 크기 정보로 memory memblock에 추가한다.

 

#address-cells와 #size-cells

#address-cells와 #size-cells는 이 속성이 있는 노드의 자식(child)부터 그 이하 노드를 대상으로 주소 셀 및 크기 셀이 표현된 값을 읽어들일 때 그 값이 표현하는 데 필요한 워드 수를 의미한다. 적용 범위가 현재 노드가 아니라 항상 하위 노드 이하를 대상으로 함에 주의해야 한다. 디바이스 트리에서 숫자 표현은 항상 워드(4바이트) 단위를 초과할 수 없다. 따라서 이보다 더 큰 숫자를 표기하려고 할 때는 한 칸 띄어서 2개의 워드를 연달아 표현해야 한다.

  • 예) regs가 표현하고자 하는 시작 주소는 0x8_0000_0000이며, 크기는 0x2000_0000(512MB)이다. 주소가 4바이트를 초과하므로 2개의 워드를 사용하기 위해서는 속성이 있는 노드의 부모 노드에서 먼저 주소 셀의 크기와 크기 셀의 크기를 적절하게 결정해두어야 한다.
node1 {
        #address-cells = <2>;
        #size-cells = <1>;
        ...
        node2 {
                regs = <0x8 0x0 0x20000000>;
        }
}

 

다음 그림은 earyl_init_dt_scan_nodes() 함수가 처리하는 일을 보여준다.

 

of_scan_flat_dt()

drivers/of/fdt.c

/**
 * of_scan_flat_dt - scan flattened tree blob and call callback on each.
 * @it: callback function
 * @data: context data pointer
 *
 * This function is used to scan the flattened device-tree, it is
 * used to extract the memory information at boot before we can
 * unflatten the tree
 */
int __init of_scan_flat_dt(int (*it)(unsigned long node,
                                     const char *uname, int depth,
                                     void *data),
                           void *data)
{
        const void *blob = initial_boot_params;
        const char *pathp;
        int offset, rc = 0, depth = -1;

        if (!blob)
                return 0;

        for (offset = fdt_next_node(blob, -1, &depth);
             offset >= 0 && depth >= 0 && !rc;
             offset = fdt_next_node(blob, offset, &depth)) {

                pathp = fdt_get_name(blob, offset, NULL);
                if (*pathp == '/')
                        pathp = kbasename(pathp);
                rc = it(offset, pathp, depth, data);
        }
        return rc;
}

디바이스 트리의 모든 노드를 뒤져 ‘ / ’로 시작하는 모든 노드에 대해 인자로 받은 함수를 호출하여 그 함수가 요청하는 값을 읽어온 경우 처리를 종료한다.

  • 코드 라인 13~15에서 디바이스 트리에서 노드만 루프를 돌며 검색하고 원하는 결과(rc = 1) 값을 읽어온 경우 루프를 빠져나간다.
  • 코드 라인 17~19에서 노드의 이름을 가져와서 ‘ / ’로 시작하는 노드인 경우에는 처음 ‘ / ’를 제외한 이름을 구한다.
  • 코드 라인 20에서 모든 노드에 대해 인자로 받은 함수(it)를 호출한다. 호출할 때 주어지는 인자들을 알아보면 오프셋은 structure 블록 내부에서 현재 순회(iteration)하고 있는 노드의 오프셋 바이트를 말한다. 루트 노드의 오프셋이 0부터 시작함을 기억해둔다. 두 번째 인자 pathp는 노드명을 가리키는 포인터다. 세 번째 인자 depth는 현재 노드의 depth를 의미하며, 루트 노드가 0부터 시작한다. 마지막 인자 data는 it 함수 호출 시 전달할 데이터다.

 

early_init_dt_scan_chosen()

drivers/of/fdt.c

int __init early_init_dt_scan_chosen(unsigned long node, const char *uname,
                                     int depth, void *data)
{
        int l;
        const char *p;

        pr_debug("search \"chosen\", depth: %d, uname: %s\n", depth, uname);

        if (depth != 1 || !data ||
            (strcmp(uname, "chosen") != 0 && strcmp(uname, "chosen@0") != 0))
                return 0;

        early_init_dt_check_for_initrd(node);

        /* Retrieve command line */
        p = of_get_flat_dt_prop(node, "bootargs", &l);
        if (p != NULL && l > 0)
                strlcpy(data, p, min((int)l, COMMAND_LINE_SIZE));

        /*
         * CONFIG_CMDLINE is meant to be a default in case nothing else
         * managed to set the command line, unless CONFIG_CMDLINE_FORCE
         * is set in which case we override whatever was found earlier.
         */
#ifdef CONFIG_CMDLINE
#if defined(CONFIG_CMDLINE_EXTEND)
        strlcat(data, " ", COMMAND_LINE_SIZE);
        strlcat(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#elif defined(CONFIG_CMDLINE_FORCE)
        strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#else
        /* No arguments from boot loader, use kernel's  cmdl*/
        if (!((char *)data)[0])
                strlcpy(data, CONFIG_CMDLINE, COMMAND_LINE_SIZE);
#endif
#endif /* CONFIG_CMDLINE */

        pr_debug("Command line is: %s\n", (char*)data);

        /* break now */
        return 1;
}

루트 노드를 스캔하여 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 읽어와서 전역 initrd_start와 initrd_end에 저장한다. 또한 “bootargs” 속성 값을 읽어와 전역 변수 boot_command_line에 저장한다.

  • 코드 라인 9~11에서 루트 노드의 다음 단계 자식 노드명이 “chosen”이 아니면 이 함수에서 필요한 노드가 아니므로 대상 노드를 처리하지 않는다.
  • 코드 라인 13에서 “linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.
  • 코드 라인 16~18에서 속성 bootargs를 찾아 전역 변수 boot_command_line에 저장한다.
  • 코드 라인 25~36에서 CONFIG_CMDLINE 커널 옵션을 사용하는 경우 “bootargs” 속성 값이 없다면 커널 빌드 시 주어진 디폴트 CONFIG_CMDLINE을 사용한다. 단, CONFIG_CMDLINE_FORCE 커널 옵션도 사용하는 경우 “bootargs” 속성 값의 유무와 상관없이 무조건 디폴트 CONFIG_CMDLINE 값을 사용한다.

 

다음 그림과 같이 커널 옵션에 따라 DTB 또는 커널 설정을 결정한다. default 설정을 사용하는 경우 부트 로더가 DTB를 로드한 후 bootargs 속성에 overwrite한 후 커널에 전달하는 과정을 알 수 있다.

 

early_init_dt_check_for_initrd()

drivers/of/fdt.c

/**
 * early_init_dt_check_for_initrd - Decode initrd location from flat tree
 * @node: reference to node containing initrd location ('chosen')
 */
static void __init early_init_dt_check_for_initrd(unsigned long node)
{
        u64 start, end;
        int len;
        const __be32 *prop;

        pr_debug("Looking for initrd properties... ");

        prop = of_get_flat_dt_prop(node, "linux,initrd-start", &len);
        if (!prop)
                return;
        start = of_read_number(prop, len/4);

        prop = of_get_flat_dt_prop(node, "linux,initrd-end", &len);
        if (!prop)
                return;
        end = of_read_number(prop, len/4);

        __early_init_dt_declare_initrd(start, end);
        phys_initrd_start = start;
        phys_initrd_size = end - start;

        pr_debug("initrd_start=0x%llx  initrd_end=0x%llx\n",
                 (unsigned long long)start, (unsigned long long)end);
}

“linux,initrd-start” 속성 값과 “linux,initrd-end” 속성 값을 찾아 전역 변수 initrd_start와 initrd_end에 저장한다.

  • 코드 라인 9~12에서 “linux,initrd-start” 속성 값을 읽어온다.
  • 코드 라인 14~17에서 “linux,initrd-end” 속성 값을 읽어온다.
  • 코드 라인 19~21에서 읽은 2개의 값을 읽어 가상 주소로 바꿔 전역 변수 initrd_start와 initrd_end에 저장한다. 그리고 물리 주소도 시작과 사이즈도 전역변수에 저장해둔다.

 

early_init_dt_scan_root()

drivers/of/fdt.c

/**
 * early_init_dt_scan_root - fetch the top level address and size cells
 */
int __init early_init_dt_scan_root(unsigned long node, const char *uname,
                                   int depth, void *data)
{
        const __be32 *prop;

        if (depth != 0)
                return 0;

        dt_root_size_cells = OF_ROOT_NODE_SIZE_CELLS_DEFAULT;
        dt_root_addr_cells = OF_ROOT_NODE_ADDR_CELLS_DEFAULT;

        prop = of_get_flat_dt_prop(node, "#size-cells", NULL);
        if (prop)
                dt_root_size_cells = be32_to_cpup(prop);
        pr_debug("dt_root_size_cells = %x\n", dt_root_size_cells);

        prop = of_get_flat_dt_prop(node, "#address-cells", NULL);
        if (prop)
                dt_root_addr_cells = be32_to_cpup(prop);
        pr_debug("dt_root_addr_cells = %x\n", dt_root_addr_cells);

        /* break now */
        return 1;
}

루트 노드를 스캔하여 “#size-cells” 속성 값과 “#address-cells” 속성 값을 읽어와서 전역 dt_root_size_cells 및 dt_root_addr_cells에 저장한다.

  • 코드 라인 6~7에서 노드의 depth가 0이 아닌 경우, 즉 루트 노드가 아닌 경우 빠져나간다.
  • 코드 라인 12~14에서 “#size-cells” 속성 값을 찾아 전역 변수 dt_root_size_cells에 저장하되, 찾지 못한 경우 기본 값 1로 한다. “#size-cells”는 size를 표현하는 cell의 수를 나타낸다.
  • 코드 라인 17~19에서 “#address-cells” 속성 값을 찾아 전역 변수 dt_root_addr_cells에 저장하되, 찾지 못한 경우 기본 값 1로 한다. “#address-cells”는 address를 표현하는 cell의 수를 나타낸다.

 

early_init_dt_scan_memory()

drivers/of/fdt.c

/**
 * early_init_dt_scan_memory - Look for and parse memory nodes
 */
int __init early_init_dt_scan_memory(unsigned long node, const char *uname,
                                     int depth, void *data)
{
        const char *type = of_get_flat_dt_prop(node, "device_type", NULL);
        const __be32 *reg, *endp;
        int l;
        bool hotpluggable;

        /* We are scanning "memory" nodes only */
        if (type == NULL || strcmp(type, "memory") != 0)
                return 0;

        reg = of_get_flat_dt_prop(node, "linux,usable-memory", &l);
        if (reg == NULL)
                reg = of_get_flat_dt_prop(node, "reg", &l);
        if (reg == NULL)
                return 0;

        endp = reg + (l / sizeof(__be32));
        hotpluggable = of_get_flat_dt_prop(node, "hotpluggable", NULL);

        pr_debug("memory scan node %s, reg size %d,\n", uname, l);

        while ((endp - reg) >= (dt_root_addr_cells + dt_root_size_cells)) {
                u64 base, size;

                base = dt_mem_next_cell(dt_root_addr_cells, &reg);
                size = dt_mem_next_cell(dt_root_size_cells, &reg);

                if (size == 0)
                        continue;
                pr_debug(" - %llx ,  %llx\n", (unsigned long long)base,
                    (unsigned long long)size);

                early_init_dt_add_memory_arch(base, size);

                if (!hotpluggable)
                        continue;

                if (early_init_dt_mark_hotplug_memory_arch(base, size))
                        pr_warn("failed to mark hotplug range 0x%llx - 0x%llx\n",
                                base, base + size);
        }

        return 0;
}

“memory” 노드를 스캔하여 “reg” 속성 값을 읽어와서 파싱한 물리 메모리 시작 주소 및 크기 정보로 memory memblock에 추가한다.

  • 코드 라인 4~11에서 노드에 대해 device_type 속성을 알아와서 memory 타입이 아닌 경우 그냥 리턴한다. powerpc 아키텍처에서는 device_type이 설정되지 않았어도 루트 다음에 “memory@0” 노드인 경우에는 계속 진행한다.
  • 코드 라인 13~17에서 “linux,usable-memory” 속성 또는 “reg” 속성을 찾아 사용 가능 메모리 크기를 정한다.
    • powerpc 아키텍처에서만 사용하는 속성이다.
  • 코드 라인 20에서 “hotpluggable” 속성이 있으면 그 여부를 저장한다.
  • 코드 라인 24~35에서 사용 가능 메모리 크기만큼 reg 값에 있는 메모리 base와 size를 사용하여 계산한 후 early_init_dt_add_memory_arch( )를 호출하여 메모리 블록을 추가한다. 배열인 경우에는 그 수만큼 루프를 돈다.
  • 코드 라인 37~43에서 hotplug 메모리의 경우 memblock에 hotplug 표식을 해둔다.

 

early_init_dt_add_memory_arch()

drivers/of/fdt.c

void __init __weak early_init_dt_add_memory_arch(u64 base, u64 size)
{
        const u64 phys_offset = MIN_MEMBLOCK_ADDR;

        if (size < PAGE_SIZE - (base & ~PAGE_MASK)) {
                pr_warn("Ignoring memory block 0x%llx - 0x%llx\n",
                        base, base + size);
                return;
        }

        if (!PAGE_ALIGNED(base)) {
                size -= PAGE_SIZE - (base & ~PAGE_MASK);
                base = PAGE_ALIGN(base);
        }
        size &= PAGE_MASK;

        if (base > MAX_MEMBLOCK_ADDR) {
                pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
                                base, base + size);
                return;
        }

        if (base + size - 1 > MAX_MEMBLOCK_ADDR) {
                pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                                ((u64)MAX_MEMBLOCK_ADDR) + 1, base + size);
                size = MAX_MEMBLOCK_ADDR - base + 1;
        }

        if (base + size < phys_offset) {
                pr_warning("Ignoring memory block 0x%llx - 0x%llx\n",
                           base, base + size);
                return;
        }
        if (base < phys_offset) {
                pr_warning("Ignoring memory range 0x%llx - 0x%llx\n",
                           base, phys_offset);
                size -= phys_offset - base;
                base = phys_offset;
        }
        memblock_add(base, size);
}

메모리 시작 주소와 크기로 memory memblock에 추가한다. 단, 물리 메모리 범위를 넘어가는 경우 size를 조정한다.

  • 코드 라인 3에서 물리 메모리의 하한 주소를 설정한다.
  • 코드 라인 5~9에서 사이즈가 너무 작으면 경고 메시지를 출력하고 함수를 빠져나간다.
  • 코드 라인 11~15에서 시작 주소가 페이지 단위로 정렬되지 않았다면 시작 주소를 페이지 단위로 정렬하고 그 차이만큼 size를 줄인다. 그런 후 페이지 단위로 내림 정렬한다.
  • 코드 라인 17~21에서 시작 주소가 물리 메모리 상한 주소를 초과한다면 더 이상 처리하지 않고 함수를 빠져나간다.
  • 코드 라인 23~27에서 끝 주소가 시스템 최대 처리 상한 주소를 초과한다면 초과한 만큼의 크기를 조절한다.
  • 코드 라인 29~33에서 끝 주소가 물리 메모리 최소 주소 미만인 경우에는 더 이상 처리하지 않고 함수를 빠져나간다.
  • 코드 라인 34~39에서 시작 주소가 물리 메모리 최소 주소 미만인 경우에는 시작 주소를 물리 메모리 하한 주소로 조정하고, 크기도 그 차이만큼 감소시켜 조정한다.
  • 코드 라인 40에서 memblock_add( ) 함수를 사용하여 메모리 블록을 추가한다.

 

참고

 

setup_processor()

  • 몇 개의 전역 변수에 CPU 아키텍처와 밀접한 구조체를 설정하고 재진입 익셉션 핸들러를 위한 스택 할당 등 CPU와 관련된 초기화를 수행한다.

 

setup_processor()

  • read_cpuid_id()
    • read_cpuid() 함수를 호출하는 단순 매크로
    • MIDR 레지스터를 통해 CPU_ID를 알아온다.
  • lookup_processor_type()
    • proc_info_list 구조체 포인터를 알아온다.
    • rpi2: __v7_ca7mp_proc_info: 위치를 가리킨다.
  • __get_cpu_architecture()
    • MMFR0 레지스터를 통해 CPU 아키텍처를 알아온다.
      • rpi2: 9(CPU_ARCH_ARMv7으로 define 됨)
  • 전역변수에 문자열 및 특정 구조체 포인터 대입
    • cpu_name
      • 라즈베리파이2: 문자열 “ARMv7 Processor”를 가리키는 주소가 담김
    • __cpu_architecture
      • 정수 값 9(CPU_ARCH_ARMv7으로 define 됨)
    • processor
      • v7_processor_functions 객체를 가리키는 processor 구조체 포인터
    • cpu_tlb
      • v7wbi_tlb_fns  객체를 가리키는 cpu_tlb_fns 구조체 포인터
    • cpu_user
      • v6_user_fns 객체를 가리키는 cpu_user_fns 구조체와 포인터
    • cpu_cache
      • v7_cache_fns 객체를 가리키는 cpu_cache_fns 구조체 포인터
        • rpi2: multi 캐시를 사용하지 않으므로 이 전역 변수는 사용되지 않음
    • 그외 설정되는 전역 변수 들
      • elf_hwcap, cachepolicy, erratum_a15_798181_handler, cacheid
  • cpuid_init_hwcaps()
    • elf_hwcap 전역 변수에 CPU 아키텍처가 지원하는 하드웨어 캐파(capacity)들을 대입한다.
  •  init_default_cache_policy()
    • cachepolicy 전역 변수에 아키텍처가 지원하는 1차 페이지 테이블(ARM 32bit에서는 PMD)의 캐시 정책 정책 값을 대입한다.
    • 라즈베리파이2: 4 (CPOLICY_WRITEALLOC)
  •  erratum_a15_798181_init()
    • CONFIG_ARM_ERRATA_798181가 설정되어 있는 경우만 동작
    • erratum_a15_798181_handler 전역 변수에 핸들러 함수 등록
      •  erratum_a15_798181_broadcast 또는 erratum_a15_798181_partial
  •  elf_hwcap_fixup()
    • elf_hwcap 전역 변수에서 CPU 아키텍처에 따라 HWCAP_TLS 또는 HWCAP_SWP를 제거한다.
  •  cacheid_init()
    • cacheid 전역변수 값에 캐시 형태를 설정한다.
    • 라즈베리파이2: CACHEID_VIPT_NONALIASING | CACHEID_VIPT_I_ALIASING
  •  cpu_init()
    • per-CPU 스택 설정
    • CPU 아키텍처 전용 초기화 함수 호출
    • 재진입 exception 핸들러를 위한 스택 설정

arch/arm/kernel/setup.c

static void __init setup_processor(void)
{
        struct proc_info_list *list;

        /*
         * locate processor in the list of supported processor
         * types.  The linker builds this table for us from the
         * entries in arch/arm/mm/proc-*.S
         */
        list = lookup_processor_type(read_cpuid_id());
        if (!list) {
                pr_err("CPU configuration botched (ID %08x), unable to continue.\n",
                       read_cpuid_id());
                while (1);
        }

        cpu_name = list->cpu_name;
        __cpu_architecture = __get_cpu_architecture();

#ifdef MULTI_CPU
        processor = *list->proc;
#endif
#ifdef MULTI_TLB
        cpu_tlb = *list->tlb;
#endif
#ifdef MULTI_USER
        cpu_user = *list->user;
#endif
#ifdef MULTI_CACHE
        cpu_cache = *list->cache;
#endif

        pr_info("CPU: %s [%08x] revision %d (ARMv%s), cr=%08lx\n",
                cpu_name, read_cpuid_id(), read_cpuid_id() & 15,
                proc_arch[cpu_architecture()], get_cr());

        snprintf(init_utsname()->machine, __NEW_UTS_LEN + 1, "%s%c",
                 list->arch_name, ENDIANNESS);
        snprintf(elf_platform, ELF_PLATFORM_SIZE, "%s%c",
                 list->elf_name, ENDIANNESS);
        elf_hwcap = list->elf_hwcap;

        cpuid_init_hwcaps();

#ifndef CONFIG_ARM_THUMB
        elf_hwcap &= ~(HWCAP_THUMB | HWCAP_IDIVT);
#endif
#ifdef CONFIG_MMU
        init_default_cache_policy(list->__cpu_mm_mmu_flags);
#endif
        erratum_a15_798181_init();

        elf_hwcap_fixup();

        cacheid_init();
        cpu_init();
}

 

read_cpuid()

  • MIDR 레지스터 값을 리턴한다.

arch/arm/include/asm/cputype.h

#define read_cpuid(reg)                                                 \
        ({                                                              \
                unsigned int __val;                                     \
                asm("mrc        p15, 0, %0, c0, c0, " __stringify(reg)  \
                    : "=r" (__val)                                      \
                    :                                                   \
                    : "cc");                                            \
                __val;                                                  \
        })

 

__stringify() 매크로

  • 인수에 문자열을 그대로 사용할 수 있도록 한다.
  • 컴파일 시 -DFOO=bar 옵션을 사용한 경우 __stringify(FOO)를 사용하는 경우 이를 bar로 변경해준다.

include/linux/stringify.h

/* Indirect stringification.  Doing two levels allows the parameter to be a
 * macro itself.  For example, compile with -DFOO=bar, __stringify(FOO)
 * converts to "bar".
 */

#define __stringify_1(x...)     #x
#define __stringify(x...)       __stringify_1(x)

 

lookup_processor_type()

  • lookup_processor_type() 함수를 통해 proc_info_list 구조체 포인터를 알아온다.

arch/arm/kernel/head-common.S

/*
 * This provides a C-API version of __lookup_processor_type
 */
ENTRY(lookup_processor_type)
        stmfd   sp!, {r4 - r6, r9, lr}
        mov     r9, r0
        bl      __lookup_processor_type
        mov     r0, r5
        ldmfd   sp!, {r4 - r6, r9, pc}
ENDPROC(lookup_processor_type)

 

 

proc_info_list 구조체

  • head.S에서 CPU 정보를 읽었었다.
  • 참고: kernel/head.S – __lookup_processor_type: | 문c
  • .init.data 섹션에 저장됨
  • 항목 설명과 라즈베리파이2에서의 값:
    • cpu_val:
      • 0x410f_c070
    • cpu_mask:
      • 0xff0f_fff0
    • __cpu_mm_mmu_flags: 메모리 주소용 페이지 엔트리 속성
      • MD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | PMD_SECT_AF | PMD_FLAGS_SMP
    • __cpu_io_mmu_flags: 입출력장치용 페이지 엔트리 속성
      • PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | PMD_SECT_AF
    • __cpu_flush:
      • __v7_ca7mp_setup() 함수 포인터
        • 내부에 CPU 설정 및 캐시 flush, ttbr 설정 등 캐시와 관련된 설정도 하는 루틴이 담겨있다.
    • arch_name:
      • 문자열 “armv7″을 가리키는 주소가 담김 (.rodata 섹션)
    • elf_name:
      • 문자열 “v7″을 가리키는 주소가 담김 (.rodata 섹션)
    • elf_hwcap:
      • 프로세서 아키텍처의 하드웨어 캐파(h/w capability)
      • rpi2: 0x168096
        • HWCAP_LPAE(b20) | HWCAP_IDIVT(b18) | HWCAP_IDIVA(b17) |  HWCAP_TLS(b15) | HWCAP_EDSP(b7) | HWCAP_FAST_MULT(b4) | HWCAP_THUMB(b2) | HWCAP_HALF(b1)
    • cpu_name:
      • 문자열 “ARMv7 Processor”를 가리키는 주소가 담김
    • proc:
      • v7_processor_functions 객체를 가리키는 processor 구조체 포인터
    • tlb:
      • v7wbi_tlb_fns 객체를 가리키는 cpu_tlb_fns 구조체 포인터
    • user:
      • v6_user_fns 객체를 가리키는 cpu_user_fns 구조체 포인터
    • cache:
      • v7_cache_fns 객체를 가리키는 cpu_cache_fns 구조체 포인터

arch/arm/include/asm/procinfo.h

/*
 * Note!  struct processor is always defined if we're
 * using MULTI_CPU, otherwise this entry is unused,
 * but still exists.
 *
 * NOTE! The following structure is defined by assembly
 * language, NOT C code.  For more information, check:
 *  arch/arm/mm/proc-*.S and arch/arm/kernel/head.S
 */
struct proc_info_list {
        unsigned int            cpu_val;
        unsigned int            cpu_mask;
        unsigned long           __cpu_mm_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_io_mmu_flags;     /* used by head.S */
        unsigned long           __cpu_flush;            /* used by head.S */
        const char              *arch_name;
        const char              *elf_name;
        unsigned int            elf_hwcap;
        const char              *cpu_name;
        struct processor        *proc;
        struct cpu_tlb_fns      *tlb;
        struct cpu_user_fns     *user;
        struct cpu_cache_fns    *cache;
};

 

__v7_ca7mp_proc_info

  • .proc.info.init 섹션에 저장된다.
  • proc_info_list 구조체와 동일
  • 라즈베리파이2: ARM Cortex A7

arch/arm/mm/proc-v7.S

        /*
         * ARM Ltd. Cortex A7 processor.
         */
        .type   __v7_ca7mp_proc_info, #object
__v7_ca7mp_proc_info:
        .long   0x410fc070
        .long   0xff0ffff0
        __v7_proc __v7_ca7mp_setup
        .size   __v7_ca7mp_proc_info, . - __v7_ca7mp_proc_info

 

__v7_proc 매크로

arch/arm/mm/proc-v7.S

  • 라즈베리파이2:
    • cpu_arch_name: 문자열 “armv7″을 가리키는 주소가 담김 (.rodata 섹션)
    • cpu_elf_name: 문자열 “v7″을 가리키는 주소가 담김 (.rodata 섹션)
.macro __v7_proc initfunc, mm_mmuflags = 0, io_mmuflags = 0, hwcaps = 0, proc_fns = v7_processor_functions
        ALT_SMP(.long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_SMP | \mm_mmuflags)
        ALT_UP(.long    PMD_TYPE_SECT | PMD_SECT_AP_WRITE | PMD_SECT_AP_READ | \
                        PMD_SECT_AF | PMD_FLAGS_UP | \mm_mmuflags)
        .long   PMD_TYPE_SECT | PMD_SECT_AP_WRITE | \
                PMD_SECT_AP_READ | PMD_SECT_AF | \io_mmuflags
        W(b)    \initfunc
        .long   cpu_arch_name
        .long   cpu_elf_name
        .long   HWCAP_SWP | HWCAP_HALF | HWCAP_THUMB | HWCAP_FAST_MULT | \
                HWCAP_EDSP | HWCAP_TLS | \hwcaps
        .long   cpu_v7_name
        .long   \proc_fns
        .long   v7wbi_tlb_fns
        .long   v6_user_fns
        .long   v7_cache_fns
.endm

 

define_processor_functions 매크로

  • <name>_processor_functions라는 오브젝트를 만들어낸다.
    • 라즈베리파이2: v7_processor_functions
  • processor 구조체와 동일
  • .init.data 섹션에 저장됨

arch/arm/mm/proc-v7.S

        @ define struct processor (see <asm/proc-fns.h> and proc-macros.S)
        define_processor_functions v7, dabort=v7_early_abort, pabort=v7_pabort, suspend=1

arch/arm/mm/proc-macros.S

.macro define_processor_functions name:req, dabort:req, pabort:req, nommu=0, suspend=0
        .type   \name\()_processor_functions, #object
        .align 2
ENTRY(\name\()_processor_functions)
        .word   \dabort
        .word   \pabort
        .word   cpu_\name\()_proc_init
        .word   cpu_\name\()_proc_fin
        .word   cpu_\name\()_reset
        .word   cpu_\name\()_do_idle
        .word   cpu_\name\()_dcache_clean_area
        .word   cpu_\name\()_switch_mm

        .if \nommu
        .word   0
        .else
        .word   cpu_\name\()_set_pte_ext
        .endif

        .if \suspend
        .word   cpu_\name\()_suspend_size
#ifdef CONFIG_ARM_CPU_SUSPEND
        .word   cpu_\name\()_do_suspend
        .word   cpu_\name\()_do_resume
#else
        .word   0
        .word   0
#endif
        .else
        .word   0
        .word   0
        .word   0
        .endif

        .size   \name\()_processor_functions, . - \name\()_processor_functions
.endm

 

processor 구조체

arch/arm/include/asm/proc-fns.h

/*
 * Don't change this structure - ASM code relies on it.
 */
extern struct processor {
        /* MISC
         * get data abort address/flags
         */
        void (*_data_abort)(unsigned long pc);
        /*
         * Retrieve prefetch fault address
         */
        unsigned long (*_prefetch_abort)(unsigned long lr);
        /*
         * Set up any processor specifics
         */
        void (*_proc_init)(void);
        /*
         * Disable any processor specifics
         */
        void (*_proc_fin)(void);
        /*
         * Special stuff for a reset
         */
        void (*reset)(unsigned long addr) __attribute__((noreturn));
        /*
         * Idle the processor
         */
        int (*_do_idle)(void);
        /*
         * Processor architecture specific
         */
        /*
         * clean a virtual address range from the
         * D-cache without flushing the cache.
         */
        void (*dcache_clean_area)(void *addr, int size);

        /*
         * Set the page table
         */
        void (*switch_mm)(phys_addr_t pgd_phys, struct mm_struct *mm);
        /*
         * Set a possibly extended PTE.  Non-extended PTEs should
         * ignore 'ext'.
         */
#ifdef CONFIG_ARM_LPAE
        void (*set_pte_ext)(pte_t *ptep, pte_t pte);
#else
        void (*set_pte_ext)(pte_t *ptep, pte_t pte, unsigned int ext);
#endif

        /* Suspend/resume */
        unsigned int suspend_size;
        void (*do_suspend)(void *);
        void (*do_resume)(void *);
} processor;

 

cpu_user_fns 구조체

arch/arm/mm/copypage-v6.c

struct cpu_user_fns v6_user_fns __initdata = {
        .cpu_clear_user_highpage = v6_clear_user_highpage_nonaliasing,
        .cpu_copy_user_highpage = v6_copy_user_highpage_nonaliasing,
};

 

define_tlb_functions 매크로

  • cpu_tlb_fns 구조체와 동일
  • v7wbi_tlb_fns 오브젝트가 만들어진다.
  • .init.data 섹션에 저장됨

arch/arm/mm/tlb-v7.S

        /* define struct cpu_tlb_fns (see <asm/tlbflush.h> and proc-macros.S) */
        define_tlb_functions v7wbi, v7wbi_tlb_flags_up, flags_smp=v7wbi_tlb_flags_smp

arch/arm/mm/proc-macros.S

.macro define_tlb_functions name:req, flags_up:req, flags_smp
        .type   \name\()_tlb_fns, #object
ENTRY(\name\()_tlb_fns)
        .long   \name\()_flush_user_tlb_range
        .long   \name\()_flush_kern_tlb_range
        .ifnb \flags_smp
                ALT_SMP(.long   \flags_smp )
                ALT_UP(.long    \flags_up )
        .else
                .long   \flags_up
        .endif
        .size   \name\()_tlb_fns, . - \name\()_tlb_fns
.endm

 

cpu_tlb_fns 구조체

arch/arm/include/asm/tlbflush.h

struct cpu_tlb_fns {
        void (*flush_user_range)(unsigned long, unsigned long, struct vm_area_struct *);
        void (*flush_kern_range)(unsigned long, unsigned long);
        unsigned long tlb_flags;
};

 

define_cache_functions 매크로

  • cpu_cache_fns 구조체와 동일
  • 라즈베리파이2: v7_cache_fns 오브젝트가 만들어진다.
  • .init.data 섹션에 저장됨

arch/arm/mm/cache-v7.S

        @ define struct cpu_cache_fns (see <asm/cacheflush.h> and proc-macros.S)
        define_cache_functions v7

arch/arm/mm/proc-macros.S

.macro define_cache_functions name:req
        .align 2
        .type   \name\()_cache_fns, #object
ENTRY(\name\()_cache_fns)
        .long   \name\()_flush_icache_all
        .long   \name\()_flush_kern_cache_all
        .long   \name\()_flush_kern_cache_louis
        .long   \name\()_flush_user_cache_all
        .long   \name\()_flush_user_cache_range
        .long   \name\()_coherent_kern_range
        .long   \name\()_coherent_user_range
        .long   \name\()_flush_kern_dcache_area
        .long   \name\()_dma_map_area
        .long   \name\()_dma_unmap_area
        .long   \name\()_dma_flush_range
        .size   \name\()_cache_fns, . - \name\()_cache_fns
.endm

 

cpu_cache_fns 구조체

arch/arm/include/asm/cacheflush.h

struct cpu_cache_fns {
        void (*flush_icache_all)(void);
        void (*flush_kern_all)(void);
        void (*flush_kern_louis)(void);
        void (*flush_user_all)(void);
        void (*flush_user_range)(unsigned long, unsigned long, unsigned int);

        void (*coherent_kern_range)(unsigned long, unsigned long);
        int  (*coherent_user_range)(unsigned long, unsigned long);
        void (*flush_kern_dcache_area)(void *, size_t);

        void (*dma_map_area)(const void *, size_t, int);
        void (*dma_unmap_area)(const void *, size_t, int);

        void (*dma_flush_range)(const void *, const void *);
};

 

cpuid_init_hwcaps()

  • ARMv7 이상의 CPU 아키텍처에서만 동작한다.
  • read_cpuid_ext()를 사용하여 레지스터 ISAR0.Devide_instrs를 읽어와서 아래 기능을 지원하면 elf_hwcap에 추가
    • HWCAP_IDIVA
      • 이 기능이 선택되면 HWCPA_IDIVT도 자동 선택된다.
    • HWCPA_IDIVT
  • read_cpuid_ext()를 사용하여 레지스터 MMFR0.vmsa를 읽어와서 값이 5(Long-descriptor translation table 지원)이상인 경우 hwcap에 추가
    • HWCAP_LPAE
      • 라즈베리파이2(bcm2709)의 경우 LPAE가 support된다.
  • 라즈베리파이2: 0x168097
    • HWCAP_LPAE(b20) | HWCAP_IDIVT(b18) | HWCAP_IDIVA(b17) |  HWCAP_TLS(b15) | HWCAP_EDSP(b7) | HWCAP_FAST_MULT(b4) | HWCAP_THUMB(b2) | HWCAP_HALF(b1) | HWCAP_SWP(b0)

arch/arm/kernel/setup.c

static void __init cpuid_init_hwcaps(void)
{
        unsigned int divide_instrs, vmsa;

        if (cpu_architecture() < CPU_ARCH_ARMv7)
                return;

        divide_instrs = (read_cpuid_ext(CPUID_EXT_ISAR0) & 0x0f000000) >> 24;

        switch (divide_instrs) {
        case 2:
                elf_hwcap |= HWCAP_IDIVA;
        case 1:
                elf_hwcap |= HWCAP_IDIVT;
        }

        /* LPAE implies atomic ldrd/strd instructions */
        vmsa = (read_cpuid_ext(CPUID_EXT_MMFR0) & 0xf) >> 0;
        if (vmsa >= 5)
                elf_hwcap |= HWCAP_LPAE;
}

 

elf_hwcap_fixup()

  • read_cpu_id()를 사용하여 레지스터 MIDR을 읽어 cpu id값을 읽어온다.
  • read_cpuid_part()를 사용하여 레지스터 MIDR의 CPU 파트를 비교하여 ARM1136이고 리비전코드가 r1p0 미만인 경우 elf_hwcap 에 HWCAP_TLS를 제거하고 루틴을 빠져나간다. 루틴을 빠져나가는 이유는 해당 조건의 CPU는 exclusive loads and store(ldrex/strex)를 지원하는 명령들이 없기 때문에 다음으로 이어지는 루틴을 실행할 필요가 없다.
    • rpi2: TLS 레지스터를 지원한다. (CP15의 TPIDRURO 레지스터)
  • read_cpuid_ext()를 사용하여 레지스터 ISAR3.SynchPrim_instrs 값 + ISAR4.SynchPrim_instrs를 읽어와서 0x13보다 크면 LDREX/STREX 명령이 지원되는 것으로 판단하여 elf_hwcap 에 HWCAP_SWP를 제거한다.
  • 라즈베리파이2: 0x168096
    • HWCAP_LPAE(b20) | HWCAP_IDIVT(b18) | HWCAP_IDIVA(b17) |  HWCAP_TLS(b15) | HWCAP_EDSP(b7) | HWCAP_FAST_MULT(b4) | HWCAP_THUMB(b2) | HWCAP_HALF(b1) | HWCAP_SWP(b0)

arch/arm/kernel/setup.c

static void __init elf_hwcap_fixup(void)
{
        unsigned id = read_cpuid_id();
        unsigned sync_prim;

        /*
         * HWCAP_TLS is available only on 1136 r1p0 and later,
         * see also kuser_get_tls_init.
         */
        if (read_cpuid_part() == ARM_CPU_PART_ARM1136 &&
            ((id >> 20) & 3) == 0) {
                elf_hwcap &= ~HWCAP_TLS;
                return;
        }

        /* Verify if CPUID scheme is implemented */
        if ((id & 0x000f0000) != 0x000f0000)
                return;

        /*
         * If the CPU supports LDREX/STREX and LDREXB/STREXB,
         * avoid advertising SWP; it may not be atomic with
         * multiprocessing cores.
         */
        sync_prim = ((read_cpuid_ext(CPUID_EXT_ISAR3) >> 8) & 0xf0) |
                    ((read_cpuid_ext(CPUID_EXT_ISAR4) >> 20) & 0x0f);
        if (sync_prim >= 0x13)
                elf_hwcap &= ~HWCAP_SWP;
}

 

init_default_cache_policy()

  • 전역 변수 unsigned int 선언된 cachepolicy 값 결정
    •  메모리 속성(pmd 엔트리 속성)을 알아내고 미리 정의되어 있는 cache_policies[].pmd와 일치하는 배열 인덱스를 찾아낸다.
  • 라즈베리파이2: cachepolicy <- 구조체 배열의 인덱스인 4 (CPOLICY_WRITEALLOC 캐시 정책)
    • .policy = “writealloc”
    • .cr_mask = 0
    • .pmd = PMD_SECT_WBWA
    • .pte = L_PTE_MT_WRITEALLOC
    • .pte_s2 = s2_policy(L_PTE_S2_MT_WRITEBACK)

arch/arm/mm/mmu.c

static unsigned long initial_pmd_value __initdata = 0;

/*
 * Initialise the cache_policy variable with the initial state specified
 * via the "pmd" value.  This is used to ensure that on ARMv6 and later,
 * the C code sets the page tables up with the same policy as the head
 * assembly code, which avoids an illegal state where the TLBs can get
 * confused.  See comments in early_cachepolicy() for more information.
 */
void __init init_default_cache_policy(unsigned long pmd)
{
        int i;

        initial_pmd_value = pmd;

        pmd &= PMD_SECT_TEX(1) | PMD_SECT_BUFFERABLE | PMD_SECT_CACHEABLE;

        for (i = 0; i < ARRAY_SIZE(cache_policies); i++)
                if (cache_policies[i].pmd == pmd) {
                        cachepolicy = i;
                        break;
                }

        if (i == ARRAY_SIZE(cache_policies))
                pr_err("ERROR: could not find cache policy\n");
}

 

cacheid_init()

  • read_cpuid_cachetype()를 통해 캐시 타입을 읽어와서 cacheid에 data 캐시와 instruction 캐시 형태를 설정한다.
  • cacheid 전역변수 값에 캐시 형태를 설정한다.
    • bit0~2: d-cache 플래그
    • bit3~5: i-cache 플래그
    • 라즈베리파이2:
      • L1 d-cache: CACHEID_VIPT_NONALIASING
      • L1 i-cache: CACHEID_VIPT_I_ALIASING
        • L1 명령 캐시가 캐시 aliasing이 필요한 캐시이다.
          • cache aliasing 용어 대신 page coloring 또는 cache coloring이라는 용어를 사용하기도 한다.
  • cacheid 관련 define
    • CACHEID_VIVT
    • CACHEID_VIPT_NONALIASING
    • CACHEID_VIPT_ALIASING
    • CACHEID_VIPT
    • CACHEID_ASID_TAGGED
    • CACHEID_VIPT_I_ALIASING
    • CACHEID_PIPT

arch/arm/kernel/setup.c

static void __init cacheid_init(void)
{
        unsigned int arch = cpu_architecture();

        if (arch == CPU_ARCH_ARMv7M) {
                cacheid = 0;
        } else if (arch >= CPU_ARCH_ARMv6) {
                unsigned int cachetype = read_cpuid_cachetype();
                if ((cachetype & (7 << 29)) == 4 << 29) {
                        /* ARMv7 register format */
                        arch = CPU_ARCH_ARMv7;
                        cacheid = CACHEID_VIPT_NONALIASING;
                        switch (cachetype & (3 << 14)) {
                        case (1 << 14):
                                cacheid |= CACHEID_ASID_TAGGED;
                                break;
                        case (3 << 14):
                                cacheid |= CACHEID_PIPT;
                                break;
                        }
                } else {
                        arch = CPU_ARCH_ARMv6;
                        if (cachetype & (1 << 23))
                                cacheid = CACHEID_VIPT_ALIASING;
                        else
                                cacheid = CACHEID_VIPT_NONALIASING;
                }
                if (cpu_has_aliasing_icache(arch))
                        cacheid |= CACHEID_VIPT_I_ALIASING;
        } else {
                cacheid = CACHEID_VIVT;
        }

        pr_info("CPU: %s data cache, %s instruction cache\n",
                cache_is_vivt() ? "VIVT" :
                cache_is_vipt_aliasing() ? "VIPT aliasing" :
                cache_is_vipt_nonaliasing() ? "PIPT / VIPT nonaliasing" : "unknown",
                cache_is_vivt() ? "VIVT" :
                icache_is_vivt_asid_tagged() ? "VIVT ASID tagged" :
                icache_is_vipt_aliasing() ? "VIPT aliasing" :
                icache_is_pipt() ? "PIPT" :
                cache_is_vipt_nonaliasing() ? "VIPT nonaliasing" : "unknown");
}

 

 cpu_init()

  • 하나의 CPU 설정을 초기화한다.
  • set_my_cpu_offset()
    • TPIDRPRW(Thread ID  레지스터)를 사용하여 per-CPU 스택을 설정한다.
  • cpu_proc_init()
    • 아키텍처에 따른 프로세서 초기화 루틴 수행
      • 아키텍처가 커널 빌드 시 고정된 경우
        • #define cpu_proc_init                   __glue(CPU_NAME,_proc_init)
          • #define __glue(name,fn)         ____glue(name,fn)
            • #define ____glue(name,fn)       name##fn
      • 아키텍처가 커널 빌드 시 고정되지 않고 MULTI_CPU를 사용한 경우 (Device Tree를 사용하는 경우 MULTI_CPU를 사용한다)
        • #define cpu_proc_init                processor._proc_init
          • cpu_v7_proc_init()
            • 함수 내부에서는 아무것도 하지 않고 그냥 함수를 리턴한다.
      • 라즈베리파이2:
        • CONFIG_CPU_V7를 사용하면 MULTI_CPU가 define 되고 따라서 processor._proc_init 구조체를 통하여 cpu_v7_proc_init() 호출
  • 아래 모드들 전환한 후 재진입 exception 핸들러들을 위해 스택을 설정하고 마지막으로 다시 SVC_MODE로 돌아온다.
    • IRQ_MODE
    • ABT_MODE
    • UND_MODE
    • FIQ_MODE

arch/arm/kernel/setup.c

/*
 * cpu_init - initialise one CPU.
 *
 * cpu_init sets up the per-CPU stacks.
 */
void notrace cpu_init(void)
{
#ifndef CONFIG_CPU_V7M
        unsigned int cpu = smp_processor_id();
        struct stack *stk = &stacks[cpu];

        if (cpu >= NR_CPUS) {
                pr_crit("CPU%u: bad primary CPU number\n", cpu);
                BUG();
        }

        /*
         * This only works on resume and secondary cores. For booting on the
         * boot cpu, smp_prepare_boot_cpu is called after percpu area setup.
         */
        set_my_cpu_offset(per_cpu_offset(cpu));

        cpu_proc_init();

        /*
         * Define the placement constraint for the inline asm directive below.
         * In Thumb-2, msr with an immediate value is not allowed.
         */
#ifdef CONFIG_THUMB2_KERNEL
#define PLC     "r"
#else
#define PLC     "I"
#endif

        /*
         * setup stacks for re-entrant exception handlers
         */
        __asm__ (
        "msr    cpsr_c, %1\n\t"
        "add    r14, %0, %2\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %3\n\t"
        "add    r14, %0, %4\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %5\n\t"
        "add    r14, %0, %6\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %7\n\t"
        "add    r14, %0, %8\n\t"
        "mov    sp, r14\n\t"
        "msr    cpsr_c, %9"
            :
            : "r" (stk),
              PLC (PSR_F_BIT | PSR_I_BIT | IRQ_MODE),
              "I" (offsetof(struct stack, irq[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | ABT_MODE),
              "I" (offsetof(struct stack, abt[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | UND_MODE),
              "I" (offsetof(struct stack, und[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | FIQ_MODE),
              "I" (offsetof(struct stack, fiq[0])),
              PLC (PSR_F_BIT | PSR_I_BIT | SVC_MODE)
            : "r14");
#endif
}

 

관련 전역 변수

arch/arm/kernel/setup.c

unsigned int processor_id;
EXPORT_SYMBOL(processor_id);
unsigned int __machine_arch_type __read_mostly;
EXPORT_SYMBOL(__machine_arch_type);
unsigned int cacheid __read_mostly;
EXPORT_SYMBOL(cacheid);

unsigned int __atags_pointer __initdata;

unsigned int system_rev;
EXPORT_SYMBOL(system_rev);

unsigned int system_serial_low;
EXPORT_SYMBOL(system_serial_low);

unsigned int system_serial_high;
EXPORT_SYMBOL(system_serial_high);

unsigned int elf_hwcap __read_mostly;
EXPORT_SYMBOL(elf_hwcap);

unsigned int elf_hwcap2 __read_mostly;
EXPORT_SYMBOL(elf_hwcap2);


#ifdef MULTI_CPU
struct processor processor __read_mostly;
#endif
#ifdef MULTI_TLB
struct cpu_tlb_fns cpu_tlb __read_mostly;
#endif
#ifdef MULTI_USER
struct cpu_user_fns cpu_user __read_mostly;
#endif
#ifdef MULTI_CACHE
struct cpu_cache_fns cpu_cache __read_mostly;
#endif
#ifdef CONFIG_OUTER_CACHE
struct outer_cache_fns outer_cache __read_mostly;
EXPORT_SYMBOL(outer_cache);
#endif

 

__read_mostly

참고: 함수선언부 관련 매크로-1 | 문c

Bitmap Operations

<kernel v5.10>

Bitmap Operations

  • 비트맵은 하나 이상의 unsigned long 타입을 사용한다.
  • nbits는 항상 컴파일 시 상수로 평가되어야 한다. 그렇지 않고 변수로 평가되는 경우 많은 inline으로 인해 코드 크기가 매우 커진다.
  • nbits가 1개의 unsigned long 데이터 타입으로 취급할 수 있는 경우와 그렇지 않은 경우의 구현 루틴이 나뉘어 있다.
    • 1개의 unsigned long 데이터로 처리하는 경우 보통 한 번의 조작 루틴을 사용하여 빠른 처리가 가능하다.
    • 여러 개의 unsigned long 데이터로 처리하는 경우 다량의 데이터 처리를 하느라 느리다.

 

bitmap 아키텍처별 헤더 및 c 파일

비트맵을 다루는 api들은 bitmap operation과 bit operations 양쪽에 구현되어 있다.
bitmap operation은 다음과 같이 기본 구현 헤더와 generic 라이브러리 파일로 준비되어 있다.
  • 기본 구현 헤더
    • include/linux/bitmap.h
  • Generic 라이브러리
    • lib/bitmap.c

 

비트맵 선언

DECLARE_BITMAP()

include/linux/bitmap.h

#define DECLARE_BITMAP(name,bits) \
        unsigned long name[BITS_TO_LONGS(bits)]

정적 비트맵을 선언하는 매크로 함수이다.

  • 예) DECLARE_BITMAP(foo, 96);
    • unsigned long foo[2];     (64bits 기준)

 

비트맵 API들

include/linux/bitmap.h

/**
 * DOC: bitmap overview
 *
 * The available bitmap operations and their rough meaning in the
 * case that the bitmap is a single unsigned long are thus:
 *
 * The generated code is more efficient when nbits is known at
 * compile-time and at most BITS_PER_LONG.
 *
 * ::
 *
 *  bitmap_zero(dst, nbits)                     *dst = 0UL
 *  bitmap_fill(dst, nbits)                     *dst = ~0UL
 *  bitmap_copy(dst, src, nbits)                *dst = *src
 *  bitmap_and(dst, src1, src2, nbits)          *dst = *src1 & *src2
 *  bitmap_or(dst, src1, src2, nbits)           *dst = *src1 | *src2
 *  bitmap_xor(dst, src1, src2, nbits)          *dst = *src1 ^ *src2
 *  bitmap_andnot(dst, src1, src2, nbits)       *dst = *src1 & ~(*src2)
 *  bitmap_complement(dst, src, nbits)          *dst = ~(*src)
 *  bitmap_equal(src1, src2, nbits)             Are *src1 and *src2 equal?
 *  bitmap_intersects(src1, src2, nbits)        Do *src1 and *src2 overlap?
 *  bitmap_subset(src1, src2, nbits)            Is *src1 a subset of *src2?
 *  bitmap_empty(src, nbits)                    Are all bits zero in *src?
 *  bitmap_full(src, nbits)                     Are all bits set in *src?
 *  bitmap_weight(src, nbits)                   Hamming Weight: number set bits
 *  bitmap_set(dst, pos, nbits)                 Set specified bit area
 *  bitmap_clear(dst, pos, nbits)               Clear specified bit area
 *  bitmap_find_next_zero_area(buf, len, pos, n, mask)  Find bit free area
 *  bitmap_find_next_zero_area_off(buf, len, pos, n, mask, mask_off)  as above
 *  bitmap_next_clear_region(map, &start, &end, nbits)  Find next clear region
 *  bitmap_next_set_region(map, &start, &end, nbits)  Find next set region
 *  bitmap_for_each_clear_region(map, rs, re, start, end)
 *                                              Iterate over all clear regions
 *  bitmap_for_each_set_region(map, rs, re, start, end)
 *                                              Iterate over all set regions
 *  bitmap_shift_right(dst, src, n, nbits)      *dst = *src >> n
 *  bitmap_shift_left(dst, src, n, nbits)       *dst = *src << n
 *  bitmap_cut(dst, src, first, n, nbits)       Cut n bits from first, copy rest
 *  bitmap_replace(dst, old, new, mask, nbits)  *dst = (*old & ~(*mask)) | (*new & *mask)
 *  bitmap_remap(dst, src, old, new, nbits)     *dst = map(old, new)(src)
 *  bitmap_bitremap(oldbit, old, new, nbits)    newbit = map(old, new)(oldbit)
 *  bitmap_onto(dst, orig, relmap, nbits)       *dst = orig relative to relmap
 *  bitmap_fold(dst, orig, sz, nbits)           dst bits = orig bits mod sz
 *  bitmap_parse(buf, buflen, dst, nbits)       Parse bitmap dst from kernel buf
 *  bitmap_parse_user(ubuf, ulen, dst, nbits)   Parse bitmap dst from user buf
 *  bitmap_parselist(buf, dst, nbits)           Parse bitmap dst from kernel buf
 *  bitmap_parselist_user(buf, dst, nbits)      Parse bitmap dst from user buf
 *  bitmap_find_free_region(bitmap, bits, order)  Find and allocate bit region
 *  bitmap_release_region(bitmap, pos, order)   Free specified bit region
 *  bitmap_allocate_region(bitmap, pos, order)  Allocate specified bit region
 *  bitmap_from_arr32(dst, buf, nbits)          Copy nbits from u32[] buf to dst
 *  bitmap_to_arr32(buf, src, nbits)            Copy nbits from buf to u32[] dst
 *  bitmap_get_value8(map, start)               Get 8bit value from map at start
 *  bitmap_set_value8(map, value, start)        Set 8bit value to map at start
 *
 * Note, bitmap_zero() and bitmap_fill() operate over the region of
 * unsigned longs, that is, bits behind bitmap till the unsigned long
 * boundary will be zeroed or filled as well. Consider to use
 * bitmap_clear() or bitmap_set() to make explicit zeroing or filling
 * respectively.
 */

 

bitmap_zero()

include/linux/bitmap.h

static inline void bitmap_zero(unsigned long *dst, unsigned int nbits)
{
        unsigned int len = BITS_TO_LONGS(nbits) * sizeof(unsigned long);
        memset(dst, 0, len);
}

비트맵 @dst의 @nbits 만큼의 비트를 0으로 클리어한다.

 

small_const_nbits()

include/linux/bitmap.h

#define small_const_nbits(nbits) \
        (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG && (nbits) > 0)

CPU 아키텍처가 한 번의 operation으로 비트맵을 처리할 수 있는지 여부를 판단한다.

  • 먼저 __builtin_constant_p()를 사용하여 인수가 상수(변수가 아닌)인지를 알아낸다.
    • 인수가 상수인 경우 true를 리턴하고,
    • 변수인 경우는 무조건 false를 리턴한다.
      • BITS_PER_LONG(32 or 64) 값 보다 작거나 같은 경우 true

 

비트맵 bitops API들

아래는 bitmap에서 사용할 수 있는 bitops 함수들이다.

  • 상위 7개의 비트맵 조작 함수는 atomic 하게 처리하도록 구현되어 있다.
  • 하위 5개의 비트맵 검색용 함수이다.
  • 참고: Bit Operations | 문c

 

include/linux/bitmap.h

/**
 * DOC: bitmap bitops
 *
 * Also the following operations in asm/bitops.h apply to bitmaps.::
 *
 *  set_bit(bit, addr)                  *addr |= bit
 *  clear_bit(bit, addr)                *addr &= ~bit
 *  change_bit(bit, addr)               *addr ^= bit
 *  test_bit(bit, addr)                 Is bit set in *addr?
 *  test_and_set_bit(bit, addr)         Set bit and return old value
 *  test_and_clear_bit(bit, addr)       Clear bit and return old value
 *  test_and_change_bit(bit, addr)      Change bit and return old value
 *  find_first_zero_bit(addr, nbits)    Position first zero bit in *addr
 *  find_first_bit(addr, nbits)         Position first set bit in *addr
 *  find_next_zero_bit(addr, nbits, bit)
 *                                      Position next zero bit in *addr >= bit
 *  find_next_bit(addr, nbits, bit)     Position next set bit in *addr >= bit
 *  find_next_and_bit(addr1, addr2, nbits, bit)
 *                                      Same as find_next_bit, but in
 *                                      (*addr1 & *addr2)
 */

 


비트맵 카운트

bitmap_weight()

include/linux/bitmap.h

static inline int bitmap_weight(const unsigned long *src, unsigned int nbits)
{
        if (small_const_nbits(nbits))
                return hweight_long(*src & BITMAP_LAST_WORD_MASK(nbits));
        return __bitmap_weight(src, nbits);
}

src 비트맵의 nbits 이내에서 1로 설정되어 있는 bit 수를 리턴한다.

 

__bitmap_weight()

lib/bitmap.c

int __bitmap_weight(const unsigned long *bitmap, unsigned int bits)
{
        unsigned int k, lim = bits/BITS_PER_LONG;
        int w = 0;

        for (k = 0; k < lim; k++)
                w += hweight_long(bitmap[k]);

        if (bits % BITS_PER_LONG)
                w += hweight_long(bitmap[k] & BITMAP_LAST_WORD_MASK(bits));

        return w;
}
EXPORT_SYMBOL(__bitmap_weight);

bitmap의 nbits 이내에서 1로 설정되어 있는 bit 수를 리턴한다.

 


할당할 연속된 공간 찾기

bitmap_find_next_zero_area()

include/linux/bitmap.h

/**                     
 * bitmap_find_next_zero_area - find a contiguous aligned zero area
 * @map: The address to base the search on
 * @size: The bitmap size in bits
 * @start: The bitnumber to start searching at
 * @nr: The number of zeroed bits we're looking for
 * @align_mask: Alignment mask for zero area
 *
 * The @align_mask should be one less than a power of 2; the effect is that
 * the bit offset of all zero areas this function finds is multiples of that
 * power of 2. A @align_mask of 0 means no alignment is required.
 */
static inline unsigned long
bitmap_find_next_zero_area(unsigned long *map,
                           unsigned long size,
                           unsigned long start,
                           unsigned int nr,  
                           unsigned long align_mask)
{
        return bitmap_find_next_zero_area_off(map, size, start, nr,
                                              align_mask, 0);
}

bitmap에서 제한된 size 범위내에서 start 위치부터 align_mask 된 연속된 nr 갯수의 0 비트를 찾아 그 비트 위치를 반환한다. (based 0)

 

bitmap_find_next_zero_area_off()

lib/bitmap.c

/**                     
 * bitmap_find_next_zero_area_off - find a contiguous aligned zero area
 * @map: The address to base the search on
 * @size: The bitmap size in bits
 * @start: The bitnumber to start searching at
 * @nr: The number of zeroed bits we're looking for
 * @align_mask: Alignment mask for zero area
 * @align_offset: Alignment offset for zero area.
 *
 * The @align_mask should be one less than a power of 2; the effect is that
 * the bit offset of all zero areas this function finds plus @align_offset
 * is multiple of that power of 2.
 */
unsigned long bitmap_find_next_zero_area_off(unsigned long *map,
                                             unsigned long size,
                                             unsigned long start,
                                             unsigned int nr,
                                             unsigned long align_mask,
                                             unsigned long align_offset)
{       
        unsigned long index, end, i;          
again:          
        index = find_next_zero_bit(map, size, start);

        /* Align allocation */
        index = __ALIGN_MASK(index + align_offset, align_mask) - align_offset;
                        
        end = index + nr;
        if (end > size)
                return end;
        i = find_next_bit(map, end, index); 
        if (i < end) {
                start = i + 1; 
                goto again;
        }
        return index;
}  
EXPORT_SYMBOL(bitmap_find_next_zero_area_off);

bitmap에서 제한된 size 범위내에서 start 위치부터 align_mask 된 연속된 nr 갯수의 0 비트를 찾아 그 비트 위치를 반환한다. (based 0)

  • bitmap_find_next_zero_area() 함수와 다르게 align_offset을 추가하여 align_mask로 정렬 시 align_offset을 더해 정렬한 후 다시 뺀다.
    • cma_alloc() 함수에서 사용된다.

 

BITMAP_FIRST_WORD_MASK()

include/linux/bitmap.h

#define BITMAP_FIRST_WORD_MASK(start) (~0UL << ((start) & (BITS_PER_LONG - 1)))

@start 이상의 비트들이 모두 1로 설정된 값에서, @start가 위치한 처음 워드(unsigned long)만을 반환한다.

 

예) 32비트 시스템에서 인수에 따른 결과는

  • start = 0: 0xffff_ffff (0이 아님에 주의)
  • start = 1: 0xffff_fffe
  • start = 16: 0xffff_0000
  • start = 32: 0xffff_ffff
  • start = 40: 0xffff_ff00

 

예) 64비트 시스템에서 인수에 따른 결과는

  • start = 0: 0xffff_ffff_ffff_ffff (0이 아님에 주의)
  • start = 1: 0xffff_ffff_ffff_fffe
  • start = 16: 0xffff_ffff_ffff_0000
  • start = 32: 0xffff_ffff_0000_0000
  • start = 40: 0xffff_ff00_0000_0000
  • start = 66: 0xffff_ffff_ffff_fffc

 

BITMAP_LAST_WORD_MASK()

include/linux/bitmap.h

#define BITMAP_LAST_WORD_MASK(nbits) (~0UL >> (-(nbits) & (BITS_PER_LONG - 1)))

@nbits 이하의 비트들이 모두 1로 설정된 값에서, @nbits가 위치한 마지막 워드(unsigned long)만을 반환한다.

 

예) 32비트 시스템에서 인수에 따른 결과는

  • nbits = 0: 0xffff_ffff (0이 아님에 주의)
  • nbits = 1: 0x0000_0001
  • nbits = 16: 0x0000_ffff
  • nbits = 32: 0xffff_ffff
  • nbits = 40: 0x0000_000f

 

예) 64비트 시스템에서 인수에 따른 결과는

  • nbits = 0: 0xffff_ffff_ffff_ffff (0이 아님에 주의)
  • nbits = 1: 0x0000_0000_0000_0001
  • nbits = 16: 0x0000_0000_0000_ffff
  • nbits = 32: 0x0000_0000_ffff_ffff
  • nbits = 40: 0x0000_00ff_ffff_ffff
  • nbits = 66: 0x0000_0000_0000_0003

 

참고