다음 글로 병합:
- Timer -2- (HRTimer) | 문c
<kernel v5.4>
다음은 클럭 코어들의 관리 카운터들이다.
다음 그림은 호출되는 클럭 API와 참조 카운터들을 보여준다.
다음 그림은 디바이스가 클럭을 사용할 때 하이라키로 구성된 클럭들의 각종 참조 카운터들을 보여준다.
drivers/clk/clkdev.c
struct clk *clk_get(struct device *dev, const char *con_id) { const char *dev_id = dev ? dev_name(dev) : NULL; struct clk_hw *hw; if (dev && dev->of_node) { hw = of_clk_get_hw(dev->of_node, 0, con_id); if (!IS_ERR(hw) || PTR_ERR(hw) == -EPROBE_DEFER) return clk_hw_create_clk(dev, hw, dev_id, con_id); } return __clk_get_sys(dev, dev_id, con_id); } EXPORT_SYMBOL(clk_get);
디바이스에서 사용할 클럭을 알아온다. @con_id가 주어진 경우 @con_id 명으로 검색하여 해당 클럭을 찾아오고, 지정되지 않은 경우 연결된 부모 클럭을 알아온다. 그리고 클럭 코어의 참조 카운터를 1 증가시킨다.
drivers/clk/clkdev.c
static struct clk *__clk_get_sys(struct device *dev, const char *dev_id, const char *con_id) { struct clk_hw *hw = clk_find_hw(dev_id, con_id); return clk_hw_create_clk(dev, hw, dev_id, con_id); }
클럭 provider 리스트에서 @con_id 명으로 클럭 hw를 검색하여 해당 클럭 hw에 연결할 클럭을 할당하고 반환한다.
drivers/clk/clkdev.c
struct clk_hw *clk_find_hw(const char *dev_id, const char *con_id) { struct clk_lookup *cl; struct clk_hw *hw = ERR_PTR(-ENOENT); mutex_lock(&clocks_mutex); cl = clk_find(dev_id, con_id); if (cl) hw = cl->clk_hw; mutex_unlock(&clocks_mutex); return hw; }
전역 클럭 리스트에서 @dev_id 및 @con_id 명으로 매치되는 클럭 hw를 반환한다. 검색이 실패한 경우 -ENOENT 에러를 반환한다.
drivers/clk/clkdev.c
/* * Find the correct struct clk for the device and connection ID. * We do slightly fuzzy matching here: * An entry with a NULL ID is assumed to be a wildcard. * If an entry has a device ID, it must match * If an entry has a connection ID, it must match * Then we take the most specific entry - with the following * order of precedence: dev+con > dev only > con only. */
static struct clk_lookup *clk_find(const char *dev_id, const char *con_id) { struct clk_lookup *p, *cl = NULL; int match, best_found = 0, best_possible = 0; if (dev_id) best_possible += 2; if (con_id) best_possible += 1; lockdep_assert_held(&clocks_mutex); list_for_each_entry(p, &clocks, node) { match = 0; if (p->dev_id) { if (!dev_id || strcmp(p->dev_id, dev_id)) continue; match += 2; } if (p->con_id) { if (!con_id || strcmp(p->con_id, con_id)) continue; match += 1; } if (match > best_found) { cl = p; if (match != best_possible) best_found = match; else break; } } return cl; }
전역 클럭 리스트에서 @dev_id 및 @con_id 명으로 클럭을 찾아 clk_lookup을 반환한다. 검색이 실패한 경우 null을 반환한다.
drivers/clk/clkdev.c
void clk_put(struct clk *clk) { __clk_put(clk); } EXPORT_SYMBOL(clk_put);
사용한 클럭을 해제한다. 그리고 사용한 클럭 코어의 참조 카운터를 1 감소시킨다.
drivers/clk/clkdev.c
void __clk_put(struct clk *clk) { struct module *owner; if (!clk || WARN_ON_ONCE(IS_ERR(clk))) return; clk_prepare_lock(); /* * Before calling clk_put, all calls to clk_rate_exclusive_get() from a * given user should be balanced with calls to clk_rate_exclusive_put() * and by that same consumer */ if (WARN_ON(clk->exclusive_count)) { /* We voiced our concern, let's sanitize the situation */ clk->core->protect_count -= (clk->exclusive_count - 1); clk_core_rate_unprotect(clk->core); clk->exclusive_count = 0; } hlist_del(&clk->clks_node); if (clk->min_rate > clk->core->req_rate || clk->max_rate < clk->core->req_rate) clk_core_set_rate_nolock(clk->core, clk->core->req_rate); owner = clk->core->owner; kref_put(&clk->core->ref, __clk_release); clk_prepare_unlock(); module_put(owner); free_clk(clk); }
사용한 클럭을 해제한다. 그리고 사용한 클럭 코어의 참조 카운터를 1 감소시킨다.
drivers/clk/clk.c
/** * clk_prepare - prepare a clock source * @clk: the clk being prepared * * clk_prepare may sleep, which differentiates it from clk_enable. In a simple * case, clk_prepare can be used instead of clk_enable to ungate a clk if the * operation may sleep. One example is a clk which is accessed over I2c. In * the complex case a clk ungate operation may require a fast and a slow part. * It is this reason that clk_prepare and clk_enable are not mutually * exclusive. In fact clk_prepare must be called before clk_enable. * Returns 0 on success, -EERROR otherwise. */
int clk_prepare(struct clk *clk) { if (!clk) return 0; return clk_core_prepare_lock(clk->core); } EXPORT_SYMBOL_GPL(clk_prepare);
상위 클럭 소스까지 prepare 시킨다.
drivers/clk/clk.c
static int clk_core_prepare_lock(struct clk_core *core) { int ret; clk_prepare_lock(); ret = clk_core_prepare(core); clk_prepare_unlock(); return ret; }
lock을 획득한 상태에서 상위 클럭 소스까지 prepare 시킨다.
drivers/clk/clk.c
static int clk_core_prepare(struct clk_core *core) { int ret = 0; lockdep_assert_held(&prepare_lock); if (!core) return 0; if (core->prepare_count == 0) { ret = clk_pm_runtime_get(core); if (ret) return ret; ret = clk_core_prepare(core->parent); if (ret) goto runtime_put; trace_clk_prepare(core); if (core->ops->prepare) ret = core->ops->prepare(core->hw); trace_clk_prepare_complete(core); if (ret) goto unprepare; } core->prepare_count++; /* * CLK_SET_RATE_GATE is a special case of clock protection * Instead of a consumer claiming exclusive rate control, it is * actually the provider which prevents any consumer from making any * operation which could result in a rate change or rate glitch while * the clock is prepared. */ if (core->flags & CLK_SET_RATE_GATE) clk_core_rate_protect(core); return 0; unprepare: clk_core_unprepare(core->parent); runtime_put: clk_pm_runtime_put(core); return ret; }
상위 클럭 소스까지 prepare 시킨다.
drivers/clk/clk.c
/** * clk_unprepare - undo preparation of a clock source * @clk: the clk being unprepared * * clk_unprepare may sleep, which differentiates it from clk_disable. In a * simple case, clk_unprepare can be used instead of clk_disable to gate a clk * if the operation may sleep. One example is a clk which is accessed over * I2c. In the complex case a clk gate operation may require a fast and a slow * part. It is this reason that clk_unprepare and clk_disable are not mutually * exclusive. In fact clk_disable must be called before clk_unprepare. */
void clk_unprepare(struct clk *clk) { if (IS_ERR_OR_NULL(clk)) return; clk_core_unprepare_lock(clk->core); } EXPORT_SYMBOL_GPL(clk_unprepare);
상위 클럭 소스까지 unprepare 시킨다
drivers/clk/clk.c
static void clk_core_unprepare_lock(struct clk_core *core) { clk_prepare_lock(); clk_core_unprepare(core); clk_prepare_unlock(); }
lock을 획득한 상태에서 상위 클럭 소스까지 unprepare 시킨다
drivers/clk/clk.c
static void clk_core_unprepare(struct clk_core *core) { lockdep_assert_held(&prepare_lock); if (!core) return; if (WARN(core->prepare_count == 0, "%s already unprepared\n", core->name)) return; if (WARN(core->prepare_count == 1 && core->flags & CLK_IS_CRITICAL, "Unpreparing critical %s\n", core->name)) return; if (core->flags & CLK_SET_RATE_GATE) clk_core_rate_unprotect(core); if (--core->prepare_count > 0) return; WARN(core->enable_count > 0, "Unpreparing enabled %s\n", core->name); trace_clk_unprepare(core); if (core->ops->unprepare) core->ops->unprepare(core->hw); clk_pm_runtime_put(core); trace_clk_unprepare_complete(core); clk_core_unprepare(core->parent); }
상위 클럭 소스까지 unprepare 시킨다.
drivers/clk/clk.c
/** * clk_enable - ungate a clock * @clk: the clk being ungated * * clk_enable must not sleep, which differentiates it from clk_prepare. In a * simple case, clk_enable can be used instead of clk_prepare to ungate a clk * if the operation will never sleep. One example is a SoC-internal clk which * is controlled via simple register writes. In the complex case a clk ungate * operation may require a fast and a slow part. It is this reason that * clk_enable and clk_prepare are not mutually exclusive. In fact clk_prepare * must be called before clk_enable. Returns 0 on success, -EERROR * otherwise. */
int clk_enable(struct clk *clk) { if (!clk) return 0; return clk_core_enable_lock(clk->core); } EXPORT_SYMBOL_GPL(clk_enable);
상위 클럭 소스까지 gate를 enable 시킨다.
drivers/clk/clk.c
static int clk_core_enable_lock(struct clk_core *core) { unsigned long flags; int ret; flags = clk_enable_lock(); ret = clk_core_enable(core); clk_enable_unlock(flags); return ret; }
lock을 획득한 상태에서 상위 클럭 소스까지 gate를 enable 시킨다.
drivers/clk/clk.c
static int clk_core_enable(struct clk_core *core) { int ret = 0; lockdep_assert_held(&enable_lock); if (!core) return 0; if (WARN(core->prepare_count == 0, "Enabling unprepared %s\n", core->name)) return -ESHUTDOWN; if (core->enable_count == 0) { ret = clk_core_enable(core->parent); if (ret) return ret; trace_clk_enable_rcuidle(core); if (core->ops->enable) ret = core->ops->enable(core->hw); trace_clk_enable_complete_rcuidle(core); if (ret) { clk_core_disable(core->parent); return ret; } } core->enable_count++; return 0; }
상위 클럭 소스까지 gate를 enable 시킨다.
drivers/clk/clk.c
/** * clk_disable - gate a clock * @clk: the clk being gated * * clk_disable must not sleep, which differentiates it from clk_unprepare. In * a simple case, clk_disable can be used instead of clk_unprepare to gate a * clk if the operation is fast and will never sleep. One example is a * SoC-internal clk which is controlled via simple register writes. In the * complex case a clk gate operation may require a fast and a slow part. It is * this reason that clk_unprepare and clk_disable are not mutually exclusive. * In fact clk_disable must be called before clk_unprepare. */
void clk_disable(struct clk *clk) { if (IS_ERR_OR_NULL(clk)) return; clk_core_disable_lock(clk->core); } EXPORT_SYMBOL_GPL(clk_disable);
상위 클럭 소스까지 gate를 disable 시킨다.
drivers/clk/clk.c
static void clk_core_disable_lock(struct clk_core *core) { unsigned long flags; flags = clk_enable_lock(); clk_core_disable(core); clk_enable_unlock(flags); }
lock을 획득한 상태에서 상위 클럭 소스까지 gate를 disable 시킨다.
drivers/clk/clk.c
static void clk_core_disable(struct clk_core *core) { lockdep_assert_held(&enable_lock); if (!core) return; if (WARN(core->enable_count == 0, "%s already disabled\n", core->name)) return; if (WARN(core->enable_count == 1 && core->flags & CLK_IS_CRITICAL, "Disabling critical %s\n", core->name)) return; if (--core->enable_count > 0) return; trace_clk_disable_rcuidle(core); if (core->ops->disable) core->ops->disable(core->hw); trace_clk_disable_complete_rcuidle(core); clk_core_disable(core->parent); }
상위 클럭 소스까지 gate를 disable 시킨다.
include/linux/clk-provider.h
/** * struct clk_rate_request - Structure encoding the clk constraints that * a clock user might require. * * @rate: Requested clock rate. This field will be adjusted by * clock drivers according to hardware capabilities. * @min_rate: Minimum rate imposed by clk users. * @max_rate: Maximum rate imposed by clk users. * @best_parent_rate: The best parent rate a parent can provide to fulfill the * requested constraints. * @best_parent_hw: The most appropriate parent clock that fulfills the * requested constraints. * */
struct clk_rate_request { unsigned long rate; unsigned long min_rate; unsigned long max_rate; unsigned long best_parent_rate; struct clk_hw *best_parent_hw; };
rate에 관련한 API 사용 시 인자 수를 줄이기 위해 사용되는 구조체이다.
다음 그림은 clk_set_rate() 함수가 rate를 변경하기 위해 호출하는 4 단계의 주요 함수만을 보여준다.
drivers/clk/clk.c
/** * clk_set_rate - specify a new rate for clk * @clk: the clk whose rate is being changed * @rate: the new rate for clk * * In the simplest case clk_set_rate will only adjust the rate of clk. * * Setting the CLK_SET_RATE_PARENT flag allows the rate change operation to * propagate up to clk's parent; whether or not this happens depends on the * outcome of clk's .round_rate implementation. If *parent_rate is unchanged * after calling .round_rate then upstream parent propagation is ignored. If * *parent_rate comes back with a new rate for clk's parent then we propagate * up to clk's parent and set its rate. Upward propagation will continue * until either a clk does not support the CLK_SET_RATE_PARENT flag or * .round_rate stops requesting changes to clk's parent_rate. * * Rate changes are accomplished via tree traversal that also recalculates the * rates for the clocks and fires off POST_RATE_CHANGE notifiers. * * Returns 0 on success, -EERROR otherwise. */
int clk_set_rate(struct clk *clk, unsigned long rate) { int ret; if (!clk) return 0; /* prevent racing with updates to the clock topology */ clk_prepare_lock(); if (clk->exclusive_count) clk_core_rate_unprotect(clk->core); ret = clk_core_set_rate_nolock(clk->core, rate); if (clk->exclusive_count) clk_core_rate_protect(clk->core); clk_prepare_unlock(); return ret; } EXPORT_SYMBOL_GPL(clk_set_rate);
클럭 rate를 변경 요청한다.
drivers/clk/clk.c
static int clk_core_set_rate_nolock(struct clk_core *core, unsigned long req_rate) { struct clk_core *top, *fail_clk; unsigned long rate = req_rate; int ret = 0; if (!core) return 0; rate = clk_core_req_round_rate_nolock(core, req_rate); /* bail early if nothing to do */ if (rate == clk_core_get_rate_nolock(core) return 0; /* fail on a direct rate set of a protected provider */ if (clk_core_rate_is_protected(core)) return -EBUSY; /* calculate new rates and get the topmost changed clock */ top = clk_calc_new_rates(core, rate); if (!top) return -EINVAL; /* notify that we are about to change rates */ fail_clk = clk_propagate_rate_change(top, PRE_RATE_CHANGE); if (fail_clk) { pr_debug("%s: failed to set %s rate\n", __func__, fail_clk->name); clk_propagate_rate_change(top, ABORT_RATE_CHANGE); return -EBUSY; } /* change the rates */ clk_change_rate(top); clk->req_rate = req_rate; err: clk_pm_runtime_put(core); return ret; }
클럭의 rate 설정을 한다. 성공 시에 0을 반환한다. 적용 시 현재 클럭 curr에서 변경이 필요한 클럭 코어 top까지 상위로 이동하면서 new rate를 산출하고, top 클럭에서 관련 클럭들의 최하위 클럭 bottom까지 rate를 재산출한다.
다음 그림은 클럭 F에서 rate를 바꿀 때 클럭 F->G까지 어떠한 단계로 바뀌는지 그 과정을 보여준다. (성공 예)
다음 그림은 위의 방법으로 실패 사례를 보여준다.
best rate 산출과 관련한 API들은 다음과 같다.
drivers/clk/clk.c
/** * clk_round_rate - round the given rate for a clk * @clk: the clk for which we are rounding a rate * @rate: the rate which is to be rounded * * Takes in a rate as input and rounds it to a rate that the clk can actually * use which is then returned. If clk doesn't support round_rate operation * then the parent rate is returned. */
long clk_round_rate(struct clk *clk, unsigned long rate) { struct clk_rate_request req; int ret; if (!clk) return 0; clk_prepare_lock(); if (clk->exclusive_count) clk_core_rate_unprotect(clk->core); clk_core_get_boundaries(clk->core, &req.min_rate, &req.max_rate); req.rate = rate; ret = clk_core_round_rate_nolock(clk->core, &req); if (clk->exclusive_count) clk_core_rate_protect(clk->core); clk_prepare_unlock(); if (ret) return ret; return req.rate; } EXPORT_SYMBOL_GPL(clk_round_rate);
요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 클럭이 (*round_rate) ops를 지원하지 않으면 부모 클럭의 rate 값이 반환된다.
drivers/clk/clk.c
/** * __clk_round_rate - round the given rate for a clk * @clk: round the rate of this clock * @rate: the rate which is to be rounded * * Caller must hold prepare_lock. Useful for clk_ops such as .set_rate */ unsigned long __clk_round_rate(struct clk *clk, unsigned long rate) { unsigned long min_rate; unsigned long max_rate; if (!clk) return 0; clk_core_get_boundaries(clk->core, &min_rate, &max_rate); return clk_core_round_rate_nolock(clk->core, rate, min_rate, max_rate); } EXPORT_SYMBOL_GPL(__clk_round_rate);
요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 만일 클럭이 (*round_rate) ops를 지원하지 않으면 부모 클럭의 rate 값이 반환된다. 호출 시 prepare_lock을 잡아야 한다.
drivers/clk/clk.c
unsigned long clk_hw_round_rate(struct clk_hw *hw, unsigned long rate) { int ret; struct clk_rate_request req; clk_core_get_boundaries(hw->core, &req.min_rate, &req.max_rate); req.rate = rate; ret = clk_core_round_rate_nolock(hw->core, &req); if (ret) return 0; return req.rate; } EXPORT_SYMBOL_GPL(clk_hw_round_rate);
요청 @rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 반환한다. 실패한 경우 0을 반환한다.
drivers/clk/clk.c
/** * __clk_determine_rate - get the closest rate actually supported by a clock * @hw: determine the rate of this clock * @req: target rate request * * Useful for clk_ops such as .set_rate and .determine_rate. */
int __clk_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { if (!hw) { req->rate = 0; return 0; } return clk_core_round_rate_nolock(hw->core, req); } EXPORT_SYMBOL_GPL(__clk_determine_rate);
요청 rate에 대해 클럭 hw가 지원하는 가장 근접한 rate를 찾아 req->rate에 담아온다. 성공 시 0을 반환한다.
drivers/clk/clk.c
static unsigned long clk_core_req_round_rate_nolock(struct clk_core *core, unsigned long req_rate) { int ret, cnt; struct clk_rate_request req; lockdep_assert_held(&prepare_lock); if (!core) return 0; /* simulate what the rate would be if it could be freely set */ cnt = clk_core_rate_nuke_protect(core); if (cnt < 0) return cnt; clk_core_get_boundaries(core, &req.min_rate, &req.max_rate); req.rate = req_rate; ret = clk_core_round_rate_nolock(core, &req); /* restore the protection */ clk_core_rate_restore_protect(core, cnt); return ret ? 0 : req.rate; }
클럭 hw가 지원하는 @req_rate에 가장 근접한 rate를 반환한다. 실패한 경우 0을 반환한다.
다음 그림은 rate 변경이 필요한지 유무를 판단하기 위해 호출하는 과정을 보여준다.
drivers/clk/clk.c
static void clk_core_get_boundaries(struct clk_core *core, unsigned long *min_rate, unsigned long *max_rate) { struct clk *clk_user; lockdep_assert_held(&prepare_lock); *min_rate = core->min_rate; *max_rate = core->max_rate; hlist_for_each_entry(clk_user, &core->clks, clks_node) *min_rate = max(*min_rate, clk_user->min_rate); hlist_for_each_entry(clk_user, &core->clks, clks_node) *max_rate = min(*max_rate, clk_user->max_rate); }
자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아온다.
다음 그림은 자식 클럭들로부터 min_rate 및 max_rate 바운더리 값을 알아오는 것을 보여준다. (min_rate가 max_rate 보다 큰 숫자임을 주의한다)
연속하여 다음 두 함수를 알아본다.
drivers/clk/clk.c
static int clk_core_round_rate_nolock(struct clk_core *core, struct clk_rate_request *req) { lockdep_assert_held(&prepare_lock); if (!core) { req->rate = 0; return 0; } clk_core_init_rate_req(core, req); if (clk_core_can_round(core)) return clk_core_determine_round_nolock(core, req); else if (core->flags & CLK_SET_RATE_PARENT) return clk_core_round_rate_nolock(core->parent, req); req->rate = core->rate; return 0; }
rate 요청에 대해 클럭 hw가 지원하는 가장 근접한 rate를 찾아 req->rate에 담고, 성공 시 0을 반환한다.
drivers/clk/clk.c
static int clk_core_determine_round_nolock(struct clk_core *core, struct clk_rate_request *req) { long rate; lockdep_assert_held(&prepare_lock); if (!core) return 0; /* * At this point, core protection will be disabled if * - if the provider is not protected at all * - if the calling consumer is the only one which has exclusivity * over the provider */ if (clk_core_rate_is_protected(core)) { req->rate = core->rate; } else if (core->ops->determine_rate) { return core->ops->determine_rate(core->hw, req); } else if (core->ops->round_rate) { rate = core->ops->round_rate(core->hw, req->rate, &req->best_parent_rate); if (rate < 0) return rate; req->rate = rate; } else { return -EINVAL; } return 0; }
rate 요청에 대해 클럭 hw가 지원하는 가장 근접한 rate를 찾아 req->rate에 담고, 성공 시 0을 반환한다.
drivers/clk/clk.c
static void clk_core_init_rate_req(struct clk_core * const core, struct clk_rate_request *req) { struct clk_core *parent; if (WARN_ON(!core || !req)) return; parent = core->parent; if (parent) { req->best_parent_hw = parent->hw; req->best_parent_rate = parent->rate; } else { req->best_parent_hw = NULL; req->best_parent_rate = 0; } }
현재 부모 클럭 및 rate로 best 결과를 초기화한다.
drivers/clk/clk.c
static bool clk_core_can_round(struct clk_core * const core) { return core->ops->determine_rate || core->ops->round_rate; }
round & determine rate를 지원하는 클럭인지 여부를 반환한다.
drivers/clk/clk.c
/* * calculate the new rates returning the topmost clock that has to be * changed. */
static struct clk_core *clk_calc_new_rates(struct clk_core *core, unsigned long rate) { struct clk_core *top = core; struct clk_core *old_parent, *parent; unsigned long best_parent_rate = 0; unsigned long new_rate; unsigned long min_rate; unsigned long max_rate; int p_index = 0; long ret; /* sanity */ if (IS_ERR_OR_NULL(core)) return NULL; /* save parent rate, if it exists */ parent = old_parent = core->parent; if (parent) best_parent_rate = parent->rate; clk_core_get_boundaries(core, &min_rate, &max_rate); /* find the closest rate and parent clk/rate */ if (clk_core_can_round(core)) { struct clk_rate_request req; req.rate = rate; req.min_rate = min_rate; req.max_rate = max_rate; clk_core_init_rate_req(core, &req); ret = clk_core_determine_round_nolock(core, &req); if (ret < 0) return NULL; best_parent_rate = req.best_parent_rate; new_rate = req.rate; parent = req.best_parent_hw ? req.best_parent_hw->core : NULL; if (new_rate < min_rate || new_rate > max_rate) return NULL; } else if (!parent || !(core->flags & CLK_SET_RATE_PARENT)) { /* pass-through clock without adjustable parent */ core->new_rate = core->rate; return NULL; } else { /* pass-through clock with adjustable parent */ top = clk_calc_new_rates(parent, rate); new_rate = parent->new_rate; goto out; } /* some clocks must be gated to change parent */ if (parent != old_parent && (core->flags & CLK_SET_PARENT_GATE) && core->prepare_count) { pr_debug("%s: %s not gated but wants to reparent\n", __func__, core->name); return NULL; } /* try finding the new parent index */ if (parent && core->num_parents > 1) { p_index = clk_fetch_parent_index(core, parent); if (p_index < 0) { pr_debug("%s: clk %s can not be parent of clk %s\n", __func__, parent->name, core->name); return NULL; } } if ((core->flags & CLK_SET_RATE_PARENT) && parent && best_parent_rate != parent->rate) top = clk_calc_new_rates(parent, best_parent_rate); out: clk_calc_subtree(core, new_rate, parent, p_index); return top; }
현재 클럭 코어로부터 변경이 필요한 상위 클럭까지 new rate를 산출하고, new rate 산출된 최상위 클럭 코어를 반환한다.
다음 그림은 요청한 rate에 대해 클럭 hw가 지원하는 가장 근접한 new rate를 산출하는 과정을 보여준다.
다음 그림은 클럭 F에서 rate를 바꾸고자 계산하는 경우 클럭 F->D까지 rate를 산출하고 다시 클럭 D->G까지 재계산하는 과정을 보여준다.
drivers/clk/clk.c
static int clk_fetch_parent_index(struct clk_core *core, struct clk_core *parent) { int i; if (!parent) return -EINVAL; for (i = 0; i < core->num_parents; i++) { /* Found it first try! */ if (core->parents[i].core == parent) return i; /* Something else is here, so keep looking */ if (core->parents[i].core) continue; /* Maybe core hasn't been cached but the hw is all we know? */ if (core->parents[i].hw) { if (core->parents[i].hw == parent->hw) break; /* Didn't match, but we're expecting a clk_hw */ continue; } /* Maybe it hasn't been cached (clk_set_parent() path) */ if (parent == clk_core_get(core, i)) break; /* Fallback to comparing globally unique names */ if (core->parents[i].name && !strcmp(parent->name, core->parents[i].name)) break; } if (i == core->num_parents) return -EINVAL; core->parents[i].core = parent; return i; }
현재 클럭 코어 @core의 부모 @parent 클럭 코어에 해당하는 인덱스 값을 알아온다. 실패하는 경우 음수 에러가 반환된다.
drivers/clk/clk.c
/* * Notify about rate changes in a subtree. Always walk down the whole tree * so that in case of an error we can walk down the whole tree again and * abort the change. */
static struct clk_core *clk_propagate_rate_change(struct clk_core *core, unsigned long event) { struct clk_core *child, *tmp_clk, *fail_clk = NULL; int ret = NOTIFY_DONE; if (core->rate == core->new_rate) return NULL; if (core->notifier_count) { ret = __clk_notify(core, event, core->rate, core->new_rate); if (ret & NOTIFY_STOP_MASK) fail_clk = core; } hlist_for_each_entry(child, &core->children, child_node) { /* Skip children who will be reparented to another clock */ if (child->new_parent && child->new_parent != core) continue; tmp_clk = clk_propagate_rate_change(child, event); if (tmp_clk) fail_clk = tmp_clk; } /* handle the new child who might not be in core->children yet */ if (core->new_child) { tmp_clk = clk_propagate_rate_change(core->new_child, event); if (tmp_clk) fail_clk = tmp_clk; } return fail_clk; }
요청 클럭 코어부터 연결된 모든 하위 트리의 클럭 코어에 rate 변화를 통지한다. 성공 시 null을 반환하고, 실패하는 경우 실패한 클럭 코어를 반환한다.
다음 그림은 new rate로 변경하기 전에 통지 체크할 클럭에 확인하는 과정을 보여준다.
drivers/clk/clk.c
/** * __clk_notify - call clk notifier chain * @core: clk that is changing rate * @msg: clk notifier type (see include/linux/clk.h) * @old_rate: old clk rate * @new_rate: new clk rate * * Triggers a notifier call chain on the clk rate-change notification * for 'clk'. Passes a pointer to the struct clk and the previous * and current rates to the notifier callback. Intended to be called by * internal clock code only. Returns NOTIFY_DONE from the last driver * called if all went well, or NOTIFY_STOP or NOTIFY_BAD immediately if * a driver returns that. */
static int __clk_notify(struct clk_core *core, unsigned long msg, unsigned long old_rate, unsigned long new_rate) { struct clk_notifier *cn; struct clk_notifier_data cnd; int ret = NOTIFY_DONE; cnd.old_rate = old_rate; cnd.new_rate = new_rate; list_for_each_entry(cn, &clk_notifier_list, node) { if (cn->clk->core == core) { cnd.clk = cn->clk; ret = srcu_notifier_call_chain(&cn->notifier_head, msg, &cnd); if (ret & NOTIFY_STOP_MASK) return ret; } } return ret; }
클럭 통지 리스트에 등록된 현재 클럭의 notifier 체인에 연결된 항목에 대해 srcu를 사용하여 모두 통지한다. 성공한 경우 NOTIFY_DONE을 반환한다.
drivers/clk/clk.c
/*** clk rate change notifiers ***/ /** * clk_notifier_register - add a clk rate change notifier * @clk: struct clk * to watch * @nb: struct notifier_block * with callback info * * Request notification when clk's rate changes. This uses an SRCU * notifier because we want it to block and notifier unregistrations are * uncommon. The callbacks associated with the notifier must not * re-enter into the clk framework by calling any top-level clk APIs; * this will cause a nested prepare_lock mutex. * * In all notification cases cases (pre, post and abort rate change) the * original clock rate is passed to the callback via struct * clk_notifier_data.old_rate and the new frequency is passed via struct * clk_notifier_data.new_rate. * * clk_notifier_register() must be called from non-atomic context. * Returns -EINVAL if called with null arguments, -ENOMEM upon * allocation failure; otherwise, passes along the return value of * srcu_notifier_chain_register(). */
int clk_notifier_register(struct clk *clk, struct notifier_block *nb) { struct clk_notifier *cn; int ret = -ENOMEM; if (!clk || !nb) return -EINVAL; clk_prepare_lock(); /* search the list of notifiers for this clk */ list_for_each_entry(cn, &clk_notifier_list, node) if (cn->clk == clk) break; /* if clk wasn't in the notifier list, allocate new clk_notifier */ if (cn->clk != clk) { cn = kzalloc(sizeof(*cn), GFP_KERNEL); if (!cn) goto out; cn->clk = clk; srcu_init_notifier_head(&cn->notifier_head); list_add(&cn->node, &clk_notifier_list); } ret = srcu_notifier_chain_register(&cn->notifier_head, nb); clk->core->notifier_count++; out: clk_prepare_unlock(); return ret; } EXPORT_SYMBOL_GPL(clk_notifier_register);
클럭의 notify chain에 nofitier_block을 등록한다.
drivers/clk/clk.c -1/2-
/* * walk down a subtree and set the new rates notifying the rate * change on the way */
static void clk_change_rate(struct clk_core *core) { struct clk_core *child; struct hlist_node *tmp; unsigned long old_rate; unsigned long best_parent_rate = 0; bool skip_set_rate = false; struct clk_core *old_parent; struct clk_core *parent = NULL; old_rate = core->rate; if (core->new_parent) { parent = core->new_parent; best_parent_rate = core->new_parent->rate; } else if (core->parent) { parent = core->parent; best_parent_rate = core->parent->rate; } if (clk_pm_runtime_get(core)) return; if (core->flags & CLK_SET_RATE_UNGATE) { unsigned long flags; clk_core_prepare(core); flags = clk_enable_lock(); clk_core_enable(core); clk_enable_unlock(flags); } if (core->new_parent && core->new_parent != core->parent) { old_parent = __clk_set_parent_before(core, core->new_parent); trace_clk_set_parent(core, core->new_parent); if (core->ops->set_rate_and_parent) { skip_set_rate = true; core->ops->set_rate_and_parent(core->hw, core->new_rate, best_parent_rate, core->new_parent_index); } else if (core->ops->set_parent) { core->ops->set_parent(core->hw, core->new_parent_index); } trace_clk_set_parent_complete(core, core->new_parent); __clk_set_parent_after(core, core->new_parent, old_parent); } if (core->flags & CLK_OPS_PARENT_ENABLE) clk_core_prepare_enable(parent); trace_clk_set_rate(core, core->new_rate); if (!skip_set_rate && core->ops->set_rate) core->ops->set_rate(core->hw, core->new_rate, best_parent_rate); trace_clk_set_rate_complete(core, core->new_rate);
요청한 클럭 코어부터 마지막 자식 클럭 코어까지 산출된 new rate 및 new 부모 클럭을 적용하고 통지한다.
drivers/clk/clk.c -2/2-
core->rate = clk_recalc(core, best_parent_rate); if (core->flags & CLK_SET_RATE_UNGATE) { unsigned long flags; flags = clk_enable_lock(); clk_core_disable(core); clk_enable_unlock(flags); clk_core_unprepare(core); } if (core->flags & CLK_OPS_PARENT_ENABLE) clk_core_disable_unprepare(parent); if (core->notifier_count && old_rate != core->rate) __clk_notify(core, POST_RATE_CHANGE, old_rate, core->rate); if (core->flags & CLK_RECALC_NEW_RATES) (void)clk_calc_new_rates(core, core->new_rate); /* * Use safe iteration, as change_rate can actually swap parents * for certain clock types. */ hlist_for_each_entry_safe(child, tmp, &core->children, child_node) { /* Skip children who will be reparented to another clock */ if (child->new_parent && child->new_parent != core) continue; clk_change_rate(child); } /* handle the new child who might not be in core->children yet */ if (core->new_child) clk_change_rate(core->new_child); clk_pm_runtime_put(core); }
다음 그림은 최종 산출된 new rate를 결정 또는 취소할 때 호출되는 과정을 보여준다.
drivers/clk/clk.c
static void clk_calc_subtree(struct clk_core *core, unsigned long new_rate, struct clk_core *new_parent, u8 p_index) { struct clk_core *child; core->new_rate = new_rate; core->new_parent = new_parent; core->new_parent_index = p_index; /* include clk in new parent's PRE_RATE_CHANGE notifications */ core->new_child = NULL; if (new_parent && new_parent != core->parent) new_parent->new_child = core; hlist_for_each_entry(child, &core->children, child_node) { child->new_rate = clk_recalc(child, new_rate); clk_calc_subtree(child, child->new_rate, NULL, 0); } }
현재 클럭 코어 및 모든 연결된 하위 클럭 코어들에 대해 새 rate, 새 부모, 새 부모 등을 갱신하게 한다.
drivers/clk/clk.c
static unsigned long clk_recalc(struct clk_core *core, unsigned long parent_rate) { unsigned long rate = parent_rate; if (core->ops->recalc_rate && !clk_pm_runtime_get(core)) { rate = core->ops->recalc_rate(core->hw, parent_rate); clk_pm_runtime_put(core); } return rate; }
@parent_rate 값을 사용하여 클럭 hw의 (*recalc_rate) ops를 호출하여 현재 클럭 코어의 재계산한 rate를 반환한다.
drivers/clk/clk.c
/** * clk_get_rate - return the rate of clk * @clk: the clk whose rate is being returned * * Simply returns the cached rate of the clk, unless CLK_GET_RATE_NOCACHE flag * is set, which means a recalc_rate will be issued. * If clk is NULL then returns 0. */
unsigned long clk_get_rate(struct clk *clk) { if (!clk) return 0; return clk_core_get_rate(clk->core); } EXPORT_SYMBOL_GPL(clk_get_rate);
클럭의 rate 값을 반환한다.
drivers/clk/clk.c
static unsigned long clk_core_get_rate(struct clk_core *core) { unsigned long rate; clk_prepare_lock(); if (core && (core->flags & CLK_GET_RATE_NOCACHE)) __clk_recalc_rates(core, 0); rate = clk_core_get_rate_nolock(core); clk_prepare_unlock(); return rate; }
클럭 코어의 rate 값을 반환한다.
drivers/clk/clk.c
/** * __clk_recalc_rates * @clk: first clk in the subtree * @msg: notification type (see include/linux/clk.h) * * Walks the subtree of clks starting with clk and recalculates rates as it * goes. Note that if a clk does not implement the .recalc_rate callback then * it is assumed that the clock will take on the rate of its parent. * * clk_recalc_rates also propagates the POST_RATE_CHANGE notification, * if necessary. */ static void __clk_recalc_rates(struct clk_core *core, unsigned long msg) { unsigned long old_rate; unsigned long parent_rate = 0; struct clk_core *child; old_rate = core->rate; if (core->parent) parent_rate = core->parent->rate; core->rate = clk_recalc(core, parent_rate); /* * ignore NOTIFY_STOP and NOTIFY_BAD return values for POST_RATE_CHANGE * & ABORT_RATE_CHANGE notifiers */ if (core->notifier_count && msg) __clk_notify(core, msg, old_rate, core->rate); hlist_for_each_entry(child, &core->children, child_node) __clk_recalc_rates(child, msg); }
요청한 클럭 코어 및 모든 연결된 하위 클럭 코어들에 대해 rate를 재산출하여 갱신한다. 그리고 부모 클럭이 통지가 필요한 클럭 코어들에 @msg를 전달한다.
drivers/clk/clk.c
static unsigned long clk_core_get_rate_nolock(struct clk_core *clk) { if (!core) return 0; if (!core->num_parents || core->parent) return core->rate; /* * Clk must have a parent because num_parents > 0 but the parent isn't * known yet. Best to return 0 as the rate of this clk until we can * properly recalc the rate based on the parent's rate. */ return 0; }
클럭 코어의 rate 값을 반환한다.
부모 클럭을 변경한다는 것은 입력 클럭 소스가 바뀐다는 의미이고 gate된 상태가 아닌 상태에서 실시간으로 변경하는 경우 glitch가 발생됨을 유의해야 한다. glitch를 방지하려면 클럭 gate를 닫고 변경한 후 클럭 gate를 열어야야 한다. 클럭 코어에 CLK_SET_PARENT_GATE 플래그를 사용하면 gate된 상태에서 부모 클럭을 변경할 수 없게 할 수 있다.
다음 그림은 clk_set_parent() 함수 이후의 호출 관계를 보여준다.
drivers/clk/clk.c
/** * clk_set_parent - switch the parent of a mux clk * @clk: the mux clk whose input we are switching * @parent: the new input to clk * * Re-parent clk to use parent as its new input source. If clk is in * prepared state, the clk will get enabled for the duration of this call. If * that's not acceptable for a specific clk (Eg: the consumer can't handle * that, the reparenting is glitchy in hardware, etc), use the * CLK_SET_PARENT_GATE flag to allow reparenting only when clk is unprepared. * * After successfully changing clk's parent clk_set_parent will update the * clk topology, sysfs topology and propagate rate recalculation via * __clk_recalc_rates. * * Returns 0 on success, -EERROR otherwise. */
int clk_set_parent(struct clk *clk, struct clk *parent) { int ret; if (!clk) return 0; clk_prepare_lock(); if (clk->exclusive_count) clk_core_rate_unprotect(clk->core); ret = clk_core_set_parent_nolock(clk->core, parent ? parent->core : NULL); if (clk->exclusive_count) clk_core_rate_protect(clk->core); clk_prepare_unlock(); return ret; } EXPORT_SYMBOL_GPL(clk_set_parent);
부모 클럭 코어를 선택한다. 성공 시 클럭 topology가 변경되며 rate 재산출이 일어난다. 성공 시 0을 반환한다.
drivers/clk/clk.c
static int clk_core_set_parent_nolock(struct clk_core *core, struct clk_core *parent) { int ret = 0; int p_index = 0; unsigned long p_rate = 0; lockdep_assert_held(&prepare_lock); if (!core) return 0; if (core->parent == parent) return 0; /* verify ops for multi-parent clks */ if (core->num_parents > 1 && !core->ops->set_parent) return -EPERM; /* check that we are allowed to re-parent if the clock is in use */ if ((core->flags & CLK_SET_PARENT_GATE) && core->prepare_count) return -EBUSY; if (clk_core_rate_is_protected(core)) return -EBUSY; /* try finding the new parent index */ if (parent) { p_index = clk_fetch_parent_index(core, parent); if (p_index < 0) { pr_debug("%s: clk %s can not be parent of clk %s\n", __func__, parent->name, core->name); return p_index; } p_rate = parent->rate; } ret = clk_pm_runtime_get(core); if (ret) return ret; /* propagate PRE_RATE_CHANGE notifications */ ret = __clk_speculate_rates(core, p_rate); /* abort if a driver objects */ if (ret & NOTIFY_STOP_MASK) goto runtime_put; /* do the re-parent */ ret = __clk_set_parent(core, parent, p_index); /* propagate rate an accuracy recalculation accordingly */ if (ret) { __clk_recalc_rates(core, ABORT_RATE_CHANGE); } else { __clk_recalc_rates(core, POST_RATE_CHANGE); __clk_recalc_accuracies(core); } runtime_put: clk_pm_runtime_put(core); return ret; }
부모 클럭(입력 클럭 소스) 코어를 선택한다. 성공 시 연결된 모든 자식 클럭들의 rate를 재계산하고 0을 반환한다.
drivers/clk/clk.c
static int __clk_set_parent(struct clk_core *core, struct clk_core *parent, u8 p_index) { unsigned long flags; int ret = 0; struct clk_core *old_parent; old_parent = __clk_set_parent_before(core, parent); trace_clk_set_parent(core, parent); /* change clock input source */ if (parent && core->ops->set_parent) ret = core->ops->set_parent(core->hw, p_index); trace_clk_set_parent_complete(core, parent); if (ret) { flags = clk_enable_lock(); clk_reparent(core, old_parent); clk_enable_unlock(flags); __clk_set_parent_after(core, old_parent, parent); return ret; } __clk_set_parent_after(core, parent, old_parent); return 0; }
부모 클럭(입력 클럭 소스)을 선택한다. 성공한 경우 0을 반환한다.
drivers/clk/clk.c
static struct clk_core *__clk_set_parent_before(struct clk_core *core, struct clk_core *parent) { unsigned long flags; struct clk_core *old_parent = core->parent; /* * 1. enable parents for CLK_OPS_PARENT_ENABLE clock * * 2. Migrate prepare state between parents and prevent race with * clk_enable(). * * If the clock is not prepared, then a race with * clk_enable/disable() is impossible since we already have the * prepare lock (future calls to clk_enable() need to be preceded by * a clk_prepare()). * * If the clock is prepared, migrate the prepared state to the new * parent and also protect against a race with clk_enable() by * forcing the clock and the new parent on. This ensures that all * future calls to clk_enable() are practically NOPs with respect to * hardware and software states. * * See also: Comment for clk_set_parent() below. */ /* enable old_parent & parent if CLK_OPS_PARENT_ENABLE is set */ if (core->flags & CLK_OPS_PARENT_ENABLE) { clk_core_prepare_enable(old_parent); clk_core_prepare_enable(parent); } /* migrate prepare count if > 0 */ if (core->prepare_count) { clk_core_prepare_enable(parent); clk_core_enable_lock(core); } /* update the clk tree topology */ flags = clk_enable_lock(); clk_reparent(core, parent); clk_enable_unlock(flags); return old_parent; }
입력 클럭 소스(부모 클럭)를 선택하기 전에 처리할 일을 수행한다. 결과로 기존 부모 클럭을 반환한다.
drivers/clk/clk.c
static void __clk_set_parent_after(struct clk_core *core, struct clk_core *parent, struct clk_core *old_parent) { /* * Finish the migration of prepare state and undo the changes done * for preventing a race with clk_enable(). */ if (core->prepare_count) { clk_core_disable_lock(core); clk_core_disable_unprepare(old_parent); } /* re-balance ref counting if CLK_OPS_PARENT_ENABLE is set */ if (core->flags & CLK_OPS_PARENT_ENABLE) { clk_core_disable_unprepare(parent); clk_core_disable_unprepare(old_parent); } }
입력 클럭 소스(부모 클럭)를 선택한 후에 처리할 일을 수행한다.
drivers/clk/clk.c
static void clk_reparent(struct clk_core *core, struct clk_core *new_parent) { bool was_orphan = core->orphan; hlist_del(&core->child_node); if (new_parent) { bool becomes_orphan = new_parent->orphan; /* avoid duplicate POST_RATE_CHANGE notifications */ if (new_parent->new_child == core) new_parent->new_child = NULL; hlist_add_head(&core->child_node, &new_parent->children); if (was_orphan != becomes_orphan) clk_core_update_orphan_status(core, becomes_orphan); } else { hlist_add_head(&core->child_node, &clk_orphan_list); if (!was_orphan) clk_core_update_orphan_status(core, true); } core->parent = new_parent; }
clock tree 토플로지를 갱신한다.
mux 타입 클럭의 (*determine_rate) 후크 함수에서 사용되는 아래 함수를 알아본다.
drivers/etc/clk-mux.c
static int clk_mux_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { struct clk_mux *mux = to_clk_mux(hw); return clk_mux_determine_rate_flags(hw, req, mux->flags); }
요청한 rate에 대해 mux 클럭 hw가 지원하는 가장 근접한 rate를 산출한다. 성공 시 0을 반환하고, req->rate 및 req->best_parent_rate에 산출된 rate가 저장되고, req->best_parent_rate에 산출된 rate와 관련된 부모 클럭 hw가 저장된다.
drivers/etc/clk-mux.c
int clk_mux_determine_rate_flags(struct clk_hw *hw, struct clk_rate_request *req, unsigned long flags) { struct clk_core *core = hw->core, *parent, *best_parent = NULL; int i, num_parents, ret; unsigned long best = 0; struct clk_rate_request parent_req = *req; /* if NO_REPARENT flag set, pass through to current parent */ if (core->flags & CLK_SET_RATE_NO_REPARENT) { parent = core->parent; if (core->flags & CLK_SET_RATE_PARENT) { ret = __clk_determine_rate(parent ? parent->hw : NULL, &parent_req); if (ret) return ret; best = parent_req.rate; } else if (parent) { best = clk_core_get_rate_nolock(parent); } else { best = clk_core_get_rate_nolock(core); } goto out; } /* find the parent that can provide the fastest rate <= rate */ num_parents = core->num_parents; for (i = 0; i < num_parents; i++) { parent = clk_core_get_parent_by_index(core, i); if (!parent) continue; if (core->flags & CLK_SET_RATE_PARENT) { parent_req = *req; ret = __clk_determine_rate(parent->hw, &parent_req); if (ret) continue; } else { parent_req.rate = clk_core_get_rate_nolock(parent); } if (mux_is_better_rate(req->rate, parent_req.rate, best, flags)) { best_parent = parent; best = parent_req.rate; } } if (!best_parent) return -EINVAL; out: if (best_parent) req->best_parent_hw = best_parent->hw; req->best_parent_rate = best; req->rate = best; return 0; } EXPORT_SYMBOL_GPL(clk_mux_determine_rate_flags);
요청한 rate에 대해 mux 클럭 hw가 지원하는 가장 근접한 rate를 산출한다. 성공 시 0을 반환하고, req->rate 및 req->best_parent_rate에 산출된 rate가 저장되고, req->best_parent_rate에 산출된 rate와 관련된 부모 클럭 hw가 저장된다.
drivers/clk/clk.c
/* * __clk_mux_determine_rate - clk_ops::determine_rate implementation for a mux type clk * @hw: mux type clk to determine rate on * @req: rate request, also used to return preferred parent and frequencies * * Helper for finding best parent to provide a given frequency. This can be used * directly as a determine_rate callback (e.g. for a mux), or from a more * complex clock that may combine a mux with other operations. * * Returns: 0 on success, -EERROR value on error */
int __clk_mux_determine_rate(struct clk_hw *hw, struct clk_rate_request *req) { return clk_mux_determine_rate_flags(hw, req, 0); } EXPORT_SYMBOL_GPL(__clk_mux_determine_rate);
mux 타입 클럭 디바이스 드라이버의 ops->determine_rate에서 호출되는 콜백함수로도 사용된다. 요청한 rate 이하 값으로 가장 가까운 rate를 구한다. req->best_parent_rate에 산출한 최적의 rate가 담기고 req->best_parent_hw는 산출된 최적의 부모 클럭 hw를 가리킨다. 단 req->min_rate ~ req->max_rate 범위를 초과하는 경우 0을 반환한다. 디폴트 플래그로 0을 사용한다.
drivers/clk/clk.c
int __clk_mux_determine_rate_closest(struct clk_hw *hw, struct clk_rate_request *req) { return clk_mux_determine_rate_flags(hw, req, CLK_MUX_ROUND_CLOSEST); } EXPORT_SYMBOL_GPL(__clk_mux_determine_rate_closest);
요청한 rate에 가장 가까운 rate를 구한다. req->best_parent_rate에 산출한 최적의 rate가 담기고 req->best_parent_hw는 산출된 최적의 부모 클럭hw를 가리킨다. 단 req->min_rate ~ req->max_rate 범위를 초과하는 경우 0을 반환한다. 디폴트 플래그로 CLK_MUX_ROUND_CLOSEST을 사용한다.
drivers/clk/clk.c
static bool mux_is_better_rate(unsigned long rate, unsigned long now, unsigned long best, unsigned long flags) { if (flags & CLK_MUX_ROUND_CLOSEST) return abs(now - rate) < abs(best - rate); return now <= rate && now > best; }
mux가 설정하고자 하는 rate 값에 now 값이 best 값보다 더 적절한 경우 true(1)를 반환한다. CLK_MUX_ROUND_CLOSEST 플래그의 사용 여부에 따라 적절한 값의 여부 판단이 바뀐다.
fixed factor 타입 클럭의 (*round_rate) 후크 함수에서 사용되는 아래 함수를 알아본다.
drivers/etc/clk-fixed-factor.c
static long clk_factor_round_rate(struct clk_hw *hw, unsigned long rate, unsigned long *prate) { struct clk_fixed_factor *fix = to_clk_fixed_factor(hw); if (clk_hw_get_flags(hw) & CLK_SET_RATE_PARENT) { unsigned long best_parent; best_parent = (rate / fix->mult) * fix->div; *prate = clk_hw_round_rate(clk_hw_get_parent(hw), best_parent); } return (*prate / fix->div) * fix->mult; }
요청한 @rate에 대해 fixed factor 타입 클럭 hw가 지원하는 가장 근접한 rate를 산출한다. 성공 시 rate를 반환하고, *prate에는 부모 클럭의 rate를 저장해온다.
샘플에 사용한 디바이스 트리의 추가된 6개의 클럭 노드 내용은 다음과 같다.
fooclk1 { phandle = <0x8100>; clock-output-names = "fooclk1"; clock-frequency = <1000000>; #clock-cells = <0x0>; compatible = "fixed-clock"; }; fooclk2 { phandle = <0x8200>; clock-output-names = "fooclk2"; clocks = <0x8100>; #clock-cells = <0x0>; compatible = "foo,divider-clock"; foo,max-div = <32>; }; fooclk3 { phandle = <0x8300>; clock-output-names = "fooclk3"; clocks = <0x8200>; #clock-cells = <0x0>; compatible = "foo,divider-clock"; foo,max-div = <8>; foo,set-rate-parent; }; fooclk4 { phandle = <0x8400>; clock-output-names = "fooclk4"; clocks = <0x8200>; #clock-cells = <0x0>; compatible = "foo,divider-clock"; foo,max-div = <5>; foo,index-power-of-two; }; fooclk5 { phandle = <0x8500>; clock-output-names = "fooclk5"; clocks = <0x8100 0x8200 0x8300 0x8400>; #clock-cells = <0x0>; compatible = "foo,mux-clock"; }; foo { compatible = "foo,foo"; clock-names = "fooclk1", "fooclk2", "fooclk3", "fooclk4", "fooclk5"; clocks = <0x8100 0x8200 0x8300 0x8400 0x8500>; };
다음 그림은 5 개의 클럭이 처음 초기화된 상태를 보여준다.
다음은 5개의 클럭에 대해 prepare 및 enable한 상태이고, cat /sys/kernel/debug/clk/clk_summary 명령을 통해 확인한 결과이다.
$ insmod clk.ko clk: loading out-of-tree module taints kernel. foo: foo_probe foo: devm_clk_get() clk1 foo: devm_clk_get() clk2 foo: devm_clk_get() clk3 foo: devm_clk_get() clk4 foo: devm_clk_get() clk5 foo: clk_prepare() clk1 rc=0 foo: clk_prepare() clk2 rc=0 foo: clk_prepare() clk3 rc=0 foo: clk_prepare() clk4 rc=0 foo: clk_prepare() clk5 rc=0 foo: clk_enable() clk1 rc=0 foo: clk_enable() clk2 rc=0 foo: clk_enable() clk3 rc=0 foo: clk_enable() clk4 rc=0 foo: clk_enable() clk5 rc=0
$ cat /sys/kernel/debug/clk/clk_summary enable prepare protect duty clock count count count rate accuracy phase cycle --------------------------------------------------------------------------------------------- fooclk1 0 0 0 1000000 0 0 50000 fooclk5 0 0 0 1000000 0 0 50000 fooclk2 0 0 0 1000000 0 0 50000 fooclk4 0 0 0 1000000 0 0 50000 fooclk3 0 0 0 1000000 0 0 50000 clk24mhz 4 5 0 24000000 0 0 50000
다음 그림은 clk2, clk3, 및 clk4의 rate를 초기값으로 설정하고, clk5의 입력을 2번으로 선택한 상태이다.
다음은 각 클럭을 설정하는 과정을 보여준다.
$ echo 40000 > /sys/bus/platform/drivers/foo/foo2 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=25 foo_clk_divider_round_rate: rate=40000, prate=1000000, round=40000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=25 foo_clk_divider_round_rate: rate=40000, prate=1000000, round=40000 foo_clk_divider_recalc_rate: parent_rate=40000 foo_readl: val=0 foo_clk_divider_recalc_rate: parent_rate=40000, round=40000 foo_clk_divider_recalc_rate: parent_rate=40000 foo_readl: val=0 foo_clk_divider_recalc_rate: parent_rate=40000, round=40000 foo_clk_divider_set_rate: rate=40000, parent_rate=1000000 foo_readl: val=0 foo_writel: val=24 foo_clk_divider_recalc_rate: parent_rate=1000000 foo_readl: val=24 foo_clk_divider_recalc_rate: parent_rate=1000000, round=40000 foo_clk_divider_set_rate: rate=40000, parent_rate=40000 foo_readl: val=0 foo_writel: val=0 foo_clk_divider_recalc_rate: parent_rate=40000 foo_readl: val=0 foo_clk_divider_recalc_rate: parent_rate=40000, round=40000 foo_clk_divider_set_rate: rate=40000, parent_rate=40000 foo_readl: val=0 foo_writel: val=0 foo_clk_divider_recalc_rate: parent_rate=40000 foo_readl: val=0 foo_clk_divider_recalc_rate: parent_rate=40000, round=40000 foo foo: clk_set_rate() clk2 val=40000 rc=0
$ echo 10000 > /sys/bus/platform/drivers/foo/foo3 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: maxdiv2=8 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=10000, prate=1000000, round=31250 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=20001, prate=1000000, round=31250 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=30002, prate=1000000, round=31250 foo_clk_divider_round_rate: rate=10000, prate=40000, round=10000 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: maxdiv2=8 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=10000, prate=1000000, round=31250 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=20001, prate=1000000, round=31250 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=30002, prate=1000000, round=31250 foo_clk_divider_round_rate: rate=10000, prate=40000, round=10000 foo_clk_divider_set_rate: rate=10000, parent_rate=40000 foo_readl: val=0 foo_writel: val=3 foo_clk_divider_recalc_rate: parent_rate=40000 foo_readl: val=3 foo_clk_divider_recalc_rate: parent_rate=40000, round=10000 foo foo: clk_set_rate() clk3 val=10000 rc=0
$ echo 5000 > /sys/bus/platform/drivers/foo/foo4 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: bestdiv=8 foo_clk_divider_round_rate: rate=5000, prate=40000, round=5000 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: bestdiv=8 foo_clk_divider_round_rate: rate=5000, prate=40000, round=5000 foo_clk_divider_set_rate: rate=5000, parent_rate=40000 foo_readl: val=0 foo_writel: val=3 foo_clk_divider_recalc_rate: parent_rate=40000 foo_readl: val=3 foo_clk_divider_recalc_rate: parent_rate=40000, round=5000 foo foo: clk_set_rate() clk4 val=5000 rc=0 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: bestdiv=8 foo_clk_divider_round_rate: rate=5000, prate=40000, round=5000 foo foo: clk_set_rate() clk4 val=5000 rc=0
$ echo 2 > /sys/bus/platform/drivers/foo/foo5 foo_clk_mux_set_parent: index=2 foo_readl: val=0 foo_writel: val=2 foo_clk_mux_set_parent: index2=2, val=2 foo foo: clk_set_parent() val=2, select=clk3 rc=0
$ cat /sys/kernel/debug/clk/clk_summary enable prepare protect duty clock count count count rate accuracy phase cycle --------------------------------------------------------------------------------------------- fooclk1 2 2 0 1000000 0 0 50000 fooclk2 3 3 0 40000 0 0 50000 fooclk4 1 1 0 5000 0 0 50000 fooclk3 2 2 0 10000 0 0 50000 fooclk5 1 1 0 10000 0 0 50000 clk24mhz 4 5 0 24000000 0 0 50000
다음 그림은 clk3의 rate를 25khz로 변경된 모습을 보여준다.
다음은 clk3의 rate를 25khz로 변경하는 과정을 보여준다.
$ echo 25000 > /sys/bus/platform/drivers/foo/foo3 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: maxdiv2=8 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=25000, prate=1000000, round=31250 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=20 foo_clk_divider_round_rate: rate=50001, prate=1000000, round=50000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=14 foo_clk_divider_round_rate: rate=75002, prate=1000000, round=71429 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=10 foo_clk_divider_round_rate: rate=100003, prate=1000000, round=100000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=8 foo_clk_divider_round_rate: rate=125004, prate=1000000, round=125000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=7 foo_clk_divider_round_rate: rate=150005, prate=1000000, round=142858 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=6 foo_clk_divider_round_rate: rate=175006, prate=1000000, round=166667 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=5 foo_clk_divider_round_rate: rate=200007, prate=1000000, round=200000 foo_clk_divider_bestdiv: bestdiv2=2, best_parent_rate=50000 foo_clk_divider_round_rate: rate=25000, prate=50000, round=25000 foo_clk_divider_bestdiv: maxdiv=8 foo_clk_divider_bestdiv: maxdiv2=8 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=32 foo_clk_divider_round_rate: rate=25000, prate=1000000, round=31250 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=20 foo_clk_divider_round_rate: rate=50001, prate=1000000, round=50000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=14 foo_clk_divider_round_rate: rate=75002, prate=1000000, round=71429 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=10 foo_clk_divider_round_rate: rate=100003, prate=1000000, round=100000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=8 foo_clk_divider_round_rate: rate=125004, prate=1000000, round=125000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=7 foo_clk_divider_round_rate: rate=150005, prate=1000000, round=142858 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=6 foo_clk_divider_round_rate: rate=175006, prate=1000000, round=166667 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=5 foo_clk_divider_round_rate: rate=200007, prate=1000000, round=200000 foo_clk_divider_bestdiv: bestdiv2=2, best_parent_rate=50000 foo_clk_divider_round_rate: rate=25000, prate=50000, round=25000 foo_clk_divider_bestdiv: maxdiv=32 foo_clk_divider_bestdiv: bestdiv=20 foo_clk_divider_round_rate: rate=50000, prate=1000000, round=50000 foo_clk_divider_recalc_rate: parent_rate=50000 foo_readl: val=3 foo_clk_divider_recalc_rate: parent_rate=50000, round=6250 foo_clk_divider_recalc_rate: parent_rate=50000 foo_readl: val=3 foo_clk_divider_recalc_rate: parent_rate=50000, round=12500 foo_clk_divider_set_rate: rate=50000, parent_rate=1000000 foo_readl: val=24 foo_writel: val=19 foo_clk_divider_recalc_rate: parent_rate=1000000 foo_readl: val=19 foo_clk_divider_recalc_rate: parent_rate=1000000, round=50000 foo_clk_divider_set_rate: rate=6250, parent_rate=50000 foo_readl: val=3 foo_writel: val=3 foo_clk_divider_recalc_rate: parent_rate=50000 foo_readl: val=3 foo_clk_divider_recalc_rate: parent_rate=50000, round=6250 foo_clk_divider_set_rate: rate=25000, parent_rate=50000 foo_readl: val=3 foo_writel: val=1 foo_clk_divider_recalc_rate: parent_rate=50000 foo_readl: val=1 foo_clk_divider_recalc_rate: parent_rate=50000, round=25000 foo foo: clk_set_rate() clk3 val=25000 rc=0
$ cat /sys/kernel/debug/clk/clk_summary enable prepare protect duty clock count count count rate accuracy phase cycle --------------------------------------------------------------------------------------------- fooclk1 2 2 0 1000000 0 0 50000 fooclk2 3 3 0 50000 0 0 50000 fooclk4 1 1 0 6250 0 0 50000 fooclk3 2 2 0 25000 0 0 50000 fooclk5 1 1 0 25000 0 0 50000 clk24mhz 4 5 0 24000000 0 0 50000
<kernel v5.4>
컴퓨터 하드웨어에는 많은 클럭 장치를 통해 클럭이 공급되고 있다. 시스템 내부에 cpu core에 들어가는 클럭부터 timer, i2c, uart 등 수 ~ 수십 종류의 클럭이 사용된다. 각각의 ARM SoC들은 개개의 클럭 디바이스 드라이버를 통해 클럭 설정을 하는데 하드웨어도 천차만별이고 구현된 드라이버도 기존 코드를 그대로 copy & paste를 통해 사용되고 심지어 clk 구조체 까지도 바꿔서 사용해오고 있다. 이를 해결해 보고자 CCF(Common Clock Framework)를 준비하였다. CCF는 커널 v3.4에서 처음 소개되었고 커널 v4.0에서 clk 구조체의 대부분을 clk_core 구조체로 옮겼다.
drivers/clk/clk.c를 제외하고 다음과 같이 많은 시스템에서 clk 구조체를 변경하여 사용했음을 알 수 있다. (커널 v4.0 기준)
./include/linux/sh_clk.h:37:struct clk { ./drivers/clk/clk.c:80:struct clk { ./arch/c6x/include/asm/clock.h:82:struct clk { ./arch/mips/include/asm/clock.h:20:struct clk { ./arch/mips/include/asm/mach-ar7/ar7.h:147:struct clk { ./arch/mips/ralink/clk.c:19:struct clk { ./arch/mips/jz4740/clock.h:45:struct clk { ./arch/mips/ath79/clock.c:31:struct clk { ./arch/mips/lantiq/clk.h:55:struct clk { ./arch/mips/bcm63xx/clk.c:19:struct clk { ./arch/blackfin/include/asm/clocks.h:61:struct clk { ./arch/blackfin/mach-common/clock.h:14:struct clk { ./arch/arm/mach-lpc32xx/clock.h:22:struct clk { ./arch/arm/mach-w90x900/clock.h:18:struct clk { ./arch/arm/mach-sa1100/clock.c:26:struct clk { ./arch/arm/mach-davinci/clock.h:87:struct clk { ./arch/arm/mach-mmp/clock.h:18:struct clk { ./arch/arm/mach-versatile/include/mach/clkdev.h:6:struct clk { ./arch/arm/mach-pxa/clock.h:11:struct clk { ./arch/arm/mach-omap1/clock.h:141:struct clk { ./arch/arm/mach-ep93xx/clock.c:30:struct clk { ./arch/m68k/include/asm/mcfclk.h:16:struct clk { ./arch/avr32/mach-at32ap/clock.h:20:struct clk { ./arch/unicore32/kernel/clock.c:30:struct clk {
다음과 같이 커널 v5.4에서 clk 구조체를 변경하여 사용한 코드가 일부 남아 있다.
./include/linux/sh_clk.h:38:struct clk { ./drivers/clk/clk.c:97:struct clk { ./arch/unicore32/kernel/clock.c:27:struct clk { ./arch/mips/include/asm/mach-ar7/ar7.h:134:struct clk { ./arch/mips/include/asm/clock.h:21:struct clk { ./arch/mips/ralink/clk.c:18:struct clk { ./arch/mips/bcm63xx/clk.c:21:struct clk { ./arch/mips/lantiq/clk.h:58:struct clk { ./arch/m68k/include/asm/mcfclk.h:17:struct clk { ./arch/arm/mach-ep93xx/clock.c:27:struct clk { ./arch/arm/mach-omap1/clock.h:138:struct clk { ./arch/arm/mach-mmp/clock.h:12:struct clk { ./arch/c6x/include/asm/clock.h:79:struct clk {
다음 그림은 클럭 다이어그램 샘플을 보여준다.
클럭을 공급하는 장치인 Clock Provider와 클럭을 가져다 사용하는 Clock Consumer의 특징을 알아본다.
다음 그림은 기본적인 Clock Provider와 Clock Consumer의 관계와 디바이스 트리에서의 표현을 보여준다.
다음 그림은 Clock Provider와 Clock Consumer들을 보여준다.
몇가지 플랫폼에서는 다음과 같이 clock provider의 rate 설정 또는 mux(parent) 입력 소스 선택을 지원하는 속성을 제공한다.
다음 그림은 clkcon 0의 입력 소스로 pll 2를 선택하게하고, pll 2의 rate를 460800hz로 지정하도록 하는 모습을 보여준다.
drivers/clk 디렉토리를 살펴보면 수 백 개의 클럭 관련 디바이스 파일이 존재한다.
common clock framework에서 사용하는 구조체는 다음과 같다.
다음 그림에서 provider로 사용된 clk 구조체와 consumer로 사용된 clk 구조체를 비교해본다.
다음 그림은 최근 커널의 Common Clk Framework를 보여준다.
다음 그림은 Common Clk Framework이 소개되었을 당시의 모습을 보여준다.
가장 많이 사용할 수 있는 공통 기본 클럭 구현이 다음과 같이 9개가 준비되어 있다. 공통 기본 클럭 구현만으로 구성 할 수 없는 시스템은 더 확장하여 구성할 수도 있다.
Fixed rate 및 Fixed factor 타입 클럭을 제외하고 각자 hw를 제어하기 위해 custom 클럭 디바이스 드라이버가 구현되어야 한다.
각자 hw를 제어하기 위해 custom 클럭 디바이스 드라이버가 구현되어야 한다.
Gpio gated 타입 클럭은 common gpio 디바이스 드라이버를 통해 hw를 제외하므로 별도의 클럭 디바이스 드라이버의 구현이 필요 없고, 그 외의 gated 타입 클럭은 hw를 제어하기 위해 custom 클럭 디바이스 드라이버가 구현되어야 한다.
클럭 하드웨어 도면은 아래 사이트를 참고한다.
다음 그림은 9개의 클럭 디바이스 사용 예를 각각 보여준다.
다음 그림은 common 클럭 타입별 커스텀 드라이버가 제어를 해야할 레지스터 부분을 보여준다.
다음 그림은 common 클럭 타입별 사용되는 ops 콜백 함수를 보여준다.
클럭 디바이스 드라이버도 머신 형태의 각 개별 custom 드라이버를 사용하지 않고 점차 Device Tree를 지원하는 형태로 바뀌어 가고 있다. 즉 런타임에 Device Tree Blob 내용을 파싱하여 시스템에 맞게 클럭을 설정하는 형태이므로 모든 설정방법이 Device Tree Script에 표현되어 있어야 한다.
다음의 추가 구조체를 사용한다.
common 클럭들을 사용하는 경우 hw 조작을 위해 관련 레지스터 주소와 함께 클럭을 등록하여 간단하게 디바이스 트리용 클럭 드라이버를 만들 수 있다. 다음 예는 가장 간단히 만들 수 있는 실제 divider 클럭 드라이버 코드를 보여준다.
drivers/clk/h8300/clk-div.c
#include <linux/clk-provider.h> #include <linux/err.h> #include <linux/io.h> #include <linux/of.h> #include <linux/of_address.h> static DEFINE_SPINLOCK(clklock); static void __init h8300_div_clk_setup(struct device_node *node) { unsigned int num_parents; struct clk_hw *hw; const char *clk_name = node->name; const char *parent_name; void __iomem *divcr = NULL; int width; int offset; num_parents = of_clk_get_parent_count(node); if (!num_parents) { pr_err("%s: no parent found\n", clk_name); return; } divcr = of_iomap(node, 0); if (divcr == NULL) { pr_err("%s: failed to map divide register\n", clk_name); goto error; } offset = (unsigned long)divcr & 3; offset = (3 - offset) * 8; divcr = (void __iomem *)((unsigned long)divcr & ~3); parent_name = of_clk_get_parent_name(node, 0); of_property_read_u32(node, "renesas,width", &width); hw = clk_hw_register_divider(NULL, clk_name, parent_name, CLK_SET_RATE_GATE, divcr, offset, width, CLK_DIVIDER_POWER_OF_TWO, &clklock); if (!IS_ERR(hw)) { of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw); return; } pr_err("%s: failed to register %s div clock (%ld)\n", __func__, clk_name, PTR_ERR(hw)); error: if (divcr) iounmap(divcr); } CLK_OF_DECLARE(h8300_div_clk, "renesas,h8300-div-clock", h8300_div_clk_setup);
2의 배수 단계로 나누는 divider 클럭이다.
다음은 위 클럭 디바이스 드라이버를 사용하는 4단계 divier 클럭 디바이스를 정의한 디바이스 트리 설정이다. (core_clk 부분)
arch/h8300/boot/dts/h8300h_sim.dts
...
xclk: oscillator {
#clock-cells = <0>;
compatible = "fixed-clock";
clock-frequency = <20000000>;
clock-output-names = "xtal";
};
core_clk: core_clk {
compatible = "renesas,h8300-div-clock";
clocks = <&xclk>;
#clock-cells = <0>;
reg = <0xfee01b 2>;
renesas,width = <2>;
};
fclk: fclk {
compatible = "fixed-factor-clock";
clocks = <&core_clk>;
#clock-cells = <0>;
clock-div = <1>;
clock-mult = <1>;
};
...
20Mhz 고정 클럭 -> 4 단계 divider -> 1/1 Factor 클럭 순으로 클럭이 공급됨을 알 수 있다.
Device Tree로 부팅한 rpi2에서 등록된 7개의 클럭을 다음의 디렉토리를 통해 살펴볼 수 있다.
# cd /sys/kernel/debug/clk # ls -l total 0 drwxr-xr-x 2 root root 0 Jan 1 1970 apb_pclk -r--r--r-- 1 root root 0 Jan 1 1970 clk_dump -r--r--r-- 1 root root 0 Jan 1 1970 clk_orphan_dump -r--r--r-- 1 root root 0 Jan 1 1970 clk_orphan_summary -r--r--r-- 1 root root 0 Jan 1 1970 clk_summary drwxr-xr-x 2 root root 0 Jan 1 1970 clock drwxr-xr-x 2 root root 0 Jan 1 1970 core drwxr-xr-x 2 root root 0 Jan 1 1970 mmc drwxr-xr-x 2 root root 0 Jan 1 1970 osc drwxr-xr-x 2 root root 0 Jan 1 1970 pwm drwxr-xr-x 2 root root 0 Jan 1 1970 uart0_pclk # cat clk_summary clock enable_cnt prepare_cnt rate accuracy phase ---------------------------------------------------------------------------------------- osc 0 0 19200000 0 0 pwm 0 0 100000000 0 0 apb_pclk 1 1 126000000 0 0 uart0_pclk 1 1 48000000 0 0 mmc 0 0 250000000 0 0 core 0 0 400000000 0 0 clock 0 0 800000000 0 0
다음은 rpi4(Ubuntu 4.18.03)에서 사용된 클럭들을 보여준다. (커널 v4.19)
$ cd /sys/kernel/debug/clk rpi4 /sys/kernel/debug/clk$ ls -l total 0 drwxr-xr-x 2 root root 0 Jan 1 1970 aux_spi1 drwxr-xr-x 2 root root 0 Jan 1 1970 aux_spi2 drwxr-xr-x 2 root root 0 Jan 1 1970 aux_uart drwxr-xr-x 2 root root 0 Jan 1 1970 aveo drwxr-xr-x 2 root root 0 Jan 1 1970 cam0 drwxr-xr-x 2 root root 0 Jan 1 1970 cam1 -r--r--r-- 1 root root 0 Jan 1 1970 clk_dump -r--r--r-- 1 root root 0 Jan 1 1970 clk_orphan_dump -r--r--r-- 1 root root 0 Jan 1 1970 clk_orphan_summary -r--r--r-- 1 root root 0 Jan 1 1970 clk_summary drwxr-xr-x 2 root root 0 Jan 1 1970 dft drwxr-xr-x 2 root root 0 Jan 1 1970 dpi drwxr-xr-x 2 root root 0 Jan 1 1970 dsi0e drwxr-xr-x 2 root root 0 Jan 1 1970 dsi0p drwxr-xr-x 2 root root 0 Jan 1 1970 dsi1e drwxr-xr-x 2 root root 0 Jan 1 1970 dsi1p drwxr-xr-x 2 root root 0 Jan 1 1970 emmc drwxr-xr-x 2 root root 0 Jan 1 1970 emmc2 drwxr-xr-x 2 root root 0 Jan 1 1970 gp0 drwxr-xr-x 2 root root 0 Jan 1 1970 gp1 drwxr-xr-x 2 root root 0 Jan 1 1970 gp2 drwxr-xr-x 2 root root 0 Jan 1 1970 h264 drwxr-xr-x 2 root root 0 Jan 1 1970 hsm drwxr-xr-x 2 root root 0 Jan 1 1970 isp drwxr-xr-x 2 root root 0 Jan 1 1970 osc drwxr-xr-x 2 root root 0 Jan 1 1970 otg drwxr-xr-x 2 root root 0 Jan 1 1970 otp drwxr-xr-x 2 root root 0 Jan 1 1970 pcm drwxr-xr-x 2 root root 0 Jan 1 1970 peri_image drwxr-xr-x 2 root root 0 Jan 1 1970 plla drwxr-xr-x 2 root root 0 Jan 1 1970 plla_ccp2 drwxr-xr-x 2 root root 0 Jan 1 1970 plla_core drwxr-xr-x 2 root root 0 Jan 1 1970 plla_dsi0 drwxr-xr-x 2 root root 0 Jan 1 1970 plla_per drwxr-xr-x 2 root root 0 Jan 1 1970 pllb drwxr-xr-x 2 root root 0 Jan 1 1970 pllb_arm drwxr-xr-x 2 root root 0 Jan 1 1970 pllc drwxr-xr-x 2 root root 0 Jan 1 1970 pllc_core0 drwxr-xr-x 2 root root 0 Jan 1 1970 pllc_core1 drwxr-xr-x 2 root root 0 Jan 1 1970 pllc_core2 drwxr-xr-x 2 root root 0 Jan 1 1970 pllc_per drwxr-xr-x 2 root root 0 Jan 1 1970 plld drwxr-xr-x 2 root root 0 Jan 1 1970 plld_core drwxr-xr-x 2 root root 0 Jan 1 1970 plld_dsi0 drwxr-xr-x 2 root root 0 Jan 1 1970 plld_dsi1 drwxr-xr-x 2 root root 0 Jan 1 1970 plld_per drwxr-xr-x 2 root root 0 Jan 1 1970 pwm drwxr-xr-x 2 root root 0 Jan 1 1970 sdram drwxr-xr-x 2 root root 0 Jan 1 1970 slim drwxr-xr-x 2 root root 0 Jan 1 1970 smi drwxr-xr-x 2 root root 0 Jan 1 1970 tec drwxr-xr-x 2 root root 0 Jan 1 1970 timer drwxr-xr-x 2 root root 0 Jan 1 1970 tsens drwxr-xr-x 2 root root 0 Jan 1 1970 uart drwxr-xr-x 2 root root 0 Jan 1 1970 v3d drwxr-xr-x 2 root root 0 Jan 1 1970 vec drwxr-xr-x 2 root root 0 Jan 1 1970 vpu rpi4 /sys/kernel/debug/clk$ cat clk_summary enable prepare protect duty clock count count count rate accuracy phase cycle --------------------------------------------------------------------------------------------- otg 0 0 0 480000000 0 0 50000 osc 5 5 0 54000000 0 0 50000 tsens 1 1 0 3375000 0 0 50000 otp 0 0 0 13500000 0 0 50000 timer 0 0 0 1000000 0 0 50000 plld 5 5 0 2999999988 0 0 50000 plld_dsi1 1 1 0 11718750 0 0 50000 plld_dsi0 1 1 0 11718750 0 0 50000 plld_per 3 3 0 749999997 0 0 50000 emmc2 1 1 0 99999999 0 0 50000 uart 1 1 0 47999999 0 0 50000 plld_core 1 1 0 599999998 0 0 50000 pllc 5 5 0 2999999988 0 0 50000 pllc_per 1 1 0 599999998 0 0 50000 emmc 0 0 0 199999999 0 0 50000 pllc_core2 1 1 0 11718750 0 0 50000 pllc_core1 1 1 0 11718750 0 0 50000 pllc_core0 2 2 0 499999998 0 0 50000 vpu 2 2 0 500000000 0 0 50000 aux_spi2 0 0 0 500000000 0 0 50000 aux_spi1 0 0 0 500000000 0 0 50000 aux_uart 0 0 0 500000000 0 0 50000 peri_image 0 0 0 500000000 0 0 50000 pllb 2 2 0 2999999988 0 0 50000 pllb_arm 1 1 0 1499999994 0 0 50000 plla 2 2 0 2999999988 0 0 50000 plla_ccp2 0 0 0 11718750 0 0 50000 plla_dsi0 0 0 0 11718750 0 0 50000 plla_per 0 0 0 11718750 0 0 50000 plla_core 2 2 0 499999998 0 0 50000 h264 0 0 0 499999998 0 0 50000 isp 0 0 0 499999998 0 0 50000 v3d 1 1 0 31257631 0 0 50000 dsi1p 0 0 0 0 0 0 50000 dsi0p 0 0 0 0 0 0 50000 dsi1e 0 0 0 0 0 0 50000 dsi0e 0 0 0 0 0 0 50000 cam1 0 0 0 0 0 0 50000 cam0 0 0 0 0 0 0 50000 dpi 0 0 0 0 0 0 50000 tec 0 0 0 0 0 0 50000 smi 0 0 0 0 0 0 50000 slim 0 0 0 0 0 0 50000 gp2 0 0 0 0 0 0 50000 gp1 0 0 0 0 0 0 50000 gp0 0 0 0 0 0 0 50000 dft 0 0 0 0 0 0 50000 aveo 0 0 0 0 0 0 50000 pcm 0 0 0 0 0 0 50000 pwm 0 0 0 0 0 0 50000 sdram 0 0 0 0 0 0 50000 hsm 0 0 0 0 0 0 50000 vec 0 0 0 0 0 0 50000
drivers/clk/clk.c
/** * of_clk_init() - Scan and init clock providers from the DT * @matches: array of compatible values and init functions for providers. * * This function scans the device tree for matching clock providers * and calls their initialization functions. It also does it by trying * to follow the dependencies. */
void __init of_clk_init(const struct of_device_id *matches) { const struct of_device_id *match; struct device_node *np; struct clock_provider *clk_provider, *next; bool is_init_done; bool force = false; LIST_HEAD(clk_provider_list); if (!matches) matches = &__clk_of_table; /* First prepare the list of the clocks providers */ for_each_matching_node_and_match(np, matches, &match) { struct clock_provider *parent; if (!of_device_is_available(np)) continue; parent = kzalloc(sizeof(*parent), GFP_KERNEL); if (!parent) { list_for_each_entry_safe(clk_provider, next, &clk_provider_list, node) { list_del(&clk_provider->node); of_node_put(clk_provider->np); kfree(clk_provider); } of_node_put(np); return; } parent->clk_init_cb = match->data; parent->np = of_node_get(np); list_add_tail(&parent->node, &clk_provider_list); } while (!list_empty(&clk_provider_list)) { is_init_done = false; list_for_each_entry_safe(clk_provider, next, &clk_provider_list, node) { if (force || parent_ready(clk_provider->np)) { /* Don't populate platform devices */ of_node_set_flag(clk_provider->np, OF_POPULATED); clk_provider->clk_init_cb(clk_provider->np); of_clk_set_defaults(clk_provider->np, true); list_del(&clk_provider->node); of_node_put(clk_provider->np); kfree(clk_provider); is_init_done = true; } } /* * We didn't manage to initialize any of the * remaining providers during the last loop, so now we * initialize all the remaining ones unconditionally * in case the clock parent was not mandatory */ if (!is_init_done) force = true; } }
Device Tree에서 clock provider를 스캔하고 초기화한다.
다음 그림에서와 같이 rpi2는 clock@0 ~ clock@6 까지 총 7개의 clock을 사용하며 이에 대한 각각의 초기화 함수가 호출되는 것을 보여준다.
다음 그림은 좌측의 A~I 까지의 클럭 디바이스 구성에 대해 부모(parent) 클럭 디바이스부터 초기화되는 과정을 보여준다.
drivers/clk/clk.c
/* * This function looks for a parent clock. If there is one, then it * checks that the provider for this parent clock was initialized, in * this case the parent clock will be ready. */
static int parent_ready(struct device_node *np) { int i = 0; while (true) { struct clk *clk = of_clk_get(np, i); /* this parent is ready we can check the next one */ if (!IS_ERR(clk)) { clk_put(clk); i++; continue; } /* at least one parent is not ready, we exit now */ if (PTR_ERR(clk) == -EPROBE_DEFER) return 0; /* * Here we make assumption that the device tree is * written correctly. So an error means that there is * no more parent. As we didn't exit yet, then the * previous parent are ready. If there is no clock * parent, no need to wait for them, then we can * consider their absence as being ready */ return 1; } }
요청한 클럭 노드의 부모 클럭 노드들 모두가 초기화 되었는지 여부를 알아온다. 1=부모 클럭 노드가 없거나 모두 초기화 된 경우, 0=부모 클럭 노드들 중 하나라도 초기화 되지 않았을 경우
drivers/clk/clk.c
static struct clk_core *clk_core_lookup(const char *name) { struct clk_core *root_clk; struct clk_core *ret; if (!name) return NULL; /* search the 'proper' clk tree first */ hlist_for_each_entry(root_clk, &clk_root_list, child_node) { ret = __clk_lookup_subtree(name, root_clk); if (ret) return ret; } /* if not found, then search the orphan tree */ hlist_for_each_entry(root_clk, &clk_orphan_list, child_node) { ret = __clk_lookup_subtree(name, root_clk); if (ret) return ret; } return NULL; }
루트 클럭 리스트와 고아(orphan) 클럭 리스트에 등록된 모든 하위 클럭들을 포함하여 요청한 이름의 클럭(clk_core)을 검색한다. 검색되지 않는 경우 null을 반환한다.
다음 그림은 “F”라는 이름의 클럭을 검색시 child 클럭을 검색하는 순서를 보여준다.
drivers/clk/clk.c
static struct clk_core *__clk_lookup_subtree(const char *name, struct clk_core *clk) { struct clk_core *child; struct clk_core *ret; if (!strcmp(clk->name, name)) return clk; hlist_for_each_entry(child, &clk->children, child_node) { ret = __clk_lookup_subtree(name, child); if (ret) return ret; } return NULL; }
현재 클럭 및 그 자식 클럭에서 요청한 이름의 클럭(clk_core)을 검색한다. 검색되지 않는 경우 null을 반환한다.
drivers/clk/clk.c
static struct clk_core *clk_core_get_parent_by_index(struct clk_core *clk, u8 index) { if (!clk || index >= clk->num_parents) return NULL; else if (!clk->parents) return clk_core_lookup(clk->parent_names[index]); else if (!clk->parents[index]) return clk->parents[index] = clk_core_lookup(clk->parent_names[index]); else return clk->parents[index]; }
부모 인덱스 값으로 부모 클럭을 찾아 반환한다.
drivers/clk/clk.c
static void clk_core_fill_parent_index(struct clk_core *core, u8 index) { struct clk_parent_map *entry = &core->parents[index]; struct clk_core *parent = ERR_PTR(-ENOENT); if (entry->hw) { parent = entry->hw->core; /* * We have a direct reference but it isn't registered yet? * Orphan it and let clk_reparent() update the orphan status * when the parent is registered. */ if (!parent) parent = ERR_PTR(-EPROBE_DEFER); } else { parent = clk_core_get(core, index); if (IS_ERR(parent) && PTR_ERR(parent) == -ENOENT && entry->name) parent = clk_core_lookup(entry->name); } /* Only cache it if it's not an error */ if (!IS_ERR(parent)) entry->core = parent; }
@index에 해당하는 부모 클럭맵을 설정한다.
drivers/clk/clk-fixed-rate.c
/** * of_fixed_clk_setup() - Setup function for simple fixed rate clock */
void __init of_fixed_clk_setup(struct device_node *node) { _of_fixed_clk_setup(node); } CLK_OF_DECLARE(fixed_clk, "fixed-clock", of_fixed_clk_setup);
Device Tree의 요청 클럭 노드 정보로 fixed rate 타입의 클럭을 설정한다.
drivers/clk/clk-fixed-rate.c
static struct clk *_of_fixed_clk_setup(struct device_node *node) { struct clk *clk; const char *clk_name = node->name; u32 rate; u32 accuracy = 0; if (of_property_read_u32(node, "clock-frequency", &rate)) return ERR_PTR(-EIO); of_property_read_u32(node, "clock-accuracy", &accuracy); of_property_read_string(node, "clock-output-names", &clk_name); clk = clk_register_fixed_rate_with_accuracy(NULL, clk_name, NULL, 0, rate, accuracy); if (IS_ERR(clk)) return clk; ret = of_clk_add_provider(node, of_clk_src_simple_get, clk); if (ret) { clk_unregister(clk); return ERR_PTR(ret); } return clk; }
Device Tree의 요청 클럭 노드 정보로 fixed rate 타입의 클럭을 설정한다.
drivers/clk/clk-fixed-factor.c
/** * of_fixed_factor_clk_setup() - Setup function for simple fixed factor clock */
void __init of_fixed_factor_clk_setup(struct device_node *node) { _of_fixed_factor_clk_setup(node); } CLK_OF_DECLARE(fixed_factor_clk, "fixed-factor-clock", of_fixed_factor_clk_setup);
Device Tree의 요청 클럭 노드 정보로 fixed factor 타입의 클럭을 설정한다.
drivers/clk/clk-fixed-factor.c
static struct clk_hw *_of_fixed_factor_clk_setup(struct device_node *node) { struct clk *clk; const char *clk_name = node->name; const char *parent_name; u32 div, mult; int ret; if (of_property_read_u32(node, "clock-div", &div)) { pr_err("%s Fixed factor clock <%s> must have a clock-div property\n", __func__, node->name); return ERR_PTR(-EIO); } if (of_property_read_u32(node, "clock-mult", &mult)) { pr_err("%s Fixed factor clock <%s> must have a clock-mult property\n", __func__, node->name); return ERR_PTR(-EIO); } of_property_read_string(node, "clock-output-names", &clk_name); if (of_match_node(set_rate_parent_matches, node)) flags |= CLK_SET_RATE_PARENT; hw = __clk_hw_register_fixed_factor(NULL, node, clk_name, NULL, 0, flags, mult, div); if (IS_ERR(hw)) { /* * Clear OF_POPULATED flag so that clock registration can be * attempted again from probe function. */ of_node_clear_flag(node, OF_POPULATED); return ERR_CAST(hw); } ret = of_clk_add_hw_provider(node, of_clk_hw_simple_get, hw); if (ret) { clk_hw_unregister_fixed_factor(hw); return ERR_PTR(ret); } return hw; }
Device Tree의 요청 클럭 노드 정보로 fixed factor 타입의 클럭을 설정한다.
다음 그림은 rpi2의 Device Tree Script (커널 v4.10 기준)에서 각 clock에 대한 연관도를 보여준다.
9개 타입의 클럭에 대해 multiplier 타입을 제외한 등록 함수들이 API로 제공된다. 그리고 fixed rate 및 fixed factor 타입의 경우 디바이스 트리를 사용한 API도 제공된다. 소스 분석은 fixed rate 타입과 fixed factor 타입으로 제한하였다.
다음 그림과 같이 클럭은 3가지 형태로 구분할 수 있으며 총 9개의 타입을 가지고 있으며 각각의 등록 함수를 보여준다.
composite 타입을 제외한 ops를 알아본다. composite 타입의 경우 별도의 ops를 사용하지 않고, 다른 클럭 타입들 중 rate, mux 및 gate 클럭의 ops를 두 개 이상 지정하여 복합 구성할 수 있다.
/drivers/clk/clk-fixed-rate.c
const struct clk_ops clk_fixed_rate_ops = { .recalc_rate = clk_fixed_rate_recalc_rate, .recalc_accuracy = clk_fixed_rate_recalc_accuracy, }; EXPORT_SYMBOL_GPL(clk_fixed_rate_ops);
/drivers/clk/clk-fixed-factor.c
const struct clk_ops clk_fixed_factor_ops = { .round_rate = clk_factor_round_rate, .set_rate = clk_factor_set_rate, .recalc_rate = clk_factor_recalc_rate, }; EXPORT_SYMBOL_GPL(clk_fixed_factor_ops);
/drivers/clk/clk-divider.c
const struct clk_ops clk_divider_ops = { .recalc_rate = clk_divider_recalc_rate, .round_rate = clk_divider_round_rate, .set_rate = clk_divider_set_rate, }; EXPORT_SYMBOL_GPL(clk_divider_ops);
/drivers/clk/clk-divider.c
const struct clk_ops clk_divider_ro_ops = { .recalc_rate = clk_divider_recalc_rate, .round_rate = clk_divider_round_rate, }; EXPORT_SYMBOL_GPL(clk_divider_ro_ops);
/drivers/clk/
const struct clk_ops clk_fractional_divider_ops = { .recalc_rate = clk_fd_recalc_rate, .round_rate = clk_fd_round_rate, .set_rate = clk_fd_set_rate, }; EXPORT_SYMBOL_GPL(clk_fractional_divider_ops);
/drivers/clk/clk-fractional-divider.c
const struct clk_ops clk_multiplier_ops = { .recalc_rate = clk_multiplier_recalc_rate, .round_rate = clk_multiplier_round_rate, .set_rate = clk_multiplier_set_rate, }; EXPORT_SYMBOL_GPL(clk_multiplier_ops);
/drivers/clk/clk-mux.c
const struct clk_ops clk_mux_ops = { .get_parent = clk_mux_get_parent, .set_parent = clk_mux_set_parent, .determine_rate = clk_mux_determine_rate, }; EXPORT_SYMBOL_GPL(clk_mux_ops);
/drivers/clk/clk-mux.c
const struct clk_ops clk_mux_ro_ops = { .get_parent = clk_mux_get_parent, }; EXPORT_SYMBOL_GPL(clk_mux_ro_ops);
/drivers/clk/clk-gpio.c
const struct clk_ops clk_gpio_gate_ops = { .enable = clk_gpio_gate_enable, .disable = clk_gpio_gate_disable, .is_enabled = clk_gpio_gate_is_enabled, }; EXPORT_SYMBOL_GPL(clk_gpio_gate_ops);
/drivers/clk/clk-gate.c
const struct clk_ops clk_gate_ops = { .enable = clk_gate_enable, .disable = clk_gate_disable, .is_enabled = clk_gate_is_enabled, }; EXPORT_SYMBOL_GPL(clk_gate_ops);
drivers/clk/clk-fixed-rate.c
struct clk *clk_register_fixed_rate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate) { return clk_register_fixed_rate_with_accuracy(dev, name, parent_name, flags, fixed_rate, 0); } EXPORT_SYMBOL_GPL(clk_register_fixed_rate);
클럭 디바이스, @name, @parent_name, @flags, @fixed_rate 정보를 인수로 accuracy가 0인 fixed rate 타입의 클럭을 등록한다.
drivers/clk/clk-fixed-rate.c
struct clk *clk_register_fixed_rate_with_accuracy(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate, unsigned long fixed_accuracy) { struct clk_hw *hw; hw = clk_hw_register_fixed_rate_with_accuracy(dev, name, parent_name, flags, fixed_rate, fixed_accuracy); if (IS_ERR(hw)) return ERR_CAST(hw); return hw->clk; } EXPORT_SYMBOL_GPL(clk_register_fixed_rate_with_accuracy);
클럭 디바이스, @name, @parent_name, @flags, @fixed_rate, @fixed_accuracy 정보를 인수로 받아 fixed rate 타입의 클럭을 등록한다.
drivers/clk/clk-fixed-rate.c
/** * clk_hw_register_fixed_rate - register fixed-rate clock with the clock * framework * @dev: device that is registering this clock * @name: name of this clock * @parent_name: name of clock's parent * @flags: framework-specific flags * @fixed_rate: non-adjustable clock rate */
struct clk_hw *clk_hw_register_fixed_rate(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate) { return clk_hw_register_fixed_rate_with_accuracy(dev, name, parent_name, flags, fixed_rate, 0); } EXPORT_SYMBOL_GPL(clk_hw_register_fixed_rate);
클럭 디바이스, @name, @parent_name, @flags, @fixed_rate 정보를 인수로 accuracy가 0인 fixed rate 타입의 클럭을 등록한다.
drivers/clk/clk-fixed-rate.c
/** * clk_hw_register_fixed_rate_with_accuracy - register fixed-rate clock with * the clock framework * @dev: device that is registering this clock * @name: name of this clock * @parent_name: name of clock's parent * @flags: framework-specific flags * @fixed_rate: non-adjustable clock rate * @fixed_accuracy: non-adjustable clock rate */
struct clk_hw *clk_hw_register_fixed_rate_with_accuracy(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned long fixed_rate, unsigned long fixed_accuracy) { struct clk_fixed_rate *fixed; struct clk_hw *hw; struct clk_init_data init; int ret; /* allocate fixed-rate clock */ fixed = kzalloc(sizeof(*fixed), GFP_KERNEL); if (!fixed) return ERR_PTR(-ENOMEM); init.name = name; init.ops = &clk_fixed_rate_ops; init.flags = flags; init.parent_names = (parent_name ? &parent_name: NULL); init.num_parents = (parent_name ? 1 : 0); /* struct clk_fixed_rate assignments */ fixed->fixed_rate = fixed_rate; fixed->fixed_accuracy = fixed_accuracy; fixed->hw.init = &init; /* register the clock */ hw = &fixed->hw; ret = clk_hw_register(dev, hw); if (ret) { kfree(fixed); hw = ERR_PTR(ret); } return hw; } EXPORT_SYMBOL_GPL(clk_hw_register_fixed_rate_with_accuracy);
클럭 디바이스, @name, @parent_name, @flags, @fixed_rate, @fixed_accuracy 정보를 인수로 받아 fixed rate 타입의 클럭을 등록한다.
다음 그림은 fixed rate 타입의 클럭을 등록하는 모습을 보여준다.
drivers/clk/clk-fixed-factor.c
struct clk *clk_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div) { struct clk_hw *hw; hw = clk_hw_register_fixed_factor(dev, name, parent_name, flags, mult, div); if (IS_ERR(hw)) return ERR_CAST(hw); return hw->clk; } EXPORT_SYMBOL_GPL(clk_register_fixed_factor);
클럭 디바이스, @name, @parent_name, @flags, @multi, @div 정보를 인수로 받아 fixed factor 타입의 클럭을 등록한다.
drivers/clk/clk-fixed-factor.c
struct clk_hw *clk_hw_register_fixed_factor(struct device *dev, const char *name, const char *parent_name, unsigned long flags, unsigned int mult, unsigned int div) { return __clk_hw_register_fixed_factor(dev, NULL, name, parent_name, -1, flags, mult, div); } EXPORT_SYMBOL_GPL(clk_hw_register_fixed_factor);
클럭 디바이스, @name, @parent_name, @flags, @multi, @div 정보를 인수로 받아 fixed factor 타입의 클럭을 등록한다.
drivers/clk/clk-fixed-factor.c
static struct clk_hw * __clk_hw_register_fixed_factor(struct device *dev, struct device_node *np, const char *name, const char *parent_name, int index, unsigned long flags, unsigned int mult, unsigned int div) { struct clk_fixed_factor *fix; struct clk_init_data init = { }; struct clk_parent_data pdata = { .index = index }; struct clk_hw *hw; int ret; fix = kmalloc(sizeof(*fix), GFP_KERNEL); if (!fix) return ERR_PTR(-ENOMEM); /* struct clk_fixed_factor assignments */ fix->mult = mult; fix->div = div; fix->hw.init = &init; init.name = name; init.ops = &clk_fixed_factor_ops; init.flags = flags; if (parent_name) init.parent_names = &parent_name; else init.parent_data = &pdata; init.num_parents = 1; hw = &fix->hw; if (dev) ret = clk_hw_register(dev, hw); else ret = of_clk_hw_register(np, hw); if (ret) { kfree(fix); hw = ERR_PTR(ret); } return hw; }
클럭 디바이스, @name, @parent_name, @flags, multi, @div 정보를 인수로 받아 fixed factor 타입의 클럭을 등록한다.
drivers/clk/clk.c
/** * clk_register - allocate a new clock, register it and return an opaque cookie * @dev: device that is registering this clock * @hw: link to hardware-specific clock data * * clk_register is the *deprecated* interface for populating the clock tree with * new clock nodes. Use clk_hw_register() instead. * * Returns: a pointer to the newly allocated struct clk which * cannot be dereferenced by driver code but may be used in conjunction with the * rest of the clock API. In the event of an error clk_register will return an * error code; drivers must test for an error code after calling clk_register. */
struct clk *clk_register(struct device *dev, struct clk_hw *hw) { return __clk_register(dev, dev_of_node(dev), hw); } EXPORT_SYMBOL_GPL(clk_register);
요청한 클럭 디바이스를 할당하여 등록한다.
static struct clk * __clk_register(struct device *dev, struct device_node *np, struct clk_hw *hw) { int ret; struct clk_core *core; const struct clk_init_data *init = hw->init; /* * The init data is not supposed to be used outside of registration path. * Set it to NULL so that provider drivers can't use it either and so that * we catch use of hw->init early on in the core. */ hw->init = NULL; core = kzalloc(sizeof(*core), GFP_KERNEL); if (!core) { ret = -ENOMEM; goto fail_out; } core->name = kstrdup_const(init->name, GFP_KERNEL); if (!core->name) { ret = -ENOMEM; goto fail_name; } if (WARN_ON(!init->ops)) { ret = -EINVAL; goto fail_ops; } core->ops = init->ops; if (dev && pm_runtime_enabled(dev)) core->rpm_enabled = true; core->dev = dev; core->of_node = np; if (dev && dev->driver) core->owner = dev->driver->owner; core->hw = hw; core->flags = init->flags; core->num_parents = init->num_parents; core->min_rate = 0; core->max_rate = ULONG_MAX; hw->core = core; ret = clk_core_populate_parent_map(core, init); if (ret) goto fail_parents; INIT_HLIST_HEAD(&core->clks); /* * Don't call clk_hw_create_clk() here because that would pin the * provider module to itself and prevent it from ever being removed. */ hw->clk = alloc_clk(core, NULL, NULL); if (IS_ERR(hw->clk)) { ret = PTR_ERR(hw->clk); goto fail_create_clk; } clk_core_link_consumer(hw->core, hw->clk); ret = __clk_core_init(core); if (!ret) return hw->clk; clk_prepare_lock(); clk_core_unlink_consumer(hw->clk); clk_prepare_unlock(); free_clk(hw->clk); hw->clk = NULL; fail_create_clk: clk_core_free_parent_map(core); fail_parents: fail_ops: kfree_const(core->name); fail_name: kfree(core); fail_out: return ERR_PTR(ret); }
요청한 클럭 디바이스를 할당하여 등록한다.
다음 그림은 부모 노드를 가진 클럭을 등록하는 모습을 보여준다.
다음 그림은 등록된 클럭의 부모 관계를 보여준다. 클럭을 사용하는 관점에서 Consumer용 클럭도 같이 표현하였다.
drivers/clk/clk.c
static int clk_core_populate_parent_map(struct clk_core *core, const struct clk_init_data *init) { u8 num_parents = init->num_parents; const char * const *parent_names = init->parent_names; const struct clk_hw **parent_hws = init->parent_hws; const struct clk_parent_data *parent_data = init->parent_data; int i, ret = 0; struct clk_parent_map *parents, *parent; if (!num_parents) return 0; /* * Avoid unnecessary string look-ups of clk_core's possible parents by * having a cache of names/clk_hw pointers to clk_core pointers. */ parents = kcalloc(num_parents, sizeof(*parents), GFP_KERNEL); core->parents = parents; if (!parents) return -ENOMEM; /* Copy everything over because it might be __initdata */ for (i = 0, parent = parents; i < num_parents; i++, parent++) { parent->index = -1; if (parent_names) { /* throw a WARN if any entries are NULL */ WARN(!parent_names[i], "%s: invalid NULL in %s's .parent_names\n", __func__, core->name); ret = clk_cpy_name(&parent->name, parent_names[i], true); } else if (parent_data) { parent->hw = parent_data[i].hw; parent->index = parent_data[i].index; ret = clk_cpy_name(&parent->fw_name, parent_data[i].fw_name, false); if (!ret) ret = clk_cpy_name(&parent->name, parent_data[i].name, false); } else if (parent_hws) { parent->hw = parent_hws[i]; } else { ret = -EINVAL; WARN(1, "Must specify parents if num_parents > 0\n"); } if (ret) { do { kfree_const(parents[i].name); kfree_const(parents[i].fw_name); } while (--i >= 0); kfree(parents); return ret; } } return 0; }
요청한 클럭의 부모들을 연결하는 부모 맵을 구성한다. @init에서 넘겨준 parent_names, parent_data 또는 parent_hws 라는 세 가지 정보들 중 하나로 부모 정보를 구성한다.
다음 그림은 해당 클럭의 부모들 연결하는 부모 맵을 구성하는 과정을 보여준다.
drivers/clk/clk.c
/** * alloc_clk - Allocate a clk consumer, but leave it unlinked to the clk_core * @core: clk to allocate a consumer for * @dev_id: string describing device name * @con_id: connection ID string on device * * Returns: clk consumer left unlinked from the consumer list */
static struct clk *alloc_clk(struct clk_core *core, const char *dev_id, const char *con_id) { struct clk *clk; clk = kzalloc(sizeof(*clk), GFP_KERNEL); if (!clk) return ERR_PTR(-ENOMEM); clk->core = core; clk->dev_id = dev_id; clk->con_id = kstrdup_const(con_id, GFP_KERNEL); clk->max_rate = ULONG_MAX; return clk; }
클럭을 할당하고 구성한 후 반환한다.
drivers/clk/clk.c -1/3-
/** * __clk_core_init - initialize the data structures in a struct clk_core * @core: clk_core being initialized * * Initializes the lists in struct clk_core, queries the hardware for the * parent and rate and sets them both. */
static int __clk_core_init(struct clk_core *core) { int ret; struct clk_core *orphan; struct hlist_node *tmp2; unsigned long rate; if (!core) return -EINVAL; clk_prepare_lock(); ret = clk_pm_runtime_get(core); if (ret) goto unlock; /* check to see if a clock with this name is already registered */ if (clk_core_lookup(core->name)) { pr_debug("%s: clk %s already initialized\n", __func__, core->name); ret = -EEXIST; goto out; } /* check that clk_ops are sane. See Documentation/driver-api/clk.rst */ if (core->ops->set_rate && !((core->ops->round_rate || core->ops->determine_rate) && core->ops->recalc_rate)) { pr_err("%s: %s must implement .round_rate or .determine_rate in addition to .recalc__ rate\n", __func__, core->name); ret = -EINVAL; goto out; } if (core->ops->set_parent && !core->ops->get_parent) { pr_err("%s: %s must implement .get_parent & .set_parent\n", __func__, core->name); ret = -EINVAL; goto out; } if (core->num_parents > 1 && !core->ops->get_parent) { pr_err("%s: %s must implement .get_parent as it has multi parents\n", __func__, core->name); ret = -EINVAL; goto out; } if (core->ops->set_rate_and_parent && !(core->ops->set_parent && core->ops->set_rate)) { pr_err("%s: %s must implement .set_parent & .set_rate\n", __func__, core->name); ret = -EINVAL; goto out; } core->parent = __clk_init_parent(core);
클럭을 초기화한다. 이 때 부모 클럭 관계와 rate, accuracy 등을 설정한다.
drivers/clk/clk.c -2/3-
. /* * Populate core->parent if parent has already been clk_core_init'd. If * parent has not yet been clk_core_init'd then place clk in the orphan * list. If clk doesn't have any parents then place it in the root * clk list. * * Every time a new clk is clk_init'd then we walk the list of orphan * clocks and re-parent any that are children of the clock currently * being clk_init'd. */ if (core->parent) { hlist_add_head(&core->child_node, &core->parent->children); core->orphan = core->parent->orphan; } else if (!core->num_parents) { hlist_add_head(&core->child_node, &clk_root_list); core->orphan = false; } else { hlist_add_head(&core->child_node, &clk_orphan_list); core->orphan = true; } /* * optional platform-specific magic * * The .init callback is not used by any of the basic clock types, but * exists for weird hardware that must perform initialization magic. * Please consider other ways of solving initialization problems before * using this callback, as its use is discouraged. */ if (core->ops->init) core->ops->init(core->hw); /* * Set clk's accuracy. The preferred method is to use * .recalc_accuracy. For simple clocks and lazy developers the default * fallback is to use the parent's accuracy. If a clock doesn't have a * parent (or is orphaned) then accuracy is set to zero (perfect * clock). */ if (core->ops->recalc_accuracy) core->accuracy = core->ops->recalc_accuracy(core->hw, __clk_get_accuracy(core->parent)); else if (core->parent) core->accuracy = core->parent->accuracy; else core->accuracy = 0; /* * Set clk's phase. * Since a phase is by definition relative to its parent, just * query the current clock phase, or just assume it's in phase. */ if (core->ops->get_phase) core->phase = core->ops->get_phase(core->hw); else core->phase = 0; /* * Set clk's duty cycle. */ clk_core_update_duty_cycle_nolock(core); /* * Set clk's rate. The preferred method is to use .recalc_rate. For * simple clocks and lazy developers the default fallback is to use the * parent's rate. If a clock doesn't have a parent (or is orphaned) * then rate is set to zero. */ if (core->ops->recalc_rate) rate = core->ops->recalc_rate(core->hw, clk_core_get_rate_nolock(core->parent)); else if (core->parent) rate = core->parent->rate; else rate = 0; core->rate = core->req_rate = rate;
drivers/clk/clk.c -3/3-
/* * Enable CLK_IS_CRITICAL clocks so newly added critical clocks * don't get accidentally disabled when walking the orphan tree and * reparenting clocks */ if (core->flags & CLK_IS_CRITICAL) { unsigned long flags; clk_core_prepare(core); flags = clk_enable_lock(); clk_core_enable(core); clk_enable_unlock(flags); } /* * walk the list of orphan clocks and reparent any that newly finds a * parent. */ hlist_for_each_entry_safe(orphan, tmp2, &clk_orphan_list, child_node) { struct clk_core *parent = __clk_init_parent(orphan); /* * We need to use __clk_set_parent_before() and _after() to * to properly migrate any prepare/enable count of the orphan * clock. This is important for CLK_IS_CRITICAL clocks, which * are enabled during init but might not have a parent yet. */ if (parent) { /* update the clk tree topology */ __clk_set_parent_before(orphan, parent); __clk_set_parent_after(orphan, parent, NULL); __clk_recalc_accuracies(orphan); __clk_recalc_rates(orphan, 0); } } kref_init(&core->ref); out: clk_pm_runtime_put(core); unlock: clk_prepare_unlock(); if (!ret) clk_debug_register(core); return ret; }
다음 그림은 clock 루트 리스트에 포함되는 클럭과 clock 고아 리스트에 포함되는 클럭의 속성 차이를 보여준다.
drivers/clk/clk.c
static struct clk_core *__clk_init_parent(struct clk_core *core) { u8 index = 0; if (core->num_parents > 1 && core->ops->get_parent) index = core->ops->get_parent(core->hw); return clk_core_get_parent_by_index(core, index); }
부모 클럭을 알아와서 연결한다.
다음과 같이 클럭 provider를 추가하는 두 개의 API가 지원된다.
drivers/clk/clk.c
/** * of_clk_add_provider() - Register a clock provider for a node * @np: Device node pointer associated with clock provider * @clk_src_get: callback for decoding clock * @data: context pointer for @clk_src_get callback. * * This function is *deprecated*. Use of_clk_add_hw_provider() instead. */
int of_clk_add_provider(struct device_node *np, struct clk *(*clk_src_get)(struct of_phandle_args *clkspec, void *data), void *data) { struct of_clk_provider *cp; int ret; cp = kzalloc(sizeof(*cp), GFP_KERNEL); if (!cp) return -ENOMEM; cp->node = of_node_get(np); cp->data = data; cp->get = clk_src_get; mutex_lock(&of_clk_mutex); list_add(&cp->link, &of_clk_providers); mutex_unlock(&of_clk_mutex); pr_debug("Added clock from %pOF\n", np); ret = of_clk_set_defaults(np, true); if (ret < 0) of_clk_del_provider(np); return ret; } EXPORT_SYMBOL_GPL(of_clk_add_provider);
디바이스 노드의 클럭 provder를 추가한다. 추가 시 clk를 반환하는 함수를 지정한다. 성공하면 0을 반환한다.
drivers/clk/clk.c
/** * of_clk_add_hw_provider() - Register a clock provider for a node * @np: Device node pointer associated with clock provider * @get: callback for decoding clk_hw * @data: context pointer for @get callback. */
int of_clk_add_hw_provider(struct device_node *np, struct clk_hw *(*get)(struct of_phandle_args *clkspec, void *data), void *data) { struct of_clk_provider *cp; int ret; cp = kzalloc(sizeof(*cp), GFP_KERNEL); if (!cp) return -ENOMEM; cp->node = of_node_get(np); cp->data = data; cp->get_hw = get; mutex_lock(&of_clk_mutex); list_add(&cp->link, &of_clk_providers); mutex_unlock(&of_clk_mutex); pr_debug("Added clk_hw provider from %pOF\n", np); ret = of_clk_set_defaults(np, true); if (ret < 0) of_clk_del_provider(np); return ret; } EXPORT_SYMBOL_GPL(of_clk_add_hw_provider);
디바이스 노드의 클럭 provider를 추가한다. 추가 시 clk_hw를 반환하는 함수를 지정한다. 성공하면 0을 반환한다.
다음 그림은 provider에 등록된 클럭을 사용하는 함수 이후의 호출 과정을 보여준다.
drivers/clk/clk.c
/** * of_clk_get_from_provider() - Lookup a clock from a clock provider * @clkspec: pointer to a clock specifier data structure * * This function looks up a struct clk from the registered list of clock * providers, an input is a clock specifier data structure as returned * from the of_parse_phandle_with_args() function call. */
struct clk *of_clk_get_from_provider(struct of_phandle_args *clkspec) { struct clk_hw *hw = of_clk_get_hw_from_clkspec(clkspec); return clk_hw_create_clk(NULL, hw, NULL, __func__); } EXPORT_SYMBOL_GPL(of_clk_get_from_provider);
클럭 providers 리스트에서 of_phandle_args 값으로 클럭을 검색하여 반환한다.
drivers/clk/clk.c
static struct clk_hw * of_clk_get_hw_from_clkspec(struct of_phandle_args *clkspec) { struct of_clk_provider *provider; struct clk_hw *hw = ERR_PTR(-EPROBE_DEFER); if (!clkspec) return ERR_PTR(-EINVAL); mutex_lock(&of_clk_mutex); list_for_each_entry(provider, &of_clk_providers, link) { if (provider->node == clkspec->np) { hw = __of_clk_get_hw_from_provider(provider, clkspec); if (!IS_ERR(hw)) break; } } mutex_unlock(&of_clk_mutex); return hw; }
of_clk_providers 리스트에서 루프를 돌며 요청 노드를 찾은 후 등록된 클럭 hw를 찾아 반환한다.
drivers/clk/clk.c
static struct clk_hw * __of_clk_get_hw_from_provider(struct of_clk_provider *provider, struct of_phandle_args *clkspec) { struct clk *clk; if (provider->get_hw) return provider->get_hw(clkspec, provider->data); clk = provider->get(clkspec, provider->data); if (IS_ERR(clk)) return ERR_CAST(clk); return __clk_get_hw(clk); }
클럭 hw를 반환한다.
다음 그림은 uart 디바이스에 사용하는 클럭을 알아오는 모습을 보여준다.
drivers/clk/clk.c
struct clk *__of_clk_get_from_provider(struct of_phandle_args *clkspec, const char *dev_id, const char *con_id) { struct of_clk_provider *provider; struct clk *clk = ERR_PTR(-EPROBE_DEFER); /* Check if we have such a provider in our array */ list_for_each_entry(provider, &of_clk_providers, link) { if (provider->node == clkspec->np) clk = provider->get(clkspec, provider->data); if (!IS_ERR(clk)) { clk = __clk_create_clk(__clk_get_hw(clk), dev_id, con_id); if (!IS_ERR(clk) && !__clk_get(clk)) { __clk_free_clk(clk); clk = ERR_PTR(-ENOENT); } break; } } return clk; }
of_clk_providers 리스트에서 루프를 돌며 요청 device_node를 찾은 후 등록된 get 후크 함수를 호출하여 clk을 찾게되면 clk를 새로 할당받아 구성한 후 반환한다.
drivers/clk/clk.c
/** * clk_hw_create_clk: Allocate and link a clk consumer to a clk_core given * a clk_hw * @dev: clk consumer device * @hw: clk_hw associated with the clk being consumed * @dev_id: string describing device name * @con_id: connection ID string on device * * This is the main function used to create a clk pointer for use by clk * consumers. It connects a consumer to the clk_core and clk_hw structures * used by the framework and clk provider respectively. */
struct clk *clk_hw_create_clk(struct device *dev, struct clk_hw *hw, const char *dev_id, const char *con_id) { struct clk *clk; struct clk_core *core; /* This is to allow this function to be chained to others */ if (IS_ERR_OR_NULL(hw)) return ERR_CAST(hw); core = hw->core; clk = alloc_clk(core, dev_id, con_id); if (IS_ERR(clk)) return clk; clk->dev = dev; if (!try_module_get(core->owner)) { free_clk(clk); return ERR_PTR(-ENOENT); } kref_get(&core->ref); clk_core_link_consumer(core, clk); return clk; }
클럭 consumer를 할당하고 클럭 core에 연결하고, 클럭을 반환한다.
다음 그림은 of_clk_get() 함수 이후의 호출 과정을 보여준다.
drivers/clk/clk.c
struct clk *of_clk_get(struct device_node *np, int index) { return __of_clk_get(np, index, np->full_name, NULL); } EXPORT_SYMBOL(of_clk_get);
지정한 디바이스 노드에서 부모 @index에 해당하는 클럭을 알아온다.
다음 그림은 부모 인덱스 값으로 부모 클럭을 알아오는 모습을 보여준다.
drivers/clk/clk.c
static struct clk *__of_clk_get(struct device_node *np, int index, const char *dev_id, const char *con_id) { struct clk_hw *hw = of_clk_get_hw(np, index, con_id); return clk_hw_create_clk(NULL, hw, dev_id, con_id); }
디바이스 노드에서 부모 index 또는 @con_id(이름)로 부모 클럭을 알아온다.
drivers/clk/clk.c
struct clk_hw *of_clk_get_hw(struct device_node *np, int index, const char *con_id) { int ret; struct clk_hw *hw; struct of_phandle_args clkspec; ret = of_parse_clkspec(np, index, con_id, &clkspec); if (ret) return ERR_PTR(ret); hw = of_clk_get_hw_from_clkspec(&clkspec); of_node_put(clkspec.np); return hw; }
디바이스 노드에서 @con_id(이름) 또는 부모 @index로 부모 클럭 hw를 알아온다.
drivers/clk/clk.c
/** * of_parse_clkspec() - Parse a DT clock specifier for a given device node * @np: device node to parse clock specifier from * @index: index of phandle to parse clock out of. If index < 0, @name is used * @name: clock name to find and parse. If name is NULL, the index is used * @out_args: Result of parsing the clock specifier * * Parses a device node's "clocks" and "clock-names" properties to find the * phandle and cells for the index or name that is desired. The resulting clock * specifier is placed into @out_args, or an errno is returned when there's a * parsing error. The @index argument is ignored if @name is non-NULL. * * Example: * * phandle1: clock-controller@1 { * #clock-cells = <2>; * } * * phandle2: clock-controller@2 { * #clock-cells = <1>; * } * * clock-consumer@3 { * clocks = <&phandle1 1 2 &phandle2 3>; * clock-names = "name1", "name2"; * } * * To get a device_node for `clock-controller@2' node you may call this * function a few different ways: * * of_parse_clkspec(clock-consumer@3, -1, "name2", &args); * of_parse_clkspec(clock-consumer@3, 1, NULL, &args); * of_parse_clkspec(clock-consumer@3, 1, "name2", &args); * * Return: 0 upon successfully parsing the clock specifier. Otherwise, -ENOENT * if @name is NULL or -EINVAL if @name is non-NULL and it can't be found in * the "clock-names" property of @np. */
static int of_parse_clkspec(const struct device_node *np, int index, const char *name, struct of_phandle_args *out_args) { int ret = -ENOENT; /* Walk up the tree of devices looking for a clock property that matches */ while (np) { /* * For named clocks, first look up the name in the * "clock-names" property. If it cannot be found, then index * will be an error code and of_parse_phandle_with_args() will * return -EINVAL. */ if (name) index = of_property_match_string(np, "clock-names", name); ret = of_parse_phandle_with_args(np, "clocks", "#clock-cells", index, out_args); if (!ret) break; if (name && index >= 0) break; /* * No matching clock found on this node. If the parent node * has a "clock-ranges" property, then we can try one of its * clocks. */ np = np->parent; if (np && !of_get_property(np, "clock-ranges", NULL)) break; index = 0; } return ret; }
디바이스 노드에서 @name 또는 @index에 해당하는 phandle 및 argument 값들을 알아와서 출력 인자 @out_args에 대입해온다.
drivers/clk/clk-conf.c
/** * of_clk_set_defaults() - parse and set assigned clocks configuration * @node: device node to apply clock settings for * @clk_supplier: true if clocks supplied by @node should also be considered * * This function parses 'assigned-{clocks/clock-parents/clock-rates}' properties * and sets any specified clock parents and rates. The @clk_supplier argument * should be set to true if @node may be also a clock supplier of any clock * listed in its 'assigned-clocks' or 'assigned-clock-parents' properties. * If @clk_supplier is false the function exits returnning 0 as soon as it * determines the @node is also a supplier of any of the clocks. */ int of_clk_set_defaults(struct device_node *node, bool clk_supplier) { int rc; if (!node) return 0; rc = __set_clk_parents(node, clk_supplier); if (rc < 0) return rc; return __set_clk_rates(node, clk_supplier); } EXPORT_SYMBOL_GPL(of_clk_set_defaults);
요청한 클럭 디바이스의 부모 클럭을 설정하고 rate를 설정한다.
drivers/clk/clk-conf.c
static int __set_clk_parents(struct device_node *node, bool clk_supplier) { struct of_phandle_args clkspec; int index, rc, num_parents; struct clk *clk, *pclk; num_parents = of_count_phandle_with_args(node, "assigned-clock-parents", "#clock-cells"); if (num_parents == -EINVAL) pr_err("clk: invalid value of clock-parents property at %pOF\n", node); for (index = 0; index < num_parents; index++) { rc = of_parse_phandle_with_args(node, "assigned-clock-parents", "#clock-cells", index, &clkspec); if (rc < 0) { /* skip empty (null) phandles */ if (rc == -ENOENT) continue; else return rc; } if (clkspec.np == node && !clk_supplier) return 0; pclk = of_clk_get_from_provider(&clkspec); if (IS_ERR(pclk)) { if (PTR_ERR(pclk) != -EPROBE_DEFER) pr_warn("clk: couldn't get parent clock %d for %pOF\n", index, node); return PTR_ERR(pclk); } rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells", index, &clkspec); if (rc < 0) goto err; if (clkspec.np == node && !clk_supplier) { rc = 0; goto err; } clk = of_clk_get_from_provider(&clkspec); if (IS_ERR(clk)) { if (PTR_ERR(clk) != -EPROBE_DEFER) pr_warn("clk: couldn't get assigned clock %d for %pOF\n", index, node); rc = PTR_ERR(clk); goto err; } rc = clk_set_parent(clk, pclk); if (rc < 0) pr_err("clk: failed to reparent %s to %s: %d\n", __clk_get_name(clk), __clk_get_name(pclk), rc); clk_put(clk); clk_put(pclk); } return 0; err: clk_put(pclk); return rc; }
부모 클럭을 선택한다.
drivers/clk/clk-conf.c
static int __set_clk_rates(struct device_node *node, bool clk_supplier) { struct of_phandle_args clkspec; struct property *prop; const __be32 *cur; int rc, index = 0; struct clk *clk; u32 rate; of_property_for_each_u32(node, "assigned-clock-rates", prop, cur, rate) { if (rate) { rc = of_parse_phandle_with_args(node, "assigned-clocks", "#clock-cells", index, &clkspec); if (rc < 0) { /* skip empty (null) phandles */ if (rc == -ENOENT) continue; else return rc; } if (clkspec.np == node && !clk_supplier) return 0; clk = of_clk_get_from_provider(&clkspec); if (IS_ERR(clk)) { if (PTR_ERR(clk) != -EPROBE_DEFER) pr_warn("clk: couldn't get clock %d for %pOF\n", index, node); return PTR_ERR(clk); } rc = clk_set_rate(clk, rate); if (rc < 0) pr_err("clk: couldn't set %s clk rate to %u (%d), current rate: %lu\\ n", __clk_get_name(clk), rate, rc, clk_get_rate(clk)); clk_put(clk); } index++; } return 0; }
요청한 클럭 디바이스의 rate를 설정한다.
다음 스크립트를 보면 pwm 노드에서 사용할 클럭 소스로 clocks BCM2835_CLOCK_PWN을 지정하였고 이 클럭을 10Mhz로 설정하는 것을 알 수 있다.
arch/arm/boot/dts/bcm283x.dtsi – raspberrypi 커널 v4.9.y
clocks: cprman@7e101000 { compatible = "brcm,bcm2835-cprman"; #clock-cells = <1>; reg = <0x7e101000 0x2000>; clocks = <&clk_osc>, <&dsi0 0>, <&dsi0 1>, <&dsi0 2>, <&dsi1 0>, <&dsi1 1>, <&dsi1 2>; }; pwm: pwm@7e20c000 { compatible = "brcm,bcm2835-pwm"; reg = <0x7e20c000 0x28>; clocks = <&clocks BCM2835_CLOCK_PWM>; assigned-clocks = <&clocks BCM2835_CLOCK_PWM>; assigned-clock-rates = <10000000>; #pwm-cells = <2>; status = "disabled"; };
다음의 플래그들은 최상위 framework인 common clock framework에서 유효하다.
include/linux/clk-provider.h
/* * flags used across common struct clk. these flags should only affect the * top-level framework. custom flags for dealing with hardware specifics * belong in struct clk_foo * * Please update clk_flags[] in drivers/clk/clk.c when making changes here! */
#define CLK_SET_RATE_GATE BIT(0) /* must be gated across rate change */ #define CLK_SET_PARENT_GATE BIT(1) /* must be gated across re-parent */ #define CLK_SET_RATE_PARENT BIT(2) /* propagate rate change up one level */ #define CLK_IGNORE_UNUSED BIT(3) /* do not gate even if unused */ /* unused */ /* unused */ #define CLK_GET_RATE_NOCACHE BIT(6) /* do not use the cached clk rate */ #define CLK_SET_RATE_NO_REPARENT BIT(7) /* don't re-parent on rate change */ #define CLK_GET_ACCURACY_NOCACHE BIT(8) /* do not use the cached clk accuracy */ #define CLK_RECALC_NEW_RATES BIT(9) /* recalc rates after notifications */ #define CLK_SET_RATE_UNGATE BIT(10) /* clock needs to run to set rate */ #define CLK_IS_CRITICAL BIT(11) /* do not gate, ever */ /* parents need enable during gate/ungate, set rate and re-parent */ #define CLK_OPS_PARENT_ENABLE BIT(12) /* duty cycle call may be forwarded to the parent clock */ #define CLK_DUTY_CYCLE_PARENT BIT(13)
drivers/clk/clk.c
struct clk { struct clk_core *core; struct device *dev; const char *dev_id; const char *con_id; unsigned long min_rate; unsigned long max_rate; unsigned int exclusive_count; struct hlist_node clks_node; };
drivers/clk/clk.c
struct clk_core { const char *name; const struct clk_ops *ops; struct clk_hw *hw; struct module *owner; struct device *dev; struct device_node *of_node; struct clk_core *parent; struct clk_parent_map *parents; u8 num_parents; u8 new_parent_index; unsigned long rate; unsigned long req_rate; unsigned long new_rate; struct clk_core *new_parent; struct clk_core *new_child; unsigned long flags; bool orphan; bool rpm_enabled; unsigned int enable_count; unsigned int prepare_count; unsigned int protect_count; unsigned long min_rate; unsigned long max_rate; unsigned long accuracy; int phase; struct clk_duty duty; struct hlist_head children; struct hlist_node child_node; struct hlist_head clks; unsigned int notifier_count; #ifdef CONFIG_DEBUG_FS struct dentry *dentry; struct hlist_node debug_node; #endif struct kref ref; };
drivers/clk/clk.c
/** * struct clk_ops - Callback operations for hardware clocks; these are to * be provided by the clock implementation, and will be called by drivers * through the clk_* api. * * @prepare: Prepare the clock for enabling. This must not return until * the clock is fully prepared, and it's safe to call clk_enable. * This callback is intended to allow clock implementations to * do any initialisation that may sleep. Called with * prepare_lock held. * * @unprepare: Release the clock from its prepared state. This will typically * undo any work done in the @prepare callback. Called with * prepare_lock held. * * @is_prepared: Queries the hardware to determine if the clock is prepared. * This function is allowed to sleep. Optional, if this op is not * set then the prepare count will be used. * * @unprepare_unused: Unprepare the clock atomically. Only called from * clk_disable_unused for prepare clocks with special needs. * Called with prepare mutex held. This function may sleep. * * @enable: Enable the clock atomically. This must not return until the * clock is generating a valid clock signal, usable by consumer * devices. Called with enable_lock held. This function must not * sleep. * * @disable: Disable the clock atomically. Called with enable_lock held. * This function must not sleep. * * @is_enabled: Queries the hardware to determine if the clock is enabled. * This function must not sleep. Optional, if this op is not * set then the enable count will be used. * * @disable_unused: Disable the clock atomically. Only called from * clk_disable_unused for gate clocks with special needs. * Called with enable_lock held. This function must not * sleep. * * @save_context: Save the context of the clock in prepration for poweroff. * * @restore_context: Restore the context of the clock after a restoration * of power. * * @recalc_rate Recalculate the rate of this clock, by querying hardware. The * parent rate is an input parameter. It is up to the caller to * ensure that the prepare_mutex is held across this call. * Returns the calculated rate. Optional, but recommended - if * this op is not set then clock rate will be initialized to 0. * * @round_rate: Given a target rate as input, returns the closest rate actually * supported by the clock. The parent rate is an input/output * parameter. * * @determine_rate: Given a target rate as input, returns the closest rate * actually supported by the clock, and optionally the parent clock * that should be used to provide the clock rate. * * @set_parent: Change the input source of this clock; for clocks with multiple * possible parents specify a new parent by passing in the index * as a u8 corresponding to the parent in either the .parent_names * or .parents arrays. This function in affect translates an * array index into the value programmed into the hardware. * Returns 0 on success, -EERROR otherwise. * * @get_parent: Queries the hardware to determine the parent of a clock. The * return value is a u8 which specifies the index corresponding to * the parent clock. This index can be applied to either the * .parent_names or .parents arrays. In short, this function * translates the parent value read from hardware into an array * index. Currently only called when the clock is initialized by * __clk_init. This callback is mandatory for clocks with * multiple parents. It is optional (and unnecessary) for clocks * with 0 or 1 parents. * * @set_rate: Change the rate of this clock. The requested rate is specified * by the second argument, which should typically be the return * of .round_rate call. The third argument gives the parent rate * which is likely helpful for most .set_rate implementation. * Returns 0 on success, -EERROR otherwise. * * @set_rate_and_parent: Change the rate and the parent of this clock. The * requested rate is specified by the second argument, which * should typically be the return of .round_rate call. The * third argument gives the parent rate which is likely helpful * for most .set_rate_and_parent implementation. The fourth * argument gives the parent index. This callback is optional (and * unnecessary) for clocks with 0 or 1 parents as well as * for clocks that can tolerate switching the rate and the parent * separately via calls to .set_parent and .set_rate. * Returns 0 on success, -EERROR otherwise. * * @recalc_accuracy: Recalculate the accuracy of this clock. The clock accuracy * is expressed in ppb (parts per billion). The parent accuracy is * an input parameter. * Returns the calculated accuracy. Optional - if this op is not * set then clock accuracy will be initialized to parent accuracy * or 0 (perfect clock) if clock has no parent. * * @get_phase: Queries the hardware to get the current phase of a clock. * Returned values are 0-359 degrees on success, negative * error codes on failure. * * @set_phase: Shift the phase this clock signal in degrees specified * by the second argument. Valid values for degrees are * 0-359. Return 0 on success, otherwise -EERROR. * * @get_duty_cycle: Queries the hardware to get the current duty cycle ratio * of a clock. Returned values denominator cannot be 0 and must be * superior or equal to the numerator. * * @set_duty_cycle: Apply the duty cycle ratio to this clock signal specified by * the numerator (2nd argurment) and denominator (3rd argument). * Argument must be a valid ratio (denominator > 0 * and >= numerator) Return 0 on success, otherwise -EERROR. * * @init: Perform platform-specific initialization magic. * This is not not used by any of the basic clock types. * Please consider other ways of solving initialization problems * before using this callback, as its use is discouraged. * * @debug_init: Set up type-specific debugfs entries for this clock. This * is called once, after the debugfs directory entry for this * clock has been created. The dentry pointer representing that * directory is provided as an argument. Called with * prepare_lock held. Returns 0 on success, -EERROR otherwise. * * * The clk_enable/clk_disable and clk_prepare/clk_unprepare pairs allow * implementations to split any work between atomic (enable) and sleepable * (prepare) contexts. If enabling a clock requires code that might sleep, * this must be done in clk_prepare. Clock enable code that will never be * called in a sleepable context may be implemented in clk_enable. * * Typically, drivers will call clk_prepare when a clock may be needed later * (eg. when a device is opened), and clk_enable when the clock is actually * required (eg. from an interrupt). Note that clk_prepare MUST have been * called before clk_enable. */
struct clk_ops { int (*prepare)(struct clk_hw *hw); void (*unprepare)(struct clk_hw *hw); int (*is_prepared)(struct clk_hw *hw); void (*unprepare_unused)(struct clk_hw *hw); int (*enable)(struct clk_hw *hw); void (*disable)(struct clk_hw *hw); int (*is_enabled)(struct clk_hw *hw); void (*disable_unused)(struct clk_hw *hw); int (*save_context)(struct clk_hw *hw); void (*restore_context)(struct clk_hw *hw); unsigned long (*recalc_rate)(struct clk_hw *hw, unsigned long parent_rate); long (*round_rate)(struct clk_hw *hw, unsigned long rate, unsigned long *parent_rate); int (*determine_rate)(struct clk_hw *hw, struct clk_rate_request *req); int (*set_parent)(struct clk_hw *hw, u8 index); u8 (*get_parent)(struct clk_hw *hw); int (*set_rate)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate); int (*set_rate_and_parent)(struct clk_hw *hw, unsigned long rate, unsigned long parent_rate, u8 index); unsigned long (*recalc_accuracy)(struct clk_hw *hw, unsigned long parent_accuracy); int (*get_phase)(struct clk_hw *hw); int (*set_phase)(struct clk_hw *hw, int degrees); int (*get_duty_cycle)(struct clk_hw *hw, struct clk_duty *duty); int (*set_duty_cycle)(struct clk_hw *hw, struct clk_duty *duty); void (*init)(struct clk_hw *hw); void (*debug_init)(struct clk_hw *hw, struct dentry *dentry); };
drivers/clk/clk.c
/** * struct clk_hw - handle for traversing from a struct clk to its corresponding * hardware-specific structure. struct clk_hw should be declared within struct * clk_foo and then referenced by the struct clk instance that uses struct * clk_foo's clk_ops * * @core: pointer to the struct clk_core instance that points back to this * struct clk_hw instance * * @clk: pointer to the per-user struct clk instance that can be used to call * into the clk API * * @init: pointer to struct clk_init_data that contains the init data shared * with the common clock framework. This pointer will be set to NULL once * a clk_register() variant is called on this clk_hw pointer. */
struct clk_hw { struct clk_core *core; struct clk *clk; const struct clk_init_data *init; };
drivers/clk/clk.c
/** * struct clk_parent_data - clk parent information * @hw: parent clk_hw pointer (used for clk providers with internal clks) * @fw_name: parent name local to provider registering clk * @name: globally unique parent name (used as a fallback) * @index: parent index local to provider registering clk (if @fw_name absent) */
struct clk_parent_data { const struct clk_hw *hw; const char *fw_name; const char *name; int index; };
다음 글에 통합
<kernel v5.4>
커널에서 사용하는 jifffies 기반의 타이머 tick을 소프트웨어 기법으로 구현한 타이머이다. 커널이 타이머를 요청할 때 만료 시간을 기재하는데 lowres 타이머는 정확하게 그 만료시점에 깨어나는 것을 보장하지 못하고 요청한 만료 시간의 최대 1/8(12.5%)의 지연된 오차를 가진다. 그 외 특징으로 lowres 타이머는 특정 아키텍처와 무관한 구조이다.
lowres 타이머는 커널 v4.8-rc1 에서 새로운 변화를 보여주면서 lowres 타이머에 대한 전반적인 설계가 변경되어 non-cascading wheel 모델이 소개되었다.
admin 권한으로 타이머 리스트를 보려면 다음과 같이 한다. (커널 v4.8~)
$ cat /proc/timer_list Timer List Version: v0.8 HRTIMER_MAX_CLOCK_BASES: 8 now at 79245435304 nsecs cpu: 0 clock 0: .base: (____ptrval____) .index: 0 .resolution: 1 nsecs .get_time: ktime_get .offset: 0 nsecs active timers: #0: <(____ptrval____)>, tick_sched_timer, S:01 # expires at 79260000000-79260000000 nsecs [in 14564696 to 14564696 nsecs] #1: <(____ptrval____)>, it_real_fn, S:01 # expires at 79374561805-79374561805 nsecs [in 129126501 to 129126501 nsecs] #2: <(____ptrval____)>, hrtimer_wakeup, S:01 # expires at 77374835623-79874835623 nsecs [in -1870599681 to 629400319 nsecs] ...
다음 그림과 같이 nohz에서 사용하는 타이머 베이스까지 두 개로 나뉘어 관리된다. 스케줄틱이 발생할 때마다 두 개의 타이머 베이스의 만료된 타이머들을 깨워 콜백함수를 호출한다.
다음 그림은 타이머들이 해시 벡터리스트로 구현되어 처리되는 모습을 보여준다. 100hz 시스템의 경우 최대 8 단계, 그 외의 시스템은 최대 9 단계 레벨로 구성된다.
include/linux/timer.h
struct timer_list { /* * All fields that change during normal runtime grouped to the * same cacheline */ struct hlist_node entry; unsigned long expires; void (*function)(struct timer_list *); u32 flags; #ifdef CONFIG_LOCKDEP struct lockdep_map lockdep_map; #endif };
타이머마다 만료 시각과 호출될 콜백 함수 정보가 담겨있다.
tvec_base 구조체명이 timer_base 구조체명으로 변경되었다.
kernel/time/timer.c
struct timer_base { raw_spinlock_t lock; struct timer_list *running_timer; #ifdef CONFIG_PREEMPT_RT spinlock_t expiry_lock; atomic_t timer_waiters; #endif unsigned long clk; unsigned long next_expiry; unsigned int cpu; bool is_idle; bool must_forward_clk; DECLARE_BITMAP(pending_map, WHEEL_SIZE); struct hlist_head vectors[WHEEL_SIZE]; } ____cacheline_aligned;
타이머들이 관리되는 타이머 베이스는 per-cpu로 관리되며, 타이머 리스트는 해시 벡터 휠로 관리된다.
kernel/time/timer.c
static DEFINE_PER_CPU(struct timer_base, timer_bases[NR_BASES]);
timer_base는 성능향상을 위해 per-cpu를 사용하여 관리한다. nohz를 사용하지 않으면 하나의 타이머 베이스에 관리된다. 그러나 nohz를 사용하는 경우 다음과 같이 두 개의 타이머 휠로 나누어 관리된다.
jiffies 값은 스케줄 틱이 발생할 때 마다 1씩 증가한다. 스케줄 틱은 CONFIG_HZ에 정한 시간에 한 번씩 발생한다.
include/linux/jiffies.h
/* * Have the 32 bit jiffies value wrap 5 minutes after boot * so jiffies wrap bugs show up earlier. */
#define INITIAL_JIFFIES ((unsigned long)(unsigned int) (-300*HZ))
jiffies 초기값은 32bit 시스템에서 부트 후 약 5분이내에 overflow 될 수 있는 값을 주었다. 64비트 시스템에서는 overflow되려면 어마 어마한 시간이 흘러야 하므로 overflow될 걱정이 없다.
컴파일 타임에 정적으로 타이머 생성 및 초기화
런타임에 동적으로 타이머 생성 및 초기화
타이머 베이스에 타이머 추가/변경/삭제
jiffies 값을 읽어 시간을 직접 비교하는 경우 jiffies overflow 되는 시점에서 시간 비교가 의도치 않는 반대의 결과를 얻을 수 있다. 따라서 다음 함수들을 사용하여야 정확한 결과를 나타내게 할 수 있으므로 절대 jiffies 시간을 직접 비교 사용하는 일이 없도록 해야 한다.
kernel/time/timer.c
/** * add_timer - start a timer * @timer: the timer to be added * * The kernel will do a ->function(->data) callback from the * timer interrupt at the ->expires point in the future. The * current time is 'jiffies'. * * The timer's ->expires, ->function (and if the handler uses it, ->data) * fields must be set prior calling this function. * * Timers with an ->expires field in the past will be executed in the next * timer tick. */
void add_timer(struct timer_list *timer) { BUG_ON(timer_pending(timer)); mod_timer(timer, timer->expires); } EXPORT_SYMBOL(add_timer);
동적 타이머를 요청한다.
kernel/time/timer.c
/** * del_timer - deactive a timer. * @timer: the timer to be deactivated * * del_timer() deactivates a timer - this works on both active and inactive * timers. * * The function returns whether it has deactivated a pending timer or not. * (ie. del_timer() of an inactive timer returns 0, del_timer() of an * active timer returns 1.) */
int del_timer(struct timer_list *timer) { struct tvec_base *base; unsigned long flags; int ret = 0; debug_assert_init(timer); if (timer_pending(timer)) { base = lock_timer_base(timer, &flags); ret = detach_if_pending(timer, base, true); spin_unlock_irqrestore(&base->lock, flags); } return ret; } EXPORT_SYMBOL(del_timer);
타이머를 타이머 베이스에서 제거하여 비활성화한다.
kernel/time/timer.c
static int detach_if_pending(struct timer_list *timer, struct timer_base *base, bool clear_pending) { unsigned idx = timer_get_idx(timer); if (!timer_pending(timer)) return 0; if (hlist_is_singular_node(&timer->entry, base->vectors + idx)) __clear_bit(idx, base->pending_map); detach_timer(timer, clear_pending); return 1; }
펜딩된 타이머인 경우 리스트에서 제거한다. 활동(pending) 중인 타이머를 제거한 경우 1을 반환한다.
kernel/time/timer.c
static inline void detach_timer(struct timer_list *timer, bool clear_pending) { struct list_head *entry = &timer->entry; debug_deactivate(timer); __hlist_del(entry); if (clear_pending) entry->pprev = NULL; entry->next = LIST_POISON2; }
타이머 리스트에서 요청 타이머를 제거한다. 만일 clear pending 요청이 있는 경우 타이머 엔트리가 다음 타이머와 연결되지 않도록 분리한다.
kernel/time/timer.c
/** * mod_timer - modify a timer's timeout * @timer: the timer to be modified * @expires: new timeout in jiffies * * mod_timer() is a more efficient way to update the expire field of an * active timer (if the timer is inactive it will be activated) * * mod_timer(timer, expires) is equivalent to: * * del_timer(timer); timer->expires = expires; add_timer(timer); * * Note that if there are multiple unserialized concurrent users of the * same timer, then mod_timer() is the only safe way to modify the timeout, * since add_timer() cannot modify an already running timer. * * The function returns whether it has modified a pending timer or not. * (ie. mod_timer() of an inactive timer returns 0, mod_timer() of an * active timer returns 1.) */
int mod_timer(struct timer_list *timer, unsigned long expires) { return __mod_timer(timer, expires, 0); } EXPORT_SYMBOL(mod_timer);
요청한 타이머를 제거하고 인수로 받은 만료 시점(timerout을 적용한 새 jiffies 시점)으로 조절하고 설정한다. inactive된 타이머도 active 시킨다.
kernel/time/timer.c
static inline int __mod_timer(struct timer_list *timer, unsigned long expires, unsigned int options) { struct timer_base *base, *new_base; unsigned int idx = UINT_MAX; unsigned long clk = 0, flags; int ret = 0; BUG_ON(!timer->function); /* * This is a common optimization triggered by the networking code - if * the timer is re-modified to have the same timeout or ends up in the * same array bucket then just return: */ if (timer_pending(timer)) { /* * The downside of this optimization is that it can result in * larger granularity than you would get from adding a new * timer with this expiry. */ long diff = timer->expires - expires; if (!diff) return 1; if (options & MOD_TIMER_REDUCE && diff <= 0) return 1; /* * We lock timer base and calculate the bucket index right * here. If the timer ends up in the same bucket, then we * just update the expiry time and avoid the whole * dequeue/enqueue dance. */ base = lock_timer_base(timer, &flags); forward_timer_base(base); if (timer_pending(timer) && (options & MOD_TIMER_REDUCE) && time_before_eq(timer->expires, expires)) { ret = 1; goto out_unlock; } clk = base->clk; idx = calc_wheel_index(expires, clk); /* * Retrieve and compare the array index of the pending * timer. If it matches set the expiry to the new value so a * subsequent call will exit in the expires check above. */ if (idx == timer_get_idx(timer)) { if (!(options & MOD_TIMER_REDUCE)) timer->expires = expires; else if (time_after(timer->expires, expires)) timer->expires = expires; ret = 1; goto out_unlock; } } else { base = lock_timer_base(timer, &flags); forward_timer_base(base); }
요청한 타이머를 제거하고 최종 결정된 만료 시점으로 타이머를 설정한 후 다시 추가 하고 active 시킨다. 타이머가 active된 상태이면 1로 반환된다.
ret = detach_if_pending(timer, base, false); if (!ret && (options & MOD_TIMER_PENDING_ONLY)) goto out_unlock; new_base = get_target_base(base, timer->flags); if (base != new_base) { /* * We are trying to schedule the timer on the new base. * However we can't change timer's base while it is running, * otherwise del_timer_sync() can't detect that the timer's * handler yet has not finished. This also guarantees that the * timer is serialized wrt itself. */ if (likely(base->running_timer != timer)) { /* See the comment in lock_timer_base() */ timer->flags |= TIMER_MIGRATING; raw_spin_unlock(&base->lock); base = new_base; raw_spin_lock(&base->lock); WRITE_ONCE(timer->flags, (timer->flags & ~TIMER_BASEMASK) | base->cpu); forward_timer_base(base); } } debug_timer_activate(timer); timer->expires = expires; /* * If 'idx' was calculated above and the base time did not advance * between calculating 'idx' and possibly switching the base, only * enqueue_timer() and trigger_dyntick_cpu() is required. Otherwise * we need to (re)calculate the wheel index via * internal_add_timer(). */ if (idx != UINT_MAX && clk == base->clk) { enqueue_timer(base, timer, idx); trigger_dyntick_cpu(base, timer); } else { internal_add_timer(base, timer); } out_unlock: raw_spin_unlock_irqrestore(&base->lock, flags); return ret; }
다음 그림은 타이머를 추가 또는 변경 시 호출되는 __mod_timer() 함수를 통해 타이머 베이스의 벡터 리스트에 타이머가 추가되는 모습을 보여준다.
kernel/time/timer.c
static void internal_add_timer(struct timer_base *base, struct timer_list *timer) { __internal_add_timer(base, timer); trigger_dyntick_cpu(base, timer); }
요청 타이머 베이스에 타이머를 추가한다.
kernel/time/timer.c
static void __internal_add_timer(struct timer_base *base, struct timer_list *timer) { unsigned int idx; idx = calc_wheel_index(timer->expires, base->clk); enqueue_timer(base, timer, idx); }
요청 타이머 베이스에 타이머를 추가한다.
kernel/time/timer.c
/* * Enqueue the timer into the hash bucket, mark it pending in * the bitmap and store the index in the timer flags. */
static void enqueue_timer(struct timer_base *base, struct timer_list *timer, unsigned int idx) { hlist_add_head(&timer->entry, base->vectors + idx); __set_bit(idx, base->pending_map); timer_set_idx(timer, idx); trace_timer_start(timer, timer->expires, timer->flags); }
요청 타이머 베이스에서 인덱스에 해당하는 타이머 벡터 리스트에 타이머를 추가한다.
kernel/time/timer.c
static inline void timer_set_idx(struct timer_list *timer, unsigned int idx) { timer->flags = (timer->flags & ~TIMER_ARRAYMASK) | idx << TIMER_ARRAYSHIFT; }
타이머의 플래그 bits[31..22]에 인덱스를 기록한다. (10 비트)
include/linux/timer.h
/** * timer_pending - is a timer pending? * @timer: the timer in question * * timer_pending will tell whether a given timer is currently pending, * or not. Callers must ensure serialization wrt. other operations done * to this timer, eg. interrupt contexts, or other CPUs on SMP. * * return value: 1 if the timer is pending, 0 if not. */
static inline int timer_pending(const struct timer_list * timer) { return timer->entry.pprev != NULL; }
타이머 벡터 리스트에서 대기중인 타이머인지 여부를 반환한다.
kernel/time/timer.c
static inline void forward_timer_base(struct timer_base *base) { #ifdef CONFIG_NO_HZ_COMMON unsigned long jnow; /* * We only forward the base when we are idle or have just come out of * idle (must_forward_clk logic), and have a delta between base clock * and jiffies. In the common case, run_timers will take care of it. */ if (likely(!base->must_forward_clk)) return; jnow = READ_ONCE(jiffies); base->must_forward_clk = base->is_idle; if ((long)(jnow - base->clk) < 2) return; /* * If the next expiry value is > jiffies, then we fast forward to * jiffies otherwise we forward to the next expiry value. */ if (time_after(base->next_expiry, jnow)) base->clk = jnow; else base->clk = base->next_expiry; #endif }
nohz를 위해 현재 요청한 타이머 베이스의 시각을 forward 한다.
kernel/time/timer.c
static int calc_wheel_index(unsigned long expires, unsigned long clk) { unsigned long delta = expires - clk; unsigned int idx; if (delta < LVL_START(1)) { idx = calc_index(expires, 0); } else if (delta < LVL_START(2)) { idx = calc_index(expires, 1); } else if (delta < LVL_START(3)) { idx = calc_index(expires, 2); } else if (delta < LVL_START(4)) { idx = calc_index(expires, 3); } else if (delta < LVL_START(5)) { idx = calc_index(expires, 4); } else if (delta < LVL_START(6)) { idx = calc_index(expires, 5); } else if (delta < LVL_START(7)) { idx = calc_index(expires, 6); } else if (LVL_DEPTH > 8 && delta < LVL_START(8)) { idx = calc_index(expires, 7); } else if ((long) delta < 0) { idx = clk & LVL_MASK; } else { /* * Force expire obscene large timeouts to expire at the * capacity limit of the wheel. */ if (expires >= WHEEL_TIMEOUT_CUTOFF) expires = WHEEL_TIMEOUT_MAX; idx = calc_index(expires, LVL_DEPTH - 1); } return idx; }
만료 시각과 클럭 베이스의 시각의 차이로 타이머 휠 인덱스 값을 산출한다.
kernel/time/timer.c
/* * Helper function to calculate the array index for a given expiry * time. */
static inline unsigned calc_index(unsigned expires, unsigned lvl) { expires = (expires + LVL_GRAN(lvl)) >> LVL_SHIFT(lvl); return LVL_OFFS(lvl) + (expires & LVL_MASK); }
만료 시각과 레벨로 타이머 휠 인덱스를 알아온다.
kernel/time/timer.c
/* * The timer wheel has LVL_DEPTH array levels. Each level provides an array of * LVL_SIZE buckets. Each level is driven by its own clock and therefor each * level has a different granularity. * * The level granularity is: LVL_CLK_DIV ^ lvl * The level clock frequency is: HZ / (LVL_CLK_DIV ^ level) * * The array level of a newly armed timer depends on the relative expiry * time. The farther the expiry time is away the higher the array level and * therefor the granularity becomes. * * Contrary to the original timer wheel implementation, which aims for 'exact' * expiry of the timers, this implementation removes the need for recascading * the timers into the lower array levels. The previous 'classic' timer wheel * implementation of the kernel already violated the 'exact' expiry by adding * slack to the expiry time to provide batched expiration. The granularity * levels provide implicit batching. * * This is an optimization of the original timer wheel implementation for the * majority of the timer wheel use cases: timeouts. The vast majority of * timeout timers (networking, disk I/O ...) are canceled before expiry. If * the timeout expires it indicates that normal operation is disturbed, so it * does not matter much whether the timeout comes with a slight delay. * * The only exception to this are networking timers with a small expiry * time. They rely on the granularity. Those fit into the first wheel level, * which has HZ granularity. * * We don't have cascading anymore. timers with a expiry time above the * capacity of the last wheel level are force expired at the maximum timeout * value of the last wheel level. From data sampling we know that the maximum * value observed is 5 days (network connection tracking), so this should not * be an issue. * * The currently chosen array constants values are a good compromise between * array size and granularity. * * This results in the following granularity and range levels: * * HZ 1000 steps * Level Offset Granularity Range * 0 0 1 ms 0 ms - 63 ms * 1 64 8 ms 64 ms - 511 ms * 2 128 64 ms 512 ms - 4095 ms (512ms - ~4s) * 3 192 512 ms 4096 ms - 32767 ms (~4s - ~32s) * 4 256 4096 ms (~4s) 32768 ms - 262143 ms (~32s - ~4m) * 5 320 32768 ms (~32s) 262144 ms - 2097151 ms (~4m - ~34m) * 6 384 262144 ms (~4m) 2097152 ms - 16777215 ms (~34m - ~4h) * 7 448 2097152 ms (~34m) 16777216 ms - 134217727 ms (~4h - ~1d) * 8 512 16777216 ms (~4h) 134217728 ms - 1073741822 ms (~1d - ~12d) * * HZ 300 * Level Offset Granularity Range * 0 0 3 ms 0 ms - 210 ms * 1 64 26 ms 213 ms - 1703 ms (213ms - ~1s) * 2 128 213 ms 1706 ms - 13650 ms (~1s - ~13s) * 3 192 1706 ms (~1s) 13653 ms - 109223 ms (~13s - ~1m) * 4 256 13653 ms (~13s) 109226 ms - 873810 ms (~1m - ~14m) * 5 320 109226 ms (~1m) 873813 ms - 6990503 ms (~14m - ~1h) * 6 384 873813 ms (~14m) 6990506 ms - 55924050 ms (~1h - ~15h) * 7 448 6990506 ms (~1h) 55924053 ms - 447392423 ms (~15h - ~5d) * 8 512 55924053 ms (~15h) 447392426 ms - 3579139406 ms (~5d - ~41d) * * HZ 250 * Level Offset Granularity Range * 0 0 4 ms 0 ms - 255 ms * 1 64 32 ms 256 ms - 2047 ms (256ms - ~2s) * 2 128 256 ms 2048 ms - 16383 ms (~2s - ~16s) * 3 192 2048 ms (~2s) 16384 ms - 131071 ms (~16s - ~2m) * 4 256 16384 ms (~16s) 131072 ms - 1048575 ms (~2m - ~17m) * 5 320 131072 ms (~2m) 1048576 ms - 8388607 ms (~17m - ~2h) * 6 384 1048576 ms (~17m) 8388608 ms - 67108863 ms (~2h - ~18h) * 7 448 8388608 ms (~2h) 67108864 ms - 536870911 ms (~18h - ~6d) * 8 512 67108864 ms (~18h) 536870912 ms - 4294967288 ms (~6d - ~49d) * * HZ 100 * Level Offset Granularity Range * 0 0 10 ms 0 ms - 630 ms * 1 64 80 ms 640 ms - 5110 ms (640ms - ~5s) * 2 128 640 ms 5120 ms - 40950 ms (~5s - ~40s) * 3 192 5120 ms (~5s) 40960 ms - 327670 ms (~40s - ~5m) * 4 256 40960 ms (~40s) 327680 ms - 2621430 ms (~5m - ~43m) * 5 320 327680 ms (~5m) 2621440 ms - 20971510 ms (~43m - ~5h) * 6 384 2621440 ms (~43m) 20971520 ms - 167772150 ms (~5h - ~1d) * 7 448 20971520 ms (~5h) 167772160 ms - 1342177270 ms (~1d - ~15d) */
kernel/time/timer.c
/* Clock divisor for the next level */ #define LVL_CLK_SHIFT 3 #define LVL_CLK_DIV (1UL << LVL_CLK_SHIFT) #define LVL_CLK_MASK (LVL_CLK_DIV - 1) #define LVL_SHIFT(n) ((n) * LVL_CLK_SHIFT) #define LVL_GRAN(n) (1UL << LVL_SHIFT(n))
kernel/time/timer.c
/* Size of each clock level */ #define LVL_BITS 6 #define LVL_SIZE (1UL << LVL_BITS) #define LVL_MASK (LVL_SIZE - 1) #define LVL_OFFS(n) ((n) * LVL_SIZE)
kernel/time/timer.c
/* * The time start value for each level to select the bucket at enqueue * time. */
#define LVL_START(n) ((LVL_SIZE - 1) << (((n) - 1) * LVL_CLK_SHIFT))
kernel/time/timer.c
/* * We are using hashed locking: Holding per_cpu(timer_bases[x]).lock means * that all timers which are tied to this base are locked, and the base itself * is locked too. * * So __run_timers/migrate_timers can safely modify all timers which could * be found in the base->vectors array. * * When a timer is migrating then the TIMER_MIGRATING flag is set and we need * to wait until the migration is done. */
static struct timer_base *lock_timer_base(struct timer_list *timer, unsigned long *flags) __acquires(timer->base->lock) { for (;;) { struct timer_base *base; u32 tf; /* * We need to use READ_ONCE() here, otherwise the compiler * might re-read @tf between the check for TIMER_MIGRATING * and spin_lock(). */ tf = READ_ONCE(timer->flags); if (!(tf & TIMER_MIGRATING)) { base = get_timer_base(tf); raw_spin_lock_irqsave(&base->lock, *flags); if (timer->flags == tf) return base; raw_spin_unlock_irqrestore(&base->lock, *flags); } cpu_relax(); } }
타이머가 사용할 현재 cpu의 타이머 베이스 락을 획득한다.
kernel/time/timer.c
static inline struct timer_base *get_timer_base(u32 tflags) { return get_timer_cpu_base(tflags, tflags & TIMER_CPUMASK); }
현재 cpu에 대한 타이머 베이스를 반환한다.
kernel/time/timer.c
static inline struct timer_base * get_target_base(struct timer_base *base, unsigned tflags) { #if defined(CONFIG_SMP) && defined(CONFIG_NO_HZ_COMMON) if (static_branch_likely(&timers_migration_enabled) && !(tflags & TIMER_PINNED)) return get_timer_cpu_base(tflags, get_nohz_timer_target()); #endif return get_timer_this_cpu_base(tflags); }
동작할 cpu의 타이머 베이스를 반환한다.
kernel/time/timer.c
static inline struct timer_base *get_timer_cpu_base(u32 tflags, u32 cpu) { struct timer_base *base = per_cpu_ptr(&timer_bases[BASE_STD], cpu); /* * If the timer is deferrable and NO_HZ_COMMON is set then we need * to use the deferrable base. */ if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && (tflags & TIMER_DEFERRABLE)) base = per_cpu_ptr(&timer_bases[BASE_DEF], cpu); return base; }
요청 cpu에 대한 타이머 베이스를 반환한다.
kernel/time/timer.c
static inline struct timer_base *get_timer_this_cpu_base(u32 tflags) { struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]); /* * If the timer is deferrable and NO_HZ_COMMON is set then we need * to use the deferrable base. */ if (IS_ENABLED(CONFIG_NO_HZ_COMMON) && (tflags & TIMER_DEFERRABLE)) base = this_cpu_ptr(&timer_bases[BASE_DEF]); return base; }
현재 cpu에 대한 타이머 베이스를 반환한다.
kernel/sched/core.c
/* * In the semi idle case, use the nearest busy CPU for migrating timers * from an idle CPU. This is good for power-savings. * * We don't do similar optimization for completely idle system, as * selecting an idle CPU will add more delays to the timers than intended * (as that CPU's timer base may not be uptodate wrt jiffies etc). */
int get_nohz_timer_target(void) { int i, cpu = smp_processor_id(); struct sched_domain *sd; if (!idle_cpu(cpu) && housekeeping_cpu(cpu, HK_FLAG_TIMER)) return cpu; rcu_read_lock(); for_each_domain(cpu, sd) { for_each_cpu(i, sched_domain_span(sd)) { if (cpu == i) continue; if (!idle_cpu(i) && housekeeping_cpu(i, HK_FLAG_TIMER)) { cpu = i; goto unlock; } } } if (!housekeeping_cpu(cpu, HK_FLAG_TIMER)) cpu = housekeeping_any_cpu(HK_FLAG_TIMER); unlock: rcu_read_unlock(); return cpu; }
절전을 위해 nohz 타이머를 위한 타겟 cpu를 알아온다.
lowres 타이머의 bottom-half로 동작하는 softirq 핸들러를 알아본다.
kernel/time/timer.c
/* * This function runs timers and the timer-tq in bottom half context. */
static __latent_entropy void run_timer_softirq(struct softirq_action *h) { struct timer_base *base = this_cpu_ptr(&timer_bases[BASE_STD]); __run_timers(base); if (IS_ENABLED(CONFIG_NO_HZ_COMMON)) __run_timers(this_cpu_ptr(&timer_bases[BASE_DEF])); }
로컬 cpu에 타이머 tick이 인입될 때 마다 호출되는 timer softirq에 등록한 함수이다. 만료된 타이머들의 해당 타이머 함수를 호출한다.
kernel/time/timer.c
/** * __run_timers - run all expired timers (if any) on this CPU. * @base: the timer vector to be processed. */
static inline void __run_timers(struct timer_base *base) { struct hlist_head heads[LVL_DEPTH]; int levels; if (!time_after_eq(jiffies, base->clk)) return; timer_base_lock_expiry(base); raw_spin_lock_irq(&base->lock); /* * timer_base::must_forward_clk must be cleared before running * timers so that any timer functions that call mod_timer() will * not try to forward the base. Idle tracking / clock forwarding * logic is only used with BASE_STD timers. * * The must_forward_clk flag is cleared unconditionally also for * the deferrable base. The deferrable base is not affected by idle * tracking and never forwarded, so clearing the flag is a NOOP. * * The fact that the deferrable base is never forwarded can cause * large variations in granularity for deferrable timers, but they * can be deferred for long periods due to idle anyway. */ base->must_forward_clk = false; while (time_after_eq(jiffies, base->clk)) { levels = collect_expired_timers(base, heads); base->clk++; while (levels--) expire_timers(base, heads + levels); } raw_spin_unlock_irq(&base->lock); timer_base_unlock_expiry(base); }
요청 타이머 베이스에서 만료된 타이머들의 함수를 실행한다.
kernel/time/timer.c
static int collect_expired_timers(struct timer_base *base, struct hlist_head *heads) { unsigned long now = READ_ONCE(jiffies); /* * NOHZ optimization. After a long idle sleep we need to forward the * base to current jiffies. Avoid a loop by searching the bitfield for * the next expiring timer. */ if ((long)(now - base->clk) > 2) { unsigned long next = __next_timer_interrupt(base); /* * If the next timer is ahead of time forward to current * jiffies, otherwise forward to the next expiry time: */ if (time_after(next, now)) { /* * The call site will increment base->clk and then * terminate the expiry loop immediately. */ base->clk = now; return 0; } base->clk = next; } return __collect_expired_timers(base, heads); }
요청 타이머 베이스에서 만료된 타이머들을 @heads 리스트에 추가한다. levels에는 수집한 최고 레벨 + 1이 저장된다.
kernel/time/timer.c
static int __collect_expired_timers(struct timer_base *base, struct hlist_head *heads) { unsigned long clk = base->clk; struct hlist_head *vec; int i, levels = 0; unsigned int idx; for (i = 0; i < LVL_DEPTH; i++) { idx = (clk & LVL_MASK) + i * LVL_SIZE; if (__test_and_clear_bit(idx, base->pending_map)) { vec = base->vectors + idx; hlist_move_list(vec, heads++); levels++; } /* Is it time to look at the next level? */ if (clk & LVL_CLK_MASK) break; /* Shift clock for the next level granularity */ clk >>= LVL_CLK_SHIFT; } return levels; }
요청 타이머 베이스에서 만료된 타이머들을 @heads 리스트로 수집한다.
다음 그림은 base->clk 값에 의해 호출되는 레벨별 타이머 벡터 리스트들을 보여준다.
kernel/time/timer.c
static void expire_timers(struct timer_base *base, struct hlist_head *head) { /* * This value is required only for tracing. base->clk was * incremented directly before expire_timers was called. But expiry * is related to the old base->clk value. */ unsigned long baseclk = base->clk - 1; while (!hlist_empty(head)) { struct timer_list *timer; void (*fn)(struct timer_list *); timer = hlist_entry(head->first, struct timer_list, entry); base->running_timer = timer; detach_timer(timer, true); fn = timer->function; if (timer->flags & TIMER_IRQSAFE) { raw_spin_unlock(&base->lock); call_timer_fn(timer, fn, baseclk); base->running_timer = NULL; raw_spin_lock(&base->lock); } else { raw_spin_unlock_irq(&base->lock); call_timer_fn(timer, fn, baseclk); base->running_timer = NULL; timer_sync_wait_running(base); raw_spin_lock_irq(&base->lock); } } }
@head 리스트의 만료된 타이머들의 콜백 함수들을 호출한다.
kernel/time/timer.c
static void call_timer_fn(struct timer_list *timer, void (*fn)(struct timer_list *), unsigned long baseclk) { int count = preempt_count(); #ifdef CONFIG_LOCKDEP /* * It is permissible to free the timer from inside the * function that is called from it, this we need to take into * account for lockdep too. To avoid bogus "held lock freed" * warnings as well as problems when looking into * timer->lockdep_map, make a copy and use that here. */ struct lockdep_map lockdep_map; lockdep_copy_map(&lockdep_map, &timer->lockdep_map); #endif /* * Couple the lock chain with the lock chain at * del_timer_sync() by acquiring the lock_map around the fn() * call here and in del_timer_sync(). */ lock_map_acquire(&lockdep_map); trace_timer_expire_entry(timer, baseclk); fn(timer); trace_timer_expire_exit(timer); lock_map_release(&lockdep_map); if (count != preempt_count()) { WARN_ONCE(1, "timer: %pS preempt leak: %08x -> %08x\n", fn, count, preempt_count()); /* * Restore the preempt count. That gives us a decent * chance to survive and extract information. If the * callback kept a lock held, bad luck, but not worse * than the BUG() we had. */ preempt_count_set(count); } }
인수로 받은 fn은 타이머에 설정된 함수이다. 디버그를 위해 trace 출력등이 사용되었다.
include/linux/timer.h
#define DEFINE_TIMER(_name, _function) \ struct timer_list _name = \ __TIMER_INITIALIZER(_function, 0)
컴파일 타임에 타이머를 초기화한다. 인자로 타이머 이름과 콜백 함수를 지정한다.
include/linux/timer.h
#define __TIMER_INITIALIZER(_function, _flags) { \ .entry = { .next = TIMER_ENTRY_STATIC }, \ .function = (_function), \ .flags = (_flags), \ __TIMER_LOCKDEP_MAP_INITIALIZER( \ __FILE__ ":" __stringify(__LINE__)) \ }
include/linux/timer.h
/** * timer_setup - prepare a timer for first use * @timer: the timer in question * @callback: the function to call when timer expires * @flags: any TIMER_* flags * * Regular timer initialization should use either DEFINE_TIMER() above, * or timer_setup(). For timers on the stack, timer_setup_on_stack() must * be used and must be balanced with a call to destroy_timer_on_stack(). */
#define timer_setup(timer, callback, flags) \ __init_timer((timer), (callback), (flags))
타이머를 사용하기 위해 준비한다. 인자로는 타이머와 콜백 함수 및 플래그를 지정한다.
include/linux/timer.h
#define __init_timer(_timer, _fn, _flags) \ init_timer_key((_timer), (_fn), (_flags), NULL, NULL)
include/linux/timer.h
#define setup_timer_on_stack(timer, callback, flags) \ __init_timer_on_stack((timer), (callback), (flags))
타이머를 전달 받은 인자들로 초기화한다.
include/linux/timer.h
#define __init_timer_on_stack(_timer, _fn, _flags) \ init_timer_on_stack_key((_timer), (_fn), (_flags), NULL, NULL)
include/linux/timer.h
static inline void init_timer_on_stack_key(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags, const char *name, struct lock_class_key *key) { init_timer_key(timer, func, flags, name, key); }
kernel/time/timer.c
/** * init_timer_key - initialize a timer * @timer: the timer to be initialized * @func: timer callback function * @flags: timer flags * @name: name of the timer * @key: lockdep class key of the fake lock used for tracking timer * sync lock dependencies * * init_timer_key() must be done to a timer prior calling *any* of the * other timer functions. */
void init_timer_key(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags, const char *name, struct lock_class_key *key) { debug_init(timer); do_init_timer(timer, func, flags, name, key); } EXPORT_SYMBOL(init_timer_key);
타이머를 전달 받은 인자들로 초기화한다.
kernel/time/timer.c
static void do_init_timer(struct timer_list *timer, void (*func)(struct timer_list *), unsigned int flags, const char *name, struct lock_class_key *key) { timer->entry.pprev = NULL; timer->function = func; timer->flags = flags | raw_smp_processor_id(); lockdep_init_map(&timer->lockdep_map, name, key, 0); }
타이머를 전달 받은 인자들로 초기화한다.
kernel/time/timer.c
void __init init_timers(void) { init_timer_cpus(); open_softirq(TIMER_SOFTIRQ, run_timer_softirq); }
타이머를 사용할 수 있도록 초기화한다.
kernel/time/timer.c
static void __init init_timer_cpus(void) { int cpu; for_each_possible_cpu(cpu) init_timer_cpu(cpu); }
모든 possible cpu에 대해 타이머를 사용할 수 있도록 초기화한다.
kernel/time/timer.c
static void __init init_timer_cpu(int cpu) { struct timer_base *base; int i; for (i = 0; i < NR_BASES; i++) { base = per_cpu_ptr(&timer_bases[i], cpu); base->cpu = cpu; raw_spin_lock_init(&base->lock); base->clk = jiffies; timer_base_init_expiry_lock(base); } }
요청 cpu에 대해 타이머를 사용할 수 있도록 초기화한다.
요청된 타이머의 관리를 위해 cpu 마다 타이머휠이 사용된다. 타이머 휠마다 tv1 ~ tv5까지의 5개 벡터로 나누어 관리하며, 각 벡터는 각각 256, 64, 64, 64, 64개의 리스트로 이루어진다.
다음 그림은 타이머 벡터 간의 cascade 조건을 보여준다.
예) base->timer_jiffies 값이 0x400_0000인 경우 tv2 -> tv1, tv3 -> tv2, tv4 -> tv3, tv5 -> tv4의 순서로 full cascade 처리된다.
다음 그림은 만료시각이 다른 각종 타이머들을 추가했을 때 타이머휠에 등록된 타이머 상태들을 보여준다.
다음 그림은 jiffies값이 35인 시점에 cpu#1에 tick이 발생하고 그 동안 tick이 발생하지 못해 처리 못했던 jiffies 들에 대한 처리를 한꺼번에 처리하도록 한다.
다음 그림은 tv2에 있는 두 개의 타이머가 tv1으로 cascade되는 모습을 보여준다.
만료 시간을 조정하여 유사한 만료 시간들끼리 모아 한꺼번에 처리하도록 slack 정렬한다. 타이머 인터럽트가 조금이라도 덜 발생하도록하여 절전과 처리 성능에 도움을 준다.
아래 그림은 100hz 시스템에서 slack으로 9 tick을 준 경우와 timeout으로 2311 tick을 준 경우에 대해 만료 값이 어떻게 변화하는지를 보여준다.
커널 v4.7까지에서는 타이머 리스트가 disable 된 상태에 있기 때문에 아래와 같이 inactive 출력된다.
$ cat /proc/timer_stats Timer Stats Version: v0.3 Sample period: 0.000 s Collection: inactive 0 total events
다음과 같이 enable 시킨다.
# echo "1" > /proc/timer_stats
그런 후 출력이 가능하다. (lowres 타이머와 hrtimer가 섞여 있다)
# cat /proc/timer_stats Timer Stats Version: v0.3 Sample period: 4.530 s Collection: active 453, 0 swapper/2 hrtimer_start_range_ns (tick_sched_timer) 38, 0 swapper/0 hrtimer_start_range_ns (tick_sched_timer) 50, 7 rcu_preempt rcu_gp_kthread (process_timeout) 5, 1632 ifplugd hrtimer_start_range_ns (hrtimer_wakeup) 16, 0 swapper/1 hrtimer_start (tick_sched_timer) 36, 0 swapper/0 hrtimer_start (tick_sched_timer) 4, 3992 sshd sk_reset_timer (tcp_write_timer) 17, 0 swapper/1 usb_hcd_poll_rh_status (rh_timer_func) 4, 1582 ifplugd hrtimer_start_range_ns (hrtimer_wakeup) 4, 2230 ntpd hrtimer_start_range_ns (posix_timer_fn) 4, 3467 kworker/u8:2 queue_delayed_work_on (delayed_work_timer_fn) 10, 0 swapper/0 sk_reset_timer (tcp_delack_timer) 3, 26686 kworker/2:1 queue_delayed_work_on (delayed_work_timer_fn) 3, 44 kworker/0:1 queue_delayed_work_on (delayed_work_timer_fn) 1, 2058 thd hrtimer_start_range_ns (hrtimer_wakeup) 25, 0 swapper/1 hrtimer_start_range_ns (tick_sched_timer) 673 total events, 148.565 events/sec