parse_early_param()

Early 부트 명령어 라인 파라메터들

아래의 항목들이 early 부트 명령어 라인 파라메터로 동작할 수 있다.

주요 항목

  • earlycon
  • earlyprintk
  • mem
  • initrd
  • cma
  • quiet
  • loglevel
  • debug
  • debug_objects
  • no_debug_objects

기타 항목

  • force_pal_cache_flush, nomca, coherent_pool, cachepoicy, nocache, nowb, ecc
  • coherentio, nocoherentio, cca, rd_start, rd_size, elfcorehdr, disable_octeon_edac, pmb, sh_mv, nmi_mode, clkin_hz=, memmap, fbmem, vmalloc, nogbpages, gbpages, noexec, nopat, reservetop, highmem, userpte, memtest, numa, kmemcheck, possible_cpus, io_delay, disable_timer_pin_1, memory_corrutpion_check, memory_corruption_check_period, memory_corruption_check_size, no-kvmaf, no-steal-acc, no-kvmclock-vsyscall, update_mptable, alloc_mptable, gart_fix_e820, nokaslr, diseble_mtrr_cleanup, enable_mtrr_cleanup, mtrr_cleanup_debug, mtrr_chunk_size, mtrr_gran_size, mtrr_spare_reg_nr, disable_mtrr_trim, stack, pci, no-kvmclolck, vsyscall, reservelow, iommu, idle, xen_emul_unplug, xen_nopv, xen_nopvsipin, add_efi_memmap, efi, efi_no_storage_paranoia, nobau, topology_updates, fadump, fadump_reserve_mem, smt-enabled, kvm_cma_resv_ratio, xmon, video, ps3fb, ps3flash, disable_ddw, hvirq, coherent_pool, iefi_debug, nodebugmon, cad, topology, nosmt, smt, possible_cpus, etr, stp, uaccess_primary, l2cache, l2cache_pf, hwthread_map, memdma, hugepagesz, vector, additional_cpus, mem_fclk_21285, balloon3_features, clocksource, tbr, rproc_mem, switches, stram_pool, noallocl2, ktext, pcie_rc_delay, maxmem, maxnodemem, isolnodes, pci_reserve, initramfs_file, disabled_cpus, dataplane, noudn, noidn, noipi, LABC, nointremap, intremap, kgdbdbgp, sysfs.deprecated, noefi, efi, intel_psate, sim_console, ekgdboc, swiotlb
  • iapic, nox2apic, disableapic, nolapic, lapic_timer_c2_ok, noapictimer, nolapic_timer, apic, disable_cpu_apicd, x2apic_phys, noapic, acpi_skip_timer_override, acpi_use_timer_override, acpi_sci, acpi_fsdp, acpi_no_static_ssdt, acpi_apic_instance, acpi_force_table_verification

 

parse_early_param()

부트 커멘드라인 문자열을 파싱하여 해당 파라메터가 early 파라메터 함수와 연결되어 있는 경우 그 함수를 호출한다.

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 구조체 엔트리들
  • 다음 조건에 해당되는 커널 파라메터를 발견하면 val 값 인수를 가지고 해당 커널 파라메터에 등록된 setup_func() 함수를 호출한다.
    • 요청 파라메터가 커널 파라메터 str과 같으면서 early 설정이 된 경우
    • 요청 파라메터가 “console”이고 커널 파라메터 str = “earlycon”인 경우
  • cmdline 문자열 중 “console” 을 발견 시 val 인수 값을 가지고 저장된 구조체에서 모든 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:
        • 라즈베리안 기본 설정은 DTB를 사용하지 않는다. bcm2709.dtsi를 사용할 수 있도록 포함시키는 경우에도 choosen 노드에서 bootargs = “”;로 설정되어 있고 stdout-path 속성은 없다.
        • 별도로 rpi2 커널에서 bcm2709_defconfig를 사용하여 컴파일 하는 경우 dtb를 사용하여 컴파일 할 수 있다.
  • 예) 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”

 

parse_early_param_1a

  • 예) 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; 
}
  • 전역 변수 boot_command_line의 내용을 tmp_cmdline에 복사한 후 parse_early_options() 함수를 호출한다.
    • boot_command_line
      • A) 부트로더가 다음 중 하나를 전달해 준다.
        • ATAG _CMDLINE 문자열
        • DTB의 “/chosen” 노드의 “bootargs” 속성 값
      • B) 커널에서도 준비한 문자열
        • 커널 옵션으로 입력한 커맨드라인 문자열 CONFIG_CMDLINE이 준비된다.
      • 위의 A) 및 B)를 아래 옵션에 따라 조합하여 사용한다.
        • 1) CONFIG_CMDLINE_EXTEND
          • A)와 B)를 합쳐서 사용한다.
        • 2) CONFIG_CLDLINE_FORCE
          • B)를 사용한다.
        • 3) no option
          • A)를 사용한다.

 

parse_early_options()

init/main.c

void __init parse_early_options(char *cmdline)
{
        parse_args("early options", cmdline, NULL, 0, 0, 0, do_early_param);
}
  • do_early_param() 함수 주소를 인수로 parse_args() 함수를 호출한다.
    • param에 null을 그리고 num에 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,
                 int (*unknown)(char *param, char *val, const char *doing))
{
        char *param, *val;
        
        /* 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 args;
                irq_was_disabled = irqs_disabled();
                ret = parse_one(param, val, doing, params, num,
                                min_level, max_level, unknown);
                if (irq_was_disabled && !irqs_disabled())
                        pr_warn("%s: option '%s' enabled irq's!\n",
                                doing, param);

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

        /* All parsed OK. */
        return NULL;
}
  • args = skip_spaces(args);
    • args 인수에는 cmdline의 주소를 담고 있는데 space(0x20, 탭, 라인피드 등) 인 경우 skip 한다.
      • 예) ”  console=xxx” -> “console=xxx”
  • while (*args) {
    • parsing할 문자열이 남아 있는 동안
  • args = next_arg(args, &param, &val);
    • 토큰을 “=” 문자로 분리하여 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)
  • if (!val && strcmp(param, “–“) == 0)
    • val 값이 없으면서 param 값이 “–” 인 경우 더 이상 파싱을 하지 않고 return
  • irq_was_disabled = irqs_disabled();
    • SCTLR의 인터럽트 마스크(“I”, 1번 비트)가 disable된 상태인지 알아온다.
    • true = irq disabled status, false = irq enabled status
  • ret = parse_one(param, val, doing, params, num, min_level, max_level, unknown);
    • 파싱된 토큰의 param과 val 그리고 doing(“early options”메시지 출력용) 및 do_early_param() 함수 포인터를 인자로 parse_one()을 호출한다.
    • params, num, min_level, max_level에는 모두 0이 들어간다. 이러한 경우 항상 unknown 핸들러 함수를 호출한다.
  • if (irq_was_disabled && !irqs_disabled())
    • irq 설정 상태가 바뀌었으면 어떤 파라메터 옵션에서 바뀌었는지 warning을 출력한다.
    • 파라메터로 인해 특정 코드(디바이스 드라이버 등)에서 irq를 enable하고 나오는지를 확인하기 위해 경고 출력을 위한 디버그 코드이다.
    • 커널이 초기 설정 중에는 인터럽트가 마스크되어 동작하지 않고 있는데 갑자기 커널 파라메터로 인해 인터럽트가 발생되면 안되기 때문에 이를 확인하기 위함이다.
  • switch (ret) {
    • 파싱한 결과가 에러인 경우 에러 메시지를 출력하고 리턴한다.
    • 성공인 경우  다음 토큰을 위해 루프를 계속 진행한다.

 

do_early_param()

init/main.c

/* Check for early params. */
static int __init do_early_param(char *param, char *val, const char *unused)
{                
        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;
}

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

  • 요청 파라메터가 커널 파라메터 str과 같으면서 early 설정이 된 경우
    • 예) param=”console”, p->str=”console”, p->early=1
  • 요청 파라메터가 “console”이고 커널 파라메터가 str = “earlycon”인 경우
    • 예) param=”console”, p->str=”earlycon”
  • for (p = __setup_start; p < __setup_end; p++) {
    • __setup_start 주소 영역 부터 __setup_end 주소 영역까지에 여러 개의 커널 파라메터인 obs_kernel_param 구조체가 있는데 순서대로 검색하여 p를 대입한다.
  • if ((p->early && parameq(param, p->str)) ||
    • p->early 항목이 설정되어 있으면서 p->str이 인수로 받은 param 문자열과 같은 경우이거나
  • (strcmp(param, “console”) == 0 && strcmp(p->str, “earlycon”) == 0)
    • p->str이 “earlycon”이면서 인수로 받은 param 문자열이 “console”인 경우
  • if (p->setup_func(val) != 0)
    • p->setup_func()을 호출하여 에러가 있는 경우 경고 메시지를 출력한다.

 

setup_earlycon()

drivers/tty/serial/earlycon.c

int __init setup_earlycon(char *buf, const char *match,
                          int (*setup)(struct earlycon_device *, const char *)) 
{
        int err;
        size_t len;

        struct uart_port *port = &early_console_dev.port;

        if (!buf || !match || !setup)
                return 0;

        len = strlen(match);

        if (strncmp(buf, match, len))
                return 0;
        if (buf[len] && (buf[len] != ','))
                return 0;

        buf += len + 1;

        err = parse_options(&early_console_dev, buf);
        /* On parsing error, pass the options buf to the setup function */
        if (!err)
                buf = NULL;

        if (port->mapbase)
                port->membase = earlycon_map(port->mapbase, 64);

        early_console_dev.con->data = &early_console_dev;
        err = setup(&early_console_dev, buf);
        if (err < 0)
                return err;
        if (!early_console_dev.con->write)
                return -ENODEV;

        register_console(early_console_dev.con);
        return 0;
}
  • struct uart_port *port = &early_console_dev.port;
  • if (strncmp(buf, match, len)) return 0;
    • 부트 명령행 파라메터 buf와 디바이스명 match가 동일하지 않으면 함수를 빠져나간다.
  •  if (buf[len] && (buf[len] != ‘,’)) return 0;
    • 디바이스명 다음에 ‘,’가 없으면 함수를 빠져나간다.
  • err = parse_options(&early_console_dev, buf);
    • 옵션들을 파싱하여 port->mapbase에 포트 주소, options에 주소뒤의 나머지 옵션문자열, baud에 uart speed를 저장한다.
  • if (port->mapbase) port->membase = earlycon_map(port->mapbase, 64);
    • 물리주소를 가상주소로 변환하여 알아온다.
  • err = setup(&early_console_dev, buf);
    • 인수로 전달받은 setup 함수를 호출한다.
  • if (!early_console_dev.con->write) return -ENODEV;
    • early 콘솔이 설정되지 않았으면 에러를 리턴한다.
  • register_console(early_console_dev.con);
    • early 콘솔 디바이스를 등록한다.

 

drivers/tty/serial/earlycon.c

static struct earlycon_device early_console_dev = {
        .con = &early_con,
};

 

static struct console early_con = {
        .name =         "uart", /* 8250 console switch requires this name */
        .flags =        CON_PRINTBUFFER | CON_BOOT,
        .index =        -1,
};

 

parse_options()

drivers/tty/serial/earlycon.c

static int __init parse_options(struct earlycon_device *device,
                                char *options)
{
        struct uart_port *port = &device->port;
        int mmio, mmio32, length;
        unsigned long addr;

        if (!options)
                return -ENODEV;

        mmio = !strncmp(options, "mmio,", 5);
        mmio32 = !strncmp(options, "mmio32,", 7);
        if (mmio || mmio32) {
                port->iotype = (mmio ? UPIO_MEM : UPIO_MEM32);
                options += mmio ? 5 : 7;
                addr = simple_strtoul(options, NULL, 0);
                port->mapbase = addr;
                if (mmio32)
                        port->regshift = 2;
        } else if (!strncmp(options, "io,", 3)) {
                port->iotype = UPIO_PORT;
                options += 3;
                addr = simple_strtoul(options, NULL, 0);
                port->iobase = addr;
                mmio = 0;
        } else if (!strncmp(options, "0x", 2)) {
                port->iotype = UPIO_MEM;
                addr = simple_strtoul(options, NULL, 0);
                port->mapbase = addr;
        } else {
                return -EINVAL;
        }

        port->uartclk = BASE_BAUD * 16;

        options = strchr(options, ',');
        if (options) {
                options++;
                device->baud = simple_strtoul(options, NULL, 0);
                length = min(strcspn(options, " ") + 1,
                             (size_t)(sizeof(device->options)));
                strlcpy(device->options, options, length);
        }

        if (port->iotype == UPIO_MEM || port->iotype == UPIO_MEM32)
                pr_info("Early serial console at MMIO%s 0x%llx (options '%s')\n",
                        mmio32 ? "32" : "",
                        (unsigned long long)port->mapbase,
                        device->options);
        else
                pr_info("Early serial console at I/O port 0x%lx (options '%s')\n",
                        port->iobase,
                        device->options);

        return 0;
}

earlycon 옵션 문자열을 파싱하여 port->iotype, port->mapbase, port->regshift, port->iobase, port->uartclk, device->baud, device->options 등을 설정한다.

 

Console Driver

1) 표준 8250 UART 드라이버

표준 8250 UART 포트를 console로 사용하는 드라이버이다.

  • 예1)
    • earlycon=uart8250,io,0x3f8,9600n8
    • earlycon=uart8250,mmio,0xff5e0000,115200n8
    • earlycon=uart8250,mmio32,0xff5e0000,115200n8
  • 예2)
    • console=uart8250,io,0x3f8,9600n8
    • console=uart8250,mmio,0xff5e0000,115200n8
    • console=uart8250,mmio32,0xff5e0000,115200n8

 

early_serial8250_setup()

drivers/tty/serial/8250/8250_early.c

static int __init early_serial8250_setup(struct earlycon_device *device,
                                         const char *options)
{
        if (!(device->port.membase || device->port.iobase))
                return 0;

        if (!device->baud) {
                device->baud = probe_baud(&device->port);
                snprintf(device->options, sizeof(device->options), "%u",
                         device->baud);
        }

        init_port(device);

        early_device = device;
        device->con->write = early_serial8250_write;
        return 0;
}
EARLYCON_DECLARE(uart8250, early_serial8250_setup);
EARLYCON_DECLARE(uart, early_serial8250_setup);
  • EARLYCON_DECLARE(uart8250, early_serial8250_setup);
    • uart8250_setup_earlycon() 함수를 만들고 함수 내부에서 early_serial8250_setup() 함수 주소를 인수로 setup_earlycon() 함수를 호출한다.
    • early_param(“earlycon”, uart8250_setup_earlycon) 매크로 함수 호출
      • __setup_param(“earlycon”, uart8250_setup_earlycon, uart8250_setup_earlycon, 1)
        • static const char __setup_str_uart8250_setup_earlycon[] = “earlycon” 문자열 생성
        • struct obs_kernel_param __setup_uart8250_setup_earlycon = { __setup_str_uart8250_setup_earlycon, uart8250_setup_earycon, 1 } 구조체 생성
      • 결국 obs_kernel_param 구조체 형태로 { “earlycon”, uart8250_setup_earlycon, 1 }이 생성된다.

 

serial8250_console 전역 드라이버 객체

drivers/tty/serial/8250/8250_core.c

static struct console serial8250_console = {
        .name           = "ttyS",
        .write          = serial8250_console_write,
        .device         = uart_console_device,
        .setup          = serial8250_console_setup,
        .early_setup    = serial8250_console_early_setup,
        .flags          = CON_PRINTBUFFER | CON_ANYTIME,
        .index          = -1,
        .data           = &serial8250_reg,
};

 

serial8250_console_setup()

drivers/tty/serial/8250/8250_core.c

static int serial8250_console_setup(struct console *co, char *options)
{
        struct uart_port *port;
        int baud = 9600;
        int bits = 8; 
        int parity = 'n'; 
        int flow = 'n'; 

        /*
         * Check whether an invalid uart number has been specified, and
         * if so, search for the first available port that does have
         * console support.
         */
        if (co->index >= nr_uarts)
                co->index = 0; 
        port = &serial8250_ports[co->index].port;
        if (!port->iobase && !port->membase)
                return -ENODEV;

        if (options)
                uart_parse_options(options, &baud, &parity, &bits, &flow);

        return uart_set_options(port, co, baud, parity, bits, flow);
}

 

serial8250_console_early_setup()

drivers/tty/serial/8250/8250_core.c

static int serial8250_console_early_setup(void)
{
        return serial8250_find_port_for_earlycon();
}

 

 

2) AMBA UART 드라이버

AMBA UART 포트를 console로 사용하는 드라이버로 CONFIG_SERIAL_AMBA_PL011_CONSOLE 옵션이 동작할 때 사용할 수 있다.

  • rpi2에서 사용하는 UART 드라이버이다.
  • 예) console=ttyAMA0,115200
    • ttyAMA는 rpi2의 정규 console로 사용되는 uart이다.
  • 예) earlycon=pl011,0x3f201000,115200n8
    • pl011은 rpi2의 early console로 사용되는 uart이다.

 

pl011_early_console_setup()

drivers/tty/serial/amba-pl011.c

static int __init pl011_early_console_setup(struct earlycon_device *device,
                                            const char *opt)
{
        if (!device->port.membase)
                return -ENODEV;

        device->con->write = pl011_early_write;
        return 0;
}
EARLYCON_DECLARE(pl011, pl011_early_console_setup);
OF_EARLYCON_DECLARE(pl011, "arm,pl011", pl011_early_console_setup);
  • EARLYCON_DECLARE(pl011, pl011_early_console_setup);
    • pl011_setup_earlycon() 함수를 만들고 함수 내부에서 pl011_early_console_setup() 함수 주소를 인수로 setup_earlycon() 함수를 호출한다.
    • early_param(“earlycon”, pl011_setup_earlycon) 매크로 함수 호출
      • __setup_param(“earlycon”, pl011_setup_earlycon, pl011_setup_earlycon, 1)
        • static const char __setup_str_pl011_setup_earlycon[] = “earlycon” 문자열 생성
        • struct obs_kernel_param __setup_pl011_setup_earlycon = { __setup_str_pl011_setup_earlycon, pl011_setup_earycon, 1 } 구조체 생성
      • 결국 obs_kernel_param 구조체 형태로 { “earlycon”, pl011_setup_earlycon, 1 }이 생성된다.
    • cmdline parsing하여 검색을 위한 테이블
  • OF_EARLYCON_DECLARE(pl011, “arm.pl011”, pl011_early_console_setup);
    • _OF_DECLARE(earlycon, pl011, “arm.pl011”, pl011_early_console_setup, void *) 매크로 호출
      • const struct of_device_id __of_table_pl011 = { .compatible = “arm.pl011”, data = (pl011_early_console_setup == (void *) NULL) ? pl011_early_console_setup : pl011_early_console_setup } 구조체 생성
    • DTB 검색을 위한 테이블

 

amba_console 전역 드라이버 객체

drivers/tty/serial/amba-pl011.c

static struct uart_driver amba_reg;
static struct console amba_console = {
        .name           = "ttyAMA",
        .write          = pl011_console_write,
        .device         = uart_console_device,
        .setup          = pl011_console_setup,
        .flags          = CON_PRINTBUFFER,
        .index          = -1,
        .data           = &amba_reg,
};

 

pl011_console_setup()

drivers/tty/serial/amba-pl011.c

static int __init pl011_console_setup(struct console *co, char *options)
{
        struct uart_amba_port *uap;
        int baud = 38400;
        int bits = 8; 
        int parity = 'n'; 
        int flow = 'n'; 
        int ret; 

        /*
         * Check whether an invalid uart number has been specified, and
         * if so, search for the first available port that does have
         * console support.
         */
        if (co->index >= UART_NR)
                co->index = 0; 
        uap = amba_ports[co->index];
        if (!uap)
                return -ENODEV;

        /* Allow pins to be muxed in and configured */
        pinctrl_pm_select_default_state(uap->port.dev);

        ret = clk_prepare(uap->clk);
        if (ret)
                return ret; 

        if (dev_get_platdata(uap->port.dev)) {
                struct amba_pl011_data *plat;

                plat = dev_get_platdata(uap->port.dev);
                if (plat->init)
                        plat->init();
        }

        uap->port.uartclk = clk_get_rate(uap->clk);

        if (options)
                uart_parse_options(options, &baud, &parity, &bits, &flow);
        else
                pl011_console_get_options(uap, &baud, &parity, &bits);

        return uart_set_options(&uap->port, co, baud, parity, bits, flow);
}

 

3) DTB earlycon

setup_of_earlycon()

drivers/of/fdt.c

static int __init setup_of_earlycon(char *buf)
{
        if (buf)
                return 0;

        return early_init_dt_scan_chosen_serial();
}
early_param("earlycon", setup_of_earlycon);

이 함수는 CONFIG_SERIAL_EARLYCON이 설정되어 있는 경우 아래와 같이 동작한다.

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

예) DTB의 내용 일부

/ {
   (...생략...)  
        chosen {
                bootargs = "console=ttyS0,115200n8 earlyprintk";
                stdout-path = &uart0;
        };
   (...생략...)  
                uart0: uart@7e201000 {
                        compatible = "arm,pl011";
   (...생략...)  

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

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

 

early_init_dt_scan_chosen_serial()

drivers/of/fdt.c

static int __init early_init_dt_scan_chosen_serial(void)
{
        int offset;
        const char *p;
        int l;
        const struct of_device_id *match = __earlycon_of_table;
        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;

        /* Get the node specified by stdout-path */
        offset = fdt_path_offset(fdt, p);
        if (offset < 0) 
                return -ENODEV;

        while (match->compatible[0]) {
                unsigned long addr;
                if (fdt_node_check_compatible(fdt, offset, match->compatible)) {
                        match++;
                        continue;
                }

                addr = fdt_translate_address(fdt, offset);
                if (!addr)
                        return -ENXIO;

                of_setup_earlycon(addr, match->data);
                return 0;
        }
        return -ENODEV;
}
  • const struct of_device_id *match = __earlycon_of_table;
    • match <- __earlycon_of_table의 주소
  • const void *fdt = initial_boot_params;
    • fdt <- DTB의 주소
  • offset = fdt_path_offset(fdt, “/chosen”);
    • “/chosen” 노드를 검색한다.
  • offset = fdt_path_offset(fdt, “/chosen@0”);
    • 없으면 “/chosen@0” 노드를 검색한다.
  • p = fdt_getprop(fdt, offset, “stdout-path”, &l);
    • 발견한 노드에서 “stdout-path” 속성을 검색한다.
  • p = fdt_getprop(fdt, offset, “linux,stdout-path”, &l);
    • 없으면 “linux,stdout-path” 속성을 검색한다.
  • offset = fdt_path_offset(fdt, p);
    • 발견한 속성의 offset
  • while (match->compatible[0])
    • __earlycon_of_table 주소 부터 compatible[0]이 있는 동안 검색한다.
    • if (fdt_node_check_compatible(fdt, offset, match->compatible)) {
      • compatible 속성 값에서 디바이스명(match->compatible)이 매치되지 않으면?
      • fdt_node_check_compatible()
        • compatible 속성 값에서 문자열 비교: 0=match, 1=non match, 길이=”compatible” 속성이 발견되지 않는 경우
  • addr = fdt_translate_address(fdt, offset);
    • 노드를 분석하여 addr 값을 알아온다.
  • of_setup_earlycon(addr, match->data);
    • match->data에는 디바이스의 setup 함수 주소가 담겨있다.
    • rpi2 예)
      • match->data = pl011_early_console_setup()

 

of_setup_earlycon()

drivers/tty/serial/earlycon.c

int __init of_setup_earlycon(unsigned long addr,
                             int (*setup)(struct earlycon_device *, const char *))
{
        int err;
        struct uart_port *port = &early_console_dev.port;

        port->iotype = UPIO_MEM;
        port->mapbase = addr;
        port->uartclk = BASE_BAUD * 16;
        port->membase = earlycon_map(addr, SZ_4K); 

        early_console_dev.con->data = &early_console_dev;
        err = setup(&early_console_dev, NULL);
        if (err < 0) 
                return err;
        if (!early_console_dev.con->write)
                return -ENODEV;
        

        register_console(early_console_dev.con);
        return 0;
}

 

earlyprintk/earlycon

  • earlyprintk:
    • 커널 2.6.12에서 도입
    • 정식 드라이버 로딩 전 커널의 디버깅 출력용
  • earlycon:
    • 2014년 3월 arm64에서 제안. 커널 3.16에 도입. (i386은 2007년)
    • 디버깅 목적으로 다른 드라이버보다 더 빨리 기동이 필요
    • 동작되면 “bootconsole [uart0] enabled”라고 출력.
  • 참고: Linux earlyprintk/earlycon support on ARM

 

Documentation/kernel-parameters.txt

        earlyprintk=    [X86,SH,BLACKFIN,ARM,M68k]
                        earlyprintk=vga
                        earlyprintk=efi
                        earlyprintk=xen
                        earlyprintk=serial[,ttySn[,baudrate]]
                        earlyprintk=serial[,0x...[,baudrate]]
                        earlyprintk=ttySn[,baudrate]
                        earlyprintk=dbgp[debugController#]

                        earlyprintk is useful when the kernel crashes before
                        the normal console is initialized. It is not enabled by
                        default because it has some cosmetic problems.

                        Append ",keep" to not disable it when the real console
                        takes over.

                        Only one of vga, efi, serial, or usb debug port can
                        be used at a time.

                        Currently only ttyS0 and ttyS1 may be specified by
                        name.  Other I/O ports may be explicitly specified
                        on some architectures (x86 and arm at least) by
                        replacing ttySn with an I/O port address, like this:
                                earlyprintk=serial,0x1008,115200
                        You can find the port for a given device in
                        /proc/tty/driver/serial:
                                2: uart:ST16650V2 port:00001008 irq:18 ...

                        Interaction with the standard serial driver is not
                        very good.

                        The VGA and EFI output is eventually overwritten by
                        the real console.

                        The xen output can only be used by Xen PV guests.

 

 

        earlyprintk=    [X86,SH,BLACKFIN,ARM,M68k]
                        earlyprintk=vga
                        earlyprintk=efi
                        earlyprintk=xen
                        earlyprintk=serial[,ttySn[,baudrate]]
                        earlyprintk=serial[,0x...[,baudrate]]
                        earlyprintk=ttySn[,baudrate]
                        earlyprintk=dbgp[debugController#]

                        earlyprintk is useful when the kernel crashes before
                        the normal console is initialized. It is not enabled by
                        default because it has some cosmetic problems.

                        Append ",keep" to not disable it when the real console
                        takes over.

                        Only one of vga, efi, serial, or usb debug port can
                        be used at a time.

                        Currently only ttyS0 and ttyS1 may be specified by
                        name.  Other I/O ports may be explicitly specified
                        on some architectures (x86 and arm at least) by
                        replacing ttySn with an I/O port address, like this:
                                earlyprintk=serial,0x1008,115200
                        You can find the port for a given device in
                        /proc/tty/driver/serial:
                                2: uart:ST16650V2 port:00001008 irq:18 ...

                        Interaction with the standard serial driver is not
                        very good.

                        The VGA and EFI output is eventually overwritten by
                        the real console.

                        The xen output can only be used by Xen PV guests.

 

earlycon에서 사용하는 테이블

  • 두 개의 테이블 사용
    • setup_param 테이블
      • obs_kernel_param 구조체
      • .init.setup 섹션에 위치하고 .init.data 섹션에 포함되어 있다.
    • <name>_of_table
      • of_device_id 구조체
        • 여러가지 이름으로 만들어진다.
          • name=earlycon의 경우 __earlycon_of_table
      • init.data 섹션에 위치한다.

parse_early_param_2

 

매크로 사용

  • 두 개의 earlycon이 사용하는 매크로
    • EARLYCON_DECLARE(name, func) 매크로 호출
      • <name>_setup_earlycon(buf) 함수 생성
        • setup_earlycon(buf, name, func) 함수 호출
      • early_param(“earlycon”, <name>_setup_earlycon) 호출
        • __setup_param(“earlycon”, <name>_setup_earlycon, <name>_setup_earlycon, 1)
          • const char __setup_str_<unique_id>[] = “earlycon” 문자열 생성
          • struct obs_kernel_param __setup_<unique_id> = { __setup_str_<unique_id>, <name>_setup_earycon, 1 } 구조체 생성
      • cmdline을 파싱한 토큰과 매치되는 테이블
    • OF_EARLYCON_DECLARE(name, compat, fn) 매크로 호출
      • _OF_DECLARE(earlycon, name, compat, fn, void *) 매크로 호출
        • const struct of_device_id __of_table_<name> = { .compatible = <compat>, data = (fn == (void *) NULL) ? fn : fn } 구조체 생성
      • DTB를 파싱하여 매치되는 테이블
  • 라즈베리파이2 예)
    • EARLYCON_DECLARE(pl011, pl011_early_console_setup) 호출
      • pl011_setup_earlycon(buf) 함수 생성
        • setup_earlycon(buf, pl011, pl011_early_console_setup) 함수 호출
      • early_param(“earlycon”, pl011_setup_earlycon) 호출
        • __setup_param(“earlycon”, pl011_setup_earlycon, pl011_setup_earlycon, 1)
          • const char __setup_str_pl011_setup_earlycon[] = “earlycon” 문자열 생성
          • struct obs_kernel_param __setup_pl011_setup_earlycon = { __setup_str_pl011_setup_earlycon, pl011_setup_earycon, 1 } 구조체 생성
    • OF_EARLYCON_DECLARE(pl011, “arm.pl011”, pl011_early_console_setup) 매크로 호출
      • _OF_DECLARE(earlycon, pl011, “arm.pl011”, pl011_early_console_setup, void *) 매크로 호출
        • const struct of_device_id __of_table_pl011 = { .compatible = “arm.pl011”, data = (pl011_early_console_setup == (void *) NULL) ? pl011_early_console_setup : pl011_early_console_setup } 구조체 생성

 

EARLYCON_DECLARE() & OF_EARLYCON_DECLARE() 매크로 소스

  • 아래는 라즈베리파이2의 pl011 이라는 이름의 시리얼 드라이버가 earlycon을 사용하여 테이블을 선언하는 소스

parse_early_param_3

 

pl011_early_console_setup()

  • 콘솔 디바이스  write 콜백 함수에 pl011_early_write() 함수 대입

parse_early_param_4a

 

setup_param 및 <name>_of_table 테이블

parse_early_param_6

  • DTB용 테이블 예)
    • OF_EARLYCON_DECLARE(pl011, “arm,pl011”, pl011_early_console_setup);
      • __of_table_pl011 구조체가 earlycon_of_table 섹션에 생성된다.

 

earlyprintk

등록

  • 아키텍처마다 다르지만 다음과 같은 옵션들을 사용한다.
    • earlyprintk= [X86,SH,BLACKFIN,ARM,M68k]
    • earlyprintk=vga
    • earlyprintk=efi
    • earlyprintk=xen
    • earlyprintk=serial[,ttySn[,baudrate]]
    • earlyprintk=serial[,0x…[,baudrate]]
    • earlyprintk=ttySn[,baudrate]
    • earlyprintk=dbgp[debugController#]
    • earlyprintk=pciserial,bus:device.function[,baudrate]-
  • ARM 아키텍처에서는 뒤에 붙은 인수를 사용하지 않고 다음과 같다.
    • “earlycon” 부트 명령행 파라메터가 같이 사용한다.
    • CONFIG_DEBUG_LL에서 지정한 디바이스로 출력이된다..

 

setup_early_printk()

kernel/early_printk.c

tatic int __init setup_early_printk(char *buf)
{
        early_console = &early_console_dev;
        register_console(&early_console_dev);
        return 0;
}

early_param("earlyprintk", setup_early_printk);
  • early_param(“earlyprintk”, setup_early_printk);
    • “earlyprintk” 커널 파라메터를 등록하고 setup_early_printk() 함수에 연결한다.
    • 부트 커멘드라인으로 “earlyprintk” 항목이 입력된 경우 setup_early_printk() 함수를 호출한다.
  • setup_early_printk()
    • early_console로 전역 early_console_dev 주소를 가리키게 하고 디바이스로 등록한다.

 

early_console_dev 구조체

arch/arm/kernel/early_printk.c

static struct console early_console_dev = { 
        .name =         "earlycon",
        .write =        early_console_write,
        .flags =        CON_PRINTBUFFER | CON_BOOT,
        .index =        -1,
};
  • “earlycon”이라는 디바이스명이고 early_printk() 함수를 사용하여 출력 시 early_console_write() 함수를 호출하게 한다.

 

사용

early_printk()

kernel/printk/printk.c

#ifdef CONFIG_EARLY_PRINTK
struct console *early_console;

asmlinkage __visible void early_printk(const char *fmt, ...) 
{
        va_list ap;
        char buf[512];
        int n;

        if (!early_console)
                return;

        va_start(ap, fmt);
        n = vscnprintf(buf, sizeof(buf), fmt, ap); 
        va_end(ap);

        early_console->write(early_console, buf, n);
}
#endif
  • early_printk() 함수 출력은 CONFIG_EARLY_PRINTK 커널 옵션을 사용할 때만 다음과 같은 3가지 동작 중 하나를 선택 사용하게 된다.
    • 최 우선 순위로 “earlycon” 부트 커멘드라인 파라메터를 사용한 경우 이 명령에서 가리키는 출력 디바이스가 사용된다.
    • 다음 우선 순위로 CONFIG_DEBUG_LL 커널 옵션을 사용한 경우 kernel/head.S에서 사용했었던 포트(uart)로의 출력 된다.
    • 아무 옵션도 사용하지 않은 경우 어떠한 출력도 하지 않는다.

early_console_write()

kernel/early_printk.c

static void early_console_write(struct console *con, const char *s, unsigned n)
{
        early_write(s, n); 
}

 

early_write()

kernel/early_printk.c

static void early_write(const char *s, unsigned n)
{
        while (n-- > 0) {
                if (*s == '\n')
                        printch('\r');
                printch(*s);
                s++;
        }
}

 

printch()

arch/arm/kernel/debug.S

ENTRY(printch)
                addruart_current r3, r1, r2
                mov     r1, r0 
                mov     r0, #0
                b       1b
ENDPROC(printch)

 

addruart_current

arch/arm/kernel/debug.S

                .macro  addruart_current, rx, tmp1, tmp2
                addruart        \tmp1, \tmp2, \rx
                mrc             p15, 0, \rx, c1, c0
                tst             \rx, #1
                moveq           \rx, \tmp1
                movne           \rx, \tmp2
                .endm
  • rpi2: CONFIG_DEBUG_LL_INCLUDE=”mach/debug-macro.S”
    • addruart는 arch/arm/mach-bcm2709/include/mach/debug-macro.S에 있다.

 

addruart()

uart base 물리주소와 가상 주소를 알아온다.

  • rpi2: 출력 아래 두 개의 소스가 준비되어 있는데 rpi2는 첫 번째 addruart를 사용한다.

arch/arm/mach-bcm2709/include/mach/debug-macro.S

#include <mach/platform.h>

                .macro  addruart, rp, rv, tmp 
                ldr     \rp, =UART0_BASE
                ldr     \rv, =IO_ADDRESS(UART0_BASE)
                .endm

#include <debug/pl01x.S>
  • UART0_BASE
    • 0x3f20_1000 물리주소
    • dtb에서 명시된 0x7e20_1000은 버스 주소를 사용한다.
  • IO_ADDRESS()
    • virtual address로 변환
    • 예) IO_ADDRESS(0x3f20_1000)
      • 0xf320_1000

 

arch/arm/include/debug/pl01x.S

#ifdef CONFIG_DEBUG_UART_PHYS
                .macro  addruart, rp, rv, tmp 
                ldr     \rp, =CONFIG_DEBUG_UART_PHYS
                ldr     \rv, =CONFIG_DEBUG_UART_VIRT
                .endm
#endif
  • rpi2: CONFIG_DEBUG_UART_PHYS 커널 옵션을 사용하지 않는다.

 

구조체

struct obs_kernel_param

include/linux/init.h

struct obs_kernel_param {
        const char *str;
        int (*setup_func)(char *); 
        int early;
};
  • str
    • 부트 커멘드라인 파라메터 명
    • 예) “console”, “earlycon”, “earlyprintk”
  • setup_func
    • 부트 커멘드라인 파라메터와 연결되어 있는 함수
  • early
    • 1인 경우 early 파라메터

 

참고

답글 남기기

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