blob: b8ff7aa1dbf88ef942f788c53419376f75b0ca21 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include "os/mynewt.h"
#if !MYNEWT_VAL(FAULT_STUB)
#include "modlog/modlog.h"
#include "fault/fault.h"
#include "fault_priv.h"
struct fault_domain {
uint8_t success_delta;
uint8_t failure_delta;
#if MYNEWT_VAL(FAULT_DOMAIN_NAMES)
const char *name;
#endif
};
uint8_t fault_chronic_counts[MYNEWT_VAL(FAULT_MAX_DOMAINS)];
static struct fault_domain fault_domains[MYNEWT_VAL(FAULT_MAX_DOMAINS)];
static fault_thresh_fn *fault_thresh_cb;
void
fault_configure_cb(fault_thresh_fn *cb)
{
fault_thresh_cb = cb;
}
static struct fault_domain *
fault_get_domain(int domain_id)
{
if (domain_id < 0 || domain_id >= MYNEWT_VAL(FAULT_MAX_DOMAINS)) {
return NULL;
}
return &fault_domains[domain_id];
}
bool
fault_domain_is_registered(int domain_id)
{
const struct fault_domain *dom;
dom = fault_get_domain(domain_id);
if (dom == NULL) {
return false;
}
return dom->failure_delta != 0;
}
static struct fault_domain *
fault_get_registered_domain(int domain_id)
{
struct fault_domain *dom;
dom = fault_get_domain(domain_id);
if (dom == NULL) {
return NULL;
}
if (!fault_domain_is_registered(domain_id)) {
return NULL;
}
return dom;
}
static bool
fault_recorder_is_saturated(const struct fault_recorder *recorder)
{
return debouncer_val(&recorder->deb) == recorder->deb.max;
}
static int
fault_recorder_state(const struct fault_recorder *recorder)
{
if (fault_recorder_is_saturated(recorder)) {
return FAULT_STATE_ERROR;
} else if (debouncer_state(&recorder->deb)) {
return FAULT_STATE_WARN;
} else {
return FAULT_STATE_GOOD;
}
}
static int
fault_adjust_chronic_count(int domain_id, int delta)
{
int count;
/* Increase and persist the chronic failure count if it would not wrap. */
count = fault_chronic_counts[domain_id] + delta;
if (count < 0) {
count = 0;
} else if (count > UINT8_MAX) {
count = UINT8_MAX;
}
return fault_set_chronic_count(domain_id, count);
}
static int
fault_decrease_chronic_count(int domain_id)
{
const struct fault_domain *dom;
dom = fault_get_registered_domain(domain_id);
assert(dom != NULL);
return fault_adjust_chronic_count(domain_id, -dom->success_delta);
}
static int
fault_increase_chronic_count(int domain_id)
{
const struct fault_domain *dom;
dom = fault_get_registered_domain(domain_id);
assert(dom != NULL);
return fault_adjust_chronic_count(domain_id, dom->failure_delta);
}
/**
* @brief processes the result of a fault-capable operation.
*
* @param recorder The fault recorder associated with the
* operation.
* @param success Whether the operation was successful.
*/
int
fault_process(struct fault_recorder *recorder, bool is_failure)
{
int prev_state;
int state;
int delta;
prev_state = fault_recorder_state(recorder);
/* There is nothing to do if we are already at the maximum fault count. */
if (is_failure && prev_state == FAULT_STATE_ERROR) {
return FAULT_STATE_ERROR;
}
if (is_failure) {
delta = 1;
} else {
delta = -1;
}
debouncer_adjust(&recorder->deb, delta);
state = fault_recorder_state(recorder);
if (state == FAULT_STATE_GOOD) {
/* The domain seems to be working; decrease chronic failure count. */
fault_decrease_chronic_count(recorder->domain_id);
} else {
if (state == FAULT_STATE_ERROR) {
/* Fault detected; trigger a crash in debug builds. */
DEBUG_PANIC();
/* Increase chronic fail count and persist. */
fault_increase_chronic_count(recorder->domain_id);
}
}
/* Notify the application if the fault state changed. */
if (prev_state != state && fault_thresh_cb != NULL) {
fault_thresh_cb(recorder->domain_id, prev_state, state, recorder->arg);
}
return state;
}
int
fault_success(struct fault_recorder *recorder)
{
return fault_process(recorder, false);
}
int
fault_failure(struct fault_recorder *recorder)
{
return fault_process(recorder, true);
}
void
fault_fatal(int domain_id, void *arg, bool is_failure)
{
struct fault_recorder recorder;
int rc;
rc = fault_recorder_init(&recorder, domain_id, 1, 1, arg);
assert(rc == 0);
fault_process(&recorder, is_failure);
}
void
fault_fatal_success(int domain_id, void *arg)
{
fault_fatal(domain_id, arg, false);
}
void
fault_fatal_failure(int domain_id, void *arg)
{
fault_fatal(domain_id, arg, true);
}
int
fault_get_chronic_count(int domain_id, uint8_t *out_count)
{
if (fault_get_registered_domain(domain_id) == NULL) {
return SYS_EINVAL;
}
*out_count = fault_chronic_counts[domain_id];
return 0;
}
int
fault_set_chronic_count(int domain_id, uint8_t count)
{
int rc;
if (fault_get_registered_domain(domain_id) == NULL) {
return SYS_EINVAL;
}
if (fault_chronic_counts[domain_id] == count) {
return 0;
}
fault_chronic_counts[domain_id] = count;
rc = fault_conf_persist_chronic_counts();
DEBUG_ASSERT(rc == 0);
return rc;
}
int
fault_recorder_init(struct fault_recorder *recorder,
int domain_id,
uint16_t warn_thresh,
uint16_t error_thresh,
void *arg)
{
int rc;
if (fault_get_registered_domain(domain_id) == NULL) {
return SYS_EINVAL;
}
if (warn_thresh > error_thresh) {
return SYS_EINVAL;
}
rc = debouncer_init(&recorder->deb, 0, warn_thresh, error_thresh);
if (rc != 0) {
return rc;
}
/* If this fault recorder is associated with a chronically-failing domain,
* start the recorder with an initial acute failure count. For recorders
* with high failure thresholds, this ensures that at least a few successes
* are required before the domain is considered stable.
*/
if (fault_chronic_counts[domain_id] > 0) {
debouncer_set(&recorder->deb, warn_thresh / 2);
}
recorder->domain_id = domain_id;
recorder->arg = arg;
return 0;
}
const char *
fault_domain_name(int domain_id)
{
#if MYNEWT_VAL(FAULT_DOMAIN_NAMES)
const struct fault_domain *domain;
domain = fault_get_registered_domain(domain_id);
if (domain != NULL) {
return domain->name;
}
#endif
return NULL;
}
int
fault_register_domain_priv(int domain_id, uint8_t success_delta,
uint8_t failure_delta, const char *name)
{
struct fault_domain *dom;
if (failure_delta == 0) {
return SYS_EINVAL;
}
dom = fault_get_domain(domain_id);
if (dom == NULL) {
return SYS_EINVAL;
}
if (fault_domain_is_registered(domain_id)) {
return SYS_EALREADY;
}
*dom = (struct fault_domain) {
.success_delta = success_delta,
.failure_delta = failure_delta,
#if MYNEWT_VAL(FAULT_DOMAIN_NAMES)
.name = name,
#endif
};
return 0;
}
void
fault_init(void)
{
int rc;
rc = fault_conf_init();
SYSINIT_PANIC_ASSERT(rc == 0);
}
#endif /* !MYNEWT_VAL(FAULT_STUB) */