Device Resource Management

 

Device Resource Management

디바이스의 사용이 끝나고 해당 디바이스가 detach 될 때 할당하여 사용한 여러 가지 리소스들을 할당 해제하여야 한다. 이 리소스들을 모두 기억해 두었다가 한꺼번에 할당 해제할 수 있는 방법이 소개되었다. 이는 허태준씨가 ATA 장치를 위한 서브시스템을 개발하다가 디바이스 리소스 관리를 쉽게 할 수 있는 방법이 필요해 이를 지원할 수 있는 API들을 2007년 커널 v2.6.21에 제공하였다.

 

디바이스 리소스 관리

 

다음 그림과 같이 디바이스 사용할 때 할당했던 리소스들을 리스트에 기억해두면 나중에 디바이스를 detach할 때 리소스의 할당 해제를 잊지 않고 할 수 있는 장점이 있다. (리소스 리크를 막을 수 있다)

 

  • 디바이스 리소스 추가/해제 API
    • devres_alloc()
    • devres_free()
    • devres_add()
    • devres_find()
    • devres_get()
    • devres_remove()
    • devres_destroy()
    • devres_release()
    • devres_release_all()
    • devres_for_each_res()

 

디바이스 리소스 그룹 관리

 

다음 그림과 같이 디바이스의 동작을 위해 관련 리소스들을 그루핑하여 할당 시도하고 실패할 때 그루핑한 해당 범위의 리소스들을 자동으로 할당 해제할 수 있다.

다음 그림과 같이 네스트된 그루핑도 허용한다.

 

  • 그룹 관련 API
    • devres_open_group()
    • devres_close_group()
    • devres_remove_group()
    • devres_release_group()

 

관리되는 리소스(Managed Resource) APIs

다음과 같이 할당과 관련된 많은 API들이 계속 포함되고 있다.

  • Custom 액션 관련 API
    • devm_add_action()
    • devm_remove_action()
  • Managed kmalloc 할당 및 해제
    • devm_kmalloc()
    • devm_kstrdup()
    • devm_kvasprintf()
    • devm_kasprintf()
    • devm_kmemdup()
    • devm_kzalloc()
    • devm_kmalloc_array()
    • devm_kcalloc()
    • devm_kfree()
  • Managed 페이지 할당
    • devm_get_free_pages()
    • devm_free_pages()
  • IO remap 관련
    • devm_ioremap()
    • devm_ioremap_nocache()
    • devm_ioremap_resource()
    • devm_iounmap()
  • IO port map 관련
    • devm_ioport_map()
    • devm_ioport_unmap()
  • I/O 또는 메모리 할당/해제 관련
    • devm_request_resource()
    • devm_release_resource()
    • devm_request_region()
    • devm_request_mem_region()
    • devm_release_region()
    • devm_release_mem_region()
  • IRQ 관련
  • DMA 관련
    • dmam_alloc_coherent()
    • dmam_free_coherent()
    • dmam_alloc_noncoherent()
    • dmam_free_noncoherent()
    • dmam_declare_coherent_memory()
    • dmam_release_declared_memory()
  • DMA pool 관련
    • dmam_pool_create()
    • dmam_pool_destroy()
  • PCI 관련
    • pcim_enable_device()
    • pcim_pin_device()
    • pcim_release()
  • PCI iomap 관련
    • pcim_iomap()
    • pcim_iounmap()
    • pcim_iomap_table()
    • pcim_iomap_regions()
    • pcim_iomap_regions_request_all()
    • pcim_iounmap_regions()

 

디바이스 리소스 APIs

디바이스 리소스 할당 및 해제

devres_alloc()

drivers/base/devres.c

/**
 * devres_alloc - Allocate device resource data
 * @release: Release function devres will be associated with
 * @size: Allocation size
 * @gfp: Allocation flags
 *
 * Allocate devres of @size bytes.  The allocated area is zeroed, then
 * associated with @release.  The returned pointer can be passed to
 * other devres_*() functions.
 *
 * RETURNS:
 * Pointer to allocated devres on success, NULL on failure.
 */
void * devres_alloc(dr_release_t release, size_t size, gfp_t gfp)
{
        struct devres *dr;

        dr = alloc_dr(release, size, gfp | __GFP_ZERO);
        if (unlikely(!dr))
                return NULL;
        return dr->data;
}
EXPORT_SYMBOL_GPL(devres_alloc);

디바이스 리소스 데이터를 할당하고 반환한다. 실패 시 null을 반환한다.

  • release: 디바이스가 해제될 때 연동할 해제 함수를 지정한다.
  • size: object 할당 사이즈
  • gfp: object 할당 시 사용할 gfp 플래그

 

  • 코드 라인 18에서 디바이스 리소스와 디바이스 리소스 데이터를 할당하고 디바이스 리소스는 0으로 초기화한다.
  • 코드 라인 21에서 디바이스 리소스 데이터를 반환한다.

 

 

devres_free()

drivers/base/devres.c

/**
 * devres_free - Free device resource data
 * @res: Pointer to devres data to free
 *
 * Free devres created with devres_alloc().
 */
void devres_free(void *res)
{
        if (res) {
                struct devres *dr = container_of(res, struct devres, data);

                BUG_ON(!list_empty(&dr->node.entry));
                kfree(dr);
        }
}
EXPORT_SYMBOL_GPL(devres_free);

디바이스 리소스 데이터를 할당 해제한다.

  • 코드 라인 10에서 디바이스 리소스 데이터를 포함하는 디바이스 리소스 dr을 알아온다.
    • res: 디바이스 리소스 데이터
  • 코드 라인 13에서 디바이스 리소스 dr을 할당 해제한다.

 

 

디바이스 리소스 추가

devres_add()

drivers/base/devres.c

/**
 * devres_add - Register device resource
 * @dev: Device to add resource to
 * @res: Resource to register
 *
 * Register devres @res to @dev.  @res should have been allocated
 * using devres_alloc().  On driver detach, the associated release
 * function will be invoked and devres will be freed automatically.
 */
void devres_add(struct device *dev, void *res)
{
        struct devres *dr = container_of(res, struct devres, data);
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);
        add_dr(dev, &dr->node);
        spin_unlock_irqrestore(&dev->devres_lock, flags);
}
EXPORT_SYMBOL_GPL(devres_add);

디바이스에 디바이스 리소스 데이터를 추가한다.

  • 코드 라인 12에서 디바이스 리소스 데이터를 포함하는 디바이스 리소스 dr을 알아온다.
  • 코드 라인 16에서 디바이스에 디바이스 리소스 dr을 추가한다.

 

 

add_dr()

drivers/base/devres.c

static void add_dr(struct device *dev, struct devres_node *node)
{
        devres_log(dev, node, "ADD");
        BUG_ON(!list_empty(&node->entry));
        list_add_tail(&node->entry, &dev->devres_head);
}

디바이스에 디바이스 리소스 데이터를 추가한다.

 

디바이스 리소스 검색

devres_find()

drivers/base/devres.c

/**
 * devres_find - Find device resource
 * @dev: Device to lookup resource from
 * @release: Look for resources associated with this release function
 * @match: Match function (optional)
 * @match_data: Data for the match function
 *
 * Find the latest devres of @dev which is associated with @release
 * and for which @match returns 1.  If @match is NULL, it's considered
 * to match all.
 *
 * RETURNS:
 * Pointer to found devres, NULL if not found.
 */
void * devres_find(struct device *dev, dr_release_t release,
                   dr_match_t match, void *match_data)
{
        struct devres *dr;
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);
        dr = find_dr(dev, release, match, match_data);
        spin_unlock_irqrestore(&dev->devres_lock, flags);

        if (dr)
                return dr->data;
        return NULL;
}
EXPORT_SYMBOL_GPL(devres_find);

디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾아 디바이스 리소스 데이터를 반환한다. 찾지 못한 경우 null을 반환한다.

  • 코드 라인 22에서 디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾는다.
  • 코드 라인 26~27에서 찾은 경우 디바이스 리소스 데이터를 반환한다.
  • 코드 라인 28에서 못 찾은 경우 null을 반환한다.

 

 

find_dr()

drivers/base/devres.c

static struct devres *find_dr(struct device *dev, dr_release_t release,
                              dr_match_t match, void *match_data)
{
        struct devres_node *node;

        list_for_each_entry_reverse(node, &dev->devres_head, entry) {
                struct devres *dr = container_of(node, struct devres, node);

                if (node->release != release)
                        continue;
                if (match && !match(dev, dr->data, match_data))
                        continue;
                return dr;
        }

        return NULL;
}

디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾는다. 찾지못한 경우 null을 반환한다.

  • 코드 라인 6~7에서 디바이스에 등록된 디바이스 리소스 dr을 역방향으로 순회한다.
  • 코드 라인 9~10에서 순회 중인 디바이스 리소스가 인수로 지정한 release 함수를 사용하지 않는 경우 skip 한다.
  • 코드 라인 11~12에서 인수로 match 함수가 주어진 경우 순회 중인 디바이스 리소스의 데이터가 매치되지 않으면 skip 한다.
  • 코드 라인 13에서 디바이스 리소스를 반환한다.
  • 코드 라인 16에서 루프를 돌 때까지 조건에 맞는 디바이스 리소스를 찾지 못한 경우 null을 반환한다.

 

devres_get()

drivers/base/devres.c

/**
 * devres_get - Find devres, if non-existent, add one atomically
 * @dev: Device to lookup or add devres for
 * @new_res: Pointer to new initialized devres to add if not found
 * @match: Match function (optional)
 * @match_data: Data for the match function
 *
 * Find the latest devres of @dev which has the same release function
 * as @new_res and for which @match return 1.  If found, @new_res is
 * freed; otherwise, @new_res is added atomically.
 *
 * RETURNS:
 * Pointer to found or added devres.
 */
void * devres_get(struct device *dev, void *new_res,
                  dr_match_t match, void *match_data)
{
        struct devres *new_dr = container_of(new_res, struct devres, data);
        struct devres *dr;
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);
        dr = find_dr(dev, new_dr->node.release, match, match_data);
        if (!dr) {
                add_dr(dev, &new_dr->node);
                dr = new_dr;
                new_dr = NULL;
        }
        spin_unlock_irqrestore(&dev->devres_lock, flags);
        devres_free(new_dr);

        return dr->data;
}
EXPORT_SYMBOL_GPL(devres_get);

디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾아 디바이스 리소스 데이터를 반환한다. 찾지 못한 경우 요청한 디바이스 리소스 데이터를 추가한다.

  • 코드 라인 18에서 요청한 새 디바이스 리소스 데이터로 디바이스 리소스를 알아와 new_dr에 대입한다.
  • 코드 라인 23에서 디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾는다.
  • 코드 라인 24~28에서 찾지 못한 경우 디바이스에 새 디바이스 리소스를 추가한다. 반환 시 사용할 dr에 추가한 새 디바이스 리소스를 대입한다.
  • 코드 라인 30에서 찾은 경우 새 디바이스 리소스는 할당 해제한다.
  • 코드 라인 32에서 찾았거나 없어서 추가한 디바이스 리소스 데이터를 반환한다.

 

 

디바이스 리소스 할당 해제

devres_remove()

drivers/base/devres.c

/**
 * devres_remove - Find a device resource and remove it
 * @dev: Device to find resource from
 * @release: Look for resources associated with this release function
 * @match: Match function (optional)
 * @match_data: Data for the match function
 *
 * Find the latest devres of @dev associated with @release and for
 * which @match returns 1.  If @match is NULL, it's considered to
 * match all.  If found, the resource is removed atomically and
 * returned.
 *
 * RETURNS:
 * Pointer to removed devres on success, NULL if not found.
 */
void * devres_remove(struct device *dev, dr_release_t release,
                     dr_match_t match, void *match_data)
{
        struct devres *dr;
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);
        dr = find_dr(dev, release, match, match_data);
        if (dr) {
                list_del_init(&dr->node.entry);
                devres_log(dev, &dr->node, "REM");
        }
        spin_unlock_irqrestore(&dev->devres_lock, flags);

        if (dr)
                return dr->data;
        return NULL;
}
EXPORT_SYMBOL_GPL(devres_remove);

디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾아 디바이스에서 등록 해제하고 반환한다. 찾지 못한 경우 null을 반환한다.

  • 코드 라인 23에서 디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾는다.
  • 코드 라인 24~31에서 찾은 경우 디바이스에서 등록 해제한다. 디바이스 리소스 데이터를 반환한다.
  • 코드 라인 32에서 못 찾은 경우 null을 반환한다.

 

 

디바이스 리소스 할당 해제(디바이스 리소스 데이터 포함)

devres_release()

drivers/base/devres.c

/**
 * devres_release - Find a device resource and destroy it, calling release
 * @dev: Device to find resource from
 * @release: Look for resources associated with this release function
 * @match: Match function (optional)
 * @match_data: Data for the match function
 *
 * Find the latest devres of @dev associated with @release and for
 * which @match returns 1.  If @match is NULL, it's considered to
 * match all.  If found, the resource is removed atomically, the
 * release function called and the resource freed.
 *
 * RETURNS:
 * 0 if devres is found and freed, -ENOENT if not found.
 */
int devres_release(struct device *dev, dr_release_t release,
                   dr_match_t match, void *match_data)
{
        void *res;

        res = devres_remove(dev, release, match, match_data);
        if (unlikely(!res))
                return -ENOENT;

        (*release)(dev, res);
        devres_free(res);
        return 0;
}
EXPORT_SYMBOL_GPL(devres_release);

디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾아 디바이스에서 등록 해제하고 할당 해제하고 성공 결과 값으로 0을 반환한다.  찾지 못한 경우 에러 코드 -ENOENT를 반환한다.

  • 코드 라인 21에서 디바이스에 등록된 디바이스 리소스들에서 동일한 release 함수를 사용하고 디바이스 리소스 데이터가 매치되는 디바이스 리소스를 찾는다.
  • 코드 라인 22~23에서 낮은 확률로 찾지 못한 경우 에러 코드 -ENOENT를 반환한다.
  • 코드 라인 25에서 인수로 주어진 release 함수를 호출하여 디바이스 리소스 데이터를 할당 해제 한다.
  • 코드 라인 26~27에서 디바이스 리소스를 할당 해제하고 성공 결과 값으로 0을 반환한다.

 

 

디바이스에 연결된 디바이스 리소스 모두 할당 해제(디바이스 리소스 데이터 포함)

devres_release_all()

drivers/base/devres.c

/**
 * devres_release_all - Release all managed resources
 * @dev: Device to release resources for
 *
 * Release all resources associated with @dev.  This function is
 * called on driver detach.
 */
int devres_release_all(struct device *dev)
{
        unsigned long flags;

        /* Looks like an uninitialized device structure */
        if (WARN_ON(dev->devres_head.next == NULL))
                return -ENODEV;
        spin_lock_irqsave(&dev->devres_lock, flags);
        return release_nodes(dev, dev->devres_head.next, &dev->devres_head,
                             flags);
}

디바이스에 등록된 모든 디바이스 리소스들을 할당 해제한다. 이 함수는 디바이스 드라이버가 detach 되는 경우 호출된다.

  • 코드 라인 13~14에서 디바이스에 등록된 디바이스 리소스가 하나도 없는 경우 경고 메시지를 출력하고 에러 코드 -ENODEV를 반환한다.
  • 코드 라인 16에서 디바이스에 등록된 모든 디바이스 리소스를 할당 해제한다.

 

 

release_nodes()
static int release_nodes(struct device *dev, struct list_head *first,
                         struct list_head *end, unsigned long flags)
        __releases(&dev->devres_lock)
{
        LIST_HEAD(todo);
        int cnt;
        struct devres *dr, *tmp;

        cnt = remove_nodes(dev, first, end, &todo);

        spin_unlock_irqrestore(&dev->devres_lock, flags);

        /* Release.  Note that both devres and devres_group are
         * handled as devres in the following loop.  This is safe.
         */
        list_for_each_entry_safe_reverse(dr, tmp, &todo, node.entry) {
                devres_log(dev, &dr->node, "REL");
                dr->node.release(dev, dr->data);
                kfree(dr);
        }

        return cnt;
}

디바이스에 등록된 first 엔트리부터 end 엔트리 직전 범위의 모든 디바이스 리소스를 할당 해제한다. 제거한 디바이스 리소스 수가 반환된다.

  • 코드 라인 5에서 todo 리스트를 준비해둔다.
  • 코드 라인 9에서 디바이스에 등록된 first 엔트리부터 end 엔트리 직전 범위의 모든 디바이스 리소스를 할당 해제하고 todo 리스트에 추가해둔다.
  • 코드 라인 16~20에서 todo 리스트에 있는 디바이스 리소스를 역방향으로 순회하며 디바이스 리소스 데이터를 할당해제 한 후 디바이스 리소스도 할당 해제한다.
  • 코드 라인 22에서 제거한 디바이스 리소스 수를 반환한다.

 

remove_nodes()

drivers/base/devres.c – 1/2

static int remove_nodes(struct device *dev,
                        struct list_head *first, struct list_head *end,
                        struct list_head *todo)
{
        int cnt = 0, nr_groups = 0;
        struct list_head *cur;

        /* First pass - move normal devres entries to @todo and clear
         * devres_group colors.
         */ 
        cur = first;
        while (cur != end) {
                struct devres_node *node;
                struct devres_group *grp;

                node = list_entry(cur, struct devres_node, entry);
                cur = cur->next;

                grp = node_to_group(node);
                if (grp) {
                        /* clear color of group markers in the first pass */
                        grp->color = 0;
                        nr_groups++;
                } else {
                        /* regular devres entry */
                        if (&node->entry == first)
                                first = first->next; 
                        list_move_tail(&node->entry, todo);
                        cnt++;
                }
        }

        if (!nr_groups)
                return cnt;
  • 코드 라인 11~17에서 first 디바이스 리소스부터 end 직전까지의 디바이스 리소스를 순회하여 node를 알아온다.
  • 코드 라인 19~23에서 node가 그룹 노드인 경우에 한해 그룹을 알아온다.
    • node->release에 지정된 함수가 group_open_release() 또는 group_close_release() 함수 둘 중 하나인 경우 그룹에 소속된 노드이다.
    • 그룹 노드는 디바이스 리소스 데이터 할당이 없으므로 release 함수가 호출되더라도 내부가 blank 되어 아무 일도 하지 않는다.
  • 코드 라인 20~23에서 노드가 그룹에 소속된 경우 그룹의 color 값을 0으로 초기화하고 nr_groups 카운터를 증가시킨다.
    • nr_groups: 발견된 그룹 노드 수
  • 코드 라인 24~30에서 노드가 디바이스 리소스인 경우 노드를 todo 리스트에 옮기고 cnt를 증가시킨다. first로 지정된 노드가 일반 노드인 경우 first를 다음 노드로 갱신한다.
    • cnt: 발견된 일반(devres) 노드 수
  • 코드 라인 33~34에서 그룹 노드가 하나도 발견되지 않은 경우 일반(devres) 노드 수를 반환한다.

 

drivers/base/devres.c – 2/2

        /* Second pass - Scan groups and color them.  A group gets
         * color value of two iff the group is wholly contained in
         * [cur, end).  That is, for a closed group, both opening and
         * closing markers should be in the range, while just the
         * opening marker is enough for an open group.
         */ 
        cur = first;
        while (cur != end) {
                struct devres_node *node;
                struct devres_group *grp;

                node = list_entry(cur, struct devres_node, entry);
                cur = cur->next;

                grp = node_to_group(node);
                BUG_ON(!grp || list_empty(&grp->node[0].entry));

                grp->color++;
                if (list_empty(&grp->node[1].entry))
                        grp->color++;

                BUG_ON(grp->color <= 0 || grp->color > 2);
                if (grp->color == 2) {
                        /* No need to update cur or end.  The removed
                         * nodes are always before both.
                         */
                        list_move_tail(&grp->node[0].entry, todo);
                        list_del_init(&grp->node[1].entry);
                }
        }

        return cnt;
}
  • 코드 라인 7~12에서 first 노드는 첫 그룹 노드가 되었다. first 노드 부터 end 노드 직전까지 디바이스 리소스를 순회한다. 실제로는 일반 노드는 제거하고 그룹 노드를 순회한다.
  • 코드 라인 14~17에서 그룹의 color를 1 증가시킨다. (color=1)
  • 코드 라인 18~19에서 그룹이 아직 close되지 않은 경우 그룹의 color를 1 추가 증가 시킨다. (color=2)
  • 코드 라인 21에서 color값은 1 또는 2가 아닌 경우 경고 메시지를 출력한다.
    • 그룹은 네스트 되어도 상관 없지만 개별 그룹은 각각 open과 close 한 번씩만 사용되어야 한다.
  • 코드 라인 22~28에서 colse 되지 않은 그룹인 경우 open 노드를 todo로 옮기고 close 노드는 그냥 제거한다.
  • 코드 라인 31에서 일반(devres) 노드 수를 반환한다.

 

node_to_group()

drivers/base/devres.c

static struct devres_group * node_to_group(struct devres_node *node)
{
        if (node->release == &group_open_release)
                return container_of(node, struct devres_group, node[0]);
        if (node->release == &group_close_release)
                return container_of(node, struct devres_group, node[1]);
        return NULL;
}

노드가 디바이스 리소스 그룹에 포함된 경우 디바이스 리소스 그룹을 반환한다.

  • 디바이스 리소스 그룹은 다음과 같이 두 개의 노드를 가지고 있다.
    • open 그룹용 노드
    • close 그룹용 노드

 

디바이스 리소스 그룹 APIs

그룹의 open 및 close 마크 처리

devres_open_group()

drivers/base/devres.c

/**
 * devres_open_group - Open a new devres group
 * @dev: Device to open devres group for
 * @id: Separator ID
 * @gfp: Allocation flags
 *
 * Open a new devres group for @dev with @id.  For @id, using a
 * pointer to an object which won't be used for another group is
 * recommended.  If @id is NULL, address-wise unique ID is created.
 *
 * RETURNS:
 * ID of the new group, NULL on failure.
 */
void * devres_open_group(struct device *dev, void *id, gfp_t gfp)
{
        struct devres_group *grp;
        unsigned long flags;

        grp = kmalloc(sizeof(*grp), gfp);
        if (unlikely(!grp))
                return NULL;

        grp->node[0].release = &group_open_release;
        grp->node[1].release = &group_close_release;
        INIT_LIST_HEAD(&grp->node[0].entry);
        INIT_LIST_HEAD(&grp->node[1].entry);
        set_node_dbginfo(&grp->node[0], "grp<", 0);
        set_node_dbginfo(&grp->node[1], "grp>", 0);
        grp->id = grp;
        if (id)
                grp->id = id;

        spin_lock_irqsave(&dev->devres_lock, flags);
        add_dr(dev, &grp->node[0]);
        spin_unlock_irqrestore(&dev->devres_lock, flags);
        return grp->id;
}
EXPORT_SYMBOL_GPL(devres_open_group);

디바이스에 디바이스 리소스 그룹을 open 한다. 그룹의 id를 반환하고, 할당이 실패한 경우 null을 반환한다.

  • 코드 라인 19~21에서 두 개의 노드로 구성된 디바이스 리소스 그룹을 할당한다.
  • 코드 라인 23~28에서 디바이스 리소스 그룹을 초기화한다.
    • 첫 번째 노드는 open 그룹으로 초기화하고, 두 번째 노드는 close 그룹으로  초기화한다.
    • 첫 번째 노드의 release 함수가 group_opon_release() 빈 함수를 가리키고 open 그룹을 식별해낼 수 있게 한다.
    • 두 번째 노드의 release 함수가 group_close_release() 빈 함수를 가리키고 close 그룹을 식별해낼 수 있게 한다.
  • 코드 라인 29~31에서 인수로 id가 지정된 경우 그룹에 id를 지정하고, id가 null로 지정된 경우 자신의 그룹을 가리킨다.
  • 코드 라인 34에서 open 그룹인 첫 번째 노드만 디바이스에 디바이스 리소스로 추가한다.
  • 코드 라인 36에서 그룹의 id를 반환한다.

 

 

devres_close_group()

drivers/base/devres.c

/**
 * devres_close_group - Close a devres group
 * @dev: Device to close devres group for
 * @id: ID of target group, can be NULL
 *
 * Close the group identified by @id.  If @id is NULL, the latest open
 * group is selected.
 */
void devres_close_group(struct device *dev, void *id)
{
        struct devres_group *grp;
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);

        grp = find_group(dev, id);
        if (grp)
                add_dr(dev, &grp->node[1]);
        else
                WARN_ON(1);

        spin_unlock_irqrestore(&dev->devres_lock, flags);
}
EXPORT_SYMBOL_GPL(devres_close_group);

디바이스에서 디바이스 리소스 그룹을 close 한다.

  • 코드 라인 16에서 id에 해당하는 그룹을 찾아온다.
  • 코드 라인 17에서 그룹이 존재하면 그룹에 있는 두 번째 노드를 close 그룹으로 추가한다.

 

 

요청 그룹 삭제(그룹의 디바이스 리소스 제외)

devres_remove_group()

drivers/base/devres.c

/**
 * devres_remove_group - Remove a devres group
 * @dev: Device to remove group for
 * @id: ID of target group, can be NULL
 *
 * Remove the group identified by @id.  If @id is NULL, the latest
 * open group is selected.  Note that removing a group doesn't affect
 * any other resources.
 */
void devres_remove_group(struct device *dev, void *id)
{
        struct devres_group *grp;
        unsigned long flags;

        spin_lock_irqsave(&dev->devres_lock, flags);

        grp = find_group(dev, id);
        if (grp) {
                list_del_init(&grp->node[0].entry);
                list_del_init(&grp->node[1].entry);
                devres_log(dev, &grp->node[0], "REM");
        } else
                WARN_ON(1);

        spin_unlock_irqrestore(&dev->devres_lock, flags);

        kfree(grp);
}
EXPORT_SYMBOL_GPL(devres_remove_group);

디바이스에 등록된 디바이스 리소스들 중 요청한 id에 해당하는 디바이스 리소스 그룹(open 그룹과 close 그룹)만을 할당 해제한다.

  • 코드 라인 17에서 id에 해당하는 그룹을 찾아온다.
  • 코드 라인 18~21에서 그룹이 존재하면 그룹에 있는 첫 번째 노드인 open 그룹과 두 번째 노드인 close 그룹을 제거한다.
  • 코드 라인 27에서 그룹을 할당 해제한다.

 

 

요청 그룹 범위내 디바이스 리소스들 모두 삭제

devres_release_group()

drivers/base/devres.c

/**
 * devres_release_group - Release resources in a devres group
 * @dev: Device to release group for
 * @id: ID of target group, can be NULL
 *
 * Release all resources in the group identified by @id.  If @id is
 * NULL, the latest open group is selected.  The selected group and
 * groups properly nested inside the selected group are removed.
 *
 * RETURNS:
 * The number of released non-group resources.
 */
int devres_release_group(struct device *dev, void *id)
{
        struct devres_group *grp;
        unsigned long flags;
        int cnt = 0;

        spin_lock_irqsave(&dev->devres_lock, flags);

        grp = find_group(dev, id);
        if (grp) {
                struct list_head *first = &grp->node[0].entry;
                struct list_head *end = &dev->devres_head;

                if (!list_empty(&grp->node[1].entry))
                        end = grp->node[1].entry.next;

                cnt = release_nodes(dev, first, end, flags);
        } else {
                WARN_ON(1);
                spin_unlock_irqrestore(&dev->devres_lock, flags);
        }

        return cnt;
}
EXPORT_SYMBOL_GPL(devres_release_group);

디바이스에 등록된 디바이스 리소스들 중 요청한 id에 해당하는 디바이스 리소스 그룹 범위의 모든 디바이스 리소스를 할당 해제 시킨다. 할당 해제한 그룹이 아닌 디바이스 리소스 수를 반환한다.

  • 코드 라인 21에서 id에 해당하는 그룹을 찾아온다.
  • 코드 라인 22~23에서 그룹이 존재하면 그룹에서 open 그룹에 해당하는 첫 번째 노드를 first에 대입한다. 마지막까지 처리하기 위해 리스트의 head를 end에 대입한다.
  • 코드 라인 25~26에서 그룹에서 close 그룹이 존재하는 경우 그룹의 끝까지만 처리하기 위해 end를 close 그룹 다음 노드로 지정한다.
  • 코드 라인 28에서 first ~ end 직전 범위의 모든 그룹을 포함하는 디바이스 리소스를 할당 해제 시킨다.
  • 코드 라인 27에서 할당 해제한 그룹이 아닌 디바이스 리소스 수를 반환한다.

 

 

관리되는 IRQ 리소스 APIs

디바이스 리소스 관리용 IRQ 요청

devm_request_irq()

include/linux/interrupt.h

static inline int __must_check
devm_request_irq(struct device *dev, unsigned int irq, irq_handler_t handler,
                 unsigned long irqflags, const char *devname, void *dev_id)
{
        return devm_request_threaded_irq(dev, irq, handler, NULL, irqflags,
                                         devname, dev_id);
}

관리되는(managed) 디바이스용으로 irq 라인을 할당한다. 성공하는 경우 0을 반환하고, 실패하는 경우 에러 코드 값을 반환한다.

 

devm_request_threaded_irq()

kernel/irq/devres.c

/**
 *      devm_request_threaded_irq - allocate an interrupt line for a managed device
 *      @dev: device to request interrupt for
 *      @irq: Interrupt line to allocate
 *      @handler: Function to be called when the IRQ occurs
 *      @thread_fn: function to be called in a threaded interrupt context. NULL
 *                  for devices which handle everything in @handler
 *      @irqflags: Interrupt type flags
 *      @devname: An ascii name for the claiming device
 *      @dev_id: A cookie passed back to the handler function
 *
 *      Except for the extra @dev argument, this function takes the
 *      same arguments and performs the same function as
 *      request_threaded_irq().  IRQs requested with this function will be
 *      automatically freed on driver detach.
 *
 *      If an IRQ allocated with this function needs to be freed
 *      separately, devm_free_irq() must be used.
 */
int devm_request_threaded_irq(struct device *dev, unsigned int irq,
                              irq_handler_t handler, irq_handler_t thread_fn,
                              unsigned long irqflags, const char *devname,
                              void *dev_id)
{
        struct irq_devres *dr;
        int rc;

        dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
                          GFP_KERNEL);
        if (!dr)
                return -ENOMEM;

        rc = request_threaded_irq(irq, handler, thread_fn, irqflags, devname,
                                  dev_id);
        if (rc) {
                devres_free(dr);
                return rc;
        }

        dr->irq = irq;
        dr->dev_id = dev_id;
        devres_add(dev, dr);

        return 0;
}
EXPORT_SYMBOL(devm_request_threaded_irq);

관리되는(managed) 디바이스용으로 스레디드 irq 라인을 할당한다. 성공하는 경우 0을 반환하고, 실패하는 경우 에러 코드 값을 반환한다.

  • 코드 라인 28~31에서 irq 디바이스 리소스를 할당한다.
    • 첫 번째 release 인수로 주어지는 devm_irq_release() 함수는 irq 라인 할당 해제를 담당한다.
  • 코드 라인 33~38에서 스레디드 irq 라인을 할당 요청한다.
    • thread_fn이 null로 요청된 경우 irq thread를 사용하지 않는다.
  • 코드 라인 40~44에서 할당 요청이 성공한 경우 irq 디바이스 리소스에 irq 번호와 디바이스를 설정하고 요청한 디바이스에 등록한다.
    • 이렇게 등록된 irq 디바이스 리소스는 디바이스가 detach될 때 devres_release_all() 함수를 호출하여 한꺼번에 등록된 모든 디바이스 리소스들을 할당 해제할 수 있다.

 

devm_request_any_context_irq()

kernel/irq/devres.c

/**
 *      devm_request_any_context_irq - allocate an interrupt line for a managed device
 *      @dev: device to request interrupt for
 *      @irq: Interrupt line to allocate
 *      @handler: Function to be called when the IRQ occurs
 *      @thread_fn: function to be called in a threaded interrupt context. NULL
 *                  for devices which handle everything in @handler
 *      @irqflags: Interrupt type flags
 *      @devname: An ascii name for the claiming device
 *      @dev_id: A cookie passed back to the handler function
 *
 *      Except for the extra @dev argument, this function takes the
 *      same arguments and performs the same function as
 *      request_any_context_irq().  IRQs requested with this function will be
 *      automatically freed on driver detach.
 *
 *      If an IRQ allocated with this function needs to be freed
 *      separately, devm_free_irq() must be used.
 */
int devm_request_any_context_irq(struct device *dev, unsigned int irq,
                              irq_handler_t handler, unsigned long irqflags,
                              const char *devname, void *dev_id)
{
        struct irq_devres *dr;
        int rc;

        dr = devres_alloc(devm_irq_release, sizeof(struct irq_devres),
                          GFP_KERNEL);
        if (!dr)
                return -ENOMEM;

        rc = request_any_context_irq(irq, handler, irqflags, devname, dev_id);
        if (rc) {
                devres_free(dr);
                return rc;
        }

        dr->irq = irq;
        dr->dev_id = dev_id;
        devres_add(dev, dr);

        return 0;
}
EXPORT_SYMBOL(devm_request_any_context_irq);

관리되는(managed) 디바이스용으로 context irq 라인을 할당한다. 성공하는 경우 0을 반환하고, 실패하는 경우 에러 코드 값을 반환한다.

  • 코드 라인 27~30에서 irq 디바이스 리소스를 할당한다.
    • 첫 번째 release 인수로 주어지는 devm_irq_release() 함수는 irq 라인 할당 해제를 담당한다.
  • 코드 라인 32~36에서 context irq 라인을 할당 요청한다.
  • 코드 라인 38~42에서 할당 요청이 성공한 경우 irq 디바이스 리소스에 irq 번호와 디바이스를 설정하고 요청한 디바이스에 등록한다.
    • 이렇게 등록된 irq 디바이스 리소스는 디바이스가 detach될 때 devres_release_all() 함수를 호출하여 한꺼번에 등록된 모든 디바이스 리소스들을 할당 해제할 수 있다.

 

devm_irq_release()

kernel/irq/devres.c

static void devm_irq_release(struct device *dev, void *res)
{
        struct irq_devres *this = res;

        free_irq(this->irq, this->dev_id);
}

요청한 irq 디바이스 리소스에 저장된 irq와 dev_id를 사용하여 irq 라인 할당 해제를 수행한다.

 

디바이스 리소스 관리용 IRQ 해제

devm_free_irq()

kernel/irq/devres.c

/**
 *      devm_free_irq - free an interrupt
 *      @dev: device to free interrupt for
 *      @irq: Interrupt line to free
 *      @dev_id: Device identity to free
 *
 *      Except for the extra @dev argument, this function takes the
 *      same arguments and performs the same function as free_irq().
 *      This function instead of free_irq() should be used to manually
 *      free IRQs allocated with devm_request_irq().
 */
void devm_free_irq(struct device *dev, unsigned int irq, void *dev_id)
{
        struct irq_devres match_data = { irq, dev_id };

        WARN_ON(devres_destroy(dev, devm_irq_release, devm_irq_match,
                               &match_data));
        free_irq(irq, dev_id);
}
EXPORT_SYMBOL(devm_free_irq);

요청한 디바이스에서 irq 번호와 dev_id에 해당하는 irq 디바이스 리소스를 찾아 할당 해제한다.

  • 코드 라인 14에서 매치 시킬 irq 번호와 dev_id를 준비한다.
  • 코드 라인 16~17에서 요청한 디바이스에서 irq 번호와 dev_id에 해당하는 irq 디바이스 리소스를 찾아 할당 해제한다.
  • 코드 라인 18에서 irq 라인 할당 해제를 수행한다.

 

devm_irq_match()

kernel/irq/devres.c

 

static int devm_irq_match(struct device *dev, void *res, void *data)
{
        struct irq_devres *this = res, *match = data;

        return this->irq == match->irq && this->dev_id == match->dev_id;
}

irq 번호와 dev_id가 같은지 여부를 반환한다.

 

구조체

devres 구조체

drivers/base/devres.c

struct devres {
        struct devres_node              node;
        /* -- 3 pointers */
        unsigned long long              data[]; /* guarantee ull alignment */
};
  • node
    • 디바이스 리소스 노드
  • data[]
    • managed 리소스에 해당하는 데이터

 

devres_node 구조체

drivers/base/devres.c

struct devres_node {
        struct list_head                entry;
        dr_release_t                    release;
#ifdef CONFIG_DEBUG_DEVRES
        const char                      *name;
        size_t                          size;
#endif
};
  • entry
    • 디바이스에 등록될 노드 엔트리
  • release
    • managed 리소스를 할당 해제할 함수가 지정된다.
  • *name
    • 디버그용 디바이스 리소스 명
  • size
    • 디버그용 managed 리소스 사이즈

 

devres_group 구조체

drivers/base/devres.c

struct devres_group {
        struct devres_node              node[2];
        void                            *id;
        int                             color;
        /* -- 8 pointers */
};
  • node[2]
    • 첫 번째는 open 그룹에 해당하는 디바이스 리소스 노드
    • 두 번째는 close 그룹에 해당하는 디바이스 리소스 노드
  • *id
    • 그룹 식별 id
  • color
    • 할당 해제 시 내부에서 사용할 color 값으로 정상인 경우 0~2의 범위로 사용된다.

 

irq_devres 구조체

kernel/irq/devres.c

/*
 * Device resource management aware IRQ request/free implementation.
 */
struct irq_devres {
        unsigned int irq;
        void *dev_id;
};

 

참고

댓글 남기기