early_irq_init()

 

IRQ 디스크립터를 관리하는 두 가지 방법

CONFIG_SPARSE_IRQ 커널 옵션을 사용유무에 따라 IRQ 번호를 관리하는 방법이 두 가지로 나뉜다.

  • Sparse IRQ
    • 커널 옵션을 사용하는 경우 필요한 IRQ 번호에 대한 irq_desc 구조체를 동적으로 할당하고 Radix Tree를 사용하여 관리한다.
    • arm64 시스템은 기본적으로 CONFIG_SPARSE_IRQ 커널 옵션을 구성하여 사용한다.
    • 관련 함수
      • irq_alloc_desc*()
      • irq_free_desc*()
  • Flat IRQ
    • 커널 옵션을 사용하지 않는 경우 max IRQ 번호만큼 irq_dest 구조체 배열을 컴파일 시 정적으로 할당하여 사용한다.

 

early_irq_init()

1) Sparse IRQ

kernel/irq/irqdesc.c

#ifdef CONFIG_SPARSE_IRQ
int __init early_irq_init(void)
{
        int i, initcnt, node = first_online_node;
        struct irq_desc *desc;

        init_irq_default_affinity();

        /* Let arch update nr_irqs and return the nr of preallocated irqs */
        initcnt = arch_probe_nr_irqs();
        printk(KERN_INFO "NR_IRQS:%d nr_irqs:%d %d\n", NR_IRQS, nr_irqs, initcnt);

        if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS))
                nr_irqs = IRQ_BITMAP_BITS;

        if (WARN_ON(initcnt > IRQ_BITMAP_BITS))
                initcnt = IRQ_BITMAP_BITS;

        if (initcnt > nr_irqs)
                nr_irqs = initcnt;

        for (i = 0; i < initcnt; i++) {
                desc = alloc_desc(i, node, NULL);
                set_bit(i, allocated_irqs);
                irq_insert_desc(i, desc);
        }
        return arch_early_irq_init();
}
#endif

irq_desc를 관리할 수 있는 radix 트리를 구성하고 초기화한다.

  • init_irq_default_affinity();
    • 전역 irq_default_affinity 변수에 cpu 비트맵 매스크를 할당하고 모두 1로 설정한다.
  • initcnt = arch_probe_nr_irqs();
    • 사용할 최대 irq 갯수를 알아온다.
  • if (WARN_ON(nr_irqs > IRQ_BITMAP_BITS)) nr_irqs = IRQ_BITMAP_BITS;
    • 전역 nr_irqs가 IRQ_BITMAP_BITS를 초과하지 않도록 조정한다.
      • IRQ_BITMAP_BITS는 CONFIG_SPARSE_IRQ에서 NR_IRQS+8192개이고 그렇지 않은 경우 NR_IRQS이다.
  • if (WARN_ON(initcnt > IRQ_BITMAP_BITS)) initcnt = IRQ_BITMAP_BITS;
    • initcnt가 IRQ_BITMAP_BITS를 초과하지 않도록 조정한다.
  • if (initcnt > nr_irqs) nr_irqs = initcnt;
    • initcnt가 nr_irqs를 초과하는 경우 nr_irqs에 initcnt를 대입한다.
  • for (i = 0; i < initcnt; i++) {
    • initcnt 수 만큼 루프를 돌며
  • desc = alloc_desc(i, node, NULL);
    • irq_desc 구조체를 할당받는다.
  • set_bit(i, allocated_irqs);
    • 전역 allocated_irqs 비트맵의 i번 cpu를 1로 설정한다.
  • irq_insert_desc(i, desc);
    • 전역 irq_desc_tree에 i를 키로 추가하고 할당받은 irq_desc를 가리키게 한다.
  • return arch_early_irq_init();
    • arch_early_irq_init() 함수가 있는 아키텍처 코드를 수행한다.
      • 현재 x86, ia64 아키텍처에서 제공된다.

 

다음 그림은 sparse하게 irq descriptor가 관리되는 모습을 보여준다.

early_irq_init-1

 

2) Flat IRQ

#else /* !CONFIG_SPARSE_IRQ */
struct irq_desc irq_desc[NR_IRQS] __cacheline_aligned_in_smp = {
 [0 ... NR_IRQS-1] = {
 .handle_irq = handle_bad_irq,
 .depth = 1,
 .lock = __RAW_SPIN_LOCK_UNLOCKED(irq_desc->lock),
 }
};

int __init early_irq_init(void)
{
        int count, i, node = first_online_node;
        struct irq_desc *desc;

        init_irq_default_affinity();

        printk(KERN_INFO "NR_IRQS:%d\n", NR_IRQS);

        desc = irq_desc;
        count = ARRAY_SIZE(irq_desc);

        for (i = 0; i < count; i++) {
                desc[i].kstat_irqs = alloc_percpu(unsigned int);
                alloc_masks(&desc[i], GFP_KERNEL, node);
                raw_spin_lock_init(&desc[i].lock);
                lockdep_set_class(&desc[i].lock, &irq_desc_lock_class);
                desc_set_defaults(i, &desc[i], node, NULL);
        }
        return arch_early_irq_init();
}
#endif

irq_desc를 관리하는 전역 irq_desc[] 배열을 초기화한다.

  • init_irq_default_affinity();
    • 전역 irq_default_affinity 변수에 cpu 비트맵 매스크를 할당하고 모두 1로 설정한다.
  • for (i = 0; i < count; i++) {
    • count(irq_desc[] 배열의 인덱스 수) 만큼 루프를 돌며
  • desc[i].kstat_irqs = alloc_percpu(unsigned int);
    • irq_desc[i].kstat_irqs에 per-cpu int 타입을 할당한다.
  • alloc_masks(&desc[i], GFP_KERNEL, node);
    • irq descriptor에서 사용하는 cpu 비트맵 마스크를 할당받는다.
  • desc_set_defaults(i, &desc[i], node, NULL);
    • irq descriptor를 초기화한다.
  • return arch_early_irq_init();
    • arch_early_irq_init() 함수가 있는 아키텍처 코드를 수행한다.
      • 현재 x86, ia64 아키텍처에서 제공된다.

 

다음 그림은 flat하게 irq descriptor가 관리되는 모습을 보여준다.

early_irq_init-2

 

init_irq_default_affinity()

kernel/irq/irqdesc.c

static void __init init_irq_default_affinity(void)
{
        alloc_cpumask_var(&irq_default_affinity, GFP_NOWAIT);
        cpumask_setall(irq_default_affinity);
}

전역 irq_default_affinity 변수에 cpu 비트맵 매스크를 할당하고 모두 1로 설정한다.

 

arch_probe_nr_irqs()

arch/arm/kernel/irq.c

int __init arch_probe_nr_irqs(void)
{
        nr_irqs = machine_desc->nr_irqs ? machine_desc->nr_irqs : NR_IRQS;
        return nr_irqs;
}

아키텍처의 머신 디스크립터에서 제공되는 최대 IRQ 갯수를 알아온다. 만일 설정되어 있지 않은 경우 NR_IRQS 값으로 정한다.

 

NR_IRQS

irq 최대 갯수

  • CONFIG_SPARSE_IRQ 커널 옵션 사용 유무에 따라
    • 사용하는 경우
      • NR_IRQ_LEGACY(16) 값을 사용한다.
    • 사용하지 않는 경우
      • mach/irqs.h에서 지정된 값을 사용한다.
      • rpi2:
        • arch/arm/mach-bcm2709/include/mach/irqs.h
        • NR_IRQS = 608 (hard irq(128) + fiq irq(128) + gpio irq(160) + spare irq(64) + free irq(128))

 

alloc_desc()

kernel/irq/irqdesc.c

static struct irq_desc *alloc_desc(int irq, int node, struct module *owner)
{
        struct irq_desc *desc;
        gfp_t gfp = GFP_KERNEL;

        desc = kzalloc_node(sizeof(*desc), gfp, node);
        if (!desc)
                return NULL;
        /* allocate based on nr_cpu_ids */
        desc->kstat_irqs = alloc_percpu(unsigned int);
        if (!desc->kstat_irqs)
                goto err_desc;

        if (alloc_masks(desc, gfp, node))
                goto err_kstat;

        raw_spin_lock_init(&desc->lock);
        lockdep_set_class(&desc->lock, &irq_desc_lock_class);

        desc_set_defaults(irq, desc, node, owner);

        return desc;

err_kstat:
        free_percpu(desc->kstat_irqs);
err_desc:
        kfree(desc);
        return NULL;
}

irq_desc 구조체를 할당받은 후 초기화한다.

  • 요청 irq 번호용으로 irq_desc  구조체를 할당 받고
  • 그 멤버 per-cpu 변수 kstat_irqs에 int형으로 per-cpu 할당을 받아 연결한다.
  • irq descriptor에서 사용하는 cpu 비트맵 마스크를 할당받는다.
  • 해당 irq descriptor를 초기화하고 owner 모듈을 지정한다.

 

다음 그림은 하나의 irq descriptor가 할당되고 초기화되는 모습을 보여준다.

alloc_desc-1

 

alloc_masks()

kernel/irq/irqdesc.c

static int alloc_masks(struct irq_desc *desc, gfp_t gfp, int node)
{
        if (!zalloc_cpumask_var_node(&desc->irq_data.affinity, gfp, node))
                return -ENOMEM;

#ifdef CONFIG_GENERIC_PENDING_IRQ
        if (!zalloc_cpumask_var_node(&desc->pending_mask, gfp, node)) {
                free_cpumask_var(desc->irq_data.affinity);
                return -ENOMEM;
        }
#endif  
        return 0;
}

irq descriptor에서 사용하는 cpu 비트맵 마스크를 할당받는다.

  • irq descriptor의 irq_data.affinity에 cpu 비트맵을 할당받는다.
  • 만일 CONFIG_GENERIC_PENDING_IRQ 커널 옵션을 사용하는 경우 irq descriptor의 pending_mask에도 cpu 비트맵을 할당받는다.

 

desc_set_defaults()

kernel/irq/irqdesc.c

static void desc_set_defaults(unsigned int irq, struct irq_desc *desc, int node,
                struct module *owner)
{
        int cpu;

        desc->irq_data.irq = irq;
        desc->irq_data.chip = &no_irq_chip;
        desc->irq_data.chip_data = NULL;
        desc->irq_data.handler_data = NULL;
        desc->irq_data.msi_desc = NULL;
        irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
        irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
        desc->handle_irq = handle_bad_irq;
        desc->depth = 1;
        desc->irq_count = 0;
        desc->irqs_unhandled = 0;
        desc->name = NULL;
        desc->owner = owner;
        for_each_possible_cpu(cpu)
                *per_cpu_ptr(desc->kstat_irqs, cpu) = 0;
        desc_smp_init(desc, node);
}

해당 irq descriptor를 초기화하고 owner 모듈을 지정한다.

  • irq_settings_clr_and_set(desc, ~0, _IRQ_DEFAULT_INIT_FLAGS);
    • irq descriptor의 status_use_accessors의 모든 비트를 클리어한 후 _IRQ_DEFAULT_INIT_FLAGS 비트들을 설정한다.설정한다.
      • _IRQ_DEFAULT_INIT_FLAGS
        • arm: IRQ_NOPROBE(0x400, bit 10)
        • arm: IRQ_NOREQUEST(0x800, bit 11)
  • irqd_set(&desc->irq_data, IRQD_IRQ_DISABLED);
    • irq_data의 state_use_accessors에 IRQD_IRQ_DISABLED 비트를 설정한다.
      • IRQD_IRQ_DISABLED
        • arm: 0x10000 (bit 16)
  • desc->handle_irq = handle_bad_irq;
    • handle_irq가 지정되지 않을 때 동작하게 하기 위해 에러 처리용 핸들러 함수인 handle_bad_irq() 함수를 지정해둔다.
  • desc_smp_init(desc, node);
    • irq descriptor에서 사용하는 cpu 비트맵을 irq_default_affinity 비트맵 내용으로 초기화한다.

 

irq_settings_clr_and_set()

kernel/irq/settings.h

static inline void
irq_settings_clr_and_set(struct irq_desc *desc, u32 clr, u32 set)
{       
        desc->status_use_accessors &= ~(clr & _IRQF_MODIFY_MASK);
        desc->status_use_accessors |= (set & _IRQF_MODIFY_MASK);
}

irq descriptor의 status_use_accessors에서 인수 clr로 요청한 비트를 clear하고 인수 set으로 요청한 비트를 설정한다.

 

irqd_set()

kernel/irq/internals.h

static inline void irqd_set(struct irq_data *d, unsigned int mask)
{
        d->state_use_accessors |= mask;
}

irq_data의 state_use_accessors의 인수 mask 비트를 설정한다.

 

desc_smp_init()

kernel/irq/irqdesc.c

static void desc_smp_init(struct irq_desc *desc, int node)
{
        desc->irq_data.node = node;
        cpumask_copy(desc->irq_data.affinity, irq_default_affinity);
#ifdef CONFIG_GENERIC_PENDING_IRQ
        cpumask_clear(desc->pending_mask);
#endif
}

irq descriptor에서 사용하는 cpu 비트맵을 irq_default_affinity 비트맵 내용으로 초기화한다.

  • cpumask_copy(desc->irq_data.affinity, irq_default_affinity);
    • irq descriptor의 irq_data.affinity 비트맵에 irq_default_affinity 비트맵을 복사한다.
  • cpumask_clear(desc->pending_mask);
    • irq descriptor의 pending_mask 비트맵을 모두 clear한다.

 

irq_insert_desc()

kernel/irq/irqdesc.c

static void irq_insert_desc(unsigned int irq, struct irq_desc *desc)
{
        radix_tree_insert(&irq_desc_tree, irq, desc);
}

전역 irq_desc_tree에 요청 irq를 키로 추가하고 irq descriptor를 가리키게 한다.

 

구조체 및 데이터

IRQ 디스크립터

include/linux/irqdesc.h

/**
 * struct irq_desc - interrupt descriptor
 * @irq_data:           per irq and chip data passed down to chip functions
 * @kstat_irqs:         irq stats per cpu
 * @handle_irq:         highlevel irq-events handler
 * @preflow_handler:    handler called before the flow handler (currently used by sparc)
 * @action:             the irq action chain
 * @status:             status information
 * @core_internal_state__do_not_mess_with_it: core internal status information
 * @depth:              disable-depth, for nested irq_disable() calls
 * @wake_depth:         enable depth, for multiple irq_set_irq_wake() callers
 * @irq_count:          stats field to detect stalled irqs
 * @last_unhandled:     aging timer for unhandled count
 * @irqs_unhandled:     stats field for spurious unhandled interrupts
 * @threads_handled:    stats field for deferred spurious detection of threaded handlers
 * @threads_handled_last: comparator field for deferred spurious detection of theraded handlers
 * @lock:               locking for SMP
 * @affinity_hint:      hint to user space for preferred irq affinity
 * @affinity_notify:    context for notification of affinity changes
 * @pending_mask:       pending rebalanced interrupts
 * @threads_oneshot:    bitfield to handle shared oneshot threads
 * @threads_active:     number of irqaction threads currently running
 * @wait_for_threads:   wait queue for sync_irq to wait for threaded handlers
 * @nr_actions:         number of installed actions on this descriptor
 * @no_suspend_depth:   number of irqactions on a irq descriptor with
 *                      IRQF_NO_SUSPEND set
 * @force_resume_depth: number of irqactions on a irq descriptor with
 *                      IRQF_FORCE_RESUME set
 * @dir:                /proc/irq/ procfs entry
 * @name:               flow handler name for /proc/interrupts output
 */

 

struct irq_desc {
        struct irq_data         irq_data;
        unsigned int __percpu   *kstat_irqs;
        irq_flow_handler_t      handle_irq;     
#ifdef CONFIG_IRQ_PREFLOW_FASTEOI
        irq_preflow_handler_t   preflow_handler;
#endif  
        struct irqaction        *action;        /* IRQ action list */
        unsigned int            status_use_accessors;
        unsigned int            core_internal_state__do_not_mess_with_it;
        unsigned int            depth;          /* nested irq disables */
        unsigned int            wake_depth;     /* nested wake enables */
        unsigned int            irq_count;      /* For detecting broken IRQs */
        unsigned long           last_unhandled; /* Aging timer for unhandled count */
        unsigned int            irqs_unhandled;
        atomic_t                threads_handled;
        int                     threads_handled_last;
        raw_spinlock_t          lock;
        struct cpumask          *percpu_enabled;
#ifdef CONFIG_SMP      
        const struct cpumask    *affinity_hint;
        struct irq_affinity_notify *affinity_notify;
#ifdef CONFIG_GENERIC_PENDING_IRQ
        cpumask_var_t           pending_mask;   
#endif
#endif
        unsigned long           threads_oneshot;
        atomic_t                threads_active; 
        wait_queue_head_t       wait_for_threads;
#ifdef CONFIG_PM_SLEEP
        unsigned int            nr_actions;
        unsigned int            no_suspend_depth;
        unsigned int            cond_suspend_depth;
        unsigned int            force_resume_depth;
#endif
#ifdef CONFIG_PROC_FS
        struct proc_dir_entry   *dir;
#endif
        int                     parent_irq;
        struct module           *owner;
        const char              *name;
} ____cacheline_internodealigned_in_smp;
  • irq_data
    • chip 기능에 전달되는 irq와 chip 데이터
  • kstat_irqs
    • cpu 별 irq 통계 카운터
  • handle_irq
    • 하이 레벨 irq 이벤트 핸들러
  • action
    • irq action 체인
  • status
    • 상태 정보
  • core_internal_state__do_not_mess_with_it
    • 코어 내부 상태 정보
    • irqd->state으로 표현( state가 매크로)
  • depth
    • 네스트된 irq_disable() 호출을 위한 disable depth
  • wake_depth
    • 다중 irq_set_irq_wake() 호출을 위한 enable depth
  • irq_count
    • stalled irq들을 감지하기 위한 상태 필드
  • last_unhandled
    • 언핸들드 카운트를 위한 aging 타이머
  • irqs_unhandled
    • 가짜 언핸들된 인터럽트들을 위한 상태 필드
  • threads_handled
    • 스레드 핸들러의 연기된 가짜 감지를 위한 상태 필드
  • threads_handled_last
    • 스레드 핸들러의 연기된 가짜 감지를 위한 비교 필드
  • lock
    • SMP lock
  • affinity_hint
  • affinity_notify
    • affnity가 변경 시 통지를 위한 context
  • pending_mask
    • 지연된 리밸런스된 인터럽트들
  • threads_oneshot
  • threads_active
    • 현재 동작중인 irqaction 스레드의 수
  • wait_for_threads
    • 스레드된 핸들러를 위해 기다릴 sync_irq를 위한 대기큐
  • nr_actions
    • 이 irq 디스크립터에  설치된 액션 수
  • no_suspend_depth
    • IRQF_NO_SUSPEND 셋을 가진 irq 디스크립터의 irqactions 수
  • force_resume_depth
    • IRQF_FORCE_RESUME 셋을 가진 irq 디스크립터의 irqactions 수
  • dir
    • /proc/irq/에 표시될 procfs 엔트리
    • default로 irq 숫자 (예: /proc/irq/83)
  • name
    • /proc/interrupts에 출력될 flow 핸들러 이름
    • 예) uart-pl011

 

irq_data 구조체

include/linux/irq.h

/**
 * struct irq_data - per irq and irq chip data passed down to chip functions
 * @mask:               precomputed bitmask for accessing the chip registers
 * @irq:                interrupt number
 * @hwirq:              hardware interrupt number, local to the interrupt domain
 * @node:               node index useful for balancing
 * @state_use_accessors: status information for irq chip functions.
 *                      Use accessor functions to deal with it
 * @chip:               low level interrupt hardware access
 * @domain:             Interrupt translation domain; responsible for mapping
 *                      between hwirq number and linux irq number.
 * @parent_data:        pointer to parent struct irq_data to support hierarchy
 *                      irq_domain
 * @handler_data:       per-IRQ data for the irq_chip methods
 * @chip_data:          platform-specific per-chip private data for the chip
 *                      methods, to allow shared chip implementations
 * @msi_desc:           MSI descriptor
 * @affinity:           IRQ affinity on SMP
 *
 * The fields here need to overlay the ones in irq_desc until we
 * cleaned up the direct references and switched everything over to
 * irq_data.
 */
struct irq_data {
        u32                     mask;
        unsigned int            irq;
        unsigned long           hwirq;
        unsigned int            node;
        unsigned int            state_use_accessors;
        struct irq_chip         *chip;
        struct irq_domain       *domain;
#ifdef  CONFIG_IRQ_DOMAIN_HIERARCHY
        struct irq_data         *parent_data;
#endif
        void                    *handler_data;
        void                    *chip_data;
        struct msi_desc         *msi_desc;
        cpumask_var_t           affinity;
};
  • mask
    • chip 레지스터들에 접근하기 위한 미리 계산된 비트마스크
  • irq
    • 인터럽트 번호
  • hwirq
    • 하드웨어 인터럽트 번호
  • node
    • 밸런싱에 유용한 노드 인덱스
  • state_use_accessors
    • irq chip 기능들에 대한 상태 정보
  • chip
    • 로우 레벨 인터럽트 컨트롤러 하드웨어
  • domain
    • 인터럽트 변환 도메인
    • hwirq와 리눅스 irq 간의 변환
  • parent_data
    • 하이라키 irq_domain을 지원하기 위한 부모 irq_data를 가리킨다.
  • handler_data
    • irq_chip 메소드를 위한 per-IRQ 데이터
  • chip_data
    • chip 메소드와 공유 chip 구현을 허락하기 위한 플랫폼 specific per-chip private 데이터
  • msi
    • MSI 디스크립터
  • affinity
    • SMP에서의 IRQ affinity

 

IRQ Data 상태 (irq_data.state )

include/linux/irq.h

/*
 * Bit masks for irq_data.state
 *
 * IRQD_TRIGGER_MASK            - Mask for the trigger type bits
 * IRQD_SETAFFINITY_PENDING     - Affinity setting is pending
 * IRQD_NO_BALANCING            - Balancing disabled for this IRQ
 * IRQD_PER_CPU                 - Interrupt is per cpu
 * IRQD_AFFINITY_SET            - Interrupt affinity was set
 * IRQD_LEVEL                   - Interrupt is level triggered
 * IRQD_WAKEUP_STATE            - Interrupt is configured for wakeup
 *                                from suspend
 * IRDQ_MOVE_PCNTXT             - Interrupt can be moved in process
 *                                context
 * IRQD_IRQ_DISABLED            - Disabled state of the interrupt
 * IRQD_IRQ_MASKED              - Masked state of the interrupt
 * IRQD_IRQ_INPROGRESS          - In progress state of the interrupt
 * IRQD_WAKEUP_ARMED            - Wakeup mode armed
 */
enum {
        IRQD_TRIGGER_MASK               = 0xf,
        IRQD_SETAFFINITY_PENDING        = (1 <<  8),
        IRQD_NO_BALANCING               = (1 << 10),
        IRQD_PER_CPU                    = (1 << 11),
        IRQD_AFFINITY_SET               = (1 << 12),
        IRQD_LEVEL                      = (1 << 13),
        IRQD_WAKEUP_STATE               = (1 << 14),
        IRQD_MOVE_PCNTXT                = (1 << 15),
        IRQD_IRQ_DISABLED               = (1 << 16),
        IRQD_IRQ_MASKED                 = (1 << 17),
        IRQD_IRQ_INPROGRESS             = (1 << 18),
        IRQD_WAKEUP_ARMED               = (1 << 19),
};
  • IRQD_TRIGGER_MASK
    • 트리거 타입 마스크 (4비트)
  • IRQD_SETAFFINITY_PENDING
    • affinity 설정이 지연되는 중
  • IRQD_NO_BALANCING
    • no 밸런싱
  • IRQD_PER_CPU
    • 코어별 전용 인터럽트 배정되는 경우 per-cpu 인터럽트로 구성
  • IRQD_AFFINITY_SET
    • affnity 설정된 경우
  • IRQD_LEVEL
    • 레벨 트리거 설정된 경우 (그렇지 않으면 edge 트리거)
  • IRQD_WAKERUP_STATE
    • suspend된 작업을 깨워 동작시키는 인터럽트 형태로 설정
  • IRQD_MOVE_PCNTXT
    • 인터럽트가 process context로 이동된 경우
  • IRQD_IRQ_DISABLED
    • 인터럽트를 disable한 상태
  • IRQD_IRQ_MASKED
    • 인터럽트의 masked 상태
  • IRQD_IRQ_INPROGRESS
    • 인터럽트가 진행중인 상태
  • IRQD_WAKERUP_ARMED
    • 꺠우는 모드 armed

 

참고

답글 남기기

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