Earlycon & Earlyprintk

 

<kernel v5.0>

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

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

주요 항목

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

각종 early 파라메터들

  • 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

 

earlyprintk & earlycon

  • earlyprintk:
    • 커널 2.6.12에서 도입
    • 정식 드라이버 로딩 전 커널의 디버깅 출력용
    • ARM 아키텍처에서는 뒤에 붙은 인수를 사용하지 않고 다음과 같다.
      • “earlycon” 부트 명령행 파라메터가 같이 사용한다.
      • CONFIG_DEBUG_LL에서 지정한 디바이스로 출력이된다..
  • earlycon:
    • 2014년 3월 arm64에서 제안. 커널 3.16에 도입. (i386은 2007년)
    • 디버깅 목적으로 다른 드라이버보다 더 빨리 기동이 필요
    • 동작되면 “bootconsole [uart0] enabled”라고 출력.
  • 참고: Linux earlyprintk/earlycon support on ARM

 

Documentation/admin-guide/kernel-parameters.txt

        earlycon=       [KNL] Output early console device and options.

                        [ARM64] The early console is determined by the
                        stdout-path property in device tree's chosen node,
                        or determined by the ACPI SPCR table.

                        [X86] When used with no options the early console is
                        determined by the ACPI SPCR table.

                cdns,<addr>[,options]
                        Start an early, polled-mode console on a Cadence
                        (xuartps) serial port at the specified address. Only
                        supported option is baud rate. If baud rate is not
                        specified, the serial port must already be setup and
                        configured.

                uart[8250],io,<addr>[,options]
                uart[8250],mmio,<addr>[,options]
                uart[8250],mmio32,<addr>[,options]
                uart[8250],mmio32be,<addr>[,options]
                uart[8250],0x<addr>[,options]
                        Start an early, polled-mode console on the 8250/16550
                        UART at the specified I/O port or MMIO address.
                        MMIO inter-register address stride is either 8-bit
                        (mmio) or 32-bit (mmio32 or mmio32be).
                        If none of [io|mmio|mmio32|mmio32be], <addr> is assumed
                        to be equivalent to 'mmio'. 'options' are specified
                        in the same format described for "console=ttyS<n>"; if
                        unspecified, the h/w is not initialized.

                pl011,<addr>
                pl011,mmio32,<addr>
                        Start an early, polled-mode console on a pl011 serial
                        port at the specified address. The pl011 serial port
                        must already be setup and configured. Options are not
                        yet supported.  If 'mmio32' is specified, then only
                        the driver will use only 32-bit accessors to read/write
                        the device registers.

                meson,<addr>
                        Start an early, polled-mode console on a meson serial
                        port at the specified address. The serial port must
                        already be setup and configured. Options are not yet
                        supported.

                msm_serial,<addr>
                        Start an early, polled-mode console on an msm serial
                        port at the specified address. The serial port
                        must already be setup and configured. Options are not
                        yet supported.

                msm_serial_dm,<addr>
                        Start an early, polled-mode console on an msm serial
                        dm port at the specified address. The serial port
                        must already be setup and configured. Options are not
                        yet supported.

                owl,<addr>
                        Start an early, polled-mode console on a serial port
                        of an Actions Semi SoC, such as S500 or S900, at the
                        specified address. The serial port must already be
                        setup and configured. Options are not yet supported.

                rda,<addr>
                        Start an early, polled-mode console on a serial port
                        of an RDA Micro SoC, such as RDA8810PL, at the
                        specified address. The serial port must already be
                        setup and configured. Options are not yet supported.

                smh     Use ARM semihosting calls for early console.

                s3c2410,<addr>
                s3c2412,<addr>
                s3c2440,<addr>
                s3c6400,<addr>
                s5pv210,<addr>
                exynos4210,<addr>
                        Use early console provided by serial driver available
                        on Samsung SoCs, requires selecting proper type and
                        a correct base address of the selected UART port. The
                        serial port must already be setup and configured.
                        Options are not yet supported.

                lantiq,<addr>
                        Start an early, polled-mode console on a lantiq serial
                        (lqasc) port at the specified address. The serial port
                        must already be setup and configured. Options are not
                        yet supported.

                lpuart,<addr>
                lpuart32,<addr>
                        Use early console provided by Freescale LP UART driver
                        found on Freescale Vybrid and QorIQ LS1021A processors.
                        A valid base address must be provided, and the serial
                        port must already be setup and configured.

                ar3700_uart,<addr>
                        Start an early, polled-mode console on the
                        Armada 3700 serial port at the specified
                        address. The serial port must already be setup
                        and configured. Options are not yet supported.

                qcom_geni,<addr>
                        Start an early, polled-mode console on a Qualcomm
                        Generic Interface (GENI) based serial port at the
                        specified address. The serial port must already be
                        setup and configured. Options are not yet supported.

 

Documentation/admin-guide/kernel-parameters.txt

        earlyprintk=    [X86,SH,ARM,M68k,S390]
                        earlyprintk=vga
                        earlyprintk=efi
                        earlyprintk=sclp
                        earlyprintk=xen
                        earlyprintk=serial[,ttySn[,baudrate]]
                        earlyprintk=serial[,0x...[,baudrate]]
                        earlyprintk=ttySn[,baudrate]
                        earlyprintk=dbgp[debugController#]
                        earlyprintk=pciserial[,force],bus:device.function[,baudrate]
                        earlyprintk=xdbc[xhciController#]

                        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.

                        The sclp output can only be used on s390.

                        The optional "force" to "pciserial" enables use of a
                        PCI device even when its classcode is not of the
                        UART class.

 


파라메터 선언

커멘드라인 파라메터를 통해 지정된 파라메터의 셋업 루틴을 호출한다.

  • early 파라메터는 컴파일 타임에 생성되어 파라메터명과 이에 대응하는 셋업 함수 및 early 여부를 “.init.setup” 섹션에 추가한다.
  • earlycon 및 earlyprintk 등에 대응하는 초기화 루틴도 이 매크로를 통해 등록한다.

 

__setup() 매크로

#define __setup(str, fn)                                                \
        __setup_param(str, fn, fn, 0)

@str 커멘드 라인 파라메터명이 지정될 때 호출될 @fn 함수를 컴파일 타임에 등록한다.

 

early_param() 매크로

include/linux/init.h

#define early_param(str, fn)                                            \
        __setup_param(str, fn, fn, 1)

커멘드 라인 명령에서 @str 문자열 지정되는 경우 대응하는 함수를 부트업 초반 정규 메모리 등의 사용이 불가능한 이른(early) 타임에 호출하기 위해 컴파일 타임에 등록한다.

  • 예) early_param(“earlycon”, param_setup_earlycon);

 

__setup_param()

include/linux/init.h

/*
 * Only for really core code.  See moduleparam.h for the normal way.
 *
 * Force the alignment so the compiler doesn't space elements of the
 * obs_kernel_param "array" too far apart in .init.setup.
 */
#define __setup_param(str, unique_id, fn, early)                        \
        static const char __setup_str_##unique_id[] __initconst         \
                __aligned(1) = str;                                     \
        static struct obs_kernel_param __setup_##unique_id              \
                __used __section(.init.setup)                           \
                __attribute__((aligned((sizeof(long)))))                \
                = { __setup_str_##unique_id, fn, early }

 

파라메터명과 대응 셋업 함수가 담긴 objs_kernel_param 구조체를 “.init.setup” 섹션에 추가한다.

  • 이렇게 추가한 파라메터는 부트업 타임에 주어지는 커멘드 라인 명령으로 호출된다.

 

 

파라메터 위치

파라메터가 저장되는 위치를 커널 v4.6-rc1 이전과 이후로 구분하여 보여준다.

 

다음 그림은 커널 v4.5까지 파라메터가 저장되는 섹션 위치와 관련된 매크로들 보여준다.

 

다음 그림은 커널 v4.6 이상 파라메터가 저장되는 섹션 위치와 관련된 매크로들 보여준다.

 

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 파라메터

 


 

Earlycon

각종 디바이스 드라이버들은 커널 부트 업 과정 중 정규 메모리 초기화 과정이 끝난 후에 각각의 필요한 드라이버들이 지정되어 로드된다. 그러나 디버그를 위해 먼저 콘솔 출력이 필요한 경우 이를 지정하여 사용할 수 있게 하였다.

 

earlycon 초기화

param_setup_earlycon()

drivers/tty/serial/earlycon.c

/* early_param wrapper for setup_earlycon() */
static int __init param_setup_earlycon(char *buf)
{
        int err;

        /* Just 'earlycon' is a valid param for devicetree and ACPI SPCR. */
        if (!buf || !buf[0]) {
                if (IS_ENABLED(CONFIG_ACPI_SPCR_TABLE)) {
                        earlycon_acpi_spcr_enable = true;
                        return 0;
                } else if (!buf) {
                        return early_init_dt_scan_chosen_stdout();
                }
        }

        err = setup_earlycon(buf);
        if (err == -ENOENT || err == -EALREADY)
                return 0;
        return err;
}
early_param("earlycon", param_setup_earlycon);

커멘드 라인 파라메터로 “earlycon=” 또는 “console=” 요청을 받은 경우 early console용 디바이스를 준비한다. 커멘드 라인 파라메터에서 “earlycon” 또는 “console” 명령 이후 디바이스명을 지정하지 않은 경우에 한해 디바이스 트리를 통해 지정한다.

 

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 등을 설정한다.

 

earlycon 디바이스 선언

earlycon 디바이스는 컴파일 타임에 생성되어 earlycon 테이블에 추가되며 “.init.data” 섹션에 위치한다.

  • 참고로 .init 섹션은 default로 부팅 후 더 이상 사용되지 않으므로 모두 제거되어 일반 메모리로 사용하기 위해 할당 해제된다.
  • 커널 v4.6-rc1 이후로 EARLYCON_DECLARE()와 OF_EARLYCON_DECLARE() 함수가 각자의 테이블에 엔트리를 추가하지 않고, 중복되지 않게 하나의 earlycon 테이블에 earlycon_id 구조체를 사용하여 추가된다.

 

EARLYCON_DECLARE() 매크로

include/linux/serial_core.h

#define EARLYCON_DECLARE(_name, fn)     OF_EARLYCON_DECLARE(_name, "", fn)

command 파라메터에서 요청할 earlycon 디바이스를 선언한다. 부트 업시 이 디바이스 이름 @_name이 지정되면 호출될 디바이스 셋업 함수를 인자 @fn으로 지정한다.

  • 커널 v4.6-rc1 이후로 디바이스 트리용 매크로를 호출하도록 통합되었다.
  • 예) EARLYCON_DECLARE(qdf2400_e44, qdf2400_e44_early_console_setup);

 

OF_EARLYCON_DECLARE() 매크로

include/linux/serial_core.h

#define OF_EARLYCON_DECLARE(_name, compat, fn)                          \
        _OF_EARLYCON_DECLARE(_name, compat, fn,                         \
                             __UNIQUE_ID(__earlycon_##_name))

디바이스 트리에서 요청할 earlycon 디바이스를 선언한다. 디바이스 트리에서 이 디바이스의 compatible 명을 지정하여 매치되는 경우 호출될 디바이스 셋업 함수를 @fn으로 지정한다.

  • 커널 v4.6-rc1 이후로 커멘드 파라메터에서 요청해도 동작되도록 통합되었다.
  • 예) OF_EARLYCON_DECLARE(pl011, “arm,pl011”, pl011_early_console_setup);

 

_OF_EARLYCON_DECLARE() 매크로

include/linux/serial_core.h

#define _OF_EARLYCON_DECLARE(_name, compat, fn, unique_id)              \
        static const struct earlycon_id unique_id                       \
             EARLYCON_USED_OR_UNUSED __initconst                        \
                = { .name = __stringify(_name),                         \
                    .compatible = compat,                               \
                    .setup = fn  };                                     \
        static const struct earlycon_id EARLYCON_USED_OR_UNUSED         \
                __section(__earlycon_table)                             \
                * const __PASTE(__p, unique_id) = &unique_id

earlycon_id 구조체 포인터가 __earlycon_table 섹션에 추가된다.

 

earlycon 테이블 위치

earlycon 테이블 위치는 커널 v4.6-rc1 이전과 이후로 나뉜다.

 

다음 그림은 커널 v4.5까지 earlycon 테이블이 저장되는 섹션 위치와 관련된 매크로들 보여준다.

 

다음 그림은 커널 v4.6 이상 earlycon 테이블이 저장되는 섹션 위치와 관련된 매크로들 보여준다.

 

다음 그림은 파라메터들이 등록된 “.init.setup” 섹션의 모습을 보여준다.

 

earlycon_id 구조체

include/linux/serial_core.h

struct earlycon_id {
        char    name[15];
        char    name_term;      /* In case compiler didn't '\0' term name */
        char    compatible[128];
        int     (*setup)(struct earlycon_device *, const char *options);
};
  • name
    • 커멘트 파라메터로 요청 시 매치될 earlycon 디바이스 이름
  • name_term
    • null이 담긴다.
  • compatible
    • OF_EARLYCON_DECLARE() 매크로를 통할 때 compatible 명이 주어지며 디바이스 트리에서 요청되는 이름과 비교될 때 사용된다.
  • (*setup)
    • earlycon 디바이스가 지정되어 호출될 때 디바이스 셋업을 위해 불려지는 후크 함수이다.

 

 


 

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);
}

 

라즈베리 파이 earlycon 디바이스

아래는 라즈베리파이2의 pl011 이라는 이름의 시리얼 드라이버가 earlycon을 사용하여 테이블을 선언한다. – 커널 v4.0 기준 소스

 

parse_early_param_3

 

pl011_early_console_setup()

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

parse_early_param_4a

 

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

 


 

earlyprintk

printk() 출력은 정규 콘솔에 출력하는데, 이 옵션이 지정되면 early_printk()를 통해 정규 콘솔이 로드되기 전에 early console 디바이스에 디버그 출력을 할 수 있다.

 

earlyprint 초기화

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 커널 옵션을 사용하지 않는다.

 

 

참고

 

댓글 남기기

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